Some more channel tree features
parent
df8136acb1
commit
b46a7f59f5
|
@ -8,6 +8,7 @@
|
|||
- Fixed the invalid initialisation of codec workers
|
||||
- Improved context menu subcontainer selection
|
||||
- Fixed client channel permission tab within the permission editor (previously you've been kick from the server)
|
||||
- Added the ability to collapse/expend the channel tree
|
||||
|
||||
* **11.04.20**
|
||||
- Only show the host message when its not empty
|
||||
|
|
|
@ -19,6 +19,7 @@ import * as React from "react";
|
|||
import {Registry} from "tc-shared/events";
|
||||
import {ChannelTreeEntry, ChannelTreeEntryEvents} from "tc-shared/ui/TreeEntry";
|
||||
import { ChannelEntryView as ChannelEntryView } from "./tree/Channel";
|
||||
import {MenuEntryType} from "tc-shared/ui/elements/ContextMenu";
|
||||
|
||||
export enum ChannelType {
|
||||
PERMANENT,
|
||||
|
@ -88,6 +89,9 @@ export interface ChannelEvents extends ChannelTreeEntryEvents {
|
|||
notify_subscribe_state_changed: {
|
||||
channel_subscribed: boolean
|
||||
},
|
||||
notify_collapsed_state_changed: {
|
||||
collapsed: boolean
|
||||
},
|
||||
|
||||
notify_children_changed: {},
|
||||
notify_clients_changed: {}, /* will also be fired when clients haven been reordered */
|
||||
|
@ -157,7 +161,7 @@ export class ChannelEntry extends ChannelTreeEntry<ChannelEvents> {
|
|||
child_channel_head?: ChannelEntry;
|
||||
|
||||
readonly events: Registry<ChannelEvents>;
|
||||
readonly view: React.Ref<ChannelEntryView>;
|
||||
readonly view: React.RefObject<ChannelEntryView>;
|
||||
|
||||
parsed_channel_name: ParsedChannelName;
|
||||
|
||||
|
@ -172,6 +176,7 @@ export class ChannelEntry extends ChannelTreeEntry<ChannelEvents> {
|
|||
private _cached_channel_description_promise_resolve: any = undefined;
|
||||
private _cached_channel_description_promise_reject: any = undefined;
|
||||
|
||||
private _flag_collapsed: boolean; //TODO: Load from config!
|
||||
private _flag_subscribed: boolean;
|
||||
private _subscribe_mode: ChannelSubscribeMode;
|
||||
|
||||
|
@ -378,6 +383,7 @@ export class ChannelEntry extends ChannelTreeEntry<ChannelEvents> {
|
|||
|
||||
let trigger_close = true;
|
||||
|
||||
const collapse_expendable = !!this.child_channel_head || this.client_list.length > 0;
|
||||
const bold = text => contextmenu.get_provider().html_format_enabled() ? "<b>" + text + "</b>" : text;
|
||||
contextmenu.spawn_context_menu(x, y, {
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
|
@ -497,6 +503,25 @@ export class ChannelEntry extends ChannelTreeEntry<ChannelEvents> {
|
|||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
type: MenuEntryType.HR,
|
||||
name: "",
|
||||
visible: collapse_expendable
|
||||
},
|
||||
{
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
icon_class: "client-channel_collapse_all",
|
||||
name: tr("Collapse sub channels"),
|
||||
visible: collapse_expendable,
|
||||
callback: () => this.channelTree.collapse_channels(this)
|
||||
},
|
||||
{
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
icon_class: "client-channel_expand_all",
|
||||
name: tr("Expend sub channels"),
|
||||
visible: collapse_expendable,
|
||||
callback: () => this.channelTree.expand_channels(this)
|
||||
},
|
||||
contextmenu.Entry.HR(),
|
||||
{
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
|
@ -665,6 +690,18 @@ export class ChannelEntry extends ChannelTreeEntry<ChannelEvents> {
|
|||
}
|
||||
}
|
||||
|
||||
get collapsed() : boolean {
|
||||
return this._flag_collapsed;
|
||||
}
|
||||
|
||||
set collapsed(flag: boolean) {
|
||||
if(this._flag_collapsed === flag)
|
||||
return;
|
||||
this._flag_collapsed = flag;
|
||||
this.events.fire("notify_collapsed_state_changed", { collapsed: flag });
|
||||
this.view.current?.forceUpdate();
|
||||
}
|
||||
|
||||
get flag_subscribed() : boolean {
|
||||
return this._flag_subscribed;
|
||||
}
|
||||
|
|
|
@ -245,7 +245,23 @@ export class ServerEntry extends ChannelTreeEntry<ServerEvents> {
|
|||
name: tr("View avatars"),
|
||||
visible: false, //TODO: Enable again as soon the new design is finished
|
||||
callback: () => spawnAvatarList(this.channelTree.client)
|
||||
}
|
||||
},
|
||||
{
|
||||
type: contextmenu.MenuEntryType.HR,
|
||||
name: ''
|
||||
},
|
||||
{
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
icon_class: "client-channel_collapse_all",
|
||||
name: tr("Collapse all channels"),
|
||||
callback: () => this.channelTree.collapse_channels()
|
||||
},
|
||||
{
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
icon_class: "client-channel_expand_all",
|
||||
name: tr("Expend all channels"),
|
||||
callback: () => this.channelTree.expand_channels()
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,20 @@
|
|||
align-items: center;
|
||||
cursor: pointer;
|
||||
|
||||
.containerArrow {
|
||||
width: 16px;
|
||||
margin-left: -16px;
|
||||
text-align: center;
|
||||
|
||||
&.down {
|
||||
align-self: normal;
|
||||
}
|
||||
|
||||
:global .arrow {
|
||||
border-color: hsla(220, 5%, 30%, 1);
|
||||
}
|
||||
}
|
||||
|
||||
.channelType {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
|
|
|
@ -220,6 +220,14 @@ interface ChannelEntryViewProperties {
|
|||
offset: number;
|
||||
}
|
||||
|
||||
|
||||
const ChannelCollapsedIndicator = (props: { collapsed: boolean, onToggle: () => void }) => {
|
||||
return <div className={channelStyle.containerArrow + (!props.collapsed ? " " + channelStyle.down : "")}><div className={"arrow " + (props.collapsed ? "right" : "down")} onClick={event => {
|
||||
event.preventDefault();
|
||||
props.onToggle();
|
||||
}} /></div>
|
||||
};
|
||||
|
||||
@ReactEventHandler<ChannelEntryView>(e => e.props.channel.events)
|
||||
@BatchUpdateAssignment(BatchUpdateType.CHANNEL_TREE)
|
||||
export class ChannelEntryView extends TreeEntry<ChannelEntryViewProperties, {}> {
|
||||
|
@ -233,19 +241,25 @@ export class ChannelEntryView extends TreeEntry<ChannelEntryViewProperties, {}>
|
|||
}
|
||||
|
||||
render() {
|
||||
const collapsed_indicator = this.props.channel.child_channel_head || this.props.channel.clients(false).length > 0;
|
||||
return <div className={this.classList(viewStyle.treeEntry, channelStyle.channelEntry, this.props.channel.isSelected() && viewStyle.selected)}
|
||||
style={{ paddingLeft: this.props.depth * 16, top: this.props.offset }}
|
||||
style={{ paddingLeft: this.props.depth * 16 + 2, top: this.props.offset }}
|
||||
onMouseDown={e => this.onMouseDown(e as any)}
|
||||
onDoubleClick={() => this.onDoubleClick()}
|
||||
onContextMenu={e => this.onContextMenu(e as any)}
|
||||
>
|
||||
<UnreadMarker entry={this.props.channel} />
|
||||
{collapsed_indicator && <ChannelCollapsedIndicator key={"collapsed-indicator"} onToggle={() => this.onCollapsedToggle()} collapsed={this.props.channel.collapsed} />}
|
||||
<ChannelEntryIcon channel={this.props.channel} />
|
||||
<ChannelEntryName channel={this.props.channel} />
|
||||
<ChannelEntryIcons channel={this.props.channel} />
|
||||
</div>;
|
||||
}
|
||||
|
||||
private onCollapsedToggle() {
|
||||
this.props.channel.collapsed = !this.props.channel.collapsed;
|
||||
}
|
||||
|
||||
private onMouseDown(event: MouseEvent) {
|
||||
if(event.button !== 0) return; /* only left mouse clicks */
|
||||
|
||||
|
|
|
@ -362,7 +362,7 @@ export class ClientEntry extends TreeEntry<ClientEntryProperties, ClientEntrySta
|
|||
render() {
|
||||
return (
|
||||
<div className={this.classList(clientStyle.clientEntry, viewStyle.treeEntry, this.props.client.isSelected() && viewStyle.selected)}
|
||||
style={{ paddingLeft: (this.props.depth * 16) + "px", top: this.props.offset }}
|
||||
style={{ paddingLeft: (this.props.depth * 16 + 2) + "px", top: this.props.offset }}
|
||||
onDoubleClick={() => this.onDoubleClick()}
|
||||
onMouseDown={e => this.onMouseDown(e as any)}
|
||||
onContextMenu={e => this.onContextMenu(e as any)}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
position: relative;
|
||||
|
||||
cursor: pointer;
|
||||
margin-left: 2px;
|
||||
padding-left: 2px;
|
||||
margin-right: 5px;
|
||||
|
||||
.server_type {
|
||||
|
@ -14,7 +14,6 @@
|
|||
flex-shrink: 0;
|
||||
|
||||
margin-right: 2px;
|
||||
margin-left: 2px;
|
||||
|
||||
z-index: 1;
|
||||
}
|
||||
|
|
|
@ -49,6 +49,7 @@ export class ChannelTreeView extends ReactComponentBase<ChannelTreeViewPropertie
|
|||
private flat_tree: FlatTreeEntry[] = [];
|
||||
private listener_client_change;
|
||||
private listener_channel_change;
|
||||
private listener_state_collapsed;
|
||||
private update_timeout;
|
||||
|
||||
private in_view_callbacks: {
|
||||
|
@ -93,6 +94,7 @@ export class ChannelTreeView extends ReactComponentBase<ChannelTreeViewPropertie
|
|||
(window as any).do_tree_update = () => this.handleTreeUpdate();
|
||||
this.listener_client_change = () => this.handleTreeUpdate();
|
||||
this.listener_channel_change = () => this.handleTreeUpdate();
|
||||
this.listener_state_collapsed = () => this.handleTreeUpdate();
|
||||
}
|
||||
|
||||
private handleTreeUpdate() {
|
||||
|
@ -147,11 +149,14 @@ export class ChannelTreeView extends ReactComponentBase<ChannelTreeViewPropertie
|
|||
private build_sub_tree(entry: ChannelEntry, depth: number) {
|
||||
entry.events.on("notify_clients_changed", this.listener_client_change);
|
||||
entry.events.on("notify_children_changed", this.listener_channel_change);
|
||||
entry.events.on("notify_collapsed_state_changed", this.listener_state_collapsed);
|
||||
|
||||
this.flat_tree.push({
|
||||
entry: entry,
|
||||
rendered: <ChannelEntryView key={"channel-" + entry.channelId} channel={entry} offset={this.build_top_offset += ChannelTreeView.EntryHeight} depth={depth} ref={entry.view} />
|
||||
});
|
||||
|
||||
if(entry.collapsed) return;
|
||||
this.flat_tree.push(...entry.clients(false).map(e => {
|
||||
return {
|
||||
entry: e,
|
||||
|
@ -171,6 +176,7 @@ export class ChannelTreeView extends ReactComponentBase<ChannelTreeViewPropertie
|
|||
if(entry instanceof ChannelEntry) {
|
||||
entry.events.off("notify_clients_changed", this.listener_client_change);
|
||||
entry.events.off("notify_children_changed", this.listener_channel_change);
|
||||
entry.events.off("notify_collapsed_state_changed", this.listener_state_collapsed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -291,6 +291,18 @@ export class ChannelTree {
|
|||
invalidPermission: !channelCreate,
|
||||
callback: () => this.spawnCreateChannel()
|
||||
},
|
||||
{
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
icon_class: "client-channel_collapse_all",
|
||||
name: tr("Collapse all channels"),
|
||||
callback: () => this.collapse_channels()
|
||||
},
|
||||
{
|
||||
type: contextmenu.MenuEntryType.ENTRY,
|
||||
icon_class: "client-channel_expand_all",
|
||||
name: tr("Expend all channels"),
|
||||
callback: () => this.expand_channels()
|
||||
},
|
||||
contextmenu.Entry.CLOSE(on_close)
|
||||
);
|
||||
}
|
||||
|
@ -1049,4 +1061,24 @@ export class ChannelTree {
|
|||
console.warn(tr("Failed to subscribe to all channels! (%o)"), error);
|
||||
});
|
||||
}
|
||||
|
||||
expand_channels(root?: ChannelEntry) {
|
||||
if(typeof root === "undefined")
|
||||
this.rootChannel().forEach(e => this.expand_channels(e));
|
||||
else {
|
||||
root.collapsed = false;
|
||||
for(const child of root.children(false))
|
||||
this.expand_channels(child);
|
||||
}
|
||||
}
|
||||
|
||||
collapse_channels(root?: ChannelEntry) {
|
||||
if(typeof root === "undefined")
|
||||
this.rootChannel().forEach(e => this.collapse_channels(e));
|
||||
else {
|
||||
root.collapsed = true;
|
||||
for(const child of root.children(false))
|
||||
this.collapse_channels(child);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue