mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-21 17:26:29 -05:00
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:
parent
5de4afc9fc
commit
c868b3649d
11 changed files with 200 additions and 29 deletions
|
@ -268,7 +268,8 @@
|
|||
}
|
||||
},
|
||||
"onlineStatus": {
|
||||
"online": "Online"
|
||||
"online": "Online",
|
||||
"noUsers": "No users online"
|
||||
},
|
||||
"error": {
|
||||
"locked": {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
4
src/redux/application-state.d.ts
vendored
4
src/redux/application-state.d.ts
vendored
|
@ -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
|
||||
}
|
||||
|
|
37
src/redux/realtime/methods.ts
Normal file
37
src/redux/realtime/methods.ts
Normal 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)
|
||||
}
|
36
src/redux/realtime/reducers.ts
Normal file
36
src/redux/realtime/reducers.ts
Normal 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
|
||||
}
|
||||
}
|
24
src/redux/realtime/reducers/build-state-from-add-user.ts
Normal file
24
src/redux/realtime/reducers/build-state-from-add-user.ts
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
22
src/redux/realtime/reducers/build-state-from-remove-user.ts
Normal file
22
src/redux/realtime/reducers/build-state-from-remove-user.ts
Normal 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
|
||||
}
|
||||
}
|
41
src/redux/realtime/types.ts
Normal file
41
src/redux/realtime/types.ts
Normal 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
|
|
@ -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
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue