diff options
author | Simon Ser <contact@emersion.fr> | 2023-06-08 15:07:28 +0200 |
---|---|---|
committer | Simon Ser <contact@emersion.fr> | 2023-06-08 15:07:28 +0200 |
commit | 44a064274df432f8df7573de830b61c82aba606a (patch) | |
tree | 0bb9fd7ce3eded30fe842a6633506a6f0bcf7d9f | |
parent | fe016807da525e1c8bd29da4ecc1d3071df0ad19 (diff) | |
download | gamja-44a064274df432f8df7573de830b61c82aba606a.tar.gz gamja-44a064274df432f8df7573de830b61c82aba606a.zip |
Add buffer switcher
-rw-r--r-- | components/app.js | 20 | ||||
-rw-r--r-- | components/switcher-form.js | 141 | ||||
-rw-r--r-- | keybindings.js | 8 | ||||
-rw-r--r-- | style.css | 36 |
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) { @@ -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) { |