mirror of
https://github.com/owncast/owncast.git
synced 2024-11-23 21:28:29 +03:00
Add current user object that holds user session values instead of standalone getters. Closes #2050
This commit is contained in:
parent
d94723bd3a
commit
80a012a3c7
12 changed files with 103 additions and 98 deletions
3
web/.vscode/settings.json
vendored
3
web/.vscode/settings.json
vendored
|
@ -9,5 +9,6 @@
|
|||
"linkify",
|
||||
"paypal",
|
||||
"toggleswitch"
|
||||
]
|
||||
],
|
||||
"deepscan.enable": true
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import { useHotkeys } from 'react-hotkeys-hook';
|
|||
import dynamic from 'next/dynamic';
|
||||
import {
|
||||
chatVisibleToggleAtom,
|
||||
chatDisplayNameAtom,
|
||||
currentUserAtom,
|
||||
appStateAtom,
|
||||
} from '../../stores/ClientConfigStore';
|
||||
import styles from './UserDropdown.module.scss';
|
||||
|
@ -34,12 +34,19 @@ export type UserDropdownProps = {
|
|||
};
|
||||
|
||||
export const UserDropdown: FC<UserDropdownProps> = ({ username: defaultUsername = undefined }) => {
|
||||
const username = defaultUsername || useRecoilValue(chatDisplayNameAtom);
|
||||
const [showNameChangeModal, setShowNameChangeModal] = useState<boolean>(false);
|
||||
const [showAuthModal, setShowAuthModal] = useState<boolean>(false);
|
||||
const [chatToggleVisible, setChatToggleVisible] = useRecoilState(chatVisibleToggleAtom);
|
||||
const appState = useRecoilValue<AppStateOptions>(appStateAtom);
|
||||
|
||||
const currentUser = useRecoilValue(currentUserAtom);
|
||||
if (!currentUser) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { displayName } = currentUser;
|
||||
const username = defaultUsername || displayName;
|
||||
|
||||
const toggleChatVisibility = () => {
|
||||
setChatToggleVisible(!chatToggleVisible);
|
||||
};
|
||||
|
|
|
@ -9,7 +9,7 @@ import IndieAuthIcon from '../../../assets/images/indieauth.png';
|
|||
|
||||
import styles from './AuthModal.module.scss';
|
||||
import {
|
||||
chatDisplayNameAtom,
|
||||
currentUserAtom,
|
||||
chatAuthenticatedAtom,
|
||||
accessTokenAtom,
|
||||
} from '../../stores/ClientConfigStore';
|
||||
|
@ -17,10 +17,15 @@ import {
|
|||
const { TabPane } = Tabs;
|
||||
|
||||
export const AuthModal: FC = () => {
|
||||
const chatDisplayName = useRecoilValue<string>(chatDisplayNameAtom);
|
||||
const currentUser = useRecoilValue(currentUserAtom);
|
||||
if (!currentUser) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const authenticated = useRecoilValue<boolean>(chatAuthenticatedAtom);
|
||||
const accessToken = useRecoilValue<string>(accessTokenAtom);
|
||||
const federationEnabled = true;
|
||||
const { displayName } = currentUser;
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -41,7 +46,7 @@ export const AuthModal: FC = () => {
|
|||
>
|
||||
<IndieAuthModal
|
||||
authenticated={authenticated}
|
||||
displayName={chatDisplayName}
|
||||
displayName={displayName}
|
||||
accessToken={accessToken}
|
||||
/>
|
||||
</TabPane>
|
||||
|
@ -56,7 +61,7 @@ export const AuthModal: FC = () => {
|
|||
>
|
||||
<FediAuthModal
|
||||
authenticated={authenticated}
|
||||
displayName={chatDisplayName}
|
||||
displayName={displayName}
|
||||
accessToken={accessToken}
|
||||
/>
|
||||
</TabPane>
|
||||
|
|
|
@ -3,11 +3,7 @@ import { useRecoilValue } from 'recoil';
|
|||
import { Input, Button, Select } from 'antd';
|
||||
import { MessageType } from '../../../interfaces/socket-events';
|
||||
import WebsocketService from '../../../services/websocket-service';
|
||||
import {
|
||||
websocketServiceAtom,
|
||||
chatDisplayNameAtom,
|
||||
chatDisplayColorAtom,
|
||||
} from '../../stores/ClientConfigStore';
|
||||
import { websocketServiceAtom, currentUserAtom } from '../../stores/ClientConfigStore';
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
|
@ -26,10 +22,14 @@ const UserColor: FC<UserColorProps> = ({ color }) => {
|
|||
};
|
||||
|
||||
export const NameChangeModal: FC = () => {
|
||||
const currentUser = useRecoilValue(currentUserAtom);
|
||||
if (!currentUser) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { displayName, displayColor } = currentUser;
|
||||
const websocketService = useRecoilValue<WebsocketService>(websocketServiceAtom);
|
||||
const chatDisplayName = useRecoilValue<string>(chatDisplayNameAtom);
|
||||
const chatDisplayColor = useRecoilValue<number>(chatDisplayColorAtom) || 0;
|
||||
const [newName, setNewName] = useState<any>(chatDisplayName);
|
||||
const [newName, setNewName] = useState<any>(displayName);
|
||||
|
||||
const handleNameChange = () => {
|
||||
const nameChange = {
|
||||
|
@ -39,8 +39,7 @@ export const NameChangeModal: FC = () => {
|
|||
websocketService.send(nameChange);
|
||||
};
|
||||
|
||||
const saveEnabled =
|
||||
newName !== chatDisplayName && newName !== '' && websocketService?.isConnected();
|
||||
const saveEnabled = newName !== displayName && newName !== '' && websocketService?.isConnected();
|
||||
|
||||
const handleColorChange = (color: string) => {
|
||||
const colorChange = {
|
||||
|
@ -63,7 +62,7 @@ export const NameChangeModal: FC = () => {
|
|||
placeholder="Your chat display name"
|
||||
maxLength={30}
|
||||
showCount
|
||||
defaultValue={chatDisplayName}
|
||||
defaultValue={displayName}
|
||||
/>
|
||||
<Button disabled={!saveEnabled} onClick={handleNameChange}>
|
||||
Change name
|
||||
|
@ -73,7 +72,7 @@ export const NameChangeModal: FC = () => {
|
|||
<Select
|
||||
style={{ width: 120 }}
|
||||
onChange={handleColorChange}
|
||||
defaultValue={chatDisplayColor.toString()}
|
||||
defaultValue={displayColor.toString()}
|
||||
getPopupContainer={triggerNode => triggerNode.parentElement}
|
||||
>
|
||||
{colorOptions.map(e => (
|
||||
|
|
|
@ -6,6 +6,7 @@ import ClientConfigService from '../../services/client-config-service';
|
|||
import ChatService from '../../services/chat-service';
|
||||
import WebsocketService from '../../services/websocket-service';
|
||||
import { ChatMessage } from '../../interfaces/chat-message.model';
|
||||
import { CurrentUser } from '../../interfaces/current-user';
|
||||
import { ServerStatus, makeEmptyServerStatus } from '../../interfaces/server-status.model';
|
||||
import appStateModel, {
|
||||
AppStateEvent,
|
||||
|
@ -44,31 +45,16 @@ export const clientConfigStateAtom = atom({
|
|||
default: makeEmptyClientConfig(),
|
||||
});
|
||||
|
||||
export const chatDisplayNameAtom = atom<string>({
|
||||
key: 'chatDisplayName',
|
||||
default: null,
|
||||
});
|
||||
|
||||
export const chatDisplayColorAtom = atom<number>({
|
||||
key: 'chatDisplayColor',
|
||||
default: null,
|
||||
});
|
||||
|
||||
export const chatUserIdAtom = atom<string>({
|
||||
key: 'chatUserIdAtom',
|
||||
default: null,
|
||||
});
|
||||
|
||||
export const isChatModeratorAtom = atom<boolean>({
|
||||
key: 'isModeratorAtom',
|
||||
default: false,
|
||||
});
|
||||
|
||||
export const accessTokenAtom = atom<string>({
|
||||
key: 'accessTokenAtom',
|
||||
default: null,
|
||||
});
|
||||
|
||||
export const currentUserAtom = atom<CurrentUser>({
|
||||
key: 'currentUserAtom',
|
||||
default: null,
|
||||
});
|
||||
|
||||
export const chatMessagesAtom = atom<ChatMessage[]>({
|
||||
key: 'chatMessages',
|
||||
default: [] as ChatMessage[],
|
||||
|
@ -126,7 +112,7 @@ export const isChatVisibleSelector = selector({
|
|||
get: ({ get }) => {
|
||||
const state: AppStateOptions = get(appStateAtom);
|
||||
const userVisibleToggle: boolean = get(chatVisibleToggleAtom);
|
||||
const accessToken: String = get(accessTokenAtom);
|
||||
const accessToken: string = get(accessTokenAtom);
|
||||
return accessToken && state.chatAvailable && userVisibleToggle;
|
||||
},
|
||||
});
|
||||
|
@ -135,7 +121,7 @@ export const isChatAvailableSelector = selector({
|
|||
key: 'isChatAvailableSelector',
|
||||
get: ({ get }) => {
|
||||
const state: AppStateOptions = get(appStateAtom);
|
||||
const accessToken: String = get(accessTokenAtom);
|
||||
const accessToken: string = get(accessTokenAtom);
|
||||
return accessToken && state.chatAvailable;
|
||||
},
|
||||
});
|
||||
|
@ -174,12 +160,8 @@ function mergeMeta(meta) {
|
|||
|
||||
export const ClientConfigStore: FC = () => {
|
||||
const [appState, appStateSend, appStateService] = useMachine(appStateModel);
|
||||
|
||||
const setChatDisplayName = useSetRecoilState<string>(chatDisplayNameAtom);
|
||||
const setChatDisplayColor = useSetRecoilState<Number>(chatDisplayColorAtom);
|
||||
const setChatUserId = useSetRecoilState<string>(chatUserIdAtom);
|
||||
const [currentUser, setCurrentUser] = useRecoilState(currentUserAtom);
|
||||
const setChatAuthenticated = useSetRecoilState<boolean>(chatAuthenticatedAtom);
|
||||
const setIsChatModerator = useSetRecoilState<boolean>(isChatModeratorAtom);
|
||||
const [clientConfig, setClientConfig] = useRecoilState<ClientConfig>(clientConfigStateAtom);
|
||||
const setServerStatus = useSetRecoilState<ServerStatus>(serverStatusState);
|
||||
const setClockSkew = useSetRecoilState<Number>(clockSkewAtom);
|
||||
|
@ -264,10 +246,13 @@ export const ClientConfigStore: FC = () => {
|
|||
}
|
||||
|
||||
console.log('setting access token', newAccessToken);
|
||||
setCurrentUser({
|
||||
...currentUser,
|
||||
displayName: newDisplayName,
|
||||
displayColor,
|
||||
});
|
||||
setAccessToken(newAccessToken);
|
||||
setLocalStorage(ACCESS_TOKEN_KEY, newAccessToken);
|
||||
setChatDisplayName(newDisplayName);
|
||||
setChatDisplayColor(displayColor);
|
||||
} catch (e) {
|
||||
sendEvent(AppStateEvent.Fail);
|
||||
console.error(`ChatService -> registerUser() ERROR: \n${e}`);
|
||||
|
@ -276,7 +261,7 @@ export const ClientConfigStore: FC = () => {
|
|||
|
||||
const resetAndReAuth = () => {
|
||||
setLocalStorage(ACCESS_TOKEN_KEY, '');
|
||||
setAccessToken('');
|
||||
setAccessToken(null);
|
||||
handleUserRegistration();
|
||||
};
|
||||
|
||||
|
@ -299,11 +284,8 @@ export const ClientConfigStore: FC = () => {
|
|||
case MessageType.CONNECTED_USER_INFO:
|
||||
handleConnectedClientInfoMessage(
|
||||
message as ConnectedClientInfoEvent,
|
||||
setChatDisplayName,
|
||||
setChatDisplayColor,
|
||||
setChatUserId,
|
||||
setIsChatModerator,
|
||||
setChatAuthenticated,
|
||||
setCurrentUser,
|
||||
);
|
||||
setChatMessages(currentState => [...currentState, message as ChatEvent]);
|
||||
break;
|
||||
|
|
|
@ -2,18 +2,18 @@ import { ConnectedClientInfoEvent } from '../../../interfaces/socket-events';
|
|||
|
||||
export function handleConnectedClientInfoMessage(
|
||||
message: ConnectedClientInfoEvent,
|
||||
setChatDisplayName: (string) => void,
|
||||
setChatDisplayColor: (number) => void,
|
||||
setChatUserId: (number) => void,
|
||||
setIsChatModerator: (boolean) => void,
|
||||
setChatAuthenticated: (boolean) => void,
|
||||
setCurrentUser: (CurrentUser) => void,
|
||||
) {
|
||||
const { user } = message;
|
||||
const { id, displayName, displayColor, scopes, authenticated } = user;
|
||||
setChatDisplayName(displayName);
|
||||
setChatDisplayColor(displayColor);
|
||||
setChatUserId(id);
|
||||
setIsChatModerator(scopes?.includes('MODERATOR'));
|
||||
setChatAuthenticated(authenticated);
|
||||
|
||||
setCurrentUser({
|
||||
id: id.toString(),
|
||||
displayName,
|
||||
displayColor,
|
||||
isModerator: scopes?.includes('MODERATOR'),
|
||||
});
|
||||
}
|
||||
export default handleConnectedClientInfoMessage;
|
||||
|
|
|
@ -8,8 +8,7 @@ import { LOCAL_STORAGE_KEYS, getLocalStorage, setLocalStorage } from '../../../u
|
|||
import {
|
||||
clientConfigStateAtom,
|
||||
chatMessagesAtom,
|
||||
chatDisplayNameAtom,
|
||||
chatUserIdAtom,
|
||||
currentUserAtom,
|
||||
isChatAvailableSelector,
|
||||
isChatVisibleSelector,
|
||||
appStateAtom,
|
||||
|
@ -134,12 +133,12 @@ export const Content: FC = () => {
|
|||
const clientConfig = useRecoilValue<ClientConfig>(clientConfigStateAtom);
|
||||
const isChatVisible = useRecoilValue<boolean>(isChatVisibleSelector);
|
||||
const isChatAvailable = useRecoilValue<boolean>(isChatAvailableSelector);
|
||||
const currentUser = useRecoilValue(currentUserAtom);
|
||||
|
||||
const [isMobile, setIsMobile] = useRecoilState<boolean | undefined>(isMobileAtom);
|
||||
const messages = useRecoilValue<ChatMessage[]>(chatMessagesAtom);
|
||||
const online = useRecoilValue<boolean>(isOnlineSelector);
|
||||
const chatDisplayName = useRecoilValue<string>(chatDisplayNameAtom);
|
||||
const chatUserId = useRecoilValue<string>(chatUserIdAtom);
|
||||
|
||||
const { viewerCount, lastConnectTime, lastDisconnectTime, streamTitle } =
|
||||
useRecoilValue<ServerStatus>(serverStatusState);
|
||||
const {
|
||||
|
@ -200,6 +199,11 @@ export const Content: FC = () => {
|
|||
window.addEventListener('resize', checkIfMobile);
|
||||
}, []);
|
||||
|
||||
if (!currentUser) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { id: currentUserId, displayName } = currentUser;
|
||||
const showChat = !chatDisabled && isChatAvailable && isChatVisible;
|
||||
|
||||
return (
|
||||
|
@ -261,8 +265,8 @@ export const Content: FC = () => {
|
|||
socialHandles={socialHandles}
|
||||
extraPageContent={extraPageContent}
|
||||
messages={messages}
|
||||
chatDisplayName={chatDisplayName}
|
||||
chatUserId={chatUserId}
|
||||
chatDisplayName={displayName}
|
||||
chatUserId={currentUserId}
|
||||
showChat={showChat}
|
||||
/>
|
||||
) : (
|
||||
|
|
|
@ -5,26 +5,20 @@ import { ChatMessage } from '../../../interfaces/chat-message.model';
|
|||
import { ChatContainer } from '../../chat/ChatContainer/ChatContainer';
|
||||
import styles from './Sidebar.module.scss';
|
||||
|
||||
import {
|
||||
chatDisplayNameAtom,
|
||||
chatUserIdAtom,
|
||||
isChatModeratorAtom,
|
||||
visibleChatMessagesSelector,
|
||||
} from '../../stores/ClientConfigStore';
|
||||
import { currentUserAtom, visibleChatMessagesSelector } from '../../stores/ClientConfigStore';
|
||||
|
||||
export const Sidebar: FC = () => {
|
||||
const chatDisplayName = useRecoilValue<string>(chatDisplayNameAtom);
|
||||
const chatUserId = useRecoilValue<string>(chatUserIdAtom);
|
||||
const isChatModerator = useRecoilValue<boolean>(isChatModeratorAtom);
|
||||
const currentUser = useRecoilValue(currentUserAtom);
|
||||
const messages = useRecoilValue<ChatMessage[]>(visibleChatMessagesSelector);
|
||||
|
||||
const { id, isModerator, displayName } = currentUser;
|
||||
return (
|
||||
<Sider className={styles.root} collapsedWidth={0} width={320}>
|
||||
<ChatContainer
|
||||
messages={messages}
|
||||
usernameToHighlight={chatDisplayName}
|
||||
chatUserId={chatUserId}
|
||||
isModerator={isChatModerator}
|
||||
usernameToHighlight={displayName}
|
||||
chatUserId={id}
|
||||
isModerator={isModerator}
|
||||
/>
|
||||
</Sider>
|
||||
);
|
||||
|
|
6
web/interfaces/current-user.ts
Normal file
6
web/interfaces/current-user.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
export interface CurrentUser {
|
||||
id: string;
|
||||
displayName: string;
|
||||
displayColor: number;
|
||||
isModerator: boolean;
|
||||
}
|
|
@ -3,23 +3,24 @@ import { ChatMessage } from '../../../../interfaces/chat-message.model';
|
|||
import { ChatContainer } from '../../../../components/chat/ChatContainer/ChatContainer';
|
||||
import {
|
||||
ClientConfigStore,
|
||||
chatDisplayNameAtom,
|
||||
chatUserIdAtom,
|
||||
currentUserAtom,
|
||||
visibleChatMessagesSelector,
|
||||
} from '../../../../components/stores/ClientConfigStore';
|
||||
|
||||
export default function ReadOnlyChatEmbed() {
|
||||
const chatDisplayName = useRecoilValue<string>(chatDisplayNameAtom);
|
||||
const chatUserId = useRecoilValue<string>(chatUserIdAtom);
|
||||
const currentUser = useRecoilValue(currentUserAtom);
|
||||
const messages = useRecoilValue<ChatMessage[]>(visibleChatMessagesSelector);
|
||||
|
||||
if (!currentUser) {
|
||||
return null;
|
||||
}
|
||||
const { id, displayName } = currentUser;
|
||||
return (
|
||||
<div>
|
||||
<ClientConfigStore />
|
||||
<ChatContainer
|
||||
messages={messages}
|
||||
usernameToHighlight={chatDisplayName}
|
||||
chatUserId={chatUserId}
|
||||
usernameToHighlight={displayName}
|
||||
chatUserId={id}
|
||||
isModerator={false}
|
||||
showInput={false}
|
||||
height="100vh"
|
||||
|
|
|
@ -3,32 +3,34 @@ import { ChatMessage } from '../../../../interfaces/chat-message.model';
|
|||
import { ChatContainer } from '../../../../components/chat/ChatContainer/ChatContainer';
|
||||
import {
|
||||
ClientConfigStore,
|
||||
chatDisplayNameAtom,
|
||||
chatUserIdAtom,
|
||||
currentUserAtom,
|
||||
visibleChatMessagesSelector,
|
||||
clientConfigStateAtom,
|
||||
isChatModeratorAtom,
|
||||
} from '../../../../components/stores/ClientConfigStore';
|
||||
import Header from '../../../../components/ui/Header/Header';
|
||||
import { ClientConfig } from '../../../../interfaces/client-config.model';
|
||||
|
||||
export default function ReadWriteChatEmbed() {
|
||||
const chatDisplayName = useRecoilValue<string>(chatDisplayNameAtom);
|
||||
const chatUserId = useRecoilValue<string>(chatUserIdAtom);
|
||||
const currentUser = useRecoilValue(currentUserAtom);
|
||||
const messages = useRecoilValue<ChatMessage[]>(visibleChatMessagesSelector);
|
||||
const clientConfig = useRecoilValue<ClientConfig>(clientConfigStateAtom);
|
||||
const isModerator = useRecoilValue<boolean>(isChatModeratorAtom);
|
||||
|
||||
const { name, chatDisabled } = clientConfig;
|
||||
|
||||
if (!currentUser) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { id, displayName, isModerator } = currentUser;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ClientConfigStore />
|
||||
<Header name={name} chatAvailable chatDisabled={chatDisabled} />
|
||||
<ChatContainer
|
||||
messages={messages}
|
||||
usernameToHighlight={chatDisplayName}
|
||||
chatUserId={chatUserId}
|
||||
usernameToHighlight={displayName}
|
||||
chatUserId={id}
|
||||
isModerator={isModerator}
|
||||
showInput
|
||||
height="80vh"
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import { RecoilRoot, useSetRecoilState } from 'recoil';
|
||||
import { RecoilRoot, useRecoilState, useSetRecoilState } from 'recoil';
|
||||
import ReadWritePage from '../pages/embed/chat/readwrite/index';
|
||||
import { ChatMessage } from '../interfaces/chat-message.model';
|
||||
import {
|
||||
chatMessagesAtom,
|
||||
chatDisplayNameAtom,
|
||||
currentUserAtom,
|
||||
clientConfigStateAtom,
|
||||
} from '../components/stores/ClientConfigStore';
|
||||
import { ClientConfig } from '../interfaces/client-config.model';
|
||||
|
@ -21,8 +21,8 @@ const testMessages =
|
|||
const messages: ChatMessage[] = JSON.parse(testMessages);
|
||||
|
||||
const Page = () => {
|
||||
const [currentUser, setCurrentUser] = useRecoilState(currentUserAtom);
|
||||
const setMessages = useSetRecoilState(chatMessagesAtom);
|
||||
const setDisplayName = useSetRecoilState(chatDisplayNameAtom);
|
||||
const setClientConfig = useSetRecoilState<ClientConfig>(clientConfigStateAtom);
|
||||
|
||||
const fakeConfig: ClientConfig = {
|
||||
|
@ -45,7 +45,11 @@ const Page = () => {
|
|||
|
||||
useEffect(() => {
|
||||
setMessages(messages);
|
||||
setDisplayName('fake-chat-user');
|
||||
setCurrentUser({
|
||||
...currentUser,
|
||||
displayName: 'fake-chat-user',
|
||||
});
|
||||
|
||||
setClientConfig(fakeConfig);
|
||||
}, []);
|
||||
|
||||
|
|
Loading…
Reference in a new issue