From b0e798b3cd35ca69cdf1b8f157908fec223fc412 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Fri, 10 Feb 2023 03:26:08 +0100 Subject: [PATCH] allow multiple message transfers by multiple peers simultaneously by putting them in a queue --- public/index.html | 17 ++++--- public/scripts/network.js | 3 +- public/scripts/ui.js | 93 ++++++++++++++++++++++++++++----------- public/service-worker.js | 2 +- public/styles.css | 10 +++++ 5 files changed, 91 insertions(+), 34 deletions(-) diff --git a/public/index.html b/public/index.html index b718aa7..2937798 100644 --- a/public/index.html +++ b/public/index.html @@ -183,10 +183,10 @@
-

Send a Message

+

PairDrop - Send a Message

- +
Cancel
@@ -198,12 +198,17 @@ -

Message Received

-
+

PairDrop - Message Received

+
+ + sent the following message: +
+
+
- +
- +
diff --git a/public/scripts/network.js b/public/scripts/network.js index 85830d0..810f799 100644 --- a/public/scripts/network.js +++ b/public/scripts/network.js @@ -482,8 +482,9 @@ class Peer { } _onTextReceived(message) { + if (!message.text) return; const escaped = decodeURIComponent(escape(atob(message.text))); - Events.fire('text-received', { text: escaped, sender: this._peerId }); + Events.fire('text-received', { text: escaped, peerId: this._peerId }); this.sendJSON({ type: 'message-transfer-complete' }); } } diff --git a/public/scripts/ui.js b/public/scripts/ui.js index af5d3ae..c49396f 100644 --- a/public/scripts/ui.js +++ b/public/scripts/ui.js @@ -1,6 +1,6 @@ const $ = query => document.getElementById(query); const $$ = query => document.body.querySelector(query); -const isURL = text => /^((https?:\/\/|www)[^\s]+)/g.test(text.toLowerCase()); +const isURL = text => /^(https?:\/\/|www)[^\s]+$/g.test(text.toLowerCase()); window.isProductionEnvironment = !window.location.host.startsWith('localhost'); window.iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; window.android = /android/i.test(navigator.userAgent); @@ -410,7 +410,7 @@ class PeerUI { class Dialog { constructor(id) { this.$el = $(id); - this.$el.querySelectorAll('[close]').forEach(el => el.addEventListener('click', _ => this.hide())) + this.$el.querySelectorAll('[close]').forEach(el => el.addEventListener('click', _ => this.hide())); this.$autoFocus = this.$el.querySelector('[autofocus]'); Events.on('peer-disconnected', e => this._onPeerDisconnected(e.detail)); } @@ -638,7 +638,7 @@ class ReceiveRequestDialog extends ReceiveDialog { _onKeyDown(e) { if (this.$el.attributes["show"] && e.code === "Escape") { - this._respondToFileTransferRequest(false) + this._respondToFileTransferRequest(false); } } @@ -657,9 +657,8 @@ class ReceiveRequestDialog extends ReceiveDialog { _showRequestDialog(request, peerId) { this.correspondingPeerId = peerId; - const peer = $(peerId); + this.$requestingPeerDisplayNameNode.innerText = $(peerId).ui._displayName(); - this.$requestingPeerDisplayNameNode.innerText = peer.ui._displayName(); const fileName = request.header[0].name; const fileNameSplit = fileName.split('.'); const fileExtension = '.' + fileNameSplit[fileNameSplit.length - 1]; @@ -685,7 +684,7 @@ class ReceiveRequestDialog extends ReceiveDialog { document.title = 'PairDrop - File Transfer Requested'; document.changeFavicon("images/favicon-96x96-notification.png"); - this.show() + this.show(); } _respondToFileTransferRequest(accepted) { @@ -703,7 +702,7 @@ class ReceiveRequestDialog extends ReceiveDialog { hide() { this.$previewBox.innerHTML = ''; super.hide(); - this._dequeueRequests(); + setTimeout(_ => this._dequeueRequests(), 500); } } @@ -948,8 +947,10 @@ class SendTextDialog extends Dialog { super('sendTextDialog'); Events.on('text-recipient', e => this._onRecipient(e.detail)); this.$text = this.$el.querySelector('#textInput'); - const button = this.$el.querySelector('form'); - button.addEventListener('submit', _ => this._send()); + this.$form = this.$el.querySelector('form'); + this.$submit = this.$el.querySelector('button[type="submit"]'); + this.$form.addEventListener('submit', _ => this._send()); + this.$text.addEventListener('input', e => this._onChange(e)); Events.on("keydown", e => this._onKeyDown(e)); } @@ -958,12 +959,24 @@ class SendTextDialog extends Dialog { if (e.code === "Escape") { this.hide(); } else if (e.code === "Enter" && (e.ctrlKey || e.metaKey)) { + if (this._textInputEmpty()) return; this._send(); - this.hide(); } } } + _textInputEmpty() { + return this.$text.innerText === "\n"; + } + + _onChange(e) { + if (this._textInputEmpty()) { + this.$submit.setAttribute('disabled', ''); + } else { + this.$submit.removeAttribute('disabled'); + } + } + _onRecipient(peerId) { this.correspondingPeerId = peerId; this.show(); @@ -983,17 +996,25 @@ class SendTextDialog extends Dialog { text: this.$text.innerText }); this.$text.value = ""; + this.hide(); } } class ReceiveTextDialog extends Dialog { constructor() { super('receiveTextDialog'); - Events.on('text-received', e => this._onText(e.detail)) + Events.on('text-received', e => this._onText(e.detail.text, e.detail.peerId)); this.$text = this.$el.querySelector('#text'); - const copy = this.$el.querySelector('#copy'); - copy.addEventListener('click', _ => this._onCopy()); - Events.on("keydown", e => this._onKeyDown(e)) + this.$copy = this.$el.querySelector('#copy'); + this.$close = this.$el.querySelector('#close'); + + this.$copy.addEventListener('click', _ => this._onCopy()); + this.$close.addEventListener('click', _ => this.hide()); + + Events.on("keydown", e => this._onKeyDown(e)); + + this.$receiveTextPeerDisplayNameNode = this.$el.querySelector('#receiveTextPeerDisplayName'); + this._receiveTextQueue = []; } async _onKeyDown(e) { @@ -1007,14 +1028,28 @@ class ReceiveTextDialog extends Dialog { } } - _onText(e) { - this.$text.innerHTML = ''; - const text = e.text; + _onText(text, peerId) { + window.blop.play(); + this._receiveTextQueue.push({text: text, peerId: peerId}); + if (this.$el.attributes["show"]) return; + this._dequeueRequests(); + } + + _dequeueRequests() { + if (!this._receiveTextQueue.length) return; + let {text, peerId} = this._receiveTextQueue.shift(); + this._showReceiveTextDialog(text, peerId); + } + + _showReceiveTextDialog(text, peerId) { + this.$receiveTextPeerDisplayNameNode.innerText = $(peerId).ui._displayName(); + if (isURL(text)) { const $a = document.createElement('a'); $a.href = text; $a.target = '_blank'; $a.textContent = text; + this.$text.innerHTML = ''; this.$text.appendChild($a); } else { this.$text.textContent = text; @@ -1022,12 +1057,17 @@ class ReceiveTextDialog extends Dialog { document.title = 'PairDrop - Message Received'; document.changeFavicon("images/favicon-96x96-notification.png"); this.show(); - window.blop.play(); } async _onCopy() { await navigator.clipboard.writeText(this.$text.textContent); Events.fire('notify-user', 'Copied to clipboard'); + this.hide(); + } + + hide() { + super.hide(); + setTimeout(_ => this._dequeueRequests(), 500); } } @@ -1123,7 +1163,7 @@ class Notifications { this.$button.removeAttribute('hidden'); this.$button.addEventListener('click', _ => this._requestPermission()); } - Events.on('text-received', e => this._messageNotification(e.detail.text)); + Events.on('text-received', e => this._messageNotification(e.detail.text, e.detail.peerId)); Events.on('files-received', e => this._downloadNotification(e.detail.files)); } @@ -1133,23 +1173,23 @@ class Notifications { Events.fire('notify-user', Notifications.PERMISSION_ERROR || 'Error'); return; } - this._notify('Notifications enabled.'); + Events.fire('notify-user', 'Notifications enabled.'); this.$button.setAttribute('hidden', 1); }); } - _notify(message, body) { + _notify(title, body) { const config = { body: body, icon: '/images/logo_transparent_128x128.png', } let notification; try { - notification = new Notification(message, config); + notification = new Notification(title, config); } catch (e) { // Android doesn't support "new Notification" if service worker is installed if (!serviceWorker || !serviceWorker.showNotification) return; - notification = serviceWorker.showNotification(message, config); + notification = serviceWorker.showNotification(title, config); } // Notification is persistent on Android. We have to close it manually @@ -1164,13 +1204,14 @@ class Notifications { return notification; } - _messageNotification(message) { + _messageNotification(message, peerId) { if (document.visibilityState !== 'visible') { + const peerDisplayName = $(peerId).ui._displayName(); if (isURL(message)) { - const notification = this._notify(message, 'Click to open link'); + const notification = this._notify(`Link received by ${peerDisplayName} - Click to open`, message); this._bind(notification, _ => window.open(message, '_blank', null, true)); } else { - const notification = this._notify(message, 'Click to copy text'); + const notification = this._notify(`Message received by ${peerDisplayName} - Click to copy`, message); this._bind(notification, _ => this._copyText(message, notification)); } } diff --git a/public/service-worker.js b/public/service-worker.js index 63193d9..2cd1430 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -1,4 +1,4 @@ -const cacheVersion = 'v7'; +const cacheVersion = 'v8'; const cacheTitle = `pairdrop-cache-${cacheVersion}`; const urlsToCache = [ 'index.html', diff --git a/public/styles.css b/public/styles.css index ce6b5a8..237d574 100644 --- a/public/styles.css +++ b/public/styles.css @@ -578,6 +578,7 @@ x-dialog .row-reverse { -moz-user-select: all; user-select: all; white-space: pre-wrap; + margin-top:36px; } #receiveTextDialog #text a { @@ -594,6 +595,15 @@ x-dialog .row-reverse { pointer-events: none; } +.row-separator { + border-bottom: solid 2.5px var(--border-color); + margin: auto -25px; +} + +#receiveTextDescriptionContainer { + margin-bottom: 25px; +} + #base64ZipPasteBtn { width: 100%; height: 40vh;