allow multiple message transfers by multiple peers simultaneously by putting them in a queue

This commit is contained in:
schlagmichdoch 2023-02-10 03:26:08 +01:00
parent 671dfa1c87
commit b0e798b3cd
5 changed files with 91 additions and 34 deletions

View file

@ -183,10 +183,10 @@
<form action="#"> <form action="#">
<x-background class="full center"> <x-background class="full center">
<x-paper shadow="2"> <x-paper shadow="2">
<h2 class="center text-center">Send a Message</h2> <h2>PairDrop - Send a Message</h2>
<div id="textInput" class="textarea" role="textbox" placeholder="Send a message" autocapitalize="none" spellcheck="false" autofocus contenteditable></div> <div id="textInput" class="textarea" role="textbox" placeholder="Send a message" autocapitalize="none" spellcheck="false" autofocus contenteditable></div>
<div class="row-reverse"> <div class="row-reverse">
<button class="button" type="submit" title="STR + ENTER" close>Send</button> <button class="button" type="submit" title="STR + ENTER" disabled close>Send</button>
<div class="separator"></div> <div class="separator"></div>
<a class="button" title="ESCAPE" close>Cancel</a> <a class="button" title="ESCAPE" close>Cancel</a>
</div> </div>
@ -198,12 +198,17 @@
<x-dialog id="receiveTextDialog"> <x-dialog id="receiveTextDialog">
<x-background class="full center"> <x-background class="full center">
<x-paper shadow="2"> <x-paper shadow="2">
<h2>Message Received</h2> <h2>PairDrop - Message Received</h2>
<div class="font-subheading" id="text"></div> <div id="receiveTextDescriptionContainer">
<span id="receiveTextPeerDisplayName"></span>
<span>sent the following message:</span>
</div>
<div class="row-separator"></div>
<div id="text"></div>
<div class="row-reverse"> <div class="row-reverse">
<button class="button" id="copy" title="CTRL/⌘ + C" close>Copy</button> <button class="button" id="copy" title="CTRL/⌘ + C">Copy</button>
<div class="separator"></div> <div class="separator"></div>
<button class="button" title="ESCAPE" close>Close</button> <button class="button" id="close" title="ESCAPE">Close</button>
</div> </div>
</x-paper> </x-paper>
</x-background> </x-background>

View file

@ -482,8 +482,9 @@ class Peer {
} }
_onTextReceived(message) { _onTextReceived(message) {
if (!message.text) return;
const escaped = decodeURIComponent(escape(atob(message.text))); 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' }); this.sendJSON({ type: 'message-transfer-complete' });
} }
} }

View file

@ -1,6 +1,6 @@
const $ = query => document.getElementById(query); const $ = query => document.getElementById(query);
const $$ = query => document.body.querySelector(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.isProductionEnvironment = !window.location.host.startsWith('localhost');
window.iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; window.iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
window.android = /android/i.test(navigator.userAgent); window.android = /android/i.test(navigator.userAgent);
@ -410,7 +410,7 @@ class PeerUI {
class Dialog { class Dialog {
constructor(id) { constructor(id) {
this.$el = $(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]'); this.$autoFocus = this.$el.querySelector('[autofocus]');
Events.on('peer-disconnected', e => this._onPeerDisconnected(e.detail)); Events.on('peer-disconnected', e => this._onPeerDisconnected(e.detail));
} }
@ -638,7 +638,7 @@ class ReceiveRequestDialog extends ReceiveDialog {
_onKeyDown(e) { _onKeyDown(e) {
if (this.$el.attributes["show"] && e.code === "Escape") { 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) { _showRequestDialog(request, peerId) {
this.correspondingPeerId = 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 fileName = request.header[0].name;
const fileNameSplit = fileName.split('.'); const fileNameSplit = fileName.split('.');
const fileExtension = '.' + fileNameSplit[fileNameSplit.length - 1]; const fileExtension = '.' + fileNameSplit[fileNameSplit.length - 1];
@ -685,7 +684,7 @@ class ReceiveRequestDialog extends ReceiveDialog {
document.title = 'PairDrop - File Transfer Requested'; document.title = 'PairDrop - File Transfer Requested';
document.changeFavicon("images/favicon-96x96-notification.png"); document.changeFavicon("images/favicon-96x96-notification.png");
this.show() this.show();
} }
_respondToFileTransferRequest(accepted) { _respondToFileTransferRequest(accepted) {
@ -703,7 +702,7 @@ class ReceiveRequestDialog extends ReceiveDialog {
hide() { hide() {
this.$previewBox.innerHTML = ''; this.$previewBox.innerHTML = '';
super.hide(); super.hide();
this._dequeueRequests(); setTimeout(_ => this._dequeueRequests(), 500);
} }
} }
@ -948,8 +947,10 @@ class SendTextDialog extends Dialog {
super('sendTextDialog'); super('sendTextDialog');
Events.on('text-recipient', e => this._onRecipient(e.detail)); Events.on('text-recipient', e => this._onRecipient(e.detail));
this.$text = this.$el.querySelector('#textInput'); this.$text = this.$el.querySelector('#textInput');
const button = this.$el.querySelector('form'); this.$form = this.$el.querySelector('form');
button.addEventListener('submit', _ => this._send()); 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)); Events.on("keydown", e => this._onKeyDown(e));
} }
@ -958,12 +959,24 @@ class SendTextDialog extends Dialog {
if (e.code === "Escape") { if (e.code === "Escape") {
this.hide(); this.hide();
} else if (e.code === "Enter" && (e.ctrlKey || e.metaKey)) { } else if (e.code === "Enter" && (e.ctrlKey || e.metaKey)) {
if (this._textInputEmpty()) return;
this._send(); 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) { _onRecipient(peerId) {
this.correspondingPeerId = peerId; this.correspondingPeerId = peerId;
this.show(); this.show();
@ -983,17 +996,25 @@ class SendTextDialog extends Dialog {
text: this.$text.innerText text: this.$text.innerText
}); });
this.$text.value = ""; this.$text.value = "";
this.hide();
} }
} }
class ReceiveTextDialog extends Dialog { class ReceiveTextDialog extends Dialog {
constructor() { constructor() {
super('receiveTextDialog'); 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'); this.$text = this.$el.querySelector('#text');
const copy = this.$el.querySelector('#copy'); this.$copy = this.$el.querySelector('#copy');
copy.addEventListener('click', _ => this._onCopy()); this.$close = this.$el.querySelector('#close');
Events.on("keydown", e => this._onKeyDown(e))
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) { async _onKeyDown(e) {
@ -1007,14 +1028,28 @@ class ReceiveTextDialog extends Dialog {
} }
} }
_onText(e) { _onText(text, peerId) {
this.$text.innerHTML = ''; window.blop.play();
const text = e.text; 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)) { if (isURL(text)) {
const $a = document.createElement('a'); const $a = document.createElement('a');
$a.href = text; $a.href = text;
$a.target = '_blank'; $a.target = '_blank';
$a.textContent = text; $a.textContent = text;
this.$text.innerHTML = '';
this.$text.appendChild($a); this.$text.appendChild($a);
} else { } else {
this.$text.textContent = text; this.$text.textContent = text;
@ -1022,12 +1057,17 @@ class ReceiveTextDialog extends Dialog {
document.title = 'PairDrop - Message Received'; document.title = 'PairDrop - Message Received';
document.changeFavicon("images/favicon-96x96-notification.png"); document.changeFavicon("images/favicon-96x96-notification.png");
this.show(); this.show();
window.blop.play();
} }
async _onCopy() { async _onCopy() {
await navigator.clipboard.writeText(this.$text.textContent); await navigator.clipboard.writeText(this.$text.textContent);
Events.fire('notify-user', 'Copied to clipboard'); 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.removeAttribute('hidden');
this.$button.addEventListener('click', _ => this._requestPermission()); 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)); Events.on('files-received', e => this._downloadNotification(e.detail.files));
} }
@ -1133,23 +1173,23 @@ class Notifications {
Events.fire('notify-user', Notifications.PERMISSION_ERROR || 'Error'); Events.fire('notify-user', Notifications.PERMISSION_ERROR || 'Error');
return; return;
} }
this._notify('Notifications enabled.'); Events.fire('notify-user', 'Notifications enabled.');
this.$button.setAttribute('hidden', 1); this.$button.setAttribute('hidden', 1);
}); });
} }
_notify(message, body) { _notify(title, body) {
const config = { const config = {
body: body, body: body,
icon: '/images/logo_transparent_128x128.png', icon: '/images/logo_transparent_128x128.png',
} }
let notification; let notification;
try { try {
notification = new Notification(message, config); notification = new Notification(title, config);
} catch (e) { } catch (e) {
// Android doesn't support "new Notification" if service worker is installed // Android doesn't support "new Notification" if service worker is installed
if (!serviceWorker || !serviceWorker.showNotification) return; 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 // Notification is persistent on Android. We have to close it manually
@ -1164,13 +1204,14 @@ class Notifications {
return notification; return notification;
} }
_messageNotification(message) { _messageNotification(message, peerId) {
if (document.visibilityState !== 'visible') { if (document.visibilityState !== 'visible') {
const peerDisplayName = $(peerId).ui._displayName();
if (isURL(message)) { 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)); this._bind(notification, _ => window.open(message, '_blank', null, true));
} else { } 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)); this._bind(notification, _ => this._copyText(message, notification));
} }
} }

View file

@ -1,4 +1,4 @@
const cacheVersion = 'v7'; const cacheVersion = 'v8';
const cacheTitle = `pairdrop-cache-${cacheVersion}`; const cacheTitle = `pairdrop-cache-${cacheVersion}`;
const urlsToCache = [ const urlsToCache = [
'index.html', 'index.html',

View file

@ -578,6 +578,7 @@ x-dialog .row-reverse {
-moz-user-select: all; -moz-user-select: all;
user-select: all; user-select: all;
white-space: pre-wrap; white-space: pre-wrap;
margin-top:36px;
} }
#receiveTextDialog #text a { #receiveTextDialog #text a {
@ -594,6 +595,15 @@ x-dialog .row-reverse {
pointer-events: none; pointer-events: none;
} }
.row-separator {
border-bottom: solid 2.5px var(--border-color);
margin: auto -25px;
}
#receiveTextDescriptionContainer {
margin-bottom: 25px;
}
#base64ZipPasteBtn { #base64ZipPasteBtn {
width: 100%; width: 100%;
height: 40vh; height: 40vh;