Implemented client rename and some small channel tree optimisations
This commit is contained in:
parent
f6d25a71f9
commit
ef780bc695
10 changed files with 90 additions and 32 deletions
|
@ -642,7 +642,8 @@ export class ChannelTree {
|
||||||
contextmenu.Entry.CLOSE(on_close)
|
contextmenu.Entry.CLOSE(on_close)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
private open_multiselect_context_menu(entries: ChannelTreeEntry<any>[], x: number, y: number) {
|
|
||||||
|
public open_multiselect_context_menu(entries: ChannelTreeEntry<any>[], x: number, y: number) {
|
||||||
const clients = entries.filter(e => e instanceof ClientEntry) as ClientEntry[];
|
const clients = entries.filter(e => e instanceof ClientEntry) as ClientEntry[];
|
||||||
const channels = entries.filter(e => e instanceof ChannelEntry) as ChannelEntry[];
|
const channels = entries.filter(e => e instanceof ChannelEntry) as ChannelEntry[];
|
||||||
const server = entries.find(e => e instanceof ServerEntry) as ServerEntry;
|
const server = entries.find(e => e instanceof ServerEntry) as ServerEntry;
|
||||||
|
|
|
@ -16,13 +16,11 @@
|
||||||
margin-left: -16px;
|
margin-left: -16px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
&.down {
|
&.down {
|
||||||
align-self: normal;
|
align-self: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global .arrow {
|
|
||||||
border-color: hsla(220, 5%, 30%, 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.channelType {
|
.channelType {
|
||||||
|
|
|
@ -18,6 +18,10 @@ html:root {
|
||||||
margin-right: 2px;
|
margin-right: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.statusIcon {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.clientName {
|
.clientName {
|
||||||
line-height: 16px;
|
line-height: 16px;
|
||||||
min-width: 2em;
|
min-width: 2em;
|
||||||
|
|
|
@ -109,6 +109,7 @@ class ChannelTreeController {
|
||||||
|
|
||||||
this.channelTree.events.register_handler(this);
|
this.channelTree.events.register_handler(this);
|
||||||
this.channelTree.channels.forEach(channel => this.initializeChannelEvents(channel));
|
this.channelTree.channels.forEach(channel => this.initializeChannelEvents(channel));
|
||||||
|
this.channelTree.clients.forEach(client => this.initializeClientEvents(client));
|
||||||
}
|
}
|
||||||
|
|
||||||
destroy() {
|
destroy() {
|
||||||
|
@ -177,7 +178,6 @@ class ChannelTreeController {
|
||||||
/* general channel tree event handlers */
|
/* general channel tree event handlers */
|
||||||
@EventHandler<ChannelTreeEvents>("notify_channel_list_received")
|
@EventHandler<ChannelTreeEvents>("notify_channel_list_received")
|
||||||
private handleChannelListReceived() {
|
private handleChannelListReceived() {
|
||||||
console.error("Channel list received");
|
|
||||||
this.channelTreeInitialized = true;
|
this.channelTreeInitialized = true;
|
||||||
this.channelTree.channels.forEach(channel => this.initializeChannelEvents(channel));
|
this.channelTree.channels.forEach(channel => this.initializeChannelEvents(channel));
|
||||||
this.channelTree.clients.forEach(channel => this.initializeClientEvents(channel));
|
this.channelTree.clients.forEach(channel => this.initializeClientEvents(channel));
|
||||||
|
@ -656,7 +656,7 @@ function initializeTreeController(events: Registry<ChannelTreeUIEvents>, channel
|
||||||
}
|
}
|
||||||
|
|
||||||
if (channelTree.selection.is_multi_select() && entry.isSelected()) {
|
if (channelTree.selection.is_multi_select() && entry.isSelected()) {
|
||||||
/* TODO: Spawn the context menu! */
|
channelTree.open_multiselect_context_menu(channelTree.selection.selected_entries, event.pageX, event.pageY);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -92,7 +92,7 @@ const ChannelName = React.memo((props: { channelName: string | undefined, alignm
|
||||||
|
|
||||||
const ChannelCollapsedIndicator = (props: { collapsed: boolean, onToggle: () => void }) => {
|
const ChannelCollapsedIndicator = (props: { collapsed: boolean, onToggle: () => void }) => {
|
||||||
return <div className={channelStyle.containerArrow + (!props.collapsed ? " " + channelStyle.down : "")}>
|
return <div className={channelStyle.containerArrow + (!props.collapsed ? " " + channelStyle.down : "")}>
|
||||||
<div className={"arrow " + (props.collapsed ? "right" : "down")} onClick={event => {
|
<div className={viewStyle.arrow + " " + (props.collapsed ? viewStyle.right : viewStyle.down)} onClick={event => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
props.onToggle();
|
props.onToggle();
|
||||||
}}/>
|
}}/>
|
||||||
|
@ -126,7 +126,7 @@ export class RendererChannel extends React.Component<{ channel: RDPChannel }, {}
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={viewStyle.treeEntry + " " + channelStyle.channelEntry + " " + (channel.selected ? viewStyle.selected : "")}
|
className={viewStyle.treeEntry + " " + channelStyle.channelEntry + " " + (channel.selected ? viewStyle.selected : "")}
|
||||||
style={{ paddingLeft: channel.offsetLeft, top: channel.offsetTop }}
|
style={{ top: channel.offsetTop }}
|
||||||
onMouseUp={event => {
|
onMouseUp={event => {
|
||||||
if (event.button !== 0) {
|
if (event.button !== 0) {
|
||||||
return; /* only left mouse clicks */
|
return; /* only left mouse clicks */
|
||||||
|
@ -156,6 +156,7 @@ export class RendererChannel extends React.Component<{ channel: RDPChannel }, {}
|
||||||
events.fire("action_channel_open_file_browser", { treeEntryId: entryId });
|
events.fire("action_channel_open_file_browser", { treeEntryId: entryId });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<div className={viewStyle.leftPadding} style={{ paddingLeft: channel.offsetLeft + "em" }} />
|
||||||
<UnreadMarkerRenderer entry={this.props.channel} ref={this.props.channel.refUnread} />
|
<UnreadMarkerRenderer entry={this.props.channel} ref={this.props.channel.refUnread} />
|
||||||
{collapsedIndicator}
|
{collapsedIndicator}
|
||||||
{channelIcon}
|
{channelIcon}
|
||||||
|
|
|
@ -14,7 +14,7 @@ const viewStyle = require("./View.scss");
|
||||||
/* TODO: Render a talk power request */
|
/* TODO: Render a talk power request */
|
||||||
export class ClientStatus extends React.Component<{ client: RDPClient }, {}> {
|
export class ClientStatus extends React.Component<{ client: RDPClient }, {}> {
|
||||||
render() {
|
render() {
|
||||||
return <IconRenderer icon={this.props.client.status} />
|
return <IconRenderer icon={this.props.client.status} className={clientStyle.statusIcon} />
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,11 +115,34 @@ interface ClientNameEditProps {
|
||||||
initialName: string;
|
initialName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare global{
|
||||||
|
interface HTMLElement {
|
||||||
|
createTextRange;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function selectText(node: HTMLElement) {
|
||||||
|
if (document.body.createTextRange) {
|
||||||
|
const range = document.body.createTextRange();
|
||||||
|
range.moveToElementText(node);
|
||||||
|
range.select();
|
||||||
|
} else if (window.getSelection) {
|
||||||
|
const selection = window.getSelection();
|
||||||
|
const range = document.createRange();
|
||||||
|
range.selectNodeContents(node);
|
||||||
|
selection.removeAllRanges();
|
||||||
|
selection.addRange(range);
|
||||||
|
} else {
|
||||||
|
console.warn("Could not select text in node: Unsupported browser.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class ClientNameEdit extends React.Component<ClientNameEditProps, {}> {
|
class ClientNameEdit extends React.Component<ClientNameEditProps, {}> {
|
||||||
private readonly refDiv: React.RefObject<HTMLDivElement> = React.createRef();
|
private readonly refDiv: React.RefObject<HTMLDivElement> = React.createRef();
|
||||||
|
|
||||||
componentDidMount(): void {
|
componentDidMount(): void {
|
||||||
this.refDiv.current.focus();
|
this.refDiv.current.focus();
|
||||||
|
selectText(this.refDiv.current);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
@ -145,7 +168,6 @@ class ClientNameEdit extends React.Component<ClientNameEditProps, {}> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO: Client rename! */
|
|
||||||
export class RendererClient extends React.Component<{ client: RDPClient }, {}> {
|
export class RendererClient extends React.Component<{ client: RDPClient }, {}> {
|
||||||
render() {
|
render() {
|
||||||
const client = this.props.client;
|
const client = this.props.client;
|
||||||
|
@ -154,7 +176,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={{ paddingLeft: client.offsetLeft, top: client.offsetTop }}
|
style={{ top: client.offsetTop }}
|
||||||
onContextMenu={event => {
|
onContextMenu={event => {
|
||||||
if (settings.static(Settings.KEY_DISABLE_CONTEXT_MENU)) {
|
if (settings.static(Settings.KEY_DISABLE_CONTEXT_MENU)) {
|
||||||
return;
|
return;
|
||||||
|
@ -176,6 +198,7 @@ export class RendererClient extends React.Component<{ client: RDPClient }, {}> {
|
||||||
}}
|
}}
|
||||||
onDoubleClick={() => events.fire("action_client_double_click", { treeEntryId: client.entryId })}
|
onDoubleClick={() => events.fire("action_client_double_click", { treeEntryId: client.entryId })}
|
||||||
>
|
>
|
||||||
|
<div className={viewStyle.leftPadding} style={{ paddingLeft: client.offsetLeft + "em" }} />
|
||||||
<UnreadMarkerRenderer entry={client} ref={client.refUnread} />
|
<UnreadMarkerRenderer entry={client} ref={client.refUnread} />
|
||||||
<ClientStatus client={client} ref={client.refStatus} />
|
<ClientStatus client={client} ref={client.refStatus} />
|
||||||
{...(client.rename ? [
|
{...(client.rename ? [
|
||||||
|
|
|
@ -209,7 +209,6 @@ export class RDPChannelTree {
|
||||||
|
|
||||||
@EventHandler<ChannelTreeUIEvents>("notify_tree_entries")
|
@EventHandler<ChannelTreeUIEvents>("notify_tree_entries")
|
||||||
private handleNotifyTreeEntries(event: ChannelTreeUIEvents["notify_tree_entries"]) {
|
private handleNotifyTreeEntries(event: ChannelTreeUIEvents["notify_tree_entries"]) {
|
||||||
console.error("Having entries");
|
|
||||||
const oldEntryInstances = this.treeEntries;
|
const oldEntryInstances = this.treeEntries;
|
||||||
this.treeEntries = {};
|
this.treeEntries = {};
|
||||||
|
|
||||||
|
@ -241,12 +240,11 @@ export class RDPChannelTree {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.treeEntries[entry.entryId] = result;
|
this.treeEntries[entry.entryId] = result;
|
||||||
result.handlePositionUpdate(index * ChannelTreeView.EntryHeight, entry.depth * 16 + 2);
|
result.handlePositionUpdate(index * ChannelTreeView.EntryHeight, entry.depth);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}).filter(e => !!e);
|
}).filter(e => !!e);
|
||||||
|
|
||||||
console.error("Obsolete entries: %o", oldEntryInstances);
|
|
||||||
Object.keys(oldEntryInstances).map(key => oldEntryInstances[key]).forEach(entry => {
|
Object.keys(oldEntryInstances).map(key => oldEntryInstances[key]).forEach(entry => {
|
||||||
entry.destroy();
|
entry.destroy();
|
||||||
});
|
});
|
||||||
|
@ -473,9 +471,9 @@ export class RDPClient extends RDPEntry {
|
||||||
|
|
||||||
handleOpenRename(initialValue: string) {
|
handleOpenRename(initialValue: string) {
|
||||||
if(!initialValue) {
|
if(!initialValue) {
|
||||||
this.refClient.current?.forceUpdate();
|
|
||||||
this.rename = false;
|
this.rename = false;
|
||||||
this.renameDefault = undefined;
|
this.renameDefault = undefined;
|
||||||
|
this.refClient.current?.forceUpdate();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if(!this.handle.refTree.current || !this.refClient.current) {
|
if(!this.handle.refTree.current || !this.refClient.current) {
|
||||||
|
|
|
@ -38,7 +38,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={{ paddingLeft: server.offsetLeft, top: server.offsetTop }}
|
style={{ top: server.offsetTop }}
|
||||||
onMouseUp={event => {
|
onMouseUp={event => {
|
||||||
if (event.button !== 0) {
|
if (event.button !== 0) {
|
||||||
return; /* only left mouse clicks */
|
return; /* only left mouse clicks */
|
||||||
|
@ -59,6 +59,7 @@ export class ServerRenderer extends React.Component<{ server: RDPServer }, {}> {
|
||||||
events.fire("action_show_context_menu", { treeEntryId: server.entryId, pageX: event.pageX, pageY: event.pageY });
|
events.fire("action_show_context_menu", { treeEntryId: server.entryId, pageX: event.pageX, pageY: event.pageY });
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<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}/>
|
<div className={"icon client-server_green " + serverStyle.server_type}/>
|
||||||
<div className={serverStyle.name}>{name}</div>
|
<div className={serverStyle.name}>{name}</div>
|
||||||
|
|
|
@ -29,21 +29,6 @@ export interface ChannelTreeViewState {
|
||||||
tree: RDPEntry[];
|
tree: RDPEntry[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
export function renderFlatTreeEntry(entry: FlatTreeEntry) {
|
|
||||||
if(entry.rendered) { return entry.rendered; }
|
|
||||||
|
|
||||||
if(entry.type === "channel") {
|
|
||||||
entry.rendered = <RendererChannel entryId={entry.entryId} offsetTop={entry.index * ChannelTreeView.EntryHeight} offsetLeft={entry.depth * 16 + 2} key={entry.entryId} />;
|
|
||||||
} else if(entry.type === "client" || entry.type === "client-local") {
|
|
||||||
entry.rendered = <RendererClient entryId={entry.entryId} offsetTop={entry.index * ChannelTreeView.EntryHeight} offsetLeft={entry.depth * 16 + 2} key={entry.entryId} localClient={entry.type === "client-local"} />;
|
|
||||||
} else {
|
|
||||||
entry.rendered = <div className={viewStyle.treeEntry} style={{ top: entry.index * ChannelTreeView.EntryHeight + "px" }} key={entry.entryId}><a>{entry.type + " - " + entry.entryId}</a></div>;
|
|
||||||
}
|
|
||||||
return entry.rendered;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
@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> {
|
||||||
|
|
|
@ -91,6 +91,13 @@ html:root {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.leftPadding {
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
padding-left: 2px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
&.move {
|
&.move {
|
||||||
.treeEntry.selected {
|
.treeEntry.selected {
|
||||||
background-color: var(--channel-tree-entry-move);
|
background-color: var(--channel-tree-entry-move);
|
||||||
|
@ -114,3 +121,43 @@ html:root {
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.arrow {
|
||||||
|
display: inline-block;
|
||||||
|
border: solid hsla(220, 5%, 30%, 1);
|
||||||
|
|
||||||
|
border-width: 0 .2em .2em 0;
|
||||||
|
padding: .21em;
|
||||||
|
height: .5em;
|
||||||
|
width: .5em;
|
||||||
|
|
||||||
|
&.right {
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
-webkit-transform: rotate(-45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.left {
|
||||||
|
transform: rotate(135deg);
|
||||||
|
-webkit-transform: rotate(135deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.up {
|
||||||
|
transform: rotate(-135deg);
|
||||||
|
-webkit-transform: rotate(-135deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.down {
|
||||||
|
transform: rotate(45deg);
|
||||||
|
-webkit-transform: rotate(45deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media all and (max-width: 250px) {
|
||||||
|
.channelTree .leftPadding {
|
||||||
|
font-size: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.treeEntry {
|
||||||
|
padding-left: 10px; /* space for the arrow */
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue