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 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 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";
import { html, Component, createRef } from "../lib/index.js"; import { html, Component, createRef } from "../lib/index.js";
import { strip as stripANSI } from "../lib/ansi.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 commands from "../commands.js";
import { setup as setupKeybindings } from "../keybindings.js"; import { setup as setupKeybindings } from "../keybindings.js";
import * as store from "../store.js"; import * as store from "../store.js";
@ -221,6 +222,13 @@ export default class App extends Component {
this.handleRegisterSubmit = this.handleRegisterSubmit.bind(this); this.handleRegisterSubmit = this.handleRegisterSubmit.bind(this);
this.handleVerifyClick = this.handleVerifyClick.bind(this); this.handleVerifyClick = this.handleVerifyClick.bind(this);
this.handleVerifySubmit = this.handleVerifySubmit.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(); this.bufferStore = new store.Buffer();
@ -632,7 +640,10 @@ export default class App extends Component {
}); });
this.setState({ connectParams: params }); 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; client.debug = this.debug;
this.clients.set(serverID, client); 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) { executeCommand(s) {
let parts = s.split(" "); let parts = s.split(" ");
let name = parts[0].toLowerCase().slice(1); let name = parts[0].toLowerCase().slice(1);
@ -1681,6 +1696,15 @@ export default class App extends Component {
this.dismissDialog(); this.dismissDialog();
} }
handleOpenSettingsClick() {
this.openDialog("settings");
}
handleSettingsChange(settings) {
store.settings.put(settings);
this.setState({ settings });
}
componentDidMount() { componentDidMount() {
this.baseTitle = document.title; this.baseTitle = document.title;
setupKeybindings(this); setupKeybindings(this);
@ -1742,6 +1766,7 @@ export default class App extends Component {
onReconnect=${() => this.reconnect()} onReconnect=${() => this.reconnect()}
onAddNetwork=${this.handleAddNetworkClick} onAddNetwork=${this.handleAddNetworkClick}
onManageNetwork=${() => this.handleManageNetworkClick(activeBuffer.server)} onManageNetwork=${() => this.handleManageNetworkClick(activeBuffer.server)}
onOpenSettings=${this.handleOpenSettingsClick}
/> />
</section> </section>
`; `;
@ -1850,6 +1875,18 @@ export default class App extends Component {
</> </>
`; `;
break; 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; let error = null;
@ -1906,6 +1943,7 @@ export default class App extends Component {
buffer=${activeBuffer} buffer=${activeBuffer}
server=${activeServer} server=${activeServer}
bouncerNetwork=${activeBouncerNetwork} bouncerNetwork=${activeBouncerNetwork}
settings=${this.state.settings}
onChannelClick=${this.handleChannelClick} onChannelClick=${this.handleChannelClick}
onNickClick=${this.handleNickClick} onNickClick=${this.handleNickClick}
onAuthClick=${() => this.handleAuthClick(activeBuffer.server)} onAuthClick=${() => this.handleAuthClick(activeBuffer.server)}

View File

@ -74,6 +74,12 @@ export default function BufferHeader(props) {
onClick=${props.onReconnect} onClick=${props.onReconnect}
>Reconnect</button> >Reconnect</button>
`; `;
let settingsButton = html`
<button
key="settings"
onClick="${props.onOpenSettings}"
>Settings</button>
`;
if (props.server.isBouncer) { if (props.server.isBouncer) {
if (props.server.bouncerNetID) { if (props.server.bouncerNetID) {
@ -99,27 +105,16 @@ export default function BufferHeader(props) {
} else if (props.server.status === ServerStatus.DISCONNECTED) { } else if (props.server.status === ServerStatus.DISCONNECTED) {
actions.push(reconnectButton); actions.push(reconnectButton);
} }
actions.push(html` actions.push(settingsButton);
<button
key="disconnect"
class="danger"
onClick=${props.onClose}
>Disconnect</button>
`);
} }
} else { } else {
if (fullyConnected) { if (fullyConnected) {
actions.push(joinButton); actions.push(joinButton);
actions.push(settingsButton);
} else if (props.server.status === ServerStatus.DISCONNECTED) { } else if (props.server.status === ServerStatus.DISCONNECTED) {
actions.push(reconnectButton); actions.push(reconnectButton);
} }
actions.push(html` actions.push(settingsButton);
<button
key="disconnect"
class="danger"
onClick=${props.onClose}
>Disconnect</button>
`);
} }
break; break;
case BufferType.CHANNEL: case BufferType.CHANNEL:

View File

@ -2,7 +2,7 @@ import { html, Component } from "../lib/index.js";
import linkify from "../lib/linkify.js"; import linkify from "../lib/linkify.js";
import * as irc from "../lib/irc.js"; import * as irc from "../lib/irc.js";
import { strip as stripANSI } from "../lib/ansi.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 * as store from "../store.js";
import Membership from "./membership.js"; import Membership from "./membership.js";
@ -546,7 +546,8 @@ function sameDate(d1, d2) {
export default class Buffer extends Component { export default class Buffer extends Component {
shouldComponentUpdate(nextProps) { shouldComponentUpdate(nextProps) {
return this.props.buffer !== nextProps.buffer; return this.props.buffer !== nextProps.buffer ||
this.props.settings !== nextProps.settings;
} }
render() { render() {
@ -557,6 +558,7 @@ export default class Buffer extends Component {
let server = this.props.server; let server = this.props.server;
let bouncerNetwork = this.props.bouncerNetwork; let bouncerNetwork = this.props.bouncerNetwork;
let settings = this.props.settings;
let serverName = server.name; let serverName = server.name;
let children = []; let children = [];
@ -633,6 +635,10 @@ export default class Buffer extends Component {
buf.messages.forEach((msg) => { buf.messages.forEach((msg) => {
let sep = []; let sep = [];
if (settings.bufferEvents === BufferEventsDisplayMode.HIDE && canFoldMessage(msg)) {
return;
}
if (!hasUnreadSeparator && buf.type != BufferType.SERVER && !isMessageBeforeReceipt(msg, buf.prevReadReceipt)) { if (!hasUnreadSeparator && buf.type != BufferType.SERVER && !isMessageBeforeReceipt(msg, buf.prevReadReceipt)) {
sep.push(html`<${UnreadSeparator} key="unread"/>`); sep.push(html`<${UnreadSeparator} key="unread"/>`);
hasUnreadSeparator = true; hasUnreadSeparator = true;
@ -651,7 +657,7 @@ export default class Buffer extends Component {
} }
// TODO: consider checking the time difference too // TODO: consider checking the time difference too
if (canFoldMessage(msg)) { if (settings.bufferEvents === BufferEventsDisplayMode.FOLD && canFoldMessage(msg)) {
foldMessages.push(msg); foldMessages.push(msg);
return; 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/account-registration",
"draft/chathistory", "draft/chathistory",
"draft/event-playback",
"draft/extended-monitor", "draft/extended-monitor",
"soju.im/bouncer-networks", "soju.im/bouncer-networks",
@ -124,6 +123,7 @@ export default class Client extends EventTarget {
saslExternal: false, saslExternal: false,
bouncerNetwork: null, bouncerNetwork: null,
ping: 0, ping: 0,
eventPlayback: true,
}; };
debug = false; debug = false;
batches = new Map(); batches = new Map();
@ -624,6 +624,9 @@ export default class Client extends EventTarget {
if (!this.params.bouncerNetwork) { if (!this.params.bouncerNetwork) {
wantCaps.push("soju.im/bouncer-networks-notify"); wantCaps.push("soju.im/bouncer-networks-notify");
} }
if (this.params.eventPlayback) {
wantCaps.push("draft/event-playback");
}
let msg = this.caps.requestAvailable(wantCaps); let msg = this.caps.requestAvailable(wantCaps);
if (msg) { if (msg) {

View File

@ -34,6 +34,12 @@ export const ReceiptType = {
READ: "read", READ: "read",
}; };
export const BufferEventsDisplayMode = {
FOLD: "fold",
EXPAND: "expand",
HIDE: "hide",
};
export function getNickURL(nick) { export function getNickURL(nick) {
return "irc:///" + encodeURIComponent(nick) + ",isuser"; return "irc:///" + encodeURIComponent(nick) + ",isuser";
} }
@ -209,6 +215,9 @@ export const State = {
buffers: new Map(), buffers: new Map(),
activeBuffer: null, activeBuffer: null,
bouncerNetworks: new Map(), bouncerNetworks: new Map(),
settings: {
bufferEvents: BufferEventsDisplayMode.FOLD,
},
}; };
}, },
updateServer(state, id, updater) { updateServer(state, id, updater) {

View File

@ -26,6 +26,7 @@ class Item {
export const autoconnect = new Item("autoconnect"); export const autoconnect = new Item("autoconnect");
export const naggedProtocolHandler = new Item("naggedProtocolHandler"); export const naggedProtocolHandler = new Item("naggedProtocolHandler");
export const settings = new Item("settings");
function debounce(f, delay) { function debounce(f, delay) {
let timeout = null; let timeout = null;