From c868b3649d51d02f2edbcd1eb70e342f10f8d578 Mon Sep 17 00:00:00 2001 From: Tilman Vatteroth Date: Sat, 4 Jun 2022 16:49:56 +0200 Subject: [PATCH] Connect online-entries in sidebar with to redux (#2081) Co-authored-by: Philip Molares Signed-off-by: Tilman Vatteroth Signed-off-by: Philip Molares --- locales/en.json | 3 +- .../sidebar/user-line/user-line.tsx | 2 +- .../active-indicator.tsx | 8 +--- .../users-online-sidebar-menu.tsx | 46 +++++++++++-------- src/redux/application-state.d.ts | 4 +- src/redux/realtime/methods.ts | 37 +++++++++++++++ src/redux/realtime/reducers.ts | 36 +++++++++++++++ .../reducers/build-state-from-add-user.ts | 24 ++++++++++ .../reducers/build-state-from-remove-user.ts | 22 +++++++++ src/redux/realtime/types.ts | 41 +++++++++++++++++ src/redux/reducers.ts | 6 ++- 11 files changed, 200 insertions(+), 29 deletions(-) create mode 100644 src/redux/realtime/methods.ts create mode 100644 src/redux/realtime/reducers.ts create mode 100644 src/redux/realtime/reducers/build-state-from-add-user.ts create mode 100644 src/redux/realtime/reducers/build-state-from-remove-user.ts create mode 100644 src/redux/realtime/types.ts diff --git a/locales/en.json b/locales/en.json index 27067d703..987ded698 100644 --- a/locales/en.json +++ b/locales/en.json @@ -268,7 +268,8 @@ } }, "onlineStatus": { - "online": "Online" + "online": "Online", + "noUsers": "No users online" }, "error": { "locked": { diff --git a/src/components/editor-page/sidebar/user-line/user-line.tsx b/src/components/editor-page/sidebar/user-line/user-line.tsx index ce0f88e8a..0780291f6 100644 --- a/src/components/editor-page/sidebar/user-line/user-line.tsx +++ b/src/components/editor-page/sidebar/user-line/user-line.tsx @@ -5,10 +5,10 @@ */ import React from 'react' -import type { ActiveIndicatorStatus } from '../users-online-sidebar-menu/active-indicator' import { ActiveIndicator } from '../users-online-sidebar-menu/active-indicator' import styles from './user-line.module.scss' import { UserAvatarForUsername } from '../../../common/user-avatar/user-avatar-for-username' +import type { ActiveIndicatorStatus } from '../../../../redux/realtime/types' export interface UserLineProps { username: string | null diff --git a/src/components/editor-page/sidebar/users-online-sidebar-menu/active-indicator.tsx b/src/components/editor-page/sidebar/users-online-sidebar-menu/active-indicator.tsx index 89ad944c1..03c0ccff7 100644 --- a/src/components/editor-page/sidebar/users-online-sidebar-menu/active-indicator.tsx +++ b/src/components/editor-page/sidebar/users-online-sidebar-menu/active-indicator.tsx @@ -1,16 +1,12 @@ /* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ import React from 'react' import styles from './active-indicator.module.scss' - -export enum ActiveIndicatorStatus { - ACTIVE = 'active', - INACTIVE = 'inactive' -} +import type { ActiveIndicatorStatus } from '../../../../redux/realtime/types' export interface ActiveIndicatorProps { status: ActiveIndicatorStatus diff --git a/src/components/editor-page/sidebar/users-online-sidebar-menu/users-online-sidebar-menu.tsx b/src/components/editor-page/sidebar/users-online-sidebar-menu/users-online-sidebar-menu.tsx index 7dae39104..fdffea384 100644 --- a/src/components/editor-page/sidebar/users-online-sidebar-menu/users-online-sidebar-menu.tsx +++ b/src/components/editor-page/sidebar/users-online-sidebar-menu/users-online-sidebar-menu.tsx @@ -4,15 +4,15 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react' +import React, { Fragment, useCallback, useEffect, useMemo, useRef } from 'react' import { Trans, useTranslation } from 'react-i18next' import { SidebarButton } from '../sidebar-button/sidebar-button' import { SidebarMenu } from '../sidebar-menu/sidebar-menu' import type { SpecificSidebarMenuProps } from '../types' import { DocumentSidebarMenuSelection } from '../types' -import { ActiveIndicatorStatus } from './active-indicator' import styles from './online-counter.module.scss' import { UserLine } from '../user-line/user-line' +import { useApplicationState } from '../../../../hooks/common/use-application-state' export const UsersOnlineSidebarMenu: React.FC = ({ className, @@ -21,22 +21,39 @@ export const UsersOnlineSidebarMenu: React.FC = ({ selectedMenuId }) => { const buttonRef = useRef(null) - const [counter] = useState(2) + const onlineUsers = useApplicationState((state) => state.realtime.users) useTranslation() useEffect(() => { - const value = `${counter}` + const value = `${Object.keys(onlineUsers).length}` buttonRef.current?.style.setProperty('--users-online', `"${value}"`) - }, [counter]) + }, [onlineUsers]) const hide = selectedMenuId !== DocumentSidebarMenuSelection.NONE && selectedMenuId !== menuId const expand = selectedMenuId === menuId - const onClickHandler = useCallback(() => { - onClick(menuId) - }, [menuId, onClick]) + const onClickHandler = useCallback(() => onClick(menuId), [menuId, onClick]) + + const onlineUserElements = useMemo(() => { + const entries = Object.entries(onlineUsers) + if (entries.length === 0) { + return ( + + + + + + ) + } else { + return entries.map(([clientId, onlineUser]) => { + return ( + + + + ) + }) + } + }, [onlineUsers]) - // TODO Use real users here - // see https://github.com/hedgedoc/react-client/issues/1988 return ( = ({ variant={'primary'}> - - - - - - - - + {onlineUserElements} ) } diff --git a/src/redux/application-state.d.ts b/src/redux/application-state.d.ts index 3b9cd7f16..9e98d9c00 100644 --- a/src/redux/application-state.d.ts +++ b/src/redux/application-state.d.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ @@ -13,6 +13,7 @@ import type { NoteDetails } from './note-details/types/note-details' import type { UiNotificationState } from './ui-notifications/types' import type { RendererStatus } from './renderer-status/types' import type { HistoryEntryWithOrigin } from '../api/history/types' +import type { RealtimeState } from './realtime/types' export interface ApplicationState { user: OptionalUserState @@ -24,4 +25,5 @@ export interface ApplicationState { noteDetails: NoteDetails uiNotifications: UiNotificationState rendererStatus: RendererStatus + realtime: RealtimeState } diff --git a/src/redux/realtime/methods.ts b/src/redux/realtime/methods.ts new file mode 100644 index 000000000..528297361 --- /dev/null +++ b/src/redux/realtime/methods.ts @@ -0,0 +1,37 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { store } from '..' +import type { AddOnlineUserAction, OnlineUser, RemoveOnlineUserAction } from './types' +import { RealtimeActionType } from './types' + +/** + * Dispatches an event to add a user + * + * @param clientId The clientId of the user to add + * @param user The user to add. + */ +export const addOnlineUser = (clientId: number, user: OnlineUser): void => { + const action: AddOnlineUserAction = { + type: RealtimeActionType.ADD_ONLINE_USER, + clientId, + user + } + store.dispatch(action) +} + +/** + * Dispatches an event to remove a user from the online users list. + * + * @param clientId The yjs client id of the user to remove from the online users list. + */ +export const removeOnlineUser = (clientId: number): void => { + const action: RemoveOnlineUserAction = { + type: RealtimeActionType.REMOVE_ONLINE_USER, + clientId + } + store.dispatch(action) +} diff --git a/src/redux/realtime/reducers.ts b/src/redux/realtime/reducers.ts new file mode 100644 index 000000000..2d2f70f00 --- /dev/null +++ b/src/redux/realtime/reducers.ts @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import type { RealtimeActions, RealtimeState } from './types' +import { RealtimeActionType } from './types' +import type { Reducer } from 'redux' +import { buildStateFromRemoveUser } from './reducers/build-state-from-remove-user' +import { buildStateFromAddUser } from './reducers/build-state-from-add-user' + +const initialState: RealtimeState = { + users: [] +} + +/** + * Applies {@link RealtimeReducer realtime actions} to the global application state. + * + * @param state the current state + * @param action the action that should get applied + * @return The new changed state + */ +export const RealtimeReducer: Reducer = ( + state = initialState, + action: RealtimeActions +) => { + switch (action.type) { + case RealtimeActionType.ADD_ONLINE_USER: + return buildStateFromAddUser(state, action.clientId, action.user) + case RealtimeActionType.REMOVE_ONLINE_USER: + return buildStateFromRemoveUser(state, action.clientId) + default: + return state + } +} diff --git a/src/redux/realtime/reducers/build-state-from-add-user.ts b/src/redux/realtime/reducers/build-state-from-add-user.ts new file mode 100644 index 000000000..0766668bd --- /dev/null +++ b/src/redux/realtime/reducers/build-state-from-add-user.ts @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import type { OnlineUser, RealtimeState } from '../types' + +/** + * Builds a new {@link RealtimeState} with a new client id that is shown as online. + * + * @param oldState The old state that will be copied + * @param clientId The identifier of the new client + * @param user The information about the new user + * @return the generated state + */ +export const buildStateFromAddUser = (oldState: RealtimeState, clientId: number, user: OnlineUser): RealtimeState => { + return { + users: { + ...oldState.users, + [clientId]: user + } + } +} diff --git a/src/redux/realtime/reducers/build-state-from-remove-user.ts b/src/redux/realtime/reducers/build-state-from-remove-user.ts new file mode 100644 index 000000000..31d51ad3a --- /dev/null +++ b/src/redux/realtime/reducers/build-state-from-remove-user.ts @@ -0,0 +1,22 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import type { RealtimeState } from '../types' + +/** + * Builds a new {@link RealtimeState} but removes the information about a client. + * + * @param oldState The old state that will be copied + * @param clientIdToRemove The identifier of the client that should be removed + * @return the generated state + */ +export const buildStateFromRemoveUser = (oldState: RealtimeState, clientIdToRemove: number): RealtimeState => { + const newUsers = { ...oldState.users } + delete newUsers[clientIdToRemove] + return { + users: newUsers + } +} diff --git a/src/redux/realtime/types.ts b/src/redux/realtime/types.ts new file mode 100644 index 000000000..c0efbd53c --- /dev/null +++ b/src/redux/realtime/types.ts @@ -0,0 +1,41 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import type { Action } from 'redux' + +export enum RealtimeActionType { + ADD_ONLINE_USER = 'realtime/add-user', + REMOVE_ONLINE_USER = 'realtime/remove-user', + UPDATE_ONLINE_USER = 'realtime/update-user' +} + +export interface RealtimeState { + users: Record +} + +export enum ActiveIndicatorStatus { + ACTIVE = 'active', + INACTIVE = 'inactive' +} + +export interface OnlineUser { + username: string + color: string + active: ActiveIndicatorStatus +} + +export interface AddOnlineUserAction extends Action { + type: RealtimeActionType.ADD_ONLINE_USER + clientId: number + user: OnlineUser +} + +export interface RemoveOnlineUserAction extends Action { + type: RealtimeActionType.REMOVE_ONLINE_USER + clientId: number +} + +export type RealtimeActions = AddOnlineUserAction | RemoveOnlineUserAction diff --git a/src/redux/reducers.ts b/src/redux/reducers.ts index 994033533..61c5d2ca3 100644 --- a/src/redux/reducers.ts +++ b/src/redux/reducers.ts @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ @@ -16,6 +16,7 @@ import { NoteDetailsReducer } from './note-details/reducer' import { UiNotificationReducer } from './ui-notifications/reducers' import { RendererStatusReducer } from './renderer-status/reducers' import type { ApplicationState } from './application-state' +import { RealtimeReducer } from './realtime/reducers' export const allReducers: Reducer = combineReducers({ user: UserReducer, @@ -26,5 +27,6 @@ export const allReducers: Reducer = combineReducers