From baaf576d82ac60bd47b839a6b59a0be41650ec5f Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Mon, 21 Feb 2022 15:44:17 +0100 Subject: [PATCH] Add a settings dialog Add an option to hide chat events or always expand them. Closes: https://todo.sr.ht/~emersion/gamja/73 --- components/app.js | 42 ++++++++++++++++++++-- components/buffer-header.js | 23 +++++------- components/buffer.js | 12 +++++-- components/settings-form.js | 71 +++++++++++++++++++++++++++++++++++++ lib/client.js | 5 ++- state.js | 9 +++++ store.js | 1 + 7 files changed, 143 insertions(+), 20 deletions(-) create mode 100644 components/settings-form.js diff --git a/components/app.js b/components/app.js index da18630..2ab64d1 100644 --- a/components/app.js +++ b/components/app.js @@ -11,12 +11,13 @@ import NetworkForm from "./network-form.js"; 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 Composer from "./composer.js"; import ScrollManager from "./scroll-manager.js"; import Dialog from "./dialog.js"; import { html, Component, createRef } from "../lib/index.js"; import { strip as stripANSI } from "../lib/ansi.js"; -import { SERVER_BUFFER, BufferType, ReceiptType, ServerStatus, Unread, State, getServerName, receiptFromMessage, isReceiptBefore, isMessageBeforeReceipt } from "../state.js"; +import { SERVER_BUFFER, BufferType, ReceiptType, ServerStatus, Unread, BufferEventsDisplayMode, State, getServerName, receiptFromMessage, isReceiptBefore, isMessageBeforeReceipt } from "../state.js"; import commands from "../commands.js"; import { setup as setupKeybindings } from "../keybindings.js"; import * as store from "../store.js"; @@ -221,6 +222,13 @@ export default class App extends Component { this.handleRegisterSubmit = this.handleRegisterSubmit.bind(this); this.handleVerifyClick = this.handleVerifyClick.bind(this); this.handleVerifySubmit = this.handleVerifySubmit.bind(this); + this.handleOpenSettingsClick = this.handleOpenSettingsClick.bind(this); + this.handleSettingsChange = this.handleSettingsChange.bind(this); + + this.state.settings = { + ...this.state.settings, + ...store.settings.load(), + }; this.bufferStore = new store.Buffer(); @@ -632,7 +640,10 @@ export default class App extends Component { }); this.setState({ connectParams: params }); - let client = new Client(fillConnectParams(params)); + let client = new Client({ + ...fillConnectParams(params), + eventPlayback: this.state.settings.bufferEvents !== BufferEventsDisplayMode.HIDE, + }); client.debug = this.debug; this.clients.set(serverID, client); @@ -1321,6 +1332,10 @@ export default class App extends Component { } } + disconnectAll() { + this.close(this.state.buffers.keys().next().value); + } + executeCommand(s) { let parts = s.split(" "); let name = parts[0].toLowerCase().slice(1); @@ -1681,6 +1696,15 @@ export default class App extends Component { this.dismissDialog(); } + handleOpenSettingsClick() { + this.openDialog("settings"); + } + + handleSettingsChange(settings) { + store.settings.put(settings); + this.setState({ settings }); + } + componentDidMount() { this.baseTitle = document.title; setupKeybindings(this); @@ -1742,6 +1766,7 @@ export default class App extends Component { onReconnect=${() => this.reconnect()} onAddNetwork=${this.handleAddNetworkClick} onManageNetwork=${() => this.handleManageNetworkClick(activeBuffer.server)} + onOpenSettings=${this.handleOpenSettingsClick} /> `; @@ -1850,6 +1875,18 @@ export default class App extends Component { `; break; + case "settings": + dialog = html` + <${Dialog} title="Settings" onDismiss=${this.dismissDialog}> + <${SettingsForm} + settings=${this.state.settings} + onChange=${this.handleSettingsChange} + onDisconnect=${() => this.disconnectAll()} + onClose=${() => this.dismissDialog()} + /> + + `; + break; } let error = null; @@ -1906,6 +1943,7 @@ export default class App extends Component { buffer=${activeBuffer} server=${activeServer} bouncerNetwork=${activeBouncerNetwork} + settings=${this.state.settings} onChannelClick=${this.handleChannelClick} onNickClick=${this.handleNickClick} onAuthClick=${() => this.handleAuthClick(activeBuffer.server)} diff --git a/components/buffer-header.js b/components/buffer-header.js index 40834fc..c6fef9e 100644 --- a/components/buffer-header.js +++ b/components/buffer-header.js @@ -74,6 +74,12 @@ export default function BufferHeader(props) { onClick=${props.onReconnect} >Reconnect `; + let settingsButton = html` + + `; if (props.server.isBouncer) { if (props.server.bouncerNetID) { @@ -99,27 +105,16 @@ export default function BufferHeader(props) { } else if (props.server.status === ServerStatus.DISCONNECTED) { actions.push(reconnectButton); } - actions.push(html` - - `); + actions.push(settingsButton); } } else { if (fullyConnected) { actions.push(joinButton); + actions.push(settingsButton); } else if (props.server.status === ServerStatus.DISCONNECTED) { actions.push(reconnectButton); } - actions.push(html` - - `); + actions.push(settingsButton); } break; case BufferType.CHANNEL: diff --git a/components/buffer.js b/components/buffer.js index 0db06b3..391ec67 100644 --- a/components/buffer.js +++ b/components/buffer.js @@ -2,7 +2,7 @@ import { html, Component } from "../lib/index.js"; import linkify from "../lib/linkify.js"; import * as irc from "../lib/irc.js"; import { strip as stripANSI } from "../lib/ansi.js"; -import { BufferType, ServerStatus, getNickURL, getChannelURL, getMessageURL, isMessageBeforeReceipt } from "../state.js"; +import { BufferType, ServerStatus, BufferEventsDisplayMode, getNickURL, getChannelURL, getMessageURL, isMessageBeforeReceipt } from "../state.js"; import * as store from "../store.js"; import Membership from "./membership.js"; @@ -546,7 +546,8 @@ function sameDate(d1, d2) { export default class Buffer extends Component { shouldComponentUpdate(nextProps) { - return this.props.buffer !== nextProps.buffer; + return this.props.buffer !== nextProps.buffer || + this.props.settings !== nextProps.settings; } render() { @@ -557,6 +558,7 @@ export default class Buffer extends Component { let server = this.props.server; let bouncerNetwork = this.props.bouncerNetwork; + let settings = this.props.settings; let serverName = server.name; let children = []; @@ -633,6 +635,10 @@ export default class Buffer extends Component { buf.messages.forEach((msg) => { let sep = []; + if (settings.bufferEvents === BufferEventsDisplayMode.HIDE && canFoldMessage(msg)) { + return; + } + if (!hasUnreadSeparator && buf.type != BufferType.SERVER && !isMessageBeforeReceipt(msg, buf.prevReadReceipt)) { sep.push(html`<${UnreadSeparator} key="unread"/>`); hasUnreadSeparator = true; @@ -651,7 +657,7 @@ export default class Buffer extends Component { } // TODO: consider checking the time difference too - if (canFoldMessage(msg)) { + if (settings.bufferEvents === BufferEventsDisplayMode.FOLD && canFoldMessage(msg)) { foldMessages.push(msg); return; } diff --git a/components/settings-form.js b/components/settings-form.js new file mode 100644 index 0000000..322bd66 --- /dev/null +++ b/components/settings-form.js @@ -0,0 +1,71 @@ +import { html, Component } from "../lib/index.js"; + +export default class SettingsForm extends Component { + state = {}; + + constructor(props) { + super(props); + + this.state.bufferEvents = props.settings.bufferEvents; + + this.handleChange = this.handleChange.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + } + + handleChange(event) { + let target = event.target; + let value = target.type == "checkbox" ? target.checked : target.value; + this.setState({ [target.name]: value }, () => { + this.props.onChange(this.state); + }); + } + + handleSubmit(event) { + event.preventDefault(); + this.props.onClose(); + } + + render() { + return html` +
+ +
+ +
+ +

+ + + +
+ `; + } +} diff --git a/lib/client.js b/lib/client.js index 3bd09b4..caaf042 100644 --- a/lib/client.js +++ b/lib/client.js @@ -19,7 +19,6 @@ const permanentCaps = [ "draft/account-registration", "draft/chathistory", - "draft/event-playback", "draft/extended-monitor", "soju.im/bouncer-networks", @@ -124,6 +123,7 @@ export default class Client extends EventTarget { saslExternal: false, bouncerNetwork: null, ping: 0, + eventPlayback: true, }; debug = false; batches = new Map(); @@ -624,6 +624,9 @@ export default class Client extends EventTarget { if (!this.params.bouncerNetwork) { wantCaps.push("soju.im/bouncer-networks-notify"); } + if (this.params.eventPlayback) { + wantCaps.push("draft/event-playback"); + } let msg = this.caps.requestAvailable(wantCaps); if (msg) { diff --git a/state.js b/state.js index 2bb9846..95846a9 100644 --- a/state.js +++ b/state.js @@ -34,6 +34,12 @@ export const ReceiptType = { READ: "read", }; +export const BufferEventsDisplayMode = { + FOLD: "fold", + EXPAND: "expand", + HIDE: "hide", +}; + export function getNickURL(nick) { return "irc:///" + encodeURIComponent(nick) + ",isuser"; } @@ -209,6 +215,9 @@ export const State = { buffers: new Map(), activeBuffer: null, bouncerNetworks: new Map(), + settings: { + bufferEvents: BufferEventsDisplayMode.FOLD, + }, }; }, updateServer(state, id, updater) { diff --git a/store.js b/store.js index c94b258..1b530d1 100644 --- a/store.js +++ b/store.js @@ -26,6 +26,7 @@ class Item { export const autoconnect = new Item("autoconnect"); export const naggedProtocolHandler = new Item("naggedProtocolHandler"); +export const settings = new Item("settings"); function debounce(f, delay) { let timeout = null;