mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-24 10:46:30 -05:00
feat(realtime): synchronize and show realtime activity state
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
9497726a7c
commit
598fc8ee11
10 changed files with 85 additions and 17 deletions
|
@ -78,10 +78,23 @@ export class RealtimeUserStatusAdapter {
|
|||
},
|
||||
) as Listener;
|
||||
|
||||
const realtimeUserSetActivityListener = connection.getTransporter().on(
|
||||
MessageType.REALTIME_USER_SET_ACTIVITY,
|
||||
(message) => {
|
||||
if (this.realtimeUser.active === message.payload.active) {
|
||||
return;
|
||||
}
|
||||
this.realtimeUser.active = message.payload.active;
|
||||
this.sendRealtimeUserStatusUpdateEvent(connection);
|
||||
},
|
||||
{ objectify: true },
|
||||
) as Listener;
|
||||
|
||||
connection.getTransporter().on('disconnected', () => {
|
||||
transporterMessagesListener.off();
|
||||
transporterRequestMessageListener.off();
|
||||
clientRemoveListener.off();
|
||||
realtimeUserSetActivityListener.off();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,8 @@ export enum MessageType {
|
|||
REALTIME_USER_STATE_SET = 'REALTIME_USER_STATE_SET',
|
||||
REALTIME_USER_SINGLE_UPDATE = 'REALTIME_USER_SINGLE_UPDATE',
|
||||
REALTIME_USER_STATE_REQUEST = 'REALTIME_USER_STATE_REQUEST',
|
||||
REALTIME_USER_SET_ACTIVITY = 'REALTIME_USER_SET_ACTIVITY',
|
||||
|
||||
READY = 'READY'
|
||||
}
|
||||
|
||||
|
@ -30,6 +32,10 @@ export interface MessagePayloads {
|
|||
}
|
||||
}
|
||||
[MessageType.REALTIME_USER_SINGLE_UPDATE]: RemoteCursor
|
||||
|
||||
[MessageType.REALTIME_USER_SET_ACTIVITY]: {
|
||||
active: boolean
|
||||
}
|
||||
}
|
||||
|
||||
export type Message<T extends MessageType> = T extends keyof MessagePayloads
|
||||
|
|
|
@ -269,7 +269,7 @@
|
|||
},
|
||||
"onlineStatus": {
|
||||
"online": "Online",
|
||||
"noUsers": "No users online"
|
||||
"you": "(You)"
|
||||
},
|
||||
"error": {
|
||||
"locked": {
|
||||
|
|
|
@ -28,6 +28,7 @@ import { useOnNoteDeleted } from './hooks/yjs/use-on-note-deleted'
|
|||
import { useRealtimeConnection } from './hooks/yjs/use-realtime-connection'
|
||||
import { useRealtimeDoc } from './hooks/yjs/use-realtime-doc'
|
||||
import { useReceiveRealtimeUsers } from './hooks/yjs/use-receive-realtime-users'
|
||||
import { useSendRealtimeActivity } from './hooks/yjs/use-send-realtime-activity'
|
||||
import { useYDocSyncClientAdapter } from './hooks/yjs/use-y-doc-sync-client-adapter'
|
||||
import { useLinter } from './linter/linter'
|
||||
import { MaxLengthWarning } from './max-length-warning/max-length-warning'
|
||||
|
@ -82,6 +83,7 @@ export const EditorPane: React.FC<EditorPaneProps> = ({ scrollState, onScroll, o
|
|||
|
||||
useBindYTextToRedux(realtimeDoc)
|
||||
useReceiveRealtimeUsers(messageTransporter)
|
||||
useSendRealtimeActivity(messageTransporter)
|
||||
|
||||
const extensions = useMemo(
|
||||
() => [
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useIsDocumentVisible } from '../../../../../hooks/common/use-is-document-visible'
|
||||
import type { MessageTransporter } from '@hedgedoc/commons'
|
||||
import { MessageType } from '@hedgedoc/commons'
|
||||
import { useEffect } from 'react'
|
||||
import { useIdle } from 'react-use'
|
||||
|
||||
const INACTIVITY_TIMEOUT_SECONDS = 30
|
||||
|
||||
/**
|
||||
* Sends the activity state (based on the fact if the tab is focused) to the backend.
|
||||
*
|
||||
* @param messageTransporter The message transporter that handles the connection to the backend
|
||||
*/
|
||||
export const useSendRealtimeActivity = (messageTransporter: MessageTransporter) => {
|
||||
const active = useIsDocumentVisible()
|
||||
const idling = useIdle(INACTIVITY_TIMEOUT_SECONDS * 1000)
|
||||
|
||||
useEffect(() => {
|
||||
messageTransporter.doAsSoonAsReady(() => {
|
||||
messageTransporter.sendMessage({
|
||||
type: MessageType.REALTIME_USER_SET_ACTIVITY,
|
||||
payload: {
|
||||
active: active && !idling
|
||||
}
|
||||
})
|
||||
})
|
||||
}, [active, idling, messageTransporter])
|
||||
}
|
|
@ -20,11 +20,3 @@
|
|||
flex: 1 1 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.active-indicator-container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex: 0 0 20px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
|
|
@ -9,22 +9,28 @@ import { createCursorCssClass } from '../../editor-pane/codemirror-extensions/re
|
|||
import { ActiveIndicator } from '../users-online-sidebar-menu/active-indicator'
|
||||
import styles from './user-line.module.scss'
|
||||
import React, { useMemo } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
||||
export interface UserLineProps {
|
||||
username: string | null
|
||||
displayName: string
|
||||
active: boolean
|
||||
own?: boolean
|
||||
color: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a user in the realtime activity status.
|
||||
*
|
||||
* @param username The name of the user to show.
|
||||
* @param color The color of the user's edits.
|
||||
* @param status The user's current online status.
|
||||
* @param username The username of the user to show
|
||||
* @param color The color of the user's edits
|
||||
* @param status The user's current online status
|
||||
* @param displayName The actual name that should be displayed
|
||||
* @param own defines if this user line renders the own user or another one
|
||||
*/
|
||||
export const UserLine: React.FC<UserLineProps> = ({ username, displayName, active, color }) => {
|
||||
export const UserLine: React.FC<UserLineProps> = ({ username, displayName, active, own = false, color }) => {
|
||||
useTranslation()
|
||||
|
||||
const avatar = useMemo(() => {
|
||||
if (username) {
|
||||
return (
|
||||
|
@ -48,9 +54,13 @@ export const UserLine: React.FC<UserLineProps> = ({ username, displayName, activ
|
|||
)}`}
|
||||
/>
|
||||
{avatar}
|
||||
<div className={styles['active-indicator-container']}>
|
||||
{own ? (
|
||||
<span className={'px-1'}>
|
||||
<Trans i18nKey={'editor.onlineStatus.you'}></Trans>
|
||||
</span>
|
||||
) : (
|
||||
<ActiveIndicator active={active} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -18,3 +18,11 @@
|
|||
background-color: #d20000;
|
||||
}
|
||||
}
|
||||
|
||||
.active-indicator-container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex: 0 0 20px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
|
|
@ -16,5 +16,9 @@ export interface ActiveIndicatorProps {
|
|||
* @param status The state of the indicator to render
|
||||
*/
|
||||
export const ActiveIndicator: React.FC<ActiveIndicatorProps> = ({ active }) => {
|
||||
return <span className={`${styles['activeIndicator']} ${active ? styles.active : styles.inactive}`} />
|
||||
return (
|
||||
<div className={styles['active-indicator-container']}>
|
||||
<span className={`${styles['activeIndicator']} ${active ? styles.active : styles.inactive}`} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ export const OwnUserLine: React.FC = () => {
|
|||
|
||||
return (
|
||||
<SidebarButton>
|
||||
<UserLine displayName={ownDisplayname} username={ownUsername} color={ownStyleIndex} active={true} />
|
||||
<UserLine displayName={ownDisplayname} username={ownUsername} color={ownStyleIndex} active={true} own={true} />
|
||||
</SidebarButton>
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue