DEV: Deleted the "ChatComposerPresenceManager"... (#30098)

...since it was mostly duplicating the work the "ComposerPresenceManager" was doing.

So now the #chat composer uses the same "presence manager" as the composer, benefiting from the "hide presence" checks, with the only difference that the "keep alive" timeout is 5s for chat and 10s for topics/posts.
This commit is contained in:
Régis Hanol 2024-12-04 12:21:55 +01:00 committed by GitHub
parent 838d7478c1
commit 1ce12ae718
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 79 additions and 139 deletions

View File

@ -54,7 +54,6 @@ export default class ChatChannel extends Component {
@service chat;
@service chatApi;
@service chatChannelsManager;
@service chatComposerPresenceManager;
@service chatDraftsManager;
@service chatEmojiPickerManager;
@service chatStateManager;

View File

@ -30,13 +30,15 @@ import { chatComposerButtons } from "discourse/plugins/chat/discourse/lib/chat-c
import ChatMessageInteractor from "discourse/plugins/chat/discourse/lib/chat-message-interactor";
import TextareaInteractor from "discourse/plugins/chat/discourse/lib/textarea-interactor";
const CHAT_PRESENCE_KEEP_ALIVE = 5 * 1000; // 5 seconds
export default class ChatComposer extends Component {
@service capabilities;
@service site;
@service siteSettings;
@service store;
@service chat;
@service chatComposerPresenceManager;
@service composerPresenceManager;
@service chatComposerWarningsTracker;
@service appEvents;
@service chatEmojiReactionStore;
@ -256,9 +258,10 @@ export default class ChatComposer extends Component {
return;
}
this.chatComposerPresenceManager.notifyState(
this.composerPresenceManager.notifyState(
this.presenceChannelName,
!this.draft.editing && this.hasContent
!this.draft.editing && this.hasContent,
CHAT_PRESENCE_KEEP_ALIVE
);
}

View File

@ -46,7 +46,6 @@ export default class ChatThread extends Component {
@service capabilities;
@service chat;
@service chatApi;
@service chatComposerPresenceManager;
@service chatHistory;
@service chatDraftsManager;
@service chatThreadComposer;

View File

@ -1,54 +0,0 @@
import { cancel, debounce } from "@ember/runloop";
import Service, { service } from "@ember/service";
import { isTesting } from "discourse-common/config/environment";
const KEEP_ALIVE_DURATION_SECONDS = 10;
// This service is loosely based on discourse-presence's ComposerPresenceManager service
// It is a singleton which receives notifications each time the value of the chat composer changes
// This service ensures that a single browser can only be 'replying' to a single chatChannel at
// one time, and automatically 'leaves' the channel if the composer value hasn't changed for 10 seconds
export default class ChatComposerPresenceManager extends Service {
@service presence;
willDestroy() {
this.leave();
}
notifyState(channelName, replying) {
if (!replying) {
this.leave();
return;
}
if (this._channelName !== channelName) {
this._enter(channelName);
this._channelName = channelName;
}
if (!isTesting()) {
this._autoLeaveTimer = debounce(
this,
this.leave,
KEEP_ALIVE_DURATION_SECONDS * 1000
);
}
}
leave() {
this._presentChannel?.leave();
this._presentChannel = null;
this._channelName = null;
if (this._autoLeaveTimer) {
cancel(this._autoLeaveTimer);
this._autoLeaveTimer = null;
}
}
_enter(channelName) {
this.leave();
this._presentChannel = this.presence.getChannel(channelName);
this._presentChannel.enter();
}
}

View File

@ -18,60 +18,58 @@ export default class ComposerPresenceDisplay extends Component {
@tracked editChannel;
setupReplyChannel = helperFn((_, on) => {
const topic = this.args.model.topic;
const { topic } = this.args.model;
if (!topic || !this.isReply) {
return;
}
const replyChannel = this.presence.getChannel(
`/discourse-presence/reply/${topic.id}`
);
replyChannel.subscribe();
const name = `/discourse-presence/reply/${topic.id}`;
const replyChannel = this.presence.getChannel(name);
this.replyChannel = replyChannel;
replyChannel.subscribe();
on.cleanup(() => replyChannel.unsubscribe());
});
setupWhisperChannel = helperFn((_, on) => {
if (
!this.args.model.topic ||
!this.isReply ||
!this.currentUser.staff ||
!this.currentUser.whisperer
) {
const { topic } = this.args.model;
const { whisperer } = this.currentUser;
if (!topic || !this.isReply || !whisperer) {
return;
}
const whisperChannel = this.presence.getChannel(
`/discourse-presence/whisper/${this.args.model.topic.id}`
);
whisperChannel.subscribe();
const name = `/discourse-presence/whisper/${topic.id}`;
const whisperChannel = this.presence.getChannel(name);
this.whisperChannel = whisperChannel;
whisperChannel.subscribe();
on.cleanup(() => whisperChannel.unsubscribe());
});
setupEditChannel = helperFn((_, on) => {
if (!this.args.model.post || !this.isEdit) {
const { post } = this.args.model;
if (!post || !this.isEdit) {
return;
}
const editChannel = this.presence.getChannel(
`/discourse-presence/edit/${this.args.model.post.id}`
);
editChannel.subscribe();
const name = `/discourse-presence/edit/${post.id}`;
const editChannel = this.presence.getChannel(name);
this.editChannel = editChannel;
editChannel.subscribe();
on.cleanup(() => editChannel.unsubscribe());
});
notifyState = helperFn((_, on) => {
const { topic, post, reply } = this.args.model;
const raw = this.isEdit ? post?.raw || "" : "";
const { topic, post, replyDirty } = this.args.model;
const entity = this.isEdit ? post : topic;
if (reply !== raw) {
this.composerPresenceManager.notifyState(this.state, entity?.id);
if (entity) {
const name = `/discourse-presence/${this.state}/${entity.id}`;
this.composerPresenceManager.notifyState(name, replyDirty);
}
on.cleanup(() => this.composerPresenceManager.leave());
@ -85,12 +83,15 @@ export default class ComposerPresenceDisplay extends Component {
return this.state === "edit";
}
@cached
get state() {
if (this.args.model.editingPost) {
const { editingPost, whisper, replyingToTopic } = this.args.model;
if (editingPost) {
return "edit";
} else if (this.args.model.whisper) {
} else if (whisper) {
return "whisper";
} else if (this.args.model.replyingToTopic) {
} else if (replyingToTopic) {
return "reply";
}
}
@ -98,6 +99,7 @@ export default class ComposerPresenceDisplay extends Component {
@cached
get users() {
let users;
if (this.isEdit) {
users = this.editChannel?.users || [];
} else {

View File

@ -15,26 +15,33 @@ export default class TopicPresenceDisplay extends Component {
@tracked whisperChannel;
setupReplyChannel = helperFn((_, on) => {
const replyChannel = this.presence.getChannel(
`/discourse-presence/reply/${this.args.topic.id}`
);
replyChannel.subscribe();
this.replyChannel = replyChannel;
const { topic } = this.args;
on.cleanup(() => replyChannel.unsubscribe());
});
setupWhisperChannels = helperFn((_, on) => {
if (!this.currentUser.staff) {
if (!topic) {
return;
}
const whisperChannel = this.presence.getChannel(
`/discourse-presence/whisper/${this.args.topic.id}`
);
whisperChannel.subscribe();
const name = `/discourse-presence/reply/${topic.id}`;
const replyChannel = this.presence.getChannel(name);
this.replyChannel = replyChannel;
replyChannel.subscribe();
on.cleanup(() => replyChannel.unsubscribe());
});
setupWhisperChannel = helperFn((_, on) => {
const { topic } = this.args;
const { whisperer } = this.currentUser;
if (!topic || !whisperer) {
return;
}
const name = `/discourse-presence/whisper/${topic.id}`;
const whisperChannel = this.presence.getChannel(name);
this.whisperChannel = whisperChannel;
whisperChannel.subscribe();
on.cleanup(() => whisperChannel.unsubscribe());
});
@ -50,7 +57,7 @@ export default class TopicPresenceDisplay extends Component {
<template>
{{this.setupReplyChannel}}
{{this.setupWhisperChannels}}
{{this.setupWhisperChannel}}
{{#if (gt this.users.length 0)}}
<div class="presence-users">

View File

@ -2,63 +2,47 @@ import { cancel, debounce } from "@ember/runloop";
import Service, { service } from "@ember/service";
import { isTesting } from "discourse-common/config/environment";
const PRESENCE_CHANNEL_PREFIX = "/discourse-presence";
const KEEP_ALIVE_DURATION_SECONDS = 10;
const KEEP_ALIVE = 10 * 1000; // 10 seconds
export default class ComposerPresenceManager extends Service {
@service currentUser;
@service presence;
@service siteSettings;
willDestroy() {
this.leave();
}
notifyState(intent, id) {
if (
this.siteSettings.allow_users_to_hide_profile &&
this.currentUser.user_option.hide_presence
) {
notifyState(name, replying = true, keepAlive = KEEP_ALIVE) {
if (!replying) {
this.leave();
return;
}
if (intent === undefined) {
return this.leave();
const canHideProfile = this.siteSettings.allow_users_to_hide_profile;
const isHidingPresence = this.currentUser.user_option.hide_presence;
if (canHideProfile && isHidingPresence) {
return;
}
if (!["reply", "whisper", "edit"].includes(intent)) {
throw `Unknown intent ${intent}`;
}
if (this._name !== name) {
this.leave();
const state = `${intent}/${id}`;
this._name = name;
this._channel = this.presence.getChannel(name);
this._channel.enter();
if (this._state !== state) {
this._enter(intent, id);
this._state = state;
}
if (!isTesting()) {
this._autoLeaveTimer = debounce(
this,
this.leave,
KEEP_ALIVE_DURATION_SECONDS * 1000
);
if (!isTesting()) {
this._autoLeaveTimer = debounce(this, this.leave, keepAlive);
}
}
}
leave() {
this._presentChannel?.leave();
this._presentChannel = null;
this._state = null;
if (this._autoLeaveTimer) {
cancel(this._autoLeaveTimer);
this._autoLeaveTimer = null;
}
}
_enter(intent, id) {
this.leave();
let channelName = `${PRESENCE_CHANNEL_PREFIX}/${intent}/${id}`;
this._presentChannel = this.presence.getChannel(channelName);
this._presentChannel.enter();
this._channel?.leave();
this._channel = null;
this._name = null;
}
}