Add a settings dialog

Add an option to hide chat events or always expand them.

Closes: https://todo.sr.ht/~emersion/gamja/73
This commit is contained in:
Simon Ser 2022-02-21 15:44:17 +01:00
parent e3c2d85a94
commit baaf576d82
7 changed files with 143 additions and 20 deletions

View file

@ -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}
/>
</section>
`;
@ -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)}

View file

@ -74,6 +74,12 @@ export default function BufferHeader(props) {
onClick=${props.onReconnect}
>Reconnect</button>
`;
let settingsButton = html`
<button
key="settings"
onClick="${props.onOpenSettings}"
>Settings</button>
`;
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`
<button
key="disconnect"
class="danger"
onClick=${props.onClose}
>Disconnect</button>
`);
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`
<button
key="disconnect"
class="danger"
onClick=${props.onClose}
>Disconnect</button>
`);
actions.push(settingsButton);
}
break;
case BufferType.CHANNEL:

View file

@ -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;
}

View file

@ -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`
<form onChange=${this.handleChange} onSubmit=${this.handleSubmit}>
<label>
<input
type="radio"
name="bufferEvents"
value="fold"
checked=${this.state.bufferEvents === "fold"}
/>
Show and fold chat events
</label>
<br/>
<label>
<input
type="radio"
name="bufferEvents"
value="expand"
checked=${this.state.bufferEvents === "expand"}
/>
Show and expand chat events
</label>
<br/>
<label>
<input
type="radio"
name="bufferEvents"
value="hide"
checked=${this.state.bufferEvents === "hide"}
/>
Hide chat events
</label>
<br/><br/>
<button type="button" class="danger" onClick=${() => this.props.onDisconnect()}>
Disconnect
</button>
<button>
Close
</button>
</form>
`;
}
}

View file

@ -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) {

View file

@ -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) {

View file

@ -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;