Adjusting channel tree size accordingly to the clients font size
parent
fd590e5e10
commit
360b02a0c7
|
@ -1,4 +1,9 @@
|
|||
# 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**
|
||||
- Reimplemented the music bot control UI
|
||||
- Fixing some bugs from earlier versions
|
||||
|
|
|
@ -97,7 +97,7 @@
|
|||
position: relative;
|
||||
|
||||
.handler {
|
||||
padding-top: 4px;
|
||||
padding-top: 5px;
|
||||
position: relative;
|
||||
|
||||
flex-grow: 0;
|
||||
|
|
|
@ -1,4 +1,12 @@
|
|||
.container {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: stretch;
|
||||
|
||||
> img {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
|
@ -16,8 +16,8 @@ html:root {
|
|||
cursor: pointer;
|
||||
|
||||
.containerArrow {
|
||||
width: 16px;
|
||||
margin-left: -16px;
|
||||
width: 1em;
|
||||
margin-left: -1em;
|
||||
text-align: center;
|
||||
|
||||
flex-shrink: 0;
|
||||
|
@ -25,15 +25,17 @@ html:root {
|
|||
&.down {
|
||||
align-self: normal;
|
||||
}
|
||||
|
||||
> * {
|
||||
font-size: .9em;
|
||||
}
|
||||
}
|
||||
|
||||
.channelType {
|
||||
font-size: 16px;
|
||||
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
|
||||
margin-right: 2px;
|
||||
margin-right: .1em;;
|
||||
}
|
||||
|
||||
.containerChannelName {
|
||||
|
@ -47,7 +49,6 @@ html:root {
|
|||
|
||||
max-width: 100%; /* important for the repetitive channel name! */
|
||||
overflow-x: hidden;
|
||||
height: 16px;
|
||||
|
||||
&.align-right {
|
||||
justify-content: right;
|
||||
|
@ -58,6 +59,7 @@ html:root {
|
|||
}
|
||||
|
||||
.channelName {
|
||||
line-height: 1.2em;
|
||||
align-self: center;
|
||||
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 {
|
||||
height: 20px!important;
|
||||
&::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
|
||||
padding-top: 0!important;
|
||||
padding-bottom: 2px!important;
|
||||
width: 100%;
|
||||
top: -1px;
|
||||
left: var(--drag-left-offset, 0);
|
||||
|
||||
border-top: 2px solid var(--channel-tree-move-border);
|
||||
|
||||
.leftPadding {
|
||||
background-color: var(--channel-tree-background);
|
||||
margin-top: -4px;
|
||||
border-top: 2px solid var(--channel-tree-move-border);
|
||||
}
|
||||
}
|
||||
|
||||
&.drag-bottom {
|
||||
padding-bottom: 0!important;
|
||||
border-bottom: 2px solid var(--channel-tree-move-border);
|
||||
|
||||
z-index: 1;
|
||||
|
||||
.leftPadding {
|
||||
background-color: var(--channel-tree-background);
|
||||
margin-bottom: -3px;
|
||||
&::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
|
||||
width: 100%;
|
||||
bottom: -1px;
|
||||
left: var(--drag-left-offset, 0);
|
||||
|
||||
border-bottom: 2px solid var(--channel-tree-move-border);
|
||||
}
|
||||
}
|
||||
|
||||
&.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;
|
||||
|
||||
&::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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,17 +15,15 @@ html:root {
|
|||
align-items: center;
|
||||
|
||||
> div {
|
||||
margin-right: 2px;
|
||||
margin-right: .1em;
|
||||
}
|
||||
|
||||
.statusIcon {
|
||||
flex-shrink: 0;
|
||||
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.clientName {
|
||||
line-height: 16px;
|
||||
line-height: 1.2em;
|
||||
min-width: 2em;
|
||||
|
||||
flex-grow: 0;
|
||||
|
|
|
@ -7,6 +7,7 @@ import {getIconManager} from "tc-shared/file/Icons";
|
|||
import {Settings, settings} from "tc-shared/settings";
|
||||
import {RDPChannel} from "tc-shared/ui/tree/RendererDataProvider";
|
||||
import {UnreadMarkerRenderer} from "tc-shared/ui/tree/RendererTreeEntry";
|
||||
import {ChannelTreeView} from "tc-shared/ui/tree/RendererView";
|
||||
|
||||
const channelStyle = require("./Channel.scss");
|
||||
const viewStyle = require("./View.scss");
|
||||
|
@ -132,7 +133,9 @@ export class RendererChannel extends React.Component<{ channel: RDPChannel }, {}
|
|||
<div
|
||||
ref={this.props.channel.refChannelContainer}
|
||||
className={viewStyle.treeEntry + " " + channelStyle.channelEntry + " " + (channel.selected ? viewStyle.selected : "") + " " + dragClass}
|
||||
style={{ top: channel.offsetTop }}
|
||||
style={{
|
||||
top: (channel.offsetTop * ChannelTreeView.EntryHeightEm) + "em",
|
||||
}}
|
||||
onMouseUp={event => {
|
||||
if (event.button !== 0) {
|
||||
return; /* only left mouse clicks */
|
||||
|
@ -171,4 +174,21 @@ export class RendererChannel extends React.Component<{ channel: RDPChannel }, {}
|
|||
</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");
|
||||
}
|
||||
}
|
|
@ -7,6 +7,7 @@ import {Settings, settings} from "tc-shared/settings";
|
|||
import {UnreadMarkerRenderer} from "tc-shared/ui/tree/RendererTreeEntry";
|
||||
import {RDPClient} from "tc-shared/ui/tree/RendererDataProvider";
|
||||
import * as DOMPurify from "dompurify";
|
||||
import {ChannelTreeView} from "tc-shared/ui/tree/RendererView";
|
||||
|
||||
const clientStyle = require("./Client.scss");
|
||||
const viewStyle = require("./View.scss");
|
||||
|
@ -176,7 +177,7 @@ export class RendererClient extends React.Component<{ client: RDPClient }, {}> {
|
|||
|
||||
return (
|
||||
<div className={clientStyle.clientEntry + " " + viewStyle.treeEntry + " " + (selected ? viewStyle.selected : "")}
|
||||
style={{ top: client.offsetTop }}
|
||||
style={{ top: (client.offsetTop * ChannelTreeView.EntryHeightEm) + "em" }}
|
||||
onContextMenu={event => {
|
||||
if (settings.static(Settings.KEY_DISABLE_CONTEXT_MENU)) {
|
||||
return;
|
||||
|
|
|
@ -329,6 +329,7 @@ export class RDPChannelTree {
|
|||
this.selection = new RDPTreeSelection(this);
|
||||
|
||||
this.documentDragStopListener = () => {
|
||||
return; /* FIXME: Remove! */
|
||||
if(this.dragOverChannelEntry) {
|
||||
this.dragOverChannelEntry.setDragHint("none");
|
||||
this.dragOverChannelEntry = undefined;
|
||||
|
@ -699,7 +700,7 @@ export class RDPChannelTree {
|
|||
}
|
||||
|
||||
this.treeEntries[entry.entryId] = result;
|
||||
result.handlePositionUpdate(index * ChannelTreeView.EntryHeight, entry.depth);
|
||||
result.handlePositionUpdate(index, entry.depth);
|
||||
|
||||
return result;
|
||||
}).filter(e => !!e);
|
||||
|
@ -734,8 +735,8 @@ export abstract class RDPEntry {
|
|||
|
||||
readonly refUnread = React.createRef<UnreadMarkerRenderer>();
|
||||
|
||||
offsetTop: number;
|
||||
offsetLeft: number;
|
||||
offsetTop: number; /* In 16px units */
|
||||
offsetLeft: number; /* In channel units */
|
||||
|
||||
selected: boolean = false;
|
||||
unread: boolean = false;
|
||||
|
|
|
@ -5,6 +5,9 @@ import {UnreadMarkerRenderer} from "./RendererTreeEntry";
|
|||
import {getIconManager} from "tc-shared/file/Icons";
|
||||
import {RDPServer} from "tc-shared/ui/tree/RendererDataProvider";
|
||||
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 viewStyle = require("./View.scss");
|
||||
|
@ -37,7 +40,7 @@ export class ServerRenderer extends React.Component<{ server: RDPServer }, {}> {
|
|||
return (
|
||||
<div
|
||||
className={serverStyle.serverEntry + " " + viewStyle.treeEntry + " " + (selected ? viewStyle.selected : "")}
|
||||
style={{ top: server.offsetTop }}
|
||||
style={{ top: (server.offsetTop * ChannelTreeView.EntryHeightEm) + "em" }}
|
||||
onMouseDown={event => {
|
||||
if (event.button !== 0) {
|
||||
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" }} />
|
||||
<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>
|
||||
{icon}
|
||||
</div>
|
||||
|
|
|
@ -36,11 +36,12 @@ export interface ChannelTreeViewProperties {
|
|||
}
|
||||
|
||||
export interface ChannelTreeViewState {
|
||||
element_scroll_offset?: number; /* in px */
|
||||
scroll_offset: number; /* in px */
|
||||
view_height: number; /* in px */
|
||||
elementScrollOffset?: number; /* in px */
|
||||
scrollOffset: number; /* in px */
|
||||
viewHeight: number; /* in px */
|
||||
fontSize: number; /* in px */
|
||||
|
||||
tree_version: number;
|
||||
treeVersion: number;
|
||||
smoothScroll: boolean;
|
||||
|
||||
/* the currently rendered tree */
|
||||
|
@ -51,7 +52,7 @@ export interface ChannelTreeViewState {
|
|||
@ReactEventHandler<ChannelTreeView>(e => e.props.events)
|
||||
@BatchUpdateAssignment(BatchUpdateType.CHANNEL_TREE)
|
||||
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 resizeObserver: ResizeObserver;
|
||||
|
@ -68,12 +69,13 @@ export class ChannelTreeView extends ReactComponentBase<ChannelTreeViewPropertie
|
|||
super(props);
|
||||
|
||||
this.state = {
|
||||
scroll_offset: 0,
|
||||
view_height: 0,
|
||||
tree_version: 0,
|
||||
scrollOffset: 0,
|
||||
viewHeight: 0,
|
||||
treeVersion: 0,
|
||||
smoothScroll: false,
|
||||
tree: [],
|
||||
treeRevision: -1
|
||||
treeRevision: -1,
|
||||
fontSize: 14
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -89,11 +91,17 @@ export class ChannelTreeView extends ReactComponentBase<ChannelTreeViewPropertie
|
|||
}
|
||||
|
||||
const bounds = entries[0].contentRect;
|
||||
if (this.state.view_height !== bounds.height) {
|
||||
if (this.state.viewHeight !== bounds.height) {
|
||||
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);
|
||||
|
@ -119,14 +127,15 @@ export class ChannelTreeView extends ReactComponentBase<ChannelTreeViewPropertie
|
|||
this.scrollFixRequested = true;
|
||||
requestAnimationFrame(() => {
|
||||
this.scrollFixRequested = false;
|
||||
this.refContainer.current.scrollTop = this.state.scroll_offset;
|
||||
this.refContainer.current.scrollTop = this.state.scrollOffset;
|
||||
this.setState({smoothScroll: true});
|
||||
});
|
||||
}
|
||||
|
||||
private visibleEntries() {
|
||||
let viewEntryCount = Math.ceil(this.state.view_height / ChannelTreeView.EntryHeight);
|
||||
const viewEntryBegin = Math.floor(this.state.scroll_offset / ChannelTreeView.EntryHeight);
|
||||
const entryHeight = ChannelTreeView.EntryHeightEm * this.state.fontSize;
|
||||
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);
|
||||
|
||||
return {
|
||||
|
@ -170,7 +179,7 @@ export class ChannelTreeView extends ReactComponentBase<ChannelTreeViewPropertie
|
|||
>
|
||||
<div
|
||||
className={viewStyle.channelTree}
|
||||
style={{height: (this.state.tree.length * ChannelTreeView.EntryHeight) + "px"}}>
|
||||
style={{height: (this.state.tree.length * ChannelTreeView.EntryHeightEm) + "em"}}>
|
||||
{elements}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -179,7 +188,7 @@ export class ChannelTreeView extends ReactComponentBase<ChannelTreeViewPropertie
|
|||
|
||||
private onScroll() {
|
||||
this.setState({
|
||||
scroll_offset: this.refContainer.current.scrollTop
|
||||
scrollOffset: this.refContainer.current.scrollTop
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -191,18 +200,18 @@ export class ChannelTreeView extends ReactComponentBase<ChannelTreeViewPropertie
|
|||
return;
|
||||
}
|
||||
|
||||
let new_index;
|
||||
let newIndex;
|
||||
const currentRange = this.visibleEntries();
|
||||
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) {
|
||||
new_index = index;
|
||||
newIndex = index;
|
||||
} else {
|
||||
if (callback) callback();
|
||||
return;
|
||||
}
|
||||
|
||||
this.refContainer.current.scrollTop = new_index * ChannelTreeView.EntryHeight;
|
||||
this.refContainer.current.scrollTop = newIndex * ChannelTreeView.EntryHeightEm * this.state.fontSize;
|
||||
|
||||
if (callback) {
|
||||
let cb = {
|
||||
|
@ -211,7 +220,7 @@ export class ChannelTreeView extends ReactComponentBase<ChannelTreeViewPropertie
|
|||
timeout: setTimeout(() => {
|
||||
this.inViewCallbacks.remove(cb);
|
||||
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);
|
||||
}
|
||||
|
@ -233,7 +242,7 @@ export class ChannelTreeView extends ReactComponentBase<ChannelTreeViewPropertie
|
|||
return undefined;
|
||||
}
|
||||
|
||||
const total_offset = container.scrollTop + pageY;
|
||||
return this.state.tree[Math.floor(total_offset / ChannelTreeView.EntryHeight)]?.entryId;
|
||||
const totalOffset = container.scrollTop + pageY;
|
||||
return this.state.tree[Math.floor(totalOffset / (ChannelTreeView.EntryHeightEm * this.state.fontSize))]?.entryId;
|
||||
}
|
||||
}
|
|
@ -30,4 +30,10 @@
|
|||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.icon {
|
||||
align-self: center;
|
||||
margin-bottom: .1em;
|
||||
margin-right: .1em;
|
||||
}
|
||||
}
|
|
@ -25,15 +25,10 @@ html:root {
|
|||
|
||||
* {
|
||||
font-family: sans-serif;
|
||||
font-size: 12px;
|
||||
white-space: pre;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
:global(.icon_em) {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.treeEntry {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
|
@ -43,7 +38,7 @@ html:root {
|
|||
flex-direction: row;
|
||||
justify-content: stretch;
|
||||
|
||||
height: 18px;
|
||||
height: 1.3em;
|
||||
padding-top: 1px;
|
||||
padding-bottom: 1px;
|
||||
|
||||
|
@ -89,18 +84,10 @@ html:root {
|
|||
|
||||
@include transition(opacity $button_hover_animation_time);
|
||||
}
|
||||
|
||||
:global(.icon-container) {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.leftPadding {
|
||||
flex-shrink: 0;
|
||||
|
||||
padding-left: 2px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
&.move {
|
||||
|
@ -205,7 +192,7 @@ html:root {
|
|||
|
||||
@media all and (max-width: 250px) {
|
||||
.channelTree .leftPadding {
|
||||
font-size: 6px;
|
||||
font-size: .4em;
|
||||
}
|
||||
|
||||
.treeEntry {
|
||||
|
|
Loading…
Reference in New Issue