Adjusting channel tree size accordingly to the clients font size

This commit is contained in:
WolverinDEV 2021-01-07 12:58:53 +01:00
parent fd590e5e10
commit 360b02a0c7
12 changed files with 126 additions and 94 deletions

View file

@ -1,4 +1,9 @@
# Changelog: # Changelog:
* **07.01.21**
- Improved general client ui memory footprint (Don't constantly rendering the channel tree)
- Improved channel tree loading performance especially on server join and switch
- The channel tree now adjusts accordingly to the clients font size
* **29.12.20** * **29.12.20**
- Reimplemented the music bot control UI - Reimplemented the music bot control UI
- Fixing some bugs from earlier versions - Fixing some bugs from earlier versions

View file

@ -97,7 +97,7 @@
position: relative; position: relative;
.handler { .handler {
padding-top: 4px; padding-top: 5px;
position: relative; position: relative;
flex-grow: 0; flex-grow: 0;

View file

@ -1,4 +1,12 @@
.container { .container {
width: 1em; width: 1em;
height: 1em; height: 1em;
display: flex;
flex-direction: column;
justify-content: stretch;
> img {
flex-grow: 1;
}
} }

View file

@ -16,8 +16,8 @@ html:root {
cursor: pointer; cursor: pointer;
.containerArrow { .containerArrow {
width: 16px; width: 1em;
margin-left: -16px; margin-left: -1em;
text-align: center; text-align: center;
flex-shrink: 0; flex-shrink: 0;
@ -25,15 +25,17 @@ html:root {
&.down { &.down {
align-self: normal; align-self: normal;
} }
> * {
font-size: .9em;
}
} }
.channelType { .channelType {
font-size: 16px;
flex-grow: 0; flex-grow: 0;
flex-shrink: 0; flex-shrink: 0;
margin-right: 2px; margin-right: .1em;;
} }
.containerChannelName { .containerChannelName {
@ -47,7 +49,6 @@ html:root {
max-width: 100%; /* important for the repetitive channel name! */ max-width: 100%; /* important for the repetitive channel name! */
overflow-x: hidden; overflow-x: hidden;
height: 16px;
&.align-right { &.align-right {
justify-content: right; justify-content: right;
@ -58,6 +59,7 @@ html:root {
} }
.channelName { .channelName {
line-height: 1.2em;
align-self: center; align-self: center;
color: var(--channel-tree-entry-color); color: var(--channel-tree-entry-color);
@ -115,57 +117,49 @@ html:root {
} }
} }
height: 18px + 2px!important;
margin-top: -1px!important;
padding-top: 1px!important;
margin-bottom: -1px!important;
padding-bottom: 1px!important;
padding-left: 1px!important;
.leftPadding {
height: 100%;
margin-left: -1px;
}
&.drag-top { &.drag-top {
height: 20px!important; &::before {
content: "";
position: absolute;
padding-top: 0!important; width: 100%;
padding-bottom: 2px!important; top: -1px;
left: var(--drag-left-offset, 0);
border-top: 2px solid var(--channel-tree-move-border); border-top: 2px solid var(--channel-tree-move-border);
.leftPadding {
background-color: var(--channel-tree-background);
margin-top: -4px;
} }
} }
&.drag-bottom { &.drag-bottom {
padding-bottom: 0!important;
border-bottom: 2px solid var(--channel-tree-move-border);
z-index: 1; z-index: 1;
.leftPadding { &::after {
background-color: var(--channel-tree-background); content: "";
margin-bottom: -3px; position: absolute;
width: 100%;
bottom: -1px;
left: var(--drag-left-offset, 0);
border-bottom: 2px solid var(--channel-tree-move-border);
} }
} }
&.drag-contain { &.drag-contain {
padding-top: 0!important;
padding-bottom: 0!important;
padding-left: 0!important;
border: 1px solid var(--channel-tree-move-border);
border-radius: 2px;
z-index: 1; z-index: 1;
&::before {
content: "";
position: absolute;
top: -1px;
bottom: -1px;
left: var(--drag-left-offset, 0);
right: 0;
margin-left: -.2em;
border: 2px solid var(--channel-tree-move-border);
}
} }
} }

View file

@ -15,17 +15,15 @@ html:root {
align-items: center; align-items: center;
> div { > div {
margin-right: 2px; margin-right: .1em;
} }
.statusIcon { .statusIcon {
flex-shrink: 0; flex-shrink: 0;
font-size: 16px;
} }
.clientName { .clientName {
line-height: 16px; line-height: 1.2em;
min-width: 2em; min-width: 2em;
flex-grow: 0; flex-grow: 0;

View file

@ -7,6 +7,7 @@ import {getIconManager} from "tc-shared/file/Icons";
import {Settings, settings} from "tc-shared/settings"; import {Settings, settings} from "tc-shared/settings";
import {RDPChannel} from "tc-shared/ui/tree/RendererDataProvider"; import {RDPChannel} from "tc-shared/ui/tree/RendererDataProvider";
import {UnreadMarkerRenderer} from "tc-shared/ui/tree/RendererTreeEntry"; import {UnreadMarkerRenderer} from "tc-shared/ui/tree/RendererTreeEntry";
import {ChannelTreeView} from "tc-shared/ui/tree/RendererView";
const channelStyle = require("./Channel.scss"); const channelStyle = require("./Channel.scss");
const viewStyle = require("./View.scss"); const viewStyle = require("./View.scss");
@ -132,7 +133,9 @@ export class RendererChannel extends React.Component<{ channel: RDPChannel }, {}
<div <div
ref={this.props.channel.refChannelContainer} ref={this.props.channel.refChannelContainer}
className={viewStyle.treeEntry + " " + channelStyle.channelEntry + " " + (channel.selected ? viewStyle.selected : "") + " " + dragClass} className={viewStyle.treeEntry + " " + channelStyle.channelEntry + " " + (channel.selected ? viewStyle.selected : "") + " " + dragClass}
style={{ top: channel.offsetTop }} style={{
top: (channel.offsetTop * ChannelTreeView.EntryHeightEm) + "em",
}}
onMouseUp={event => { onMouseUp={event => {
if (event.button !== 0) { if (event.button !== 0) {
return; /* only left mouse clicks */ return; /* only left mouse clicks */
@ -171,4 +174,21 @@ export class RendererChannel extends React.Component<{ channel: RDPChannel }, {}
</div> </div>
); );
} }
componentDidUpdate(prevProps: Readonly<{ channel: RDPChannel }>, prevState: Readonly<{}>, snapshot?: any) {
this.fixCssVariables();
}
componentDidMount() {
this.fixCssVariables();
}
private fixCssVariables() {
const container = this.props.channel.refChannelContainer.current;
if(!container) {
return;
}
container.style.setProperty("--drag-left-offset", this.props.channel.offsetLeft + "em");
}
} }

View file

@ -7,6 +7,7 @@ import {Settings, settings} from "tc-shared/settings";
import {UnreadMarkerRenderer} from "tc-shared/ui/tree/RendererTreeEntry"; import {UnreadMarkerRenderer} from "tc-shared/ui/tree/RendererTreeEntry";
import {RDPClient} from "tc-shared/ui/tree/RendererDataProvider"; import {RDPClient} from "tc-shared/ui/tree/RendererDataProvider";
import * as DOMPurify from "dompurify"; import * as DOMPurify from "dompurify";
import {ChannelTreeView} from "tc-shared/ui/tree/RendererView";
const clientStyle = require("./Client.scss"); const clientStyle = require("./Client.scss");
const viewStyle = require("./View.scss"); const viewStyle = require("./View.scss");
@ -176,7 +177,7 @@ export class RendererClient extends React.Component<{ client: RDPClient }, {}> {
return ( return (
<div className={clientStyle.clientEntry + " " + viewStyle.treeEntry + " " + (selected ? viewStyle.selected : "")} <div className={clientStyle.clientEntry + " " + viewStyle.treeEntry + " " + (selected ? viewStyle.selected : "")}
style={{ top: client.offsetTop }} style={{ top: (client.offsetTop * ChannelTreeView.EntryHeightEm) + "em" }}
onContextMenu={event => { onContextMenu={event => {
if (settings.static(Settings.KEY_DISABLE_CONTEXT_MENU)) { if (settings.static(Settings.KEY_DISABLE_CONTEXT_MENU)) {
return; return;

View file

@ -329,6 +329,7 @@ export class RDPChannelTree {
this.selection = new RDPTreeSelection(this); this.selection = new RDPTreeSelection(this);
this.documentDragStopListener = () => { this.documentDragStopListener = () => {
return; /* FIXME: Remove! */
if(this.dragOverChannelEntry) { if(this.dragOverChannelEntry) {
this.dragOverChannelEntry.setDragHint("none"); this.dragOverChannelEntry.setDragHint("none");
this.dragOverChannelEntry = undefined; this.dragOverChannelEntry = undefined;
@ -699,7 +700,7 @@ export class RDPChannelTree {
} }
this.treeEntries[entry.entryId] = result; this.treeEntries[entry.entryId] = result;
result.handlePositionUpdate(index * ChannelTreeView.EntryHeight, entry.depth); result.handlePositionUpdate(index, entry.depth);
return result; return result;
}).filter(e => !!e); }).filter(e => !!e);
@ -734,8 +735,8 @@ export abstract class RDPEntry {
readonly refUnread = React.createRef<UnreadMarkerRenderer>(); readonly refUnread = React.createRef<UnreadMarkerRenderer>();
offsetTop: number; offsetTop: number; /* In 16px units */
offsetLeft: number; offsetLeft: number; /* In channel units */
selected: boolean = false; selected: boolean = false;
unread: boolean = false; unread: boolean = false;

View file

@ -5,6 +5,9 @@ import {UnreadMarkerRenderer} from "./RendererTreeEntry";
import {getIconManager} from "tc-shared/file/Icons"; import {getIconManager} from "tc-shared/file/Icons";
import {RDPServer} from "tc-shared/ui/tree/RendererDataProvider"; import {RDPServer} from "tc-shared/ui/tree/RendererDataProvider";
import {Translatable, VariadicTranslatable} from "tc-shared/ui/react-elements/i18n"; import {Translatable, VariadicTranslatable} from "tc-shared/ui/react-elements/i18n";
import {ChannelTreeView} from "tc-shared/ui/tree/RendererView";
import {ClientIconRenderer} from "tc-shared/ui/react-elements/Icons";
import {ClientIcon} from "svg-sprites/client-icons";
const serverStyle = require("./Server.scss"); const serverStyle = require("./Server.scss");
const viewStyle = require("./View.scss"); const viewStyle = require("./View.scss");
@ -37,7 +40,7 @@ export class ServerRenderer extends React.Component<{ server: RDPServer }, {}> {
return ( return (
<div <div
className={serverStyle.serverEntry + " " + viewStyle.treeEntry + " " + (selected ? viewStyle.selected : "")} className={serverStyle.serverEntry + " " + viewStyle.treeEntry + " " + (selected ? viewStyle.selected : "")}
style={{ top: server.offsetTop }} style={{ top: (server.offsetTop * ChannelTreeView.EntryHeightEm) + "em" }}
onMouseDown={event => { onMouseDown={event => {
if (event.button !== 0) { if (event.button !== 0) {
return; /* only left mouse clicks */ return; /* only left mouse clicks */
@ -60,7 +63,7 @@ export class ServerRenderer extends React.Component<{ server: RDPServer }, {}> {
> >
<div className={viewStyle.leftPadding} style={{ paddingLeft: server.offsetLeft + "em" }} /> <div className={viewStyle.leftPadding} style={{ paddingLeft: server.offsetLeft + "em" }} />
<UnreadMarkerRenderer entry={server} ref={server.refUnread} /> <UnreadMarkerRenderer entry={server} ref={server.refUnread} />
<div className={"icon client-server_green " + serverStyle.server_type}/> <ClientIconRenderer icon={ClientIcon.ServerGreen} className={serverStyle.icon} />
<div className={serverStyle.name}>{name}</div> <div className={serverStyle.name}>{name}</div>
{icon} {icon}
</div> </div>

View file

@ -36,11 +36,12 @@ export interface ChannelTreeViewProperties {
} }
export interface ChannelTreeViewState { export interface ChannelTreeViewState {
element_scroll_offset?: number; /* in px */ elementScrollOffset?: number; /* in px */
scroll_offset: number; /* in px */ scrollOffset: number; /* in px */
view_height: number; /* in px */ viewHeight: number; /* in px */
fontSize: number; /* in px */
tree_version: number; treeVersion: number;
smoothScroll: boolean; smoothScroll: boolean;
/* the currently rendered tree */ /* the currently rendered tree */
@ -51,7 +52,7 @@ export interface ChannelTreeViewState {
@ReactEventHandler<ChannelTreeView>(e => e.props.events) @ReactEventHandler<ChannelTreeView>(e => e.props.events)
@BatchUpdateAssignment(BatchUpdateType.CHANNEL_TREE) @BatchUpdateAssignment(BatchUpdateType.CHANNEL_TREE)
export class ChannelTreeView extends ReactComponentBase<ChannelTreeViewProperties, ChannelTreeViewState> { export class ChannelTreeView extends ReactComponentBase<ChannelTreeViewProperties, ChannelTreeViewState> {
public static readonly EntryHeight = 18; public static readonly EntryHeightEm = 1.3;
private readonly refContainer = React.createRef<HTMLDivElement>(); private readonly refContainer = React.createRef<HTMLDivElement>();
private resizeObserver: ResizeObserver; private resizeObserver: ResizeObserver;
@ -68,12 +69,13 @@ export class ChannelTreeView extends ReactComponentBase<ChannelTreeViewPropertie
super(props); super(props);
this.state = { this.state = {
scroll_offset: 0, scrollOffset: 0,
view_height: 0, viewHeight: 0,
tree_version: 0, treeVersion: 0,
smoothScroll: false, smoothScroll: false,
tree: [], tree: [],
treeRevision: -1 treeRevision: -1,
fontSize: 14
}; };
} }
@ -89,11 +91,17 @@ export class ChannelTreeView extends ReactComponentBase<ChannelTreeViewPropertie
} }
const bounds = entries[0].contentRect; const bounds = entries[0].contentRect;
if (this.state.view_height !== bounds.height) { if (this.state.viewHeight !== bounds.height) {
this.setState({ this.setState({
view_height: bounds.height viewHeight: bounds.height
}); });
} }
const fontSize = parseFloat(getComputedStyle(entries[0].target).getPropertyValue("font-size"));
console.error("Updated font size to: %o", fontSize);
this.setState({
fontSize: fontSize || 0
});
}); });
this.resizeObserver.observe(this.refContainer.current); this.resizeObserver.observe(this.refContainer.current);
@ -119,14 +127,15 @@ export class ChannelTreeView extends ReactComponentBase<ChannelTreeViewPropertie
this.scrollFixRequested = true; this.scrollFixRequested = true;
requestAnimationFrame(() => { requestAnimationFrame(() => {
this.scrollFixRequested = false; this.scrollFixRequested = false;
this.refContainer.current.scrollTop = this.state.scroll_offset; this.refContainer.current.scrollTop = this.state.scrollOffset;
this.setState({smoothScroll: true}); this.setState({smoothScroll: true});
}); });
} }
private visibleEntries() { private visibleEntries() {
let viewEntryCount = Math.ceil(this.state.view_height / ChannelTreeView.EntryHeight); const entryHeight = ChannelTreeView.EntryHeightEm * this.state.fontSize;
const viewEntryBegin = Math.floor(this.state.scroll_offset / ChannelTreeView.EntryHeight); let viewEntryCount = Math.ceil(this.state.viewHeight / entryHeight);
const viewEntryBegin = Math.floor(this.state.scrollOffset / entryHeight);
const viewEntryEnd = Math.min(this.state.tree.length, viewEntryBegin + viewEntryCount); const viewEntryEnd = Math.min(this.state.tree.length, viewEntryBegin + viewEntryCount);
return { return {
@ -170,7 +179,7 @@ export class ChannelTreeView extends ReactComponentBase<ChannelTreeViewPropertie
> >
<div <div
className={viewStyle.channelTree} className={viewStyle.channelTree}
style={{height: (this.state.tree.length * ChannelTreeView.EntryHeight) + "px"}}> style={{height: (this.state.tree.length * ChannelTreeView.EntryHeightEm) + "em"}}>
{elements} {elements}
</div> </div>
</div> </div>
@ -179,7 +188,7 @@ export class ChannelTreeView extends ReactComponentBase<ChannelTreeViewPropertie
private onScroll() { private onScroll() {
this.setState({ this.setState({
scroll_offset: this.refContainer.current.scrollTop scrollOffset: this.refContainer.current.scrollTop
}); });
} }
@ -191,18 +200,18 @@ export class ChannelTreeView extends ReactComponentBase<ChannelTreeViewPropertie
return; return;
} }
let new_index; let newIndex;
const currentRange = this.visibleEntries(); const currentRange = this.visibleEntries();
if (index >= currentRange.end - 1) { if (index >= currentRange.end - 1) {
new_index = index - (currentRange.end - currentRange.begin) + 2; newIndex = index - (currentRange.end - currentRange.begin) + 2;
} else if (index < currentRange.begin) { } else if (index < currentRange.begin) {
new_index = index; newIndex = index;
} else { } else {
if (callback) callback(); if (callback) callback();
return; return;
} }
this.refContainer.current.scrollTop = new_index * ChannelTreeView.EntryHeight; this.refContainer.current.scrollTop = newIndex * ChannelTreeView.EntryHeightEm * this.state.fontSize;
if (callback) { if (callback) {
let cb = { let cb = {
@ -211,7 +220,7 @@ export class ChannelTreeView extends ReactComponentBase<ChannelTreeViewPropertie
timeout: setTimeout(() => { timeout: setTimeout(() => {
this.inViewCallbacks.remove(cb); this.inViewCallbacks.remove(cb);
callback(); callback();
}, (Math.abs(new_index - currentRange.begin) / (currentRange.end - currentRange.begin)) * 1500) }, (Math.abs(newIndex - currentRange.begin) / (currentRange.end - currentRange.begin)) * 1500)
}; };
this.inViewCallbacks.push(cb); this.inViewCallbacks.push(cb);
} }
@ -233,7 +242,7 @@ export class ChannelTreeView extends ReactComponentBase<ChannelTreeViewPropertie
return undefined; return undefined;
} }
const total_offset = container.scrollTop + pageY; const totalOffset = container.scrollTop + pageY;
return this.state.tree[Math.floor(total_offset / ChannelTreeView.EntryHeight)]?.entryId; return this.state.tree[Math.floor(totalOffset / (ChannelTreeView.EntryHeightEm * this.state.fontSize))]?.entryId;
} }
} }

View file

@ -30,4 +30,10 @@
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.icon {
align-self: center;
margin-bottom: .1em;
margin-right: .1em;
}
} }

View file

@ -25,15 +25,10 @@ html:root {
* { * {
font-family: sans-serif; font-family: sans-serif;
font-size: 12px;
white-space: pre; white-space: pre;
line-height: 1; line-height: 1;
} }
:global(.icon_em) {
font-size: 16px;
}
.treeEntry { .treeEntry {
position: absolute; position: absolute;
left: 0; left: 0;
@ -43,7 +38,7 @@ html:root {
flex-direction: row; flex-direction: row;
justify-content: stretch; justify-content: stretch;
height: 18px; height: 1.3em;
padding-top: 1px; padding-top: 1px;
padding-bottom: 1px; padding-bottom: 1px;
@ -89,18 +84,10 @@ html:root {
@include transition(opacity $button_hover_animation_time); @include transition(opacity $button_hover_animation_time);
} }
:global(.icon-container) {
height: 16px;
width: 16px;
}
} }
.leftPadding { .leftPadding {
flex-shrink: 0; flex-shrink: 0;
padding-left: 2px;
font-size: 16px;
} }
&.move { &.move {
@ -205,7 +192,7 @@ html:root {
@media all and (max-width: 250px) { @media all and (max-width: 250px) {
.channelTree .leftPadding { .channelTree .leftPadding {
font-size: 6px; font-size: .4em;
} }
.treeEntry { .treeEntry {