mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-19 05:55:34 +09:00
fix(app): backport rendering improvements (#1580)
This commit is contained in:
1 parent
0c291a01da
commit
42cdc1f877
513 files changed
+98743
-70397
No files matched your search
@@ -3,23 +3,25 @@
|
||||
import {assign, getInitialSnapshot, type SnapshotFrom, setup, transition} from 'xstate';
|
||||
|
||||
export const LOCAL_TYPING_REMOTE_SEND_DELAY_MS = 1500;
|
||||
export const LOCAL_TYPING_REMOTE_REFRESH_MS = 10000 * 0.8;
|
||||
export const LOCAL_TYPING_REMOTE_REFRESH_MS = 8000;
|
||||
export const LOCAL_TYPING_IDLE_RESET_MS = 10000;
|
||||
|
||||
export interface LocalTypingMachineInput {
|
||||
channelId?: string | null;
|
||||
localTyping?: boolean;
|
||||
remoteCooldownChannelId?: string | null;
|
||||
remoteCooldownUntil?: number | null;
|
||||
remotePending?: boolean;
|
||||
remotePendingVersion?: number;
|
||||
}
|
||||
|
||||
export interface LocalTypingMachineContext {
|
||||
channelId: string | null;
|
||||
localTyping: boolean;
|
||||
lastChangeAt: number;
|
||||
remoteCooldownChannelId: string | null;
|
||||
remoteCooldownUntil: number | null;
|
||||
remotePending: boolean;
|
||||
lastRemoteSentAt: number | null;
|
||||
remoteSendAt: number | null;
|
||||
remotePendingVersion: number;
|
||||
}
|
||||
|
||||
@@ -38,6 +40,11 @@ export type LocalTypingMachineEvent =
|
||||
channelId: string;
|
||||
now: number;
|
||||
pendingVersion: number;
|
||||
}
|
||||
| {
|
||||
type: 'localTyping.idleElapsed';
|
||||
channelId: string;
|
||||
now: number;
|
||||
};
|
||||
|
||||
export interface LocalTypingModel {
|
||||
@@ -47,21 +54,49 @@ export interface LocalTypingModel {
|
||||
remotePendingVersion: number;
|
||||
remoteCooldownChannelId: string | null;
|
||||
remoteCooldownUntil: number | null;
|
||||
remoteSendAt: number | null;
|
||||
remoteSendDelayMs: number;
|
||||
localIdleAt: number | null;
|
||||
}
|
||||
|
||||
export type LocalTypingSnapshot = SnapshotFrom<typeof localTypingStateMachine>;
|
||||
|
||||
function canScheduleRemoteSend(context: LocalTypingMachineContext, channelId: string, now: number): boolean {
|
||||
return (
|
||||
context.remoteCooldownChannelId !== channelId ||
|
||||
context.remoteCooldownUntil == null ||
|
||||
context.remoteCooldownUntil <= now
|
||||
);
|
||||
function initialLocalTypingContext(input: LocalTypingMachineInput = {}): LocalTypingMachineContext {
|
||||
return {
|
||||
channelId: input.channelId == null ? null : input.channelId,
|
||||
localTyping: input.localTyping == null ? false : input.localTyping,
|
||||
lastChangeAt: 0,
|
||||
remoteCooldownChannelId: input.remoteCooldownChannelId == null ? null : input.remoteCooldownChannelId,
|
||||
remoteCooldownUntil: input.remoteCooldownUntil == null ? null : input.remoteCooldownUntil,
|
||||
lastRemoteSentAt: null,
|
||||
remoteSendAt: null,
|
||||
remotePendingVersion: input.remotePendingVersion == null ? 0 : input.remotePendingVersion,
|
||||
};
|
||||
}
|
||||
|
||||
function matchesActiveChannel(context: LocalTypingMachineContext, channelId: string): boolean {
|
||||
return context.localTyping && context.channelId === channelId;
|
||||
function resetLocalTypingContext(context: LocalTypingMachineContext): LocalTypingMachineContext {
|
||||
return {
|
||||
channelId: null,
|
||||
localTyping: false,
|
||||
lastChangeAt: 0,
|
||||
remoteCooldownChannelId: context.remoteCooldownChannelId,
|
||||
remoteCooldownUntil: context.remoteCooldownUntil,
|
||||
lastRemoteSentAt: null,
|
||||
remoteSendAt: null,
|
||||
remotePendingVersion: context.remotePendingVersion,
|
||||
};
|
||||
}
|
||||
|
||||
function isRemoteCooldownActive(context: LocalTypingMachineContext, channelId: string, now: number): boolean {
|
||||
const cooldownUntil = context.remoteCooldownUntil == null ? 0 : context.remoteCooldownUntil;
|
||||
return context.remoteCooldownChannelId === channelId && cooldownUntil > now;
|
||||
}
|
||||
|
||||
function desiredLocalIdleAt(context: LocalTypingMachineContext): number | null {
|
||||
if (!context.localTyping || context.channelId == null) {
|
||||
return null;
|
||||
}
|
||||
return context.lastChangeAt + LOCAL_TYPING_IDLE_RESET_MS;
|
||||
}
|
||||
|
||||
export const localTypingStateMachine = setup({
|
||||
@@ -75,50 +110,73 @@ export const localTypingStateMachine = setup({
|
||||
if (event.type !== 'localTyping.started') {
|
||||
return {};
|
||||
}
|
||||
const shouldScheduleRemote = canScheduleRemoteSend(context, event.channelId, event.now);
|
||||
if (!context.localTyping || context.channelId !== event.channelId) {
|
||||
const shouldScheduleRemote = !isRemoteCooldownActive(context, event.channelId, event.now);
|
||||
return {
|
||||
channelId: event.channelId,
|
||||
localTyping: true,
|
||||
lastChangeAt: event.now,
|
||||
lastRemoteSentAt: null,
|
||||
remoteSendAt: shouldScheduleRemote ? event.now + LOCAL_TYPING_REMOTE_SEND_DELAY_MS : null,
|
||||
remotePendingVersion: shouldScheduleRemote ? context.remotePendingVersion + 1 : context.remotePendingVersion,
|
||||
};
|
||||
}
|
||||
if (context.remoteSendAt != null) {
|
||||
return {
|
||||
lastChangeAt: event.now,
|
||||
};
|
||||
}
|
||||
if (context.lastRemoteSentAt != null && event.now > context.lastRemoteSentAt) {
|
||||
return {
|
||||
lastChangeAt: event.now,
|
||||
remoteSendAt: context.lastRemoteSentAt + LOCAL_TYPING_REMOTE_REFRESH_MS,
|
||||
remotePendingVersion: context.remotePendingVersion + 1,
|
||||
};
|
||||
}
|
||||
const shouldScheduleRemote = !isRemoteCooldownActive(context, event.channelId, event.now);
|
||||
return {
|
||||
channelId: event.channelId,
|
||||
localTyping: true,
|
||||
remotePending: shouldScheduleRemote,
|
||||
lastChangeAt: event.now,
|
||||
remoteSendAt: shouldScheduleRemote ? event.now + LOCAL_TYPING_REMOTE_SEND_DELAY_MS : null,
|
||||
remotePendingVersion: shouldScheduleRemote ? context.remotePendingVersion + 1 : context.remotePendingVersion,
|
||||
};
|
||||
}),
|
||||
stopLocalTyping: assign(({context, event}) => {
|
||||
if (event.type !== 'localTyping.stopped' || !matchesActiveChannel(context, event.channelId)) {
|
||||
if (event.type !== 'localTyping.stopped' || !context.localTyping || context.channelId !== event.channelId) {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
channelId: null,
|
||||
localTyping: false,
|
||||
remotePending: false,
|
||||
};
|
||||
return resetLocalTypingContext(context);
|
||||
}),
|
||||
markRemoteSent: assign(({context, event}) => {
|
||||
if (
|
||||
event.type !== 'localTyping.remoteSent' ||
|
||||
!context.localTyping ||
|
||||
context.channelId !== event.channelId ||
|
||||
context.remotePendingVersion !== event.pendingVersion ||
|
||||
!context.remotePending
|
||||
context.remotePendingVersion !== event.pendingVersion
|
||||
) {
|
||||
return {};
|
||||
}
|
||||
return {
|
||||
remotePending: false,
|
||||
remoteCooldownChannelId: event.channelId,
|
||||
remoteCooldownUntil: event.now + LOCAL_TYPING_REMOTE_REFRESH_MS,
|
||||
lastRemoteSentAt: event.now,
|
||||
remoteSendAt: null,
|
||||
};
|
||||
}),
|
||||
markIdleElapsed: assign(({context, event}) => {
|
||||
if (
|
||||
event.type !== 'localTyping.idleElapsed' ||
|
||||
!context.localTyping ||
|
||||
context.channelId !== event.channelId ||
|
||||
event.now - context.lastChangeAt < LOCAL_TYPING_IDLE_RESET_MS
|
||||
) {
|
||||
return {};
|
||||
}
|
||||
return resetLocalTypingContext(context);
|
||||
}),
|
||||
},
|
||||
}).createMachine({
|
||||
id: 'localTyping',
|
||||
context: ({input}) => ({
|
||||
channelId: input.channelId ?? null,
|
||||
localTyping: input.localTyping ?? false,
|
||||
remoteCooldownChannelId: input.remoteCooldownChannelId ?? null,
|
||||
remoteCooldownUntil: input.remoteCooldownUntil ?? null,
|
||||
remotePending: input.remotePending ?? false,
|
||||
remotePendingVersion: input.remotePendingVersion ?? 0,
|
||||
}),
|
||||
context: ({input}) => initialLocalTypingContext(input),
|
||||
initial: 'ready',
|
||||
states: {
|
||||
ready: {
|
||||
@@ -126,6 +184,7 @@ export const localTypingStateMachine = setup({
|
||||
'localTyping.started': {actions: 'startLocalTyping'},
|
||||
'localTyping.stopped': {actions: 'stopLocalTyping'},
|
||||
'localTyping.remoteSent': {actions: 'markRemoteSent'},
|
||||
'localTyping.idleElapsed': {actions: 'markIdleElapsed'},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -146,10 +205,12 @@ export function selectLocalTypingModel(snapshot: LocalTypingSnapshot): LocalTypi
|
||||
return {
|
||||
channelId: snapshot.context.channelId,
|
||||
localTyping: snapshot.context.localTyping,
|
||||
remotePending: snapshot.context.remotePending,
|
||||
remotePending: snapshot.context.remoteSendAt != null,
|
||||
remotePendingVersion: snapshot.context.remotePendingVersion,
|
||||
remoteCooldownChannelId: snapshot.context.remoteCooldownChannelId,
|
||||
remoteCooldownUntil: snapshot.context.remoteCooldownUntil,
|
||||
remoteSendAt: snapshot.context.remoteSendAt,
|
||||
remoteSendDelayMs: LOCAL_TYPING_REMOTE_SEND_DELAY_MS,
|
||||
localIdleAt: desiredLocalIdleAt(snapshot.context),
|
||||
};
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {Message} from '@fluxer/schema/src/domains/message/MessageResponseSchemas';
|
||||
import {action, makeAutoObservable} from 'mobx';
|
||||
import {action, makeAutoObservable, observable} from 'mobx';
|
||||
|
||||
type TypingEntry = Readonly<{
|
||||
expiresAt: number;
|
||||
@@ -11,8 +11,13 @@ type TypingEntriesByChannel = Record<string, Record<string, TypingEntry>>;
|
||||
type TypingUsersCacheEntry = Readonly<{
|
||||
users: ReadonlyArray<string>;
|
||||
expiresAt: number | null;
|
||||
version: number;
|
||||
}>;
|
||||
|
||||
export interface LocalTypingResetOwner {
|
||||
reset(): void;
|
||||
}
|
||||
|
||||
export const TYPING_INDICATOR_TIMEOUT_MS = 10000;
|
||||
const EMPTY_TYPING_USERS: ReadonlyArray<string> = Object.freeze([]);
|
||||
|
||||
@@ -20,14 +25,21 @@ class TypingIndicator {
|
||||
remoteTypingUsersByChannel: TypingEntriesByChannel = {};
|
||||
localTypingUsersByChannel: TypingEntriesByChannel = {};
|
||||
private typingUsersCacheByChannel: Record<string, TypingUsersCacheEntry | undefined> = {};
|
||||
private typingUsersVersionByChannel = new Map<string, number>();
|
||||
private expiryTimerId: NodeJS.Timeout | null = null;
|
||||
private localTypingResetOwner: LocalTypingResetOwner | null = null;
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable<this, 'typingUsersCacheByChannel' | 'expiryTimerId'>(
|
||||
makeAutoObservable<
|
||||
this,
|
||||
'typingUsersCacheByChannel' | 'typingUsersVersionByChannel' | 'expiryTimerId' | 'localTypingResetOwner'
|
||||
>(
|
||||
this,
|
||||
{
|
||||
expiryTimerId: false,
|
||||
localTypingResetOwner: false,
|
||||
typingUsersCacheByChannel: false,
|
||||
typingUsersVersionByChannel: observable.shallow,
|
||||
},
|
||||
{autoBind: true},
|
||||
);
|
||||
@@ -35,8 +47,10 @@ class TypingIndicator {
|
||||
|
||||
getTypingUsers(channelId: string): ReadonlyArray<string> {
|
||||
const now = Date.now();
|
||||
const storedVersion = this.typingUsersVersionByChannel.get(channelId);
|
||||
const version = storedVersion == null ? 0 : storedVersion;
|
||||
const cached = this.typingUsersCacheByChannel[channelId];
|
||||
if (cached && (cached.expiresAt === null || cached.expiresAt > now)) {
|
||||
if (cached && cached.version === version && (cached.expiresAt === null || cached.expiresAt > now)) {
|
||||
return cached.users;
|
||||
}
|
||||
const {expiresAt, users} = this.collectTypingUsers(channelId, now);
|
||||
@@ -44,6 +58,7 @@ class TypingIndicator {
|
||||
this.typingUsersCacheByChannel[channelId] = {
|
||||
expiresAt,
|
||||
users: typedUsers,
|
||||
version,
|
||||
};
|
||||
return typedUsers;
|
||||
}
|
||||
@@ -105,7 +120,6 @@ class TypingIndicator {
|
||||
|
||||
private upsertTypingEntry(entriesByChannel: TypingEntriesByChannel, channelId: string, userId: string): void {
|
||||
const now = Date.now();
|
||||
this.removeExpiredTypingEntries(now);
|
||||
if (!entriesByChannel[channelId]) {
|
||||
entriesByChannel[channelId] = {};
|
||||
}
|
||||
@@ -116,7 +130,7 @@ class TypingIndicator {
|
||||
expiresAt: now + TYPING_INDICATOR_TIMEOUT_MS,
|
||||
};
|
||||
if (!wasVisible) {
|
||||
this.invalidateTypingUsers(channelId);
|
||||
this.invalidateTypingUsers(channelId, now);
|
||||
}
|
||||
this.scheduleNextExpiryTimer(now);
|
||||
}
|
||||
@@ -130,8 +144,9 @@ class TypingIndicator {
|
||||
if (Object.keys(channelUsers).length === 0) {
|
||||
delete entriesByChannel[channelId];
|
||||
}
|
||||
this.invalidateTypingUsers(channelId);
|
||||
this.scheduleNextExpiryTimer(Date.now());
|
||||
const now = Date.now();
|
||||
this.invalidateTypingUsers(channelId, now);
|
||||
this.scheduleNextExpiryTimer(now);
|
||||
}
|
||||
|
||||
private removeExpiredTypingEntries(now: number): void {
|
||||
@@ -153,7 +168,7 @@ class TypingIndicator {
|
||||
delete entriesByChannel[channelId];
|
||||
}
|
||||
if (removed) {
|
||||
this.invalidateTypingUsers(channelId);
|
||||
this.invalidateTypingUsers(channelId, now);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -198,8 +213,24 @@ class TypingIndicator {
|
||||
this.expiryTimerId = null;
|
||||
}
|
||||
|
||||
private invalidateTypingUsers(channelId: string): void {
|
||||
private hasActiveTypingUsers(channelId: string, now: number): boolean {
|
||||
const hasActiveEntry = (entries: Record<string, TypingEntry> | undefined) =>
|
||||
entries !== undefined && Object.values(entries).some((entry) => entry.expiresAt > now);
|
||||
return (
|
||||
hasActiveEntry(this.remoteTypingUsersByChannel[channelId]) ||
|
||||
hasActiveEntry(this.localTypingUsersByChannel[channelId])
|
||||
);
|
||||
}
|
||||
|
||||
private invalidateTypingUsers(channelId: string, now: number): void {
|
||||
delete this.typingUsersCacheByChannel[channelId];
|
||||
if (!this.hasActiveTypingUsers(channelId, now)) {
|
||||
this.typingUsersVersionByChannel.delete(channelId);
|
||||
return;
|
||||
}
|
||||
const currentVersion = this.typingUsersVersionByChannel.get(channelId);
|
||||
const nextVersion = currentVersion == null ? 1 : currentVersion + 1;
|
||||
this.typingUsersVersionByChannel.set(channelId, nextVersion);
|
||||
}
|
||||
|
||||
@action
|
||||
@@ -210,10 +241,22 @@ class TypingIndicator {
|
||||
|
||||
@action
|
||||
reset(): void {
|
||||
const resetOwner = this.localTypingResetOwner;
|
||||
if (resetOwner != null) {
|
||||
resetOwner.reset();
|
||||
}
|
||||
this.clearExpiryTimer();
|
||||
this.remoteTypingUsersByChannel = {};
|
||||
this.localTypingUsersByChannel = {};
|
||||
this.typingUsersCacheByChannel = {};
|
||||
this.typingUsersVersionByChannel.clear();
|
||||
}
|
||||
|
||||
registerLocalTypingResetOwner(owner: LocalTypingResetOwner): void {
|
||||
if (this.localTypingResetOwner != null) {
|
||||
throw new Error('Local typing reset owner is already registered');
|
||||
}
|
||||
this.localTypingResetOwner = owner;
|
||||
}
|
||||
|
||||
@action
|
||||
|
||||
@@ -10,10 +10,19 @@ import {
|
||||
selectLocalTypingModel,
|
||||
transitionLocalTypingSnapshot,
|
||||
} from '@app/features/typing/state/LocalTypingStateMachine';
|
||||
import TypingIndicator from '@app/features/typing/state/TypingIndicator';
|
||||
|
||||
class TypingManager {
|
||||
private snapshot: LocalTypingSnapshot = createLocalTypingSnapshot();
|
||||
private timeoutId: NodeJS.Timeout | null = null;
|
||||
private remoteSendTimerId: NodeJS.Timeout | null = null;
|
||||
private localIdleTimerId: NodeJS.Timeout | null = null;
|
||||
private scheduledRemoteSendAt: number | null = null;
|
||||
private scheduledRemoteChannelId: string | null = null;
|
||||
private scheduledRemotePendingVersion: number | null = null;
|
||||
private scheduledLocalIdleAt: number | null = null;
|
||||
private scheduledLocalIdleChannelId: string | null = null;
|
||||
private remoteSendTimerGeneration = 0;
|
||||
private localIdleTimerGeneration = 0;
|
||||
|
||||
typing(channelId: string): void {
|
||||
const currentUserId = Authentication.currentUserId;
|
||||
@@ -32,9 +41,6 @@ class TypingManager {
|
||||
|
||||
clear(channelId: string): void {
|
||||
const currentUserId = Authentication.currentUserId;
|
||||
if (!currentUserId) {
|
||||
return;
|
||||
}
|
||||
this.transition(
|
||||
{
|
||||
type: 'localTyping.stopped',
|
||||
@@ -44,12 +50,21 @@ class TypingManager {
|
||||
);
|
||||
}
|
||||
|
||||
private transition(event: LocalTypingMachineEvent, currentUserId: string): void {
|
||||
reset(): void {
|
||||
this.clearRemoteSendTimer();
|
||||
this.clearLocalIdleTimer();
|
||||
this.snapshot = createLocalTypingSnapshot();
|
||||
}
|
||||
|
||||
private transition(event: LocalTypingMachineEvent, currentUserId: string | null): void {
|
||||
const previousModel = selectLocalTypingModel(this.snapshot);
|
||||
this.snapshot = transitionLocalTypingSnapshot(this.snapshot, event);
|
||||
const nextModel = selectLocalTypingModel(this.snapshot);
|
||||
this.applyLocalTypingMutation(previousModel, nextModel, currentUserId, event);
|
||||
this.applyRemoteSendSchedule(previousModel, nextModel);
|
||||
if (currentUserId != null) {
|
||||
this.applyLocalTypingMutation(previousModel, nextModel, currentUserId, event);
|
||||
}
|
||||
this.applyRemoteSendSchedule(nextModel);
|
||||
this.applyLocalIdleSchedule(nextModel);
|
||||
}
|
||||
|
||||
private applyLocalTypingMutation(
|
||||
@@ -70,33 +85,68 @@ class TypingManager {
|
||||
}
|
||||
}
|
||||
|
||||
private applyRemoteSendSchedule(previousModel: LocalTypingModel, nextModel: LocalTypingModel): void {
|
||||
if (!nextModel.remotePending || !nextModel.channelId) {
|
||||
this.clearRemoteSendTimer();
|
||||
return;
|
||||
}
|
||||
private applyRemoteSendSchedule(nextModel: LocalTypingModel): void {
|
||||
if (
|
||||
previousModel.remotePending &&
|
||||
previousModel.channelId === nextModel.channelId &&
|
||||
previousModel.remotePendingVersion === nextModel.remotePendingVersion
|
||||
nextModel.remoteSendAt === this.scheduledRemoteSendAt &&
|
||||
nextModel.channelId === this.scheduledRemoteChannelId &&
|
||||
nextModel.remotePendingVersion === this.scheduledRemotePendingVersion
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.clearRemoteSendTimer();
|
||||
const channelId = nextModel.channelId;
|
||||
const pendingVersion = nextModel.remotePendingVersion;
|
||||
this.timeoutId = setTimeout(() => {
|
||||
this.sendTyping(channelId, pendingVersion);
|
||||
}, nextModel.remoteSendDelayMs);
|
||||
}
|
||||
|
||||
private sendTyping(channelId: string, pendingVersion: number): void {
|
||||
TypingCommands.sendTyping(channelId);
|
||||
this.timeoutId = null;
|
||||
const currentUserId = Authentication.currentUserId;
|
||||
if (!currentUserId) {
|
||||
if (nextModel.remoteSendAt == null || !nextModel.channelId) {
|
||||
return;
|
||||
}
|
||||
const channelId = nextModel.channelId;
|
||||
const pendingVersion = nextModel.remotePendingVersion;
|
||||
const remoteSendAt = nextModel.remoteSendAt;
|
||||
const timerGeneration = this.remoteSendTimerGeneration;
|
||||
this.scheduledRemoteSendAt = remoteSendAt;
|
||||
this.scheduledRemoteChannelId = channelId;
|
||||
this.scheduledRemotePendingVersion = pendingVersion;
|
||||
this.remoteSendTimerId = setTimeout(
|
||||
() => this.sendTyping(channelId, pendingVersion, timerGeneration),
|
||||
Math.max(0, remoteSendAt - Date.now()),
|
||||
);
|
||||
}
|
||||
|
||||
private applyLocalIdleSchedule(nextModel: LocalTypingModel): void {
|
||||
if (
|
||||
nextModel.localIdleAt === this.scheduledLocalIdleAt &&
|
||||
nextModel.channelId === this.scheduledLocalIdleChannelId
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.clearLocalIdleTimer();
|
||||
if (nextModel.localIdleAt == null || !nextModel.channelId) {
|
||||
return;
|
||||
}
|
||||
const channelId = nextModel.channelId;
|
||||
const localIdleAt = nextModel.localIdleAt;
|
||||
const timerGeneration = this.localIdleTimerGeneration;
|
||||
this.scheduledLocalIdleAt = localIdleAt;
|
||||
this.scheduledLocalIdleChannelId = channelId;
|
||||
this.localIdleTimerId = setTimeout(
|
||||
() => this.expireLocalTyping(channelId, timerGeneration),
|
||||
Math.max(0, localIdleAt - Date.now()),
|
||||
);
|
||||
}
|
||||
|
||||
private sendTyping(channelId: string, pendingVersion: number, timerGeneration: number): void {
|
||||
if (timerGeneration !== this.remoteSendTimerGeneration) {
|
||||
return;
|
||||
}
|
||||
this.remoteSendTimerId = null;
|
||||
this.scheduledRemoteSendAt = null;
|
||||
this.scheduledRemoteChannelId = null;
|
||||
this.scheduledRemotePendingVersion = null;
|
||||
const currentUserId = Authentication.currentUserId;
|
||||
if (!currentUserId) {
|
||||
this.clear(channelId);
|
||||
return;
|
||||
}
|
||||
TypingCommands.sendTyping(channelId);
|
||||
TypingCommands.startLocalTyping(channelId, currentUserId);
|
||||
this.transition(
|
||||
{
|
||||
type: 'localTyping.remoteSent',
|
||||
@@ -108,13 +158,49 @@ class TypingManager {
|
||||
);
|
||||
}
|
||||
|
||||
private clearRemoteSendTimer(): void {
|
||||
if (!this.timeoutId) {
|
||||
private expireLocalTyping(channelId: string, timerGeneration: number): void {
|
||||
if (timerGeneration !== this.localIdleTimerGeneration) {
|
||||
return;
|
||||
}
|
||||
clearTimeout(this.timeoutId);
|
||||
this.timeoutId = null;
|
||||
this.localIdleTimerId = null;
|
||||
this.scheduledLocalIdleAt = null;
|
||||
this.scheduledLocalIdleChannelId = null;
|
||||
const currentUserId = Authentication.currentUserId;
|
||||
if (!currentUserId) {
|
||||
this.clear(channelId);
|
||||
return;
|
||||
}
|
||||
this.transition(
|
||||
{
|
||||
type: 'localTyping.idleElapsed',
|
||||
channelId,
|
||||
now: Date.now(),
|
||||
},
|
||||
currentUserId,
|
||||
);
|
||||
}
|
||||
|
||||
private clearRemoteSendTimer(): void {
|
||||
this.remoteSendTimerGeneration += 1;
|
||||
if (this.remoteSendTimerId != null) {
|
||||
clearTimeout(this.remoteSendTimerId);
|
||||
this.remoteSendTimerId = null;
|
||||
}
|
||||
this.scheduledRemoteSendAt = null;
|
||||
this.scheduledRemoteChannelId = null;
|
||||
this.scheduledRemotePendingVersion = null;
|
||||
}
|
||||
|
||||
private clearLocalIdleTimer(): void {
|
||||
this.localIdleTimerGeneration += 1;
|
||||
if (this.localIdleTimerId != null) {
|
||||
clearTimeout(this.localIdleTimerId);
|
||||
this.localIdleTimerId = null;
|
||||
}
|
||||
this.scheduledLocalIdleAt = null;
|
||||
this.scheduledLocalIdleChannelId = null;
|
||||
}
|
||||
}
|
||||
|
||||
export const TypingUtils = new TypingManager();
|
||||
TypingIndicator.registerLocalTypingResetOwner(TypingUtils);
|
||||
Reference in new issue
Block a user