Add buffer switcher

This commit is contained in:
Simon Ser 2023-06-08 15:07:28 +02:00
parent fe016807da
commit 44a064274d
4 changed files with 202 additions and 3 deletions

View file

@ -13,6 +13,7 @@ import AuthForm from "./auth-form.js";
import RegisterForm from "./register-form.js"; import RegisterForm from "./register-form.js";
import VerifyForm from "./verify-form.js"; import VerifyForm from "./verify-form.js";
import SettingsForm from "./settings-form.js"; import SettingsForm from "./settings-form.js";
import SwitcherForm from "./switcher-form.js";
import Composer from "./composer.js"; import Composer from "./composer.js";
import ScrollManager from "./scroll-manager.js"; import ScrollManager from "./scroll-manager.js";
import Dialog from "./dialog.js"; import Dialog from "./dialog.js";
@ -226,6 +227,7 @@ export default class App extends Component {
this.handleOpenSettingsClick = this.handleOpenSettingsClick.bind(this); this.handleOpenSettingsClick = this.handleOpenSettingsClick.bind(this);
this.handleSettingsChange = this.handleSettingsChange.bind(this); this.handleSettingsChange = this.handleSettingsChange.bind(this);
this.handleSettingsDisconnect = this.handleSettingsDisconnect.bind(this); this.handleSettingsDisconnect = this.handleSettingsDisconnect.bind(this);
this.handleSwitchSubmit = this.handleSwitchSubmit.bind(this);
this.state.settings = { this.state.settings = {
...this.state.settings, ...this.state.settings,
@ -1903,6 +1905,13 @@ export default class App extends Component {
this.disconnectAll(); this.disconnectAll();
} }
handleSwitchSubmit(buf) {
this.dismissDialog();
if (buf) {
this.switchBuffer(buf);
}
}
componentDidMount() { componentDidMount() {
this.baseTitle = document.title; this.baseTitle = document.title;
setupKeybindings(this); setupKeybindings(this);
@ -2090,6 +2099,17 @@ export default class App extends Component {
</> </>
`; `;
break; break;
case "switch":
dialog = html`
<${Dialog} title="Switch to a channel or user" onDismiss=${this.dismissDialog}>
<${SwitcherForm}
buffers=${this.state.buffers}
servers=${this.state.servers}
bouncerNetworks=${this.state.bouncerNetworks}
onSubmit=${this.handleSwitchSubmit}/>
</>
`;
break;
} }
let error = null; let error = null;

141
components/switcher-form.js Normal file
View file

@ -0,0 +1,141 @@
import { html, Component } from "../lib/index.js";
import { BufferType, getBufferURL, getServerName } from "../state.js";
class SwitcherItem extends Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick(event) {
event.preventDefault();
this.props.onClick();
}
render() {
let class_ = this.props.selected ? "selected" : "";
return html`
<li>
<a
href=${getBufferURL(this.props.buffer)}
class=${class_}
onClick=${this.handleClick}
>
<span class="server">
${getServerName(this.props.server, this.props.bouncerNetwork)}
</span>
${this.props.buffer.name}
</a>
</li>
`;
}
}
export default class SwitcherForm extends Component {
state = {
query: "",
selected: 0,
};
constructor(props) {
super(props);
this.handleInput = this.handleInput.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.handleKeyUp = this.handleKeyUp.bind(this);
}
getSuggestions() {
let query = this.state.query.toLowerCase();
let l = [];
for (let buf of this.props.buffers.values()) {
if (buf.type === BufferType.SERVER) {
continue;
}
if (query !== "" && !buf.name.toLowerCase().includes(query)) {
continue;
}
l.push(buf);
if (l.length >= 20) {
break;
}
}
return l;
}
handleInput(event) {
let target = event.target;
this.setState({ [target.name]: target.value });
}
handleSubmit(event) {
event.preventDefault();
this.props.onSubmit(this.getSuggestions()[this.state.selected]);
}
handleKeyUp(event) {
switch (event.key) {
case "ArrowUp":
event.stopPropagation();
this.move(-1);
break;
case "ArrowDown":
event.stopPropagation();
this.move(1);
break;
}
}
move(delta) {
let numSuggestions = this.getSuggestions().length;
this.setState((state) => {
return {
selected: (state.selected + delta + numSuggestions) % numSuggestions,
};
});
}
render() {
let items = this.getSuggestions().map((buf, i) => {
let server = this.props.servers.get(buf.server);
let bouncerNetwork = null;
if (server.bouncerNetID) {
bouncerNetwork = this.props.bouncerNetworks.get(server.bouncerNetID);
}
return html`
<${SwitcherItem}
buffer=${buf}
server=${server}
bouncerNetwork=${bouncerNetwork}
selected=${this.state.selected === i}
onClick=${() => this.props.onSubmit(buf)}
/>
`;
});
return html`
<form
onInput=${this.handleInput}
onSubmit=${this.handleSubmit}
onKeyUp=${this.handleKeyUp}
>
<input
type="search"
name="query"
value=${this.state.query}
placeholder="Filter"
autocomplete="off"
autofocus
/>
<ul class="switcher-list">
${items}
</ul>
</form>
`;
}
}

View file

@ -94,6 +94,14 @@ export const keybindings = [
} }
}, },
}, },
{
key: "k",
ctrlKey: true,
description: "Switch to a buffer",
execute: (app) => {
app.openDialog("switch");
},
},
]; ];
export function setup(app) { export function setup(app) {

View file

@ -352,7 +352,8 @@ form input[type="text"],
form input[type="username"], form input[type="username"],
form input[type="password"], form input[type="password"],
form input[type="url"], form input[type="url"],
form input[type="email"] { form input[type="email"],
form input[type="search"] {
box-sizing: border-box; box-sizing: border-box;
width: 100%; width: 100%;
font-family: inherit; font-family: inherit;
@ -561,6 +562,29 @@ kbd {
border-radius: 3px; border-radius: 3px;
} }
ul.switcher-list {
list-style-type: none;
margin: 0;
padding: 0;
margin-top: 10px;
}
ul.switcher-list li a {
display: inline-block;
width: 100%;
padding: 5px 10px;
margin: 4px 0;
box-sizing: border-box;
text-decoration: none;
color: inherit;
}
ul.switcher-list li a.selected {
background-color: rgba(0, 0, 0, 0.1);
}
ul.switcher-list .server {
float: right;
opacity: 0.8;
}
@media (prefers-color-scheme: dark) { @media (prefers-color-scheme: dark) {
html { html {
scrollbar-color: var(--gray) transparent; scrollbar-color: var(--gray) transparent;
@ -588,7 +612,8 @@ kbd {
form input[type="username"], form input[type="username"],
form input[type="password"], form input[type="password"],
form input[type="url"], form input[type="url"],
form input[type="email"] { form input[type="email"],
form input[type="search"] {
color: #ffffff; color: #ffffff;
background: var(--sidebar-background); background: var(--sidebar-background);
border: 1px solid #495057; border: 1px solid #495057;
@ -598,7 +623,8 @@ kbd {
form input[type="username"]:focus, form input[type="username"]:focus,
form input[type="password"]:focus, form input[type="password"]:focus,
form input[type="url"]:focus, form input[type="url"]:focus,
form input[type="email"]:focus { form input[type="email"]:focus,
form input[type="search"]:focus {
outline: 0; outline: 0;
border-color: #3897ff; border-color: #3897ff;
} }
@ -677,6 +703,10 @@ kbd {
border: 1px solid var(--outline-color); border: 1px solid var(--outline-color);
box-shadow: inset 0 -1px 0 var(--outline-color); box-shadow: inset 0 -1px 0 var(--outline-color);
} }
ul.switcher-list li a.selected {
background-color: rgba(255, 255, 255, 0.1);
}
} }
@media (max-width: 640px) { @media (max-width: 640px) {