Improved channel private conversation mode behaviour

canary
WolverinDEV 2020-12-09 14:22:22 +01:00
parent a05b795859
commit e6bbb883e4
9 changed files with 199 additions and 30 deletions

View File

@ -2,6 +2,7 @@
* **09.12.20** * **09.12.20**
- Fixed the private messages unread indicator - Fixed the private messages unread indicator
- Properly updating the private message unread count - Properly updating the private message unread count
- Improved channel conversation mode detection support
* **08.12.20** * **08.12.20**
- Fixed the permission editor not resolving unique ids - Fixed the permission editor not resolving unique ids

View File

@ -2,15 +2,17 @@ import {
ChatEvent, ChatEvent,
ChatEventMessage, ChatEventMessage,
ChatMessage, ChatMessage,
ChatState, ConversationHistoryResponse ChatState,
ConversationHistoryResponse
} from "tc-shared/ui/frames/side/ConversationDefinitions"; } from "tc-shared/ui/frames/side/ConversationDefinitions";
import {Registry} from "tc-shared/events"; import {Registry} from "tc-shared/events";
import {ConnectionHandler, ConnectionState} from "tc-shared/ConnectionHandler"; import {ConnectionHandler} from "tc-shared/ConnectionHandler";
import {preprocessChatMessageForSend} from "tc-shared/text/chat"; import {preprocessChatMessageForSend} from "tc-shared/text/chat";
import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration"; import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
import {ErrorCode} from "tc-shared/connection/ErrorCode"; import {ErrorCode} from "tc-shared/connection/ErrorCode";
import {LogCategory, logWarn} from "tc-shared/log"; import {LogCategory, logWarn} from "tc-shared/log";
import {ChannelConversation} from "tc-shared/conversations/ChannelConversationManager"; import {ChannelConversationMode} from "tc-shared/tree/Channel";
import {guid} from "tc-shared/crypto/uid";
export const kMaxChatFrameMessageSize = 50; /* max 100 messages, since the server does not support more than 100 messages queried at once */ export const kMaxChatFrameMessageSize = 50; /* max 100 messages, since the server does not support more than 100 messages queried at once */
@ -34,6 +36,12 @@ export interface AbstractChatEvents {
}, },
notify_history_state_changed: { notify_history_state_changed: {
hasHistory: boolean hasHistory: boolean
},
notify_conversation_mode_changed: {
newMode: ChannelConversationMode
},
notify_read_state_changed: {
readable: boolean
} }
} }
@ -50,13 +58,14 @@ export abstract class AbstractChat<Events extends AbstractChatEvents> {
protected failedPermission: string; protected failedPermission: string;
protected errorMessage: string; protected errorMessage: string;
protected conversationPrivate: boolean = false; private conversationMode: ChannelConversationMode;
protected crossChannelChatSupported: boolean = true; protected crossChannelChatSupported: boolean = true;
protected unreadTimestamp: number; protected unreadTimestamp: number;
protected unreadState: boolean = false; protected unreadState: boolean = false;
protected messageSendEnabled: boolean = true; protected messageSendEnabled: boolean = true;
private conversationReadable = true;
private history = false; private history = false;
@ -65,6 +74,7 @@ export abstract class AbstractChat<Events extends AbstractChatEvents> {
this.connection = connection; this.connection = connection;
this.chatId = chatId; this.chatId = chatId;
this.unreadTimestamp = Date.now(); this.unreadTimestamp = Date.now();
this.conversationMode = ChannelConversationMode.Public;
} }
destroy() { destroy() {
@ -103,7 +113,7 @@ export abstract class AbstractChat<Events extends AbstractChatEvents> {
this.setUnreadTimestamp(Date.now()); this.setUnreadTimestamp(Date.now());
} else if(!this.isUnread() && triggerUnread) { } else if(!this.isUnread() && triggerUnread) {
this.setUnreadTimestamp(event.message.timestamp - 1); this.setUnreadTimestamp(event.message.timestamp - 1);
} else { } else if(!this.isUnread()) {
/* mark the last message as read */ /* mark the last message as read */
this.setUnreadTimestamp(event.message.timestamp); this.setUnreadTimestamp(event.message.timestamp);
} }
@ -119,7 +129,7 @@ export abstract class AbstractChat<Events extends AbstractChatEvents> {
if(!this.isUnread() && triggerUnread) { if(!this.isUnread() && triggerUnread) {
this.setUnreadTimestamp(event.timestamp - 1); this.setUnreadTimestamp(event.timestamp - 1);
} else { } else if(!this.isUnread()) {
/* mark the last message as read */ /* mark the last message as read */
this.setUnreadTimestamp(event.timestamp); this.setUnreadTimestamp(event.timestamp);
} }
@ -197,8 +207,41 @@ export abstract class AbstractChat<Events extends AbstractChatEvents> {
return this.unreadState; return this.unreadState;
} }
public getConversationMode() : ChannelConversationMode {
return this.conversationMode;
}
public isPrivate() : boolean { public isPrivate() : boolean {
return this.conversationPrivate; return this.conversationMode === ChannelConversationMode.Private;
}
protected setConversationMode(mode: ChannelConversationMode) {
if(this.conversationMode === mode) {
return;
}
this.registerChatEvent({
type: "mode-changed",
uniqueId: guid() + "-mode-change",
timestamp: Date.now(),
newMode: mode === ChannelConversationMode.Public ? "normal" : mode === ChannelConversationMode.Private ? "private" : "none"
}, true);
this.conversationMode = mode;
this.events.fire("notify_conversation_mode_changed", { newMode: mode });
}
public isReadable() {
return this.conversationReadable;
}
protected setReadable(flag: boolean) {
if(this.conversationReadable === flag) {
return;
}
this.conversationReadable = flag;
this.events.fire("notify_read_state_changed", { readable: flag });
} }
public isSendEnabled() : boolean { public isSendEnabled() : boolean {
@ -275,7 +318,6 @@ export abstract class AbstractChat<Events extends AbstractChatEvents> {
this.events.fire("notify_send_toggle", { enabled: enabled }); this.events.fire("notify_send_toggle", { enabled: enabled });
} }
public abstract canClientAccessChat() : boolean;
public abstract queryHistory(criteria: { begin?: number, end?: number, limit?: number }) : Promise<ConversationHistoryResponse>; public abstract queryHistory(criteria: { begin?: number, end?: number, limit?: number }) : Promise<ConversationHistoryResponse>;
public abstract queryCurrentMessages(); public abstract queryCurrentMessages();
public abstract sendMessage(text: string); public abstract sendMessage(text: string);

View File

@ -15,6 +15,7 @@ import {traj} from "tc-shared/i18n/localize";
import {ConnectionHandler, ConnectionState} from "tc-shared/ConnectionHandler"; import {ConnectionHandler, ConnectionState} from "tc-shared/ConnectionHandler";
import {LocalClientEntry} from "tc-shared/tree/Client"; import {LocalClientEntry} from "tc-shared/tree/Client";
import {ServerCommand} from "tc-shared/connection/ConnectionBase"; import {ServerCommand} from "tc-shared/connection/ConnectionBase";
import {ChannelConversationMode} from "tc-shared/tree/Channel";
export interface ChannelConversationEvents extends AbstractChatEvents { export interface ChannelConversationEvents extends AbstractChatEvents {
notify_messages_deleted: { messages: string[] }, notify_messages_deleted: { messages: string[] },
@ -40,13 +41,12 @@ export class ChannelConversation extends AbstractChat<ChannelConversationEvents>
this.conversationId = id; this.conversationId = id;
this.preventUnreadUpdate = true; this.preventUnreadUpdate = true;
const dateNow = Date.now();
const unreadTimestamp = handle.connection.settings.server(Settings.FN_CHANNEL_CHAT_READ(id), Date.now()); const unreadTimestamp = handle.connection.settings.server(Settings.FN_CHANNEL_CHAT_READ(id), Date.now());
this.setUnreadTimestamp(unreadTimestamp); this.setUnreadTimestamp(unreadTimestamp);
this.preventUnreadUpdate = false; this.preventUnreadUpdate = false;
this.events.on("notify_unread_state_changed", event => { this.events.on(["notify_unread_state_changed", "notify_read_state_changed"], event => {
this.handle.connection.channelTree.findChannel(this.conversationId)?.setUnread(event.unread); this.handle.connection.channelTree.findChannel(this.conversationId)?.setUnread(this.isReadable() && this.isUnread());
}); });
} }
@ -142,7 +142,6 @@ export class ChannelConversation extends AbstractChat<ChannelConversationEvents>
this.setCurrentMode("loading"); this.setCurrentMode("loading");
this.queryHistory({ end: 1, limit: kMaxChatFrameMessageSize }).then(history => { this.queryHistory({ end: 1, limit: kMaxChatFrameMessageSize }).then(history => {
this.conversationPrivate = false;
this.conversationVolatile = false; this.conversationVolatile = false;
this.failedPermission = undefined; this.failedPermission = undefined;
this.errorMessage = undefined; this.errorMessage = undefined;
@ -166,17 +165,18 @@ export class ChannelConversation extends AbstractChat<ChannelConversationEvents>
break; break;
case "private": case "private":
this.conversationPrivate = true; this.setConversationMode(ChannelConversationMode.Private);
this.setCurrentMode("normal"); this.setCurrentMode("normal");
break; break;
case "success": case "success":
this.setConversationMode(ChannelConversationMode.Public);
this.setCurrentMode("normal"); this.setCurrentMode("normal");
break; break;
case "unsupported": case "unsupported":
this.crossChannelChatSupported = false; this.crossChannelChatSupported = false;
this.conversationPrivate = true; this.setConversationMode(ChannelConversationMode.Private);
this.setCurrentMode("normal"); this.setCurrentMode("normal");
break; break;
} }
@ -295,6 +295,10 @@ export class ChannelConversation extends AbstractChat<ChannelConversationEvents>
this.handle.connection.settings.changeServer(Settings.FN_CHANNEL_CHAT_READ(this.conversationId), timestamp); this.handle.connection.settings.changeServer(Settings.FN_CHANNEL_CHAT_READ(this.conversationId), timestamp);
} }
public setConversationMode(mode: ChannelConversationMode) {
super.setConversationMode(mode);
}
public localClientSwitchedChannel(type: "join" | "leave") { public localClientSwitchedChannel(type: "join" | "leave") {
this.registerChatEvent({ this.registerChatEvent({
type: "local-user-switch", type: "local-user-switch",
@ -309,6 +313,14 @@ export class ChannelConversation extends AbstractChat<ChannelConversationEvents>
sendMessage(text: string) { sendMessage(text: string) {
this.doSendMessage(text, this.conversationId ? 2 : 3, this.conversationId).then(() => {}); this.doSendMessage(text, this.conversationId ? 2 : 3, this.conversationId).then(() => {});
} }
updateAccessState() {
if(this.isPrivate()) {
this.setReadable(this.connection.getClient().currentChannel()?.getChannelId() === this.conversationId);
} else {
this.setReadable(true);
}
}
} }
export interface ChannelConversationManagerEvents extends AbstractChatManagerEvents<ChannelConversation> { } export interface ChannelConversationManagerEvents extends AbstractChatManagerEvents<ChannelConversation> { }
@ -337,6 +349,37 @@ export class ChannelConversationManager extends AbstractChatManager<ChannelConve
} }
})); }));
this.listenerConnection.push(connection.channelTree.events.on("notify_channel_updated", event => {
const conversation = this.findConversation(event.channel.channelId);
if(!conversation) {
return;
}
if("channel_conversation_mode" in event.updatedProperties) {
conversation.setConversationMode(event.channel.properties.channel_conversation_mode);
conversation.updateAccessState();
}
}));
this.listenerConnection.push(connection.channelTree.events.on("notify_client_moved", event => {
if(event.client instanceof LocalClientEntry) {
const fromConversation = this.findConversation(event.oldChannel.channelId);
const targetConversation = this.findConversation(event.newChannel.channelId);
fromConversation?.updateAccessState();
targetConversation?.updateAccessState();
}
}));
this.listenerConnection.push(connection.channelTree.events.on("notify_client_enter_view", event => {
if(event.client instanceof LocalClientEntry) {
const targetConversation = this.findConversation(event.targetChannel.channelId);
targetConversation?.updateAccessState();
}
}));
/* TODO: Permission listener for text send power! */
this.listenerConnection.push(connection.serverConnection.command_handler_boss().register_explicit_handler("notifyconversationhistory", this.handleConversationHistory.bind(this))); this.listenerConnection.push(connection.serverConnection.command_handler_boss().register_explicit_handler("notifyconversationhistory", this.handleConversationHistory.bind(this)));
this.listenerConnection.push(connection.serverConnection.command_handler_boss().register_explicit_handler("notifyconversationindex", this.handleConversationIndex.bind(this))); this.listenerConnection.push(connection.serverConnection.command_handler_boss().register_explicit_handler("notifyconversationindex", this.handleConversationIndex.bind(this)));
this.listenerConnection.push(connection.serverConnection.command_handler_boss().register_explicit_handler("notifyconversationmessagedelete", this.handleConversationMessageDelete.bind(this))); this.listenerConnection.push(connection.serverConnection.command_handler_boss().register_explicit_handler("notifyconversationmessagedelete", this.handleConversationMessageDelete.bind(this)));

View File

@ -42,6 +42,12 @@ export enum ChannelSubscribeMode {
INHERITED INHERITED
} }
export enum ChannelConversationMode {
Public = 0,
Private = 1,
None = 2
}
export class ChannelProperties { export class ChannelProperties {
channel_order: number = 0; channel_order: number = 0;
channel_name: string = ""; channel_name: string = "";
@ -73,7 +79,7 @@ export class ChannelProperties {
//Only after request //Only after request
channel_description: string = ""; channel_description: string = "";
channel_conversation_mode: number = 0; /* 0 := Private, the default */ channel_conversation_mode: ChannelConversationMode = 0;
channel_conversation_history_length: number = -1; channel_conversation_history_length: number = -1;
} }
@ -564,6 +570,8 @@ export class ChannelEntry extends ChannelTreeEntry<ChannelEvents> {
} }
/* devel-block-end */ /* devel-block-end */
/* TODO: Validate values. Example: channel_conversation_mode */
for(let variable of variables) { for(let variable of variables) {
let key = variable.key; let key = variable.key;
let value = variable.value; let value = variable.value;

View File

@ -35,6 +35,8 @@ export abstract class AbstractConversationController<
protected currentSelectedConversation: ConversationType; protected currentSelectedConversation: ConversationType;
protected currentSelectedListener: (() => void)[]; protected currentSelectedListener: (() => void)[];
protected crossChannelChatSupported = true;
protected constructor(conversationManager: Manager) { protected constructor(conversationManager: Manager) {
this.uiEvents = new Registry<Events>(); this.uiEvents = new Registry<Events>();
this.currentSelectedListener = []; this.currentSelectedListener = [];
@ -86,6 +88,10 @@ export abstract class AbstractConversationController<
this.currentSelectedListener.push(conversation.events.on("notify_history_state_changed", () => { this.currentSelectedListener.push(conversation.events.on("notify_history_state_changed", () => {
this.reportStateToUI(conversation); this.reportStateToUI(conversation);
})); }));
this.currentSelectedListener.push(conversation.events.on("notify_read_state_changed", () => {
this.reportStateToUI(conversation);
}));
} }
handlePanelShow() { handlePanelShow() {
@ -93,8 +99,6 @@ export abstract class AbstractConversationController<
} }
protected reportStateToUI(conversation: AbstractChat<any>) { protected reportStateToUI(conversation: AbstractChat<any>) {
const crossChannelChatSupported = true; /* FIXME: Determine this form the server! */
let historyState: ChatHistoryState; let historyState: ChatHistoryState;
const localHistoryState = this.historyUiStates[conversation.getChatId()]; const localHistoryState = this.historyUiStates[conversation.getChatId()];
if(!localHistoryState) { if(!localHistoryState) {
@ -113,11 +117,11 @@ export abstract class AbstractConversationController<
switch (conversation.getCurrentMode()) { switch (conversation.getCurrentMode()) {
case "normal": case "normal":
if(conversation.isPrivate() && !conversation.canClientAccessChat()) { if(conversation.isPrivate() && !conversation.isReadable()) {
this.uiEvents.fire_react("notify_conversation_state", { this.uiEvents.fire_react("notify_conversation_state", {
chatId: conversation.getChatId(), chatId: conversation.getChatId(),
state: "private", state: "private",
crossChannelChatSupported: crossChannelChatSupported crossChannelChatSupported: this.crossChannelChatSupported
}); });
return; return;
} }
@ -133,7 +137,7 @@ export abstract class AbstractConversationController<
chatFrameMaxMessageCount: kMaxChatFrameMessageSize, chatFrameMaxMessageCount: kMaxChatFrameMessageSize,
unreadTimestamp: conversation.getUnreadTimestamp(), unreadTimestamp: conversation.getUnreadTimestamp(),
showUserSwitchEvents: conversation.isPrivate() || !crossChannelChatSupported, showUserSwitchEvents: conversation.isPrivate() || !this.crossChannelChatSupported,
sendEnabled: conversation.isSendEnabled(), sendEnabled: conversation.isSendEnabled(),
events: [...conversation.getPresentEvents(), ...conversation.getPresentMessages()] events: [...conversation.getPresentEvents(), ...conversation.getPresentMessages()]
@ -230,6 +234,18 @@ export abstract class AbstractConversationController<
return this.currentSelectedConversation; return this.currentSelectedConversation;
} }
protected setCrossChannelChatSupport(flag: boolean) {
if(this.crossChannelChatSupported === flag) {
return;
}
this.crossChannelChatSupported = flag;
const currentConversation = this.getCurrentConversation();
if(currentConversation) {
this.reportStateToUI(this.getCurrentConversation());
}
}
@EventHandler<ConversationUIEvents>("query_conversation_state") @EventHandler<ConversationUIEvents>("query_conversation_state")
protected handleQueryConversationState(event: ConversationUIEvents["query_conversation_state"]) { protected handleQueryConversationState(event: ConversationUIEvents["query_conversation_state"]) {
const conversation = this.conversationManager.findConversationById(event.chatId); const conversation = this.conversationManager.findConversationById(event.chatId);

View File

@ -15,7 +15,7 @@ import {
ChatEventPartnerAction, ChatEventPartnerAction,
ChatHistoryState, ChatHistoryState,
ChatMessage, ChatMessage,
ConversationUIEvents ConversationUIEvents, ChatEventModeChanged
} from "tc-shared/ui/frames/side/ConversationDefinitions"; } from "tc-shared/ui/frames/side/ConversationDefinitions";
import {TimestampRenderer} from "tc-shared/ui/react-elements/TimestampRenderer"; import {TimestampRenderer} from "tc-shared/ui/react-elements/TimestampRenderer";
import {BBCodeRenderer} from "tc-shared/text/bbcode"; import {BBCodeRenderer} from "tc-shared/text/bbcode";
@ -277,6 +277,34 @@ const ChatEventPartnerActionRenderer = (props: { event: ChatEventPartnerAction,
return null; return null;
}; };
const ChatEventModeChangedRenderer = (props: { event: ChatEventModeChanged, refHTMLElement: Ref<HTMLDivElement> }) => {
switch (props.event.newMode) {
case "none":
return (
<div className={cssStyle.containerSwitch + " " + cssStyle.actionClose} ref={props.refHTMLElement}>
<a><Translatable>The conversation has been disabled</Translatable></a>
<div />
</div>
);
case "private":
return (
<div className={cssStyle.containerSwitch + " " + cssStyle.actionClose} ref={props.refHTMLElement}>
<a><Translatable>The conversation has been made private</Translatable></a>
<div />
</div>
);
case "normal":
return (
<div className={cssStyle.containerSwitch + " " + cssStyle.actionClose} ref={props.refHTMLElement}>
<a><Translatable>The conversation has been made public</Translatable></a>
<div />
</div>
);
}
}
const PartnerTypingIndicator = (props: { events: Registry<ConversationUIEvents>, chatId: string, timeout?: number }) => { const PartnerTypingIndicator = (props: { events: Registry<ConversationUIEvents>, chatId: string, timeout?: number }) => {
const kTypingTimeout = props.timeout || 5000; const kTypingTimeout = props.timeout || 5000;
@ -658,6 +686,14 @@ class ConversationMessages extends React.PureComponent<ConversationMessagesPrope
refHTMLElement={reference} refHTMLElement={reference}
/>); />);
break; break;
case "mode-changed":
this.viewEntries.push(<ChatEventModeChangedRenderer
key={event.uniqueId}
event={event}
refHTMLElement={reference}
/>);
break;
} }
} }
} }

View File

@ -1,20 +1,20 @@
import * as React from "react"; import * as React from "react";
import {ConnectionHandler} from "../../../ConnectionHandler"; import {ConnectionHandler, ConnectionState} from "../../../ConnectionHandler";
import {EventHandler} from "../../../events"; import {EventHandler} from "../../../events";
import * as log from "../../../log"; import * as log from "../../../log";
import {LogCategory} from "../../../log"; import {LogCategory} from "../../../log";
import {tr} from "../../../i18n/localize"; import {tr} from "../../../i18n/localize";
import ReactDOM = require("react-dom"); import {ConversationUIEvents} from "../../../ui/frames/side/ConversationDefinitions";
import {
ConversationUIEvents
} from "../../../ui/frames/side/ConversationDefinitions";
import {ConversationPanel} from "./AbstractConversationRenderer"; import {ConversationPanel} from "./AbstractConversationRenderer";
import {AbstractConversationController} from "./AbstractConversationController"; import {AbstractConversationController} from "./AbstractConversationController";
import { import {
ChannelConversation, ChannelConversationEvents, ChannelConversation,
ChannelConversationEvents,
ChannelConversationManager, ChannelConversationManager,
ChannelConversationManagerEvents ChannelConversationManagerEvents
} from "tc-shared/conversations/ChannelConversationManager"; } from "tc-shared/conversations/ChannelConversationManager";
import {ServerFeature} from "tc-shared/connection/ServerFeatures";
import ReactDOM = require("react-dom");
export class ChannelConversationController extends AbstractConversationController< export class ChannelConversationController extends AbstractConversationController<
ConversationUIEvents, ConversationUIEvents,
@ -61,6 +61,18 @@ export class ChannelConversationController extends AbstractConversationControlle
})); }));
this.uiEvents.register_handler(this, true); this.uiEvents.register_handler(this, true);
this.listenerManager.push(connection.events().on("notify_connection_state_changed", event => {
if(event.newState === ConnectionState.CONNECTED) {
connection.serverFeatures.awaitFeatures().then(success => {
if(!success) { return; }
this.setCrossChannelChatSupport(connection.serverFeatures.supportsFeature(ServerFeature.ADVANCED_CHANNEL_CHAT));
});
} else {
this.setCrossChannelChatSupport(true);
}
}));
} }
destroy() { destroy() {
@ -84,8 +96,13 @@ export class ChannelConversationController extends AbstractConversationControlle
protected registerConversationEvents(conversation: ChannelConversation) { protected registerConversationEvents(conversation: ChannelConversation) {
super.registerConversationEvents(conversation); super.registerConversationEvents(conversation);
this.currentSelectedListener.push(conversation.events.on("notify_messages_deleted", event => { this.currentSelectedListener.push(conversation.events.on("notify_messages_deleted", event => {
this.uiEvents.fire_react("notify_chat_message_delete", { messageIds: event.messages, chatId: conversation.getChatId() }); this.uiEvents.fire_react("notify_chat_message_delete", { messageIds: event.messages, chatId: conversation.getChatId() });
})); }));
this.currentSelectedListener.push(conversation.events.on("notify_conversation_mode_changed", () => {
this.reportStateToUI(conversation);
}));
} }
} }

View File

@ -16,7 +16,8 @@ export type ChatEvent = { timestamp: number; uniqueId: string; } & (
ChatEventQueryFailed | ChatEventQueryFailed |
ChatEventPartnerInstanceChanged | ChatEventPartnerInstanceChanged |
ChatEventLocalAction | ChatEventLocalAction |
ChatEventPartnerAction ChatEventPartnerAction |
ChatEventModeChanged
); );
export interface ChatEventUnreadTrigger { export interface ChatEventUnreadTrigger {
@ -64,6 +65,11 @@ export interface ChatEventPartnerAction {
action: "disconnect" | "close" | "reconnect"; action: "disconnect" | "close" | "reconnect";
} }
export interface ChatEventModeChanged {
type: "mode-changed";
newMode: "normal" | "private" | "none"
}
/* ---------- Chat States ---------- */ /* ---------- Chat States ---------- */
export type ChatState = "normal" | "loading" | "no-permissions" | "error" | "unloaded"; export type ChatState = "normal" | "loading" | "no-permissions" | "error" | "unloaded";
export type ChatHistoryState = "none" | "loading" | "available" | "error"; export type ChatHistoryState = "none" | "loading" | "available" | "error";

View File

@ -105,9 +105,9 @@ const BlockPing = () => {
} }
if(pingInfo.javaScript === undefined) { if(pingInfo.javaScript === undefined) {
title = tr("Ping: " + pingInfo.native.toFixed(3) + "ms"); title = tra("Ping: {}ms", pingInfo.native.toFixed(3));
} else { } else {
title = tr("Native: " + pingInfo.native.toFixed(3) + "ms\nJavascript: " + pingInfo.javaScript.toFixed(3) + "ms"); title = tra("Native: {}ms\nJavascript: {}ms", pingInfo.native.toFixed(3), pingInfo.javaScript.toFixed(3));
} }
value = <div className={cssStyle.value + " " + cssStyle.ping + " " + pingClass} key={"ping"} title={title}>{pingInfo.native.toFixed(0)}ms</div>; value = <div className={cssStyle.value + " " + cssStyle.ping + " " + pingClass} key={"ping"} title={title}>{pingInfo.native.toFixed(0)}ms</div>;
} }