Using a general client tag element for all client tags and introduced the channel conversation mode variable

This commit is contained in:
WolverinDEV 2020-10-04 15:52:15 +02:00
parent 4cdabc620b
commit 1996878f12
13 changed files with 89 additions and 45 deletions

View file

@ -1,4 +1,7 @@
# Changelog: # Changelog:
* **04.10.20**
- Fixed invalid channel tree unique id assignment for the initial server entry ([#F2986](https://forum.teaspeak.de/index.php?threads/2986))
* **27.09.20** * **27.09.20**
- Middle clicking on bookmarks now directly connects in a new tab - Middle clicking on bookmarks now directly connects in a new tab

View file

@ -73,7 +73,7 @@ export class ChannelProperties {
//Only after request //Only after request
channel_description: string = ""; channel_description: string = "";
channel_flag_conversation_private: boolean = true; /* TeamSpeak mode */ channel_conversation_mode: number = 0; /* 0 := Private, the default */
channel_conversation_history_length: number = -1; channel_conversation_history_length: number = -1;
} }
@ -594,6 +594,10 @@ export class ChannelEntry extends ChannelTreeEntry<ChannelEvents> {
this._cached_channel_description_promise = undefined; this._cached_channel_description_promise = undefined;
this._cached_channel_description_promise_resolve = undefined; this._cached_channel_description_promise_resolve = undefined;
this._cached_channel_description_promise_reject = undefined; this._cached_channel_description_promise_reject = undefined;
} else if(key === "channel_flag_conversation_private") {
/* "fix" for older TeaSpeak server versions (pre. 1.4.22) */
this.properties.channel_conversation_mode = value === "1" ? 0 : 1;
variables.push({ key: "channel_conversation_mode", value: this.properties.channel_conversation_mode + "" });
} }
} }
/* devel-block(log-channel-property-updates) */ /* devel-block(log-channel-property-updates) */

View file

@ -1233,8 +1233,9 @@ export class MusicClientEntry extends ClientEntry {
name: tr("Change remote volume"), name: tr("Change remote volume"),
callback: () => { callback: () => {
let max_volume = this.channelTree.client.permissions.neededPermission(PermissionType.I_CLIENT_MUSIC_CREATE_MODIFY_MAX_VOLUME).value; let max_volume = this.channelTree.client.permissions.neededPermission(PermissionType.I_CLIENT_MUSIC_CREATE_MODIFY_MAX_VOLUME).value;
if(max_volume < 0) if(max_volume < 0) {
max_volume = 100; max_volume = 100;
}
spawnMusicBotVolumeChange(this, max_volume / 100); spawnMusicBotVolumeChange(this, max_volume / 100);
} }

View file

@ -1,5 +1,3 @@
.clientEntry, .channelEntry { .clientEntry, .channelEntry {
color: var(--server-log-tree-entry); color: var(--server-log-tree-entry);
font-weight: 700;
cursor: pointer;
} }

View file

@ -7,6 +7,7 @@ import {BBCodeRenderer} from "tc-shared/text/bbcode";
import {format_time} from "tc-shared/ui/frames/chat"; import {format_time} from "tc-shared/ui/frames/chat";
import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration"; import {CommandResult} from "tc-shared/connection/ServerConnectionDeclaration";
import {XBBCodeRenderer} from "vendor/xbbcode/react"; import {XBBCodeRenderer} from "vendor/xbbcode/react";
import {ChannelTag, ClientTag} from "tc-shared/ui/tree/EntryTags";
const cssStyle = require("./DispatcherLog.scss"); const cssStyle = require("./DispatcherLog.scss");
const cssStyleRenderer = require("./Renderer.scss"); const cssStyleRenderer = require("./Renderer.scss");
@ -26,18 +27,23 @@ export function getRegisteredLogDispatchers() : TypeInfo[] {
return Object.keys(dispatchers) as any; return Object.keys(dispatchers) as any;
} }
/* TODO: Enable context menu */
const ClientRenderer = (props: { client: EventClient, handlerId: string, braces?: boolean }) => ( const ClientRenderer = (props: { client: EventClient, handlerId: string, braces?: boolean }) => (
<div className={cssStyle.clientEntry}> <ClientTag
{props.client.client_name} handlerId={props.handlerId}
</div> clientName={props.client.client_name}
clientId={props.client.client_id}
clientUniqueId={props.client.client_unique_id}
className={cssStyle.clientEntry}
/>
); );
/* TODO: Enable context menu */
const ChannelRenderer = (props: { channel: EventChannelData, handlerId: string, braces?: boolean }) => ( const ChannelRenderer = (props: { channel: EventChannelData, handlerId: string, braces?: boolean }) => (
<div className={cssStyle.channelEntry}> <ChannelTag
{props.channel.channel_name} handlerId={props.handlerId}
</div> channelName={props.channel.channel_name}
channelId={props.channel.channel_id}
className={cssStyle.channelEntry}
/>
); );
registerDispatcher(EventType.ERROR_CUSTOM, data => <div className={cssStyleRenderer.errorMessage}>{data.message}</div>); registerDispatcher(EventType.ERROR_CUSTOM, data => <div className={cssStyleRenderer.errorMessage}>{data.message}</div>);

View file

@ -62,10 +62,11 @@ export abstract class AbstractChat<Events extends ConversationUIEvents> {
this.hasHistory = true; this.hasHistory = true;
index -= deleteMessageCount; index -= deleteMessageCount;
if(event.isOwnMessage) if(event.isOwnMessage) {
this.setUnreadTimestamp(undefined); this.setUnreadTimestamp(undefined);
else if(!this.unreadTimestamp) } else if(!this.unreadTimestamp) {
this.setUnreadTimestamp(event.message.timestamp); this.setUnreadTimestamp(event.message.timestamp);
}
/* let all other events run before */ /* let all other events run before */
this.events.fire_async("notify_chat_event", { this.events.fire_async("notify_chat_event", {
@ -333,11 +334,12 @@ export abstract class AbstractChatManager<Events extends ConversationUIEvents> {
return; return;
} }
if(conversation.currentMode() === "unloaded") if(conversation.currentMode() === "unloaded") {
conversation.queryCurrentMessages(); conversation.queryCurrentMessages();
else } else {
conversation.reportStateToUI(); conversation.reportStateToUI();
} }
}
@EventHandler<ConversationUIEvents>("query_conversation_history") @EventHandler<ConversationUIEvents>("query_conversation_history")
protected handleQueryHistory(event: ConversationUIEvents["query_conversation_history"]) { protected handleQueryHistory(event: ConversationUIEvents["query_conversation_history"]) {

View file

@ -49,14 +49,17 @@ export class Conversation extends AbstractChat<ConversationUIEvents> {
cid: this.conversationId cid: this.conversationId
} as any; } as any;
if(typeof criteria.begin === "number") if(typeof criteria.begin === "number") {
requestObject.timestamp_begin = criteria.begin; requestObject.timestamp_begin = criteria.begin;
}
if(typeof criteria.end === "number") if(typeof criteria.end === "number") {
requestObject.timestamp_end = criteria.end; requestObject.timestamp_end = criteria.end;
}
if(typeof criteria.limit === "number") if(typeof criteria.limit === "number") {
requestObject.message_count = criteria.limit; requestObject.message_count = criteria.limit;
}
return this.handle.connection.serverConnection.send_command("conversationhistory", requestObject, { flagset: [ "merge" ], process_result: false }).then(() => { return this.handle.connection.serverConnection.send_command("conversationhistory", requestObject, { flagset: [ "merge" ], process_result: false }).then(() => {
resolve({ status: "success", events: this.historyQueryResponse.map(e => { resolve({ status: "success", events: this.historyQueryResponse.map(e => {
@ -198,12 +201,14 @@ export class Conversation extends AbstractChat<ConversationUIEvents> {
} }
const timestamp = parseInt(info["timestamp"]); const timestamp = parseInt(info["timestamp"]);
if(isNaN(timestamp)) if(isNaN(timestamp)) {
return; return;
}
if(timestamp > this.lastReadMessage) if(timestamp > this.lastReadMessage) {
this.setUnreadTimestamp(this.lastReadMessage); this.setUnreadTimestamp(this.lastReadMessage);
} }
}
public handleIncomingMessage(message: ChatMessage, isOwnMessage: boolean) { public handleIncomingMessage(message: ChatMessage, isOwnMessage: boolean) {
this.registerIncomingMessage(message, isOwnMessage, "m-" + this.conversationId + "-" + message.timestamp + "-" + Math.random()); this.registerIncomingMessage(message, isOwnMessage, "m-" + this.conversationId + "-" + message.timestamp + "-" + Math.random());
@ -277,7 +282,7 @@ export class Conversation extends AbstractChat<ConversationUIEvents> {
} }
sendMessage(text: string) { sendMessage(text: string) {
this.doSendMessage(text, this.conversationId ? 2 : 3, this.conversationId); this.doSendMessage(text, this.conversationId ? 2 : 3, this.conversationId).then(() => {});
} }
} }
@ -344,7 +349,7 @@ export class ConversationManager extends AbstractChatManager<ConversationUIEvent
this.queryUnreadFlags(); this.queryUnreadFlags();
})); }));
this.uiEvents.on("notify_destroy", this.connection.channelTree.events.on("notify_channel_updated", event => { this.uiEvents.on("notify_destroy", this.connection.channelTree.events.on("notify_channel_updated", _event => {
/* TODO private flag! */ /* TODO private flag! */
})); }));
} }
@ -403,9 +408,6 @@ export class ConversationManager extends AbstractChatManager<ConversationUIEvent
} }
queryUnreadFlags() { queryUnreadFlags() {
/* FIXME: Test for old TeaSpeak servers which don't support this */
return;
const commandData = this.connection.channelTree.channels.map(e => { return { cid: e.channelId, cpw: e.cached_password() }}); const commandData = this.connection.channelTree.channels.map(e => { return { cid: e.channelId, cpw: e.cached_password() }});
this.connection.serverConnection.send_command("conversationfetch", commandData).catch(error => { this.connection.serverConnection.send_command("conversationfetch", commandData).catch(error => {
log.warn(LogCategory.CHAT, tr("Failed to query conversation indexes: %o"), error); log.warn(LogCategory.CHAT, tr("Failed to query conversation indexes: %o"), error);
@ -465,7 +467,7 @@ export class ConversationManager extends AbstractChatManager<ConversationUIEvent
} }
@EventHandler<ConversationUIEvents>("query_selected_chat") @EventHandler<ConversationUIEvents>("query_selected_chat")
private handleQuerySelectedChat(event: ConversationUIEvents["query_selected_chat"]) { private handleQuerySelectedChat() {
this.uiEvents.fire_async("notify_selected_chat", { chatId: isNaN(this.selectedConversation_) ? "unselected" : this.selectedConversation_ + ""}) this.uiEvents.fire_async("notify_selected_chat", { chatId: isNaN(this.selectedConversation_) ? "unselected" : this.selectedConversation_ + ""})
} }

View file

@ -22,10 +22,14 @@ import {TimestampRenderer} from "tc-shared/ui/react-elements/TimestampRenderer";
import {BBCodeRenderer} from "tc-shared/text/bbcode"; import {BBCodeRenderer} from "tc-shared/text/bbcode";
import {getGlobalAvatarManagerFactory} from "tc-shared/file/Avatars"; import {getGlobalAvatarManagerFactory} from "tc-shared/file/Avatars";
import {ColloquialFormat, date_format, format_date_general, formatDayTime} from "tc-shared/utils/DateUtils"; import {ColloquialFormat, date_format, format_date_general, formatDayTime} from "tc-shared/utils/DateUtils";
import {ClientTag} from "tc-shared/ui/tree/EntryTags";
const cssStyle = require("./ConversationUI.scss"); const cssStyle = require("./ConversationUI.scss");
const CMTextRenderer = React.memo((props: { text: string }) => <BBCodeRenderer settings={{ convertSingleUrls: true }} message={props.text} />); const ChatMessageTextRenderer = React.memo((props: { text: string }) => {
if(typeof props.text !== "string") { debugger; }
return <BBCodeRenderer settings={{ convertSingleUrls: true }} message={props.text || ""} />;
});
const ChatEventMessageRenderer = React.memo((props: { const ChatEventMessageRenderer = React.memo((props: {
message: ChatMessage, message: ChatMessage,
@ -57,17 +61,8 @@ const ChatEventMessageRenderer = React.memo((props: {
<div className={cssStyle.message}> <div className={cssStyle.message}>
<div className={cssStyle.info}> <div className={cssStyle.info}>
{deleteButton} {deleteButton}
{/*
<a className={cssStyle.sender} dangerouslySetInnerHTML={{ __html: generate_client({
client_database_id: props.message.sender_database_id,
client_id: -1,
client_name: props.message.sender_name,
client_unique_id: props.message.sender_unique_id,
add_braces: false
})}} />
*/}
<a className={cssStyle.sender}> <a className={cssStyle.sender}>
<div className={"htmltag-client"}>{props.message.sender_name}</div> <ClientTag clientName={props.message.sender_name} clientUniqueId={props.message.sender_unique_id} handlerId={props.handlerId} clientDatabaseId={props.message.sender_database_id} />
</a> </a>
<span> </span> { /* Only for copy purposes */} <span> </span> { /* Only for copy purposes */}
<a className={cssStyle.timestamp}> <a className={cssStyle.timestamp}>
@ -76,7 +71,7 @@ const ChatEventMessageRenderer = React.memo((props: {
<br /> { /* Only for copy purposes */ } <br /> { /* Only for copy purposes */ }
</div> </div>
<div className={cssStyle.text}> <div className={cssStyle.text}>
<CMTextRenderer text={props.message.message} /> <ChatMessageTextRenderer text={props.message.message} />
</div> </div>
<br style={{ content: " ", display: "none" }} /> { /* Only for copy purposes */ } <br style={{ content: " ", display: "none" }} /> { /* Only for copy purposes */ }
</div> </div>
@ -481,7 +476,7 @@ class ConversationMessages extends React.PureComponent<ConversationMessagesPrope
case "no-permission": case "no-permission":
contents.push(<div key={"ol-permission"} className={cssStyle.overlay}><a> contents.push(<div key={"ol-permission"} className={cssStyle.overlay}><a>
<Translatable>You don't have permissions to participate in this conversation!</Translatable><br /> <Translatable>You don't have permissions to participate in this conversation!</Translatable><br />
>{this.state.failedPermission}</a> {this.state.failedPermission}</a>
</div>); </div>);
break; break;

View file

@ -72,8 +72,9 @@ const ConversationEntryInfo = React.memo((props: { events: Registry<PrivateConve
const isTyping = Date.now() - kTypingTimeout < typingTimestamp; const isTyping = Date.now() - kTypingTimeout < typingTimestamp;
useEffect(() => { useEffect(() => {
if(!isTyping) if(!isTyping) {
return; return;
}
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
setTypingTimestamp(0); setTypingTimestamp(0);

View file

@ -75,13 +75,16 @@ export function spawnBanClient(client: ConnectionHandler, entries: BanEntry | Ba
input_duration_value.firstParent(".input-boxed").removeClass("is-invalid"); input_duration_value.firstParent(".input-boxed").removeClass("is-invalid");
} }
if (max != -1) if (max != -1) {
tooltip_duration_max.html(tr("You're allowed to ban a maximum of ") + "<b>" + max + " " + duration_data[type][max == 1 ? "1-text" : "text"] + "</b>"); tooltip_duration_max.html(tr("You're allowed to ban a maximum of ") + "<b>" + max + " " + duration_data[type][max == 1 ? "1-text" : "text"] + "</b>");
else
tooltip_duration_max.html(tr("You're allowed to ban <b>permanent</b>."));
} else { } else {
if (value && !Number.isNaN(value)) tooltip_duration_max.html(tr("You're allowed to ban <b>permanent</b>."));
}
} else {
if (value && !Number.isNaN(value)) {
input_duration_value.attr("x-saved-value", value); input_duration_value.attr("x-saved-value", value);
}
input_duration_value.attr("placeholder", tr("for ever")).val(null); input_duration_value.attr("placeholder", tr("for ever")).val(null);
tooltip_duration_max.html(tr("You're allowed to ban <b>permanent</b>.")); tooltip_duration_max.html(tr("You're allowed to ban <b>permanent</b>."));
} }

View file

@ -0,0 +1,6 @@
.client {
display: inline-block;
color: #D8D8D8;
font-weight: 700;
cursor: pointer;
}

View file

@ -0,0 +1,23 @@
import * as React from "react";
const cssStyle = require("./EntryTags.scss");
export const ClientTag = (props: { clientName: string, clientUniqueId: string, handlerId: string, clientId?: number, clientDatabaseId?: number, className?: string }) => {
return (
<div className={cssStyle.client + (props.className ? ` ${props.className}` : ``)}
onContextMenu={event => {
event.preventDefault();
/* TODO: Enable context menus */
}}
>
{props.clientName}
</div>
);
};
export const ChannelTag = (props: { channelName: string, channelId: number, handlerId: string, className?: string }) => {
return <div className={cssStyle.client + (props.className ? ` ${props.className}` : ``)}>{props.channelName}</div>;
};

View file

@ -20,7 +20,7 @@ export class AudioLibrary {
this.worker = new WorkerOwner(AudioLibrary.spawnNewWorker); this.worker = new WorkerOwner(AudioLibrary.spawnNewWorker);
} }
private static spawnNewWorker() { private static spawnNewWorker() : Worker {
/* /*
* Attention don't use () => new Worker(...). * Attention don't use () => new Worker(...).
* This confuses the worker plugin and will not emit any modules * This confuses the worker plugin and will not emit any modules