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="#">
|
<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>
|
||||||
|
|
|
@ -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' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue