allow multiple message transfers by multiple peers simultaneously by putting them in a queue
This commit is contained in:
parent
671dfa1c87
commit
b0e798b3cd
5 changed files with 91 additions and 34 deletions
|
@ -183,10 +183,10 @@
|
|||
<form action="#">
|
||||
<x-background class="full center">
|
||||
<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 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>
|
||||
<a class="button" title="ESCAPE" close>Cancel</a>
|
||||
</div>
|
||||
|
@ -198,12 +198,17 @@
|
|||
<x-dialog id="receiveTextDialog">
|
||||
<x-background class="full center">
|
||||
<x-paper shadow="2">
|
||||
<h2>Message Received</h2>
|
||||
<div class="font-subheading" id="text"></div>
|
||||
<h2>PairDrop - Message Received</h2>
|
||||
<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">
|
||||
<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>
|
||||
<button class="button" title="ESCAPE" close>Close</button>
|
||||
<button class="button" id="close" title="ESCAPE">Close</button>
|
||||
</div>
|
||||
</x-paper>
|
||||
</x-background>
|
||||
|
|
|
@ -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' });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const cacheVersion = 'v7';
|
||||
const cacheVersion = 'v8';
|
||||
const cacheTitle = `pairdrop-cache-${cacheVersion}`;
|
||||
const urlsToCache = [
|
||||
'index.html',
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue