aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Ser <contact@emersion.fr>2023-06-08 15:07:28 +0200
committerSimon Ser <contact@emersion.fr>2023-06-08 15:07:28 +0200
commit44a064274df432f8df7573de830b61c82aba606a (patch)
tree0bb9fd7ce3eded30fe842a6633506a6f0bcf7d9f
parentfe016807da525e1c8bd29da4ecc1d3071df0ad19 (diff)
downloadgamja-44a064274df432f8df7573de830b61c82aba606a.tar.gz
gamja-44a064274df432f8df7573de830b61c82aba606a.zip
Add buffer switcher
-rw-r--r--components/app.js20
-rw-r--r--components/switcher-form.js141
-rw-r--r--keybindings.js8
-rw-r--r--style.css36
4 files changed, 202 insertions, 3 deletions
diff --git a/components/app.js b/components/app.js
index a03684a..3987749 100644
--- a/components/app.js
+++ b/components/app.js
@@ -13,6 +13,7 @@ import AuthForm from "./auth-form.js";
import RegisterForm from "./register-form.js";
import VerifyForm from "./verify-form.js";
import SettingsForm from "./settings-form.js";
+import SwitcherForm from "./switcher-form.js";
import Composer from "./composer.js";
import ScrollManager from "./scroll-manager.js";
import Dialog from "./dialog.js";
@@ -226,6 +227,7 @@ export default class App extends Component {
this.handleOpenSettingsClick = this.handleOpenSettingsClick.bind(this);
this.handleSettingsChange = this.handleSettingsChange.bind(this);
this.handleSettingsDisconnect = this.handleSettingsDisconnect.bind(this);
+ this.handleSwitchSubmit = this.handleSwitchSubmit.bind(this);
this.state.settings = {
...this.state.settings,
@@ -1903,6 +1905,13 @@ export default class App extends Component {
this.disconnectAll();
}
+ handleSwitchSubmit(buf) {
+ this.dismissDialog();
+ if (buf) {
+ this.switchBuffer(buf);
+ }
+ }
+
componentDidMount() {
this.baseTitle = document.title;
setupKeybindings(this);
@@ -2090,6 +2099,17 @@ export default class App extends Component {
</>
`;
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;
diff --git a/components/switcher-form.js b/components/switcher-form.js
new file mode 100644
index 0000000..8838191
--- /dev/null
+++ b/components/switcher-form.js
@@ -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>
+ `;
+ }
+}
diff --git a/keybindings.js b/keybindings.js
index dc76c5a..c8c4984 100644
--- a/keybindings.js
+++ b/keybindings.js
@@ -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) {
diff --git a/style.css b/style.css
index 57857ec..2e7728c 100644
--- a/style.css
+++ b/style.css
@@ -352,7 +352,8 @@ form input[type="text"],
form input[type="username"],
form input[type="password"],
form input[type="url"],
-form input[type="email"] {
+form input[type="email"],
+form input[type="search"] {
box-sizing: border-box;
width: 100%;
font-family: inherit;
@@ -561,6 +562,29 @@ kbd {
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) {
html {
scrollbar-color: var(--gray) transparent;
@@ -588,7 +612,8 @@ kbd {
form input[type="username"],
form input[type="password"],
form input[type="url"],
- form input[type="email"] {
+ form input[type="email"],
+ form input[type="search"] {
color: #ffffff;
background: var(--sidebar-background);
border: 1px solid #495057;
@@ -598,7 +623,8 @@ kbd {
form input[type="username"]:focus,
form input[type="password"]:focus,
form input[type="url"]:focus,
- form input[type="email"]:focus {
+ form input[type="email"]:focus,
+ form input[type="search"]:focus {
outline: 0;
border-color: #3897ff;
}
@@ -677,6 +703,10 @@ kbd {
border: 1px solid 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) {