diff --git a/components/app.js b/components/app.js index 83466c1..ff61830 100644 --- a/components/app.js +++ b/components/app.js @@ -5,8 +5,10 @@ import BufferList from "./buffer-list.js"; import BufferHeader from "./buffer-header.js"; import MemberList from "./member-list.js"; import Connect from "./connect.js"; +import Join from "./join.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, NetworkStatus, Unread } from "../state.js"; @@ -151,6 +153,7 @@ export default class App extends Component { networks: new Map(), buffers: new Map(), activeBuffer: null, + dialog: null, error: null, }; clients = new Map(); @@ -165,11 +168,13 @@ export default class App extends Component { super(props); this.handleConnectSubmit = this.handleConnectSubmit.bind(this); + this.handleJoinSubmit = this.handleJoinSubmit.bind(this); this.handleBufferListClick = this.handleBufferListClick.bind(this); this.handleComposerSubmit = this.handleComposerSubmit.bind(this); this.handleNickClick = this.handleNickClick.bind(this); this.autocomplete = this.autocomplete.bind(this); this.handleBufferScrollTop = this.handleBufferScrollTop.bind(this); + this.handleDialogDismiss = this.handleDialogDismiss.bind(this); this.dismissError = this.dismissError.bind(this); this.saveReceipts = debounce(this.saveReceipts.bind(this), 500); @@ -833,14 +838,15 @@ export default class App extends Component { } handleJoinClick(netID) { - var channel = prompt("Join channel:"); - if (!channel) { - return; - } + this.setState({ dialog: "join", joinDialog: { network: netID } }); + } - var client = this.clients.get(netID); + handleJoinSubmit(data) { + var client = this.clients.get(this.state.joinDialog.network); - client.send({ command: "JOIN", params: [channel] }); + client.send({ command: "JOIN", params: [data.channel] }); + + this.setState({ dialog: null, joinDialog: null }); } autocomplete(prefix) { @@ -903,6 +909,10 @@ export default class App extends Component { }); } + handleDialogDismiss() { + this.setState({ dialog: null }); + } + componentDidMount() { if (this.state.connectParams.autoconnect) { this.connect(this.state.connectParams); @@ -947,6 +957,17 @@ export default class App extends Component { `; } + var dialog = null; + switch (this.state.dialog) { + case "join": + dialog = html` + <${Dialog} title="Join channel" onDismiss=${this.handleDialogDismiss}> + <${Join} onSubmit=${this.handleJoinSubmit}/> + + `; + break; + } + var error = null; if (this.state.error) { error = html` @@ -966,6 +987,7 @@ export default class App extends Component { ${memberList} <${Composer} ref=${this.composer} readOnly=${activeBuffer && activeBuffer.type == BufferType.SERVER} onSubmit=${this.handleComposerSubmit} autocomplete=${this.autocomplete}/> + ${dialog} ${error} `; } diff --git a/components/dialog.js b/components/dialog.js new file mode 100644 index 0000000..3fccc15 --- /dev/null +++ b/components/dialog.js @@ -0,0 +1,61 @@ +import { html, Component, createRef } from "../lib/index.js"; + +export default class Dialog extends Component { + body = createRef(); + + constructor(props) { + super(props); + + this.handleCloseClick = this.handleCloseClick.bind(this); + this.handleBackdropClick = this.handleBackdropClick.bind(this); + this.handleKeyDown = this.handleKeyDown.bind(this); + } + + dismiss() { + this.props.onDismiss(); + } + + handleCloseClick(event) { + event.preventDefault(); + this.dismiss(); + } + + handleBackdropClick(event) { + if (event.target.className == "dialog") { + this.dismiss(); + } + } + + handleKeyDown(event) { + if (event.key == "Escape") { + this.dismiss(); + } + } + + componentDidMount() { + window.addEventListener("keydown", this.handleKeyDown); + + var autofocus = this.body.current.querySelector("input[autofocus]"); + if (autofocus) { + autofocus.focus(); + } + } + + componentWillUnmount() { + window.removeEventListener("keydown", this.handleKeyDown); + } + + render() { + return html` +
+
+ + ${this.props.children} +
+
+ `; + } +} diff --git a/components/join.js b/components/join.js new file mode 100644 index 0000000..81e4aa9 --- /dev/null +++ b/components/join.js @@ -0,0 +1,45 @@ +import { html, Component } from "../lib/index.js"; + +export default class Join extends Component { + state = { + channel: "#", + }; + + constructor(props) { + super(props); + + this.handleChange = this.handleChange.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + } + + handleChange(event) { + var target = event.target; + var value = target.type == "checkbox" ? target.checked : target.value; + this.setState({ [target.name]: value }); + } + + handleSubmit(event) { + event.preventDefault(); + + var params = { + channel: this.state.channel, + }; + + this.props.onSubmit(params); + } + + render() { + return html` +
+ +
+ +
+ +
+ `; + } +} diff --git a/style.css b/style.css index 68866ad..ccadd02 100644 --- a/style.css +++ b/style.css @@ -136,10 +136,11 @@ body { margin: 0 auto; max-width: 300px; } -#connect input[type="text"], -#connect input[type="username"], -#connect input[type="password"], -#connect input[type="url"] { + +form input[type="text"], +form input[type="username"], +form input[type="password"], +form input[type="url"] { box-sizing: border-box; width: 100%; } @@ -274,3 +275,29 @@ details summary { .error-text { color: red; } + +.dialog { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); +} +.dialog .dialog-body { + background-color: white; + margin: auto; + margin-top: 20px; + max-width: 500px; + padding: 15px; + border: 1px solid rgba(0, 0, 0, 0.6); +} +.dialog a.dialog-close { + float: right; + text-decoration: none; + font-size: 1.5em; + color: inherit; +} +.dialog h2 { + margin-top: 0; +}