Connect online-entries in sidebar with to redux (#2081)

Co-authored-by: Philip Molares <philip.molares@udo.edu
Co-authored-by: Erik Michelson <github@erik.michelson.eu>
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
Signed-off-by: Philip Molares <philip.molares@udo.edu
Signed-off-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
Tilman Vatteroth 2022-06-04 16:49:56 +02:00 committed by GitHub
parent 5de4afc9fc
commit c868b3649d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 200 additions and 29 deletions

View file

@ -268,7 +268,8 @@
}
},
"onlineStatus": {
"online": "Online"
"online": "Online",
"noUsers": "No users online"
},
"error": {
"locked": {

View file

@ -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

View file

@ -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

View file

@ -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<SpecificSidebarMenuProps> = ({
className,
@ -21,22 +21,39 @@ export const UsersOnlineSidebarMenu: React.FC<SpecificSidebarMenuProps> = ({
selectedMenuId
}) => {
const buttonRef = useRef<HTMLButtonElement>(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 (
<SidebarButton>
<span className={'ml-3'}>
<Trans i18nKey={'editor.onlineStatus.noUsers'}></Trans>
</span>
</SidebarButton>
)
} else {
return entries.map(([clientId, onlineUser]) => {
return (
<SidebarButton key={clientId}>
<UserLine username={onlineUser.username} color={onlineUser.color} status={onlineUser.active} />
</SidebarButton>
)
})
}
}, [onlineUsers])
// TODO Use real users here
// see https://github.com/hedgedoc/react-client/issues/1988
return (
<Fragment>
<SidebarButton
@ -48,14 +65,7 @@ export const UsersOnlineSidebarMenu: React.FC<SpecificSidebarMenuProps> = ({
variant={'primary'}>
<Trans i18nKey={'editor.onlineStatus.online'} />
</SidebarButton>
<SidebarMenu expand={expand}>
<SidebarButton>
<UserLine username={null} color='red' status={ActiveIndicatorStatus.INACTIVE} />
</SidebarButton>
<SidebarButton>
<UserLine username={null} color='blue' status={ActiveIndicatorStatus.ACTIVE} />
</SidebarButton>
</SidebarMenu>
<SidebarMenu expand={expand}>{onlineUserElements}</SidebarMenu>
</Fragment>
)
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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<RealtimeState, RealtimeActions> = (
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
}
}

View file

@ -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
}
}
}

View file

@ -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
}
}

View file

@ -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<number, OnlineUser>
}
export enum ActiveIndicatorStatus {
ACTIVE = 'active',
INACTIVE = 'inactive'
}
export interface OnlineUser {
username: string
color: string
active: ActiveIndicatorStatus
}
export interface AddOnlineUserAction extends Action<RealtimeActionType> {
type: RealtimeActionType.ADD_ONLINE_USER
clientId: number
user: OnlineUser
}
export interface RemoveOnlineUserAction extends Action<RealtimeActionType> {
type: RealtimeActionType.REMOVE_ONLINE_USER
clientId: number
}
export type RealtimeActions = AddOnlineUserAction | RemoveOnlineUserAction

View file

@ -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<ApplicationState> = combineReducers<ApplicationState>({
user: UserReducer,
@ -26,5 +27,6 @@ export const allReducers: Reducer<ApplicationState> = combineReducers<Applicatio
darkMode: DarkModeConfigReducer,
noteDetails: NoteDetailsReducer,
uiNotifications: UiNotificationReducer,
rendererStatus: RendererStatusReducer
rendererStatus: RendererStatusReducer,
realtime: RealtimeReducer
})