- restructure and unify dialogs to use less space on mobile and be clearer
- give user option both options "share" and "download" on mobile - add fallback if zipper fails that downloads files individually - fix dequeuing of message queue not possible if sending peer has left
This commit is contained in:
parent
545cdc2459
commit
3a2d8c75f7
8 changed files with 410 additions and 394 deletions
|
@ -69,7 +69,7 @@
|
|||
<use xlink:href="#clear-pair-devices-icon" />
|
||||
</svg>
|
||||
</a>
|
||||
<a id="cancel-paste-mode-btn" class="button" close hidden>Done</a>
|
||||
<a id="cancel-paste-mode" class="button" hidden>Done</a>
|
||||
</header>
|
||||
<!-- Center -->
|
||||
<div id="center">
|
||||
|
@ -106,18 +106,17 @@
|
|||
<div id="pair-instructions" class="font-subheading center text-center">Input this key on another device<br>or scan the QR-Code.</div>
|
||||
<hr>
|
||||
<div id="key-input-container">
|
||||
<input id="char0" type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" autofocus contenteditable placeholder="" disabled>
|
||||
<input id="char1" type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
|
||||
<input id="char2" type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
|
||||
<input id="char3" type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
|
||||
<input id="char4" type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
|
||||
<input id="char5" type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
|
||||
<input type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" autofocus contenteditable placeholder="" disabled>
|
||||
<input type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
|
||||
<input type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
|
||||
<input type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
|
||||
<input type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
|
||||
<input type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
|
||||
</div>
|
||||
<div class="font-subheading center text-center">Enter key from another device to continue.</div>
|
||||
<div class="row-reverse space-between">
|
||||
<div class="center row-reverse">
|
||||
<button class="button" type="submit" disabled>Pair</button>
|
||||
<div class="separator"></div>
|
||||
<a class="button" close>Cancel</a>
|
||||
<button class="button" close>Cancel</button>
|
||||
</div>
|
||||
</x-paper>
|
||||
</x-background>
|
||||
|
@ -130,9 +129,9 @@
|
|||
<x-paper shadow="2">
|
||||
<h2 class="center">Unpair Devices</h2>
|
||||
<div class="font-subheading center text-center">Are you sure to unpair all devices?</div>
|
||||
<div class="row-reverse space-between">
|
||||
<div class="center row-reverse">
|
||||
<button class="button" type="submit">Unpair Devices</button>
|
||||
<a class="button" close>Cancel</a>
|
||||
<button class="button" close>Cancel</button>
|
||||
</div>
|
||||
</x-paper>
|
||||
</x-background>
|
||||
|
@ -142,25 +141,23 @@
|
|||
<x-dialog id="receive-request-dialog">
|
||||
<x-background class="full center">
|
||||
<x-paper shadow="2">
|
||||
<h2 class="center">PairDrop</h2>
|
||||
<div class="text-center file-description">
|
||||
<h2 class="center"></h2>
|
||||
<div class="center column file-description">
|
||||
<div>
|
||||
<span id="requesting-peer-display-name"></span>
|
||||
<span class="display-name"></span>
|
||||
<span>would like to share</span>
|
||||
</div>
|
||||
<div id="file-name" class="row" >
|
||||
<span id="file-stem"></span>
|
||||
<span id="file-extension"></span>
|
||||
<div class="row file-name" >
|
||||
<span class="file-stem"></span>
|
||||
<span class="file-extension"></span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span id="file-other"></span>
|
||||
<div class="row file-other">
|
||||
</div>
|
||||
<div class="row font-body2 file-size"></div>
|
||||
</div>
|
||||
<div class="font-body2 text-center file-size"></div>
|
||||
<div class="center file-preview"></div>
|
||||
<div class="row-reverse space-between">
|
||||
<div class="center row-reverse">
|
||||
<button id="accept-request" class="button" title="ENTER" autofocus>Accept</button>
|
||||
<div class="separator"></div>
|
||||
<button id="decline-request" class="button" title="ESCAPE">Decline</button>
|
||||
</div>
|
||||
</x-paper>
|
||||
|
@ -170,13 +167,23 @@
|
|||
<x-dialog id="receive-file-dialog">
|
||||
<x-background class="full center">
|
||||
<x-paper shadow="2">
|
||||
<h2 id="receive-title" class="center"></h2>
|
||||
<div class="text-center file-description"></div>
|
||||
<div class="font-body2 text-center file-size"></div>
|
||||
<h2 class="center"></h2>
|
||||
<div class="center column file-description">
|
||||
<div>
|
||||
<span class="display-name"></span>
|
||||
<span>has sent</span>
|
||||
</div>
|
||||
<div class="row file-name" >
|
||||
<span class="file-stem"></span>
|
||||
<span class="file-extension"></span>
|
||||
</div>
|
||||
<div class="row file-other"></div>
|
||||
<div class="row font-body2 file-size"></div>
|
||||
</div>
|
||||
<div class="center file-preview"></div>
|
||||
<div class="row-reverse space-between">
|
||||
<a id="share-or-download" class="button" autofocus></a>
|
||||
<div class="separator"></div>
|
||||
<div class="center row-reverse">
|
||||
<button id="share-btn" class="button" autofocus hidden>Share</button>
|
||||
<button id="download-btn" class="button" autofocus>Download</button>
|
||||
<button class="button" close>Close</button>
|
||||
</div>
|
||||
</x-paper>
|
||||
|
@ -187,16 +194,16 @@
|
|||
<form action="#">
|
||||
<x-background class="full center">
|
||||
<x-paper shadow="2">
|
||||
<h2 class="text-center">PairDrop</h2>
|
||||
<div class="text-center">
|
||||
<h2 class="text-center">Send Message</h2>
|
||||
<div class="dialog-subheader text-center">
|
||||
<span>Send a Message to</span>
|
||||
<span id="text-send-peer-display-name"></span>
|
||||
<span class="display-name"></span>
|
||||
</div>
|
||||
<div class="row-separator"></div>
|
||||
<div id="text-input" class="textarea" role="textbox" autocapitalize="none" spellcheck="false" autofocus contenteditable></div>
|
||||
<div class="row-reverse">
|
||||
<div class="center row-reverse">
|
||||
<button class="button" type="submit" title="STR + ENTER" disabled close>Send</button>
|
||||
<div class="separator"></div>
|
||||
<a class="button" title="ESCAPE" close>Cancel</a>
|
||||
<button class="button" title="ESCAPE" close>Cancel</button>
|
||||
</div>
|
||||
</x-paper>
|
||||
</x-background>
|
||||
|
@ -206,16 +213,15 @@
|
|||
<x-dialog id="receive-text-dialog">
|
||||
<x-background class="full center">
|
||||
<x-paper shadow="2">
|
||||
<h2>PairDrop - Message Received</h2>
|
||||
<div id="receive-text-description-container">
|
||||
<span id="receive-text-peer-display-name"></span>
|
||||
<span>sent the following message:</span>
|
||||
<h2 class="text-center">Message Received</h2>
|
||||
<div class="text-center dialog-subheader">
|
||||
<span class="display-name"></span>
|
||||
<span>has sent:</span>
|
||||
</div>
|
||||
<div class="row-separator"></div>
|
||||
<div id="text"></div>
|
||||
<div class="row-reverse">
|
||||
<div class="center row-reverse">
|
||||
<button id="copy" class="button" title="CTRL/⌘ + C">Copy</button>
|
||||
<div class="separator"></div>
|
||||
<button id="close" class="button" title="ESCAPE">Close</button>
|
||||
</div>
|
||||
</x-paper>
|
||||
|
|
|
@ -441,7 +441,7 @@ class Peer {
|
|||
if (!this._requestAccepted.header.length) {
|
||||
this._busy = false;
|
||||
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'process'});
|
||||
Events.fire('files-received', {sender: this._peerId, files: this._filesReceived, request: this._requestAccepted});
|
||||
Events.fire('files-received', {sender: this._peerId, files: this._filesReceived, imagesOnly: this._requestAccepted.imagesOnly, totalSize: this._requestAccepted.totalSize});
|
||||
this._filesReceived = [];
|
||||
this._requestAccepted = null;
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ class PeersUI {
|
|||
Events.on('activate-paste-mode', e => this._activatePasteMode(e.detail.files, e.detail.text));
|
||||
this.peers = {};
|
||||
|
||||
this.$cancelPasteModeBtn = $('cancel-paste-mode-btn');
|
||||
this.$cancelPasteModeBtn = $('cancel-paste-mode');
|
||||
this.$cancelPasteModeBtn.addEventListener('click', _ => this._cancelPasteMode());
|
||||
|
||||
Events.on('dragover', e => this._onDragOver(e));
|
||||
|
@ -473,10 +473,14 @@ class Dialog {
|
|||
class ReceiveDialog extends Dialog {
|
||||
constructor(id) {
|
||||
super(id);
|
||||
|
||||
this.$fileDescriptionNode = this.$el.querySelector('.file-description');
|
||||
this.$fileSizeNode = this.$el.querySelector('.file-size');
|
||||
this.$previewBox = this.$el.querySelector('.file-preview')
|
||||
this.$fileDescription = this.$el.querySelector('.file-description');
|
||||
this.$displayName = this.$el.querySelector('.display-name');
|
||||
this.$fileStem = this.$el.querySelector('.file-stem');
|
||||
this.$fileExtension = this.$el.querySelector('.file-extension');
|
||||
this.$fileOther = this.$el.querySelector('.file-other');
|
||||
this.$fileSize = this.$el.querySelector('.file-size');
|
||||
this.$previewBox = this.$el.querySelector('.file-preview');
|
||||
this.$receiveTitle = this.$el.querySelector('h2:first-of-type');
|
||||
}
|
||||
|
||||
_formatFileSize(bytes) {
|
||||
|
@ -492,6 +496,26 @@ class ReceiveDialog extends Dialog {
|
|||
return bytes + ' Bytes';
|
||||
}
|
||||
}
|
||||
|
||||
_parseFileData(displayName, files, imagesOnly, totalSize) {
|
||||
if (files.length > 1) {
|
||||
let fileOtherText = ` and ${files.length - 1} other `;
|
||||
if (files.length === 2) {
|
||||
fileOtherText += imagesOnly ? 'image' : 'file';
|
||||
} else {
|
||||
fileOtherText += imagesOnly ? 'images' : 'files';
|
||||
}
|
||||
this.$fileOther.innerText = fileOtherText;
|
||||
}
|
||||
|
||||
const fileName = files[0].name;
|
||||
const fileNameSplit = fileName.split('.');
|
||||
const fileExtension = '.' + fileNameSplit[fileNameSplit.length - 1];
|
||||
this.$fileStem.innerText = fileName.substring(0, fileName.length - fileExtension.length);
|
||||
this.$fileExtension.innerText = fileExtension;
|
||||
this.$displayName.innerText = displayName;
|
||||
this.$fileSize.innerText = this._formatFileSize(totalSize);
|
||||
}
|
||||
}
|
||||
|
||||
class ReceiveFileDialog extends ReceiveDialog {
|
||||
|
@ -499,24 +523,25 @@ class ReceiveFileDialog extends ReceiveDialog {
|
|||
constructor() {
|
||||
super('receive-file-dialog');
|
||||
|
||||
this.$shareOrDownloadBtn = this.$el.querySelector('#share-or-download');
|
||||
this.$receiveTitleNode = this.$el.querySelector('#receive-title')
|
||||
this.$downloadBtn = this.$el.querySelector('#download-btn');
|
||||
this.$shareBtn = this.$el.querySelector('#share-btn');
|
||||
|
||||
Events.on('files-received', e => this._onFilesReceived(e.detail.sender, e.detail.files, e.detail.request));
|
||||
Events.on('files-received', e => this._onFilesReceived(e.detail.sender, e.detail.files, e.detail.imagesOnly, e.detail.totalSize));
|
||||
this._filesQueue = [];
|
||||
}
|
||||
|
||||
_onFilesReceived(sender, files, request) {
|
||||
this._nextFiles(sender, files, request);
|
||||
_onFilesReceived(sender, files, imagesOnly, totalSize) {
|
||||
const displayName = $(sender).ui._displayName()
|
||||
this._filesQueue.push({peer: sender, displayName: displayName, files: files, imagesOnly: imagesOnly, totalSize: totalSize});
|
||||
this._nextFiles();
|
||||
window.blop.play();
|
||||
}
|
||||
|
||||
_nextFiles(sender, nextFiles, nextRequest) {
|
||||
if (nextFiles) this._filesQueue.push({peerId: sender, files: nextFiles, request: nextRequest});
|
||||
_nextFiles() {
|
||||
if (this._busy) return;
|
||||
this._busy = true;
|
||||
const {peerId, files, request} = this._filesQueue.shift();
|
||||
this._displayFiles(peerId, files, request);
|
||||
const {peer, displayName, files, imagesOnly, totalSize} = this._filesQueue.shift();
|
||||
this._displayFiles(peer, displayName, files, imagesOnly, totalSize);
|
||||
}
|
||||
|
||||
_dequeueFile() {
|
||||
|
@ -547,7 +572,6 @@ class ReceiveFileDialog extends ReceiveDialog {
|
|||
let element = document.createElement(previewElement[mime]);
|
||||
element.src = URL.createObjectURL(file);
|
||||
element.controls = true;
|
||||
element.classList.add('element-preview');
|
||||
element.onload = _ => {
|
||||
this.$previewBox.appendChild(element);
|
||||
resolve(true)
|
||||
|
@ -558,30 +582,32 @@ class ReceiveFileDialog extends ReceiveDialog {
|
|||
});
|
||||
}
|
||||
|
||||
async _displayFiles(peerId, files, request) {
|
||||
if (this.continueCallback) this.$shareOrDownloadBtn.removeEventListener("click", this.continueCallback);
|
||||
|
||||
let url;
|
||||
let title;
|
||||
let filenameDownload;
|
||||
|
||||
let descriptor = request.imagesOnly ? "Image" : "File";
|
||||
|
||||
let size = this._formatFileSize(request.totalSize);
|
||||
let description = files[0].name;
|
||||
|
||||
let shareInsteadOfDownload = (window.iOS || window.android) && !!navigator.share && navigator.canShare({files});
|
||||
async _displayFiles(peerId, displayName, files, imagesOnly, totalSize) {
|
||||
this._parseFileData(displayName, files, imagesOnly, totalSize);
|
||||
|
||||
let descriptor, url, filenameDownload;
|
||||
if (files.length === 1) {
|
||||
url = URL.createObjectURL(files[0])
|
||||
title = `PairDrop - ${descriptor} Received`
|
||||
filenameDownload = files[0].name;
|
||||
descriptor = imagesOnly ? 'Image' : 'File';
|
||||
} else {
|
||||
title = `PairDrop - ${files.length} ${descriptor}s Received`
|
||||
description += ` and ${files.length-1} other ${descriptor.toLowerCase()}`;
|
||||
if(files.length>2) description += "s";
|
||||
descriptor = imagesOnly ? 'Images' : 'Files';
|
||||
}
|
||||
this.$receiveTitle.innerText = `${descriptor} Received`;
|
||||
|
||||
if(!shareInsteadOfDownload) {
|
||||
const canShare = (window.iOS || window.android) && !!navigator.share && navigator.canShare({files});
|
||||
if (canShare) {
|
||||
this.$shareBtn.removeAttribute('hidden');
|
||||
this.$shareBtn.onclick = _ => {
|
||||
navigator.share({files: files})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let downloadZipped = false;
|
||||
if (files.length > 1) {
|
||||
downloadZipped = true;
|
||||
try {
|
||||
let bytesCompleted = 0;
|
||||
zipper.createNewZipWriter();
|
||||
for (let i=0; i<files.length; i++) {
|
||||
|
@ -589,7 +615,7 @@ class ReceiveFileDialog extends ReceiveDialog {
|
|||
onprogress: (progress) => {
|
||||
Events.fire('set-progress', {
|
||||
peerId: peerId,
|
||||
progress: (bytesCompleted + progress) / request.totalSize,
|
||||
progress: (bytesCompleted + progress) / totalSize,
|
||||
status: 'process'
|
||||
})
|
||||
}
|
||||
|
@ -609,49 +635,58 @@ class ReceiveFileDialog extends ReceiveDialog {
|
|||
let minutes = now.getMinutes().toString();
|
||||
minutes = minutes.length < 2 ? "0" + minutes : minutes;
|
||||
filenameDownload = `PairDrop_files_${year+month+date}_${hours+minutes}.zip`;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
downloadZipped = false;
|
||||
}
|
||||
}
|
||||
|
||||
this.$receiveTitleNode.textContent = title;
|
||||
this.$fileDescriptionNode.textContent = description;
|
||||
this.$fileSizeNode.textContent = size;
|
||||
|
||||
if (shareInsteadOfDownload) {
|
||||
this.$shareOrDownloadBtn.innerText = "Share";
|
||||
this.continue = _ => {
|
||||
navigator.share({files: files})
|
||||
.catch(err => console.error(err));
|
||||
this.$downloadBtn.innerText = "Download";
|
||||
this.$downloadBtn.onclick = _ => {
|
||||
if (downloadZipped) {
|
||||
let tmpZipBtn = document.createElement("a");
|
||||
tmpZipBtn.download = filenameDownload;
|
||||
tmpZipBtn.href = url;
|
||||
tmpZipBtn.click();
|
||||
} else {
|
||||
this._downloadFilesIndividually(files);
|
||||
}
|
||||
this.continueCallback = _ => this.continue();
|
||||
} else {
|
||||
this.$shareOrDownloadBtn.innerText = "Download again";
|
||||
this.continue = _ => {
|
||||
let tmpBtn = document.createElement("a");
|
||||
tmpBtn.download = filenameDownload;
|
||||
tmpBtn.href = url;
|
||||
tmpBtn.click();
|
||||
};
|
||||
this.continueCallback = _ => {
|
||||
this.continue();
|
||||
this.hide();
|
||||
};
|
||||
}
|
||||
this.$shareOrDownloadBtn.addEventListener("click", this.continueCallback);
|
||||
|
||||
if (!canShare) {
|
||||
this.$downloadBtn.innerText = "Download again";
|
||||
}
|
||||
Events.fire('notify-user', `${descriptor} downloaded successfully`);
|
||||
this.$downloadBtn.style.pointerEvents = "none";
|
||||
setTimeout(_ => this.$downloadBtn.style.pointerEvents = "unset", 2000);
|
||||
};
|
||||
|
||||
this.createPreviewElement(files[0]).finally(_ => {
|
||||
document.title = files.length === 1
|
||||
? 'File received - PairDrop'
|
||||
: `(${files.length}) Files received - PairDrop`;
|
||||
document.changeFavicon("images/favicon-96x96-notification.png");
|
||||
this.show();
|
||||
Events.fire('set-progress', {peerId: peerId, progress: 1, status: 'process'})
|
||||
this.continue();
|
||||
this.show();
|
||||
|
||||
if (canShare) {
|
||||
this.$shareBtn.click();
|
||||
} else {
|
||||
this.$downloadBtn.click();
|
||||
}
|
||||
}).catch(r => console.error(r));
|
||||
}
|
||||
|
||||
_downloadFilesIndividually(files) {
|
||||
let tmpBtn = document.createElement("a");
|
||||
for (let i=0; i<files.length; i++) {
|
||||
tmpBtn.download = files[i].name;
|
||||
tmpBtn.href = URL.createObjectURL(files[i]);
|
||||
tmpBtn.click();
|
||||
}
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.$shareOrDownloadBtn.removeAttribute('href');
|
||||
this.$shareOrDownloadBtn.removeAttribute('download');
|
||||
this.$shareBtn.setAttribute('hidden', '');
|
||||
this.$previewBox.innerHTML = '';
|
||||
super.hide();
|
||||
this._dequeueFile();
|
||||
|
@ -663,11 +698,6 @@ class ReceiveRequestDialog extends ReceiveDialog {
|
|||
constructor() {
|
||||
super('receive-request-dialog');
|
||||
|
||||
this.$requestingPeerDisplayNameNode = this.$el.querySelector('#requesting-peer-display-name');
|
||||
this.$fileStemNode = this.$el.querySelector('#file-stem');
|
||||
this.$fileExtensionNode = this.$el.querySelector('#file-extension');
|
||||
this.$fileOtherNode = this.$el.querySelector('#file-other');
|
||||
|
||||
this.$acceptRequestBtn = this.$el.querySelector('#accept-request');
|
||||
this.$declineRequestBtn = this.$el.querySelector('#decline-request');
|
||||
this.$acceptRequestBtn.addEventListener('click', _ => this._respondToFileTransferRequest(true));
|
||||
|
@ -699,32 +729,18 @@ class ReceiveRequestDialog extends ReceiveDialog {
|
|||
_showRequestDialog(request, peerId) {
|
||||
this.correspondingPeerId = peerId;
|
||||
|
||||
this.$requestingPeerDisplayNameNode.innerText = $(peerId).ui._displayName();
|
||||
|
||||
const fileName = request.header[0].name;
|
||||
const fileNameSplit = fileName.split('.');
|
||||
const fileExtension = '.' + fileNameSplit[fileNameSplit.length - 1];
|
||||
this.$fileStemNode.innerText = fileName.substring(0, fileName.length - fileExtension.length);
|
||||
this.$fileExtensionNode.innerText = fileExtension
|
||||
|
||||
if (request.header.length >= 2) {
|
||||
let fileOtherText = ` and ${request.header.length - 1} other `;
|
||||
fileOtherText += request.imagesOnly ? 'image' : 'file';
|
||||
if (request.header.length > 2) fileOtherText += "s";
|
||||
this.$fileOtherNode.innerText = fileOtherText;
|
||||
}
|
||||
|
||||
this.$fileSizeNode.innerText = this._formatFileSize(request.totalSize);
|
||||
const displayName = $(peerId).ui._displayName();
|
||||
this._parseFileData(displayName, request.header, request.imagesOnly, request.totalSize);
|
||||
|
||||
if (request.thumbnailDataUrl?.substring(0, 22) === "data:image/jpeg;base64") {
|
||||
let element = document.createElement('img');
|
||||
element.src = request.thumbnailDataUrl;
|
||||
element.classList.add('element-preview');
|
||||
|
||||
this.$previewBox.appendChild(element)
|
||||
}
|
||||
|
||||
document.title = 'File Transfer Requested - PairDrop';
|
||||
this.$receiveTitle.innerText = `${request.imagesOnly ? 'Image' : 'File'} Transfer Request`
|
||||
|
||||
document.title = `${request.imagesOnly ? 'Image' : 'File'} Transfer Requested - PairDrop`;
|
||||
document.changeFavicon("images/favicon-96x96-notification.png");
|
||||
this.show();
|
||||
}
|
||||
|
@ -999,7 +1015,7 @@ class SendTextDialog extends Dialog {
|
|||
super('send-text-dialog');
|
||||
Events.on('text-recipient', e => this._onRecipient(e.detail.peerId, e.detail.deviceName));
|
||||
this.$text = this.$el.querySelector('#text-input');
|
||||
this.$peerDisplayName = this.$el.querySelector('#text-send-peer-display-name');
|
||||
this.$peerDisplayName = this.$el.querySelector('.display-name');
|
||||
this.$form = this.$el.querySelector('form');
|
||||
this.$submit = this.$el.querySelector('button[type="submit"]');
|
||||
this.$form.addEventListener('submit', e => this._onSubmit(e));
|
||||
|
@ -1072,7 +1088,7 @@ class ReceiveTextDialog extends Dialog {
|
|||
|
||||
Events.on("keydown", e => this._onKeyDown(e));
|
||||
|
||||
this.$receiveTextPeerDisplayNameNode = this.$el.querySelector('#receive-text-peer-display-name');
|
||||
this.$displayNameNode = this.$el.querySelector('.display-name');
|
||||
this._receiveTextQueue = [];
|
||||
}
|
||||
|
||||
|
@ -1102,7 +1118,7 @@ class ReceiveTextDialog extends Dialog {
|
|||
}
|
||||
|
||||
_showReceiveTextDialog(text, peerId) {
|
||||
this.$receiveTextPeerDisplayNameNode.innerText = $(peerId).ui._displayName();
|
||||
this.$displayNameNode.innerText = $(peerId).ui._displayName();
|
||||
|
||||
if (isURL(text)) {
|
||||
const $a = document.createElement('a');
|
||||
|
@ -1198,7 +1214,7 @@ class Base64ZipDialog extends Dialog {
|
|||
}
|
||||
|
||||
_setPasteBtnToProcessing() {
|
||||
this.$pasteBtn.pointerEvents = "none";
|
||||
this.$pasteBtn.style.pointerEvents = "none";
|
||||
this.$pasteBtn.innerText = "Processing...";
|
||||
}
|
||||
|
||||
|
|
|
@ -564,7 +564,7 @@ x-dialog x-background {
|
|||
z-index: 10;
|
||||
transition: opacity 300ms;
|
||||
will-change: opacity;
|
||||
padding: 35px;
|
||||
padding: 15px;
|
||||
overflow: overlay;
|
||||
}
|
||||
|
||||
|
@ -575,19 +575,20 @@ x-dialog x-paper {
|
|||
padding: 16px 24px;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
transition: transform 300ms;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
#pair-device-dialog x-paper {
|
||||
position: absolute;
|
||||
top: max(50%, 350px);
|
||||
height: 650px;
|
||||
margin-top: -325px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
position: absolute;
|
||||
top: max(50%, 350px);
|
||||
margin-top: -328.5px;
|
||||
width: calc(100vw - 20px);
|
||||
height: 625px;
|
||||
}
|
||||
|
||||
x-dialog:not([show]) {
|
||||
|
@ -602,12 +603,6 @@ x-dialog:not([show]) x-background {
|
|||
opacity: 0;
|
||||
}
|
||||
|
||||
x-dialog .row-reverse>.button {
|
||||
margin-top: 0;
|
||||
margin-bottom: -16px;
|
||||
width: 50%;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
x-dialog a {
|
||||
color: var(--primary-color);
|
||||
|
@ -646,7 +641,7 @@ x-dialog .font-subheading {
|
|||
}
|
||||
|
||||
#key-input-container>input:nth-of-type(4) {
|
||||
margin-left: 18px;
|
||||
margin-left: 5%;
|
||||
}
|
||||
|
||||
#room-key {
|
||||
|
@ -658,16 +653,11 @@ x-dialog .font-subheading {
|
|||
}
|
||||
|
||||
#room-key-qr-code {
|
||||
padding: inherit;
|
||||
margin: auto;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
margin: 16px;
|
||||
}
|
||||
|
||||
#pair-device-dialog hr {
|
||||
margin-top: 40px;
|
||||
margin-bottom: 40px;
|
||||
width: 100%;
|
||||
margin: 40px -24px;
|
||||
}
|
||||
|
||||
#pair-device-dialog x-background {
|
||||
|
@ -681,29 +671,24 @@ x-dialog .row {
|
|||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
x-dialog h2 {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
#receive-request-dialog h2,
|
||||
#receive-file-dialog h2 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
x-dialog .row-reverse {
|
||||
margin: 40px -24px 0;
|
||||
/* button row*/
|
||||
x-paper > div:last-child {
|
||||
margin: auto -24px -15px;
|
||||
border-top: solid 2.5px var(--border-color);
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.separator {
|
||||
border: solid 1.25px var(--border-color);
|
||||
margin-bottom: -16px;
|
||||
x-paper > div:last-child > .button {
|
||||
height: 100%;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
x-paper > div:last-child > .button:not(:last-child) {
|
||||
border-left: solid 2.5px var(--border-color);
|
||||
}
|
||||
|
||||
.file-description {
|
||||
word-break: break-word;
|
||||
width: 80%;
|
||||
margin: auto;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.file-description .row {
|
||||
|
@ -715,26 +700,26 @@ x-dialog .row-reverse {
|
|||
word-break: normal;
|
||||
}
|
||||
|
||||
#file-name {
|
||||
.file-name {
|
||||
font-style: italic;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
#file-stem {
|
||||
max-width: 80%;
|
||||
.file-stem {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-all;
|
||||
max-height: 20px;
|
||||
}
|
||||
|
||||
.file-size{
|
||||
margin-bottom: 30px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Send Text Dialog */
|
||||
|
||||
x-dialog .dialog-subheader {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
#text-input {
|
||||
min-height: 120px;
|
||||
min-height: 200px;
|
||||
margin: 14px auto;
|
||||
}
|
||||
|
||||
/* Receive Text Dialog */
|
||||
|
@ -742,14 +727,14 @@ x-dialog .row-reverse {
|
|||
#receive-text-dialog #text {
|
||||
width: 100%;
|
||||
word-break: break-all;
|
||||
max-height: 300px;
|
||||
max-height: calc(100vh - 393px);
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
-webkit-user-select: all;
|
||||
-moz-user-select: all;
|
||||
user-select: all;
|
||||
white-space: pre-wrap;
|
||||
margin-top:36px;
|
||||
padding: 15px 0;
|
||||
}
|
||||
|
||||
#receive-text-dialog #text a {
|
||||
|
@ -768,11 +753,7 @@ x-dialog .row-reverse {
|
|||
|
||||
.row-separator {
|
||||
border-bottom: solid 2.5px var(--border-color);
|
||||
margin: auto -25px;
|
||||
}
|
||||
|
||||
#receive-text-description-container {
|
||||
margin-bottom: 25px;
|
||||
margin: auto -24px;
|
||||
}
|
||||
|
||||
#base64-paste-btn {
|
||||
|
@ -800,7 +781,6 @@ x-dialog .row-reverse {
|
|||
padding: 2px 16px 0;
|
||||
box-sizing: border-box;
|
||||
min-height: 36px;
|
||||
min-width: 100px;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
font-weight: 700;
|
||||
|
@ -811,6 +791,7 @@ x-dialog .row-reverse {
|
|||
user-select: none;
|
||||
background: inherit;
|
||||
color: var(--primary-color);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.button[disabled] {
|
||||
|
@ -848,7 +829,7 @@ x-dialog .row-reverse {
|
|||
opacity: 0.1;
|
||||
}
|
||||
|
||||
#cancel-paste-mode-btn {
|
||||
#cancel-paste-mode {
|
||||
z-index: 2;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
@ -875,7 +856,6 @@ button::-moz-focus-inner {
|
|||
|
||||
|
||||
/* Icon Button */
|
||||
|
||||
.icon-button {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
|
@ -885,10 +865,7 @@ button::-moz-focus-inner {
|
|||
border-radius: 50%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Text Input */
|
||||
|
||||
.textarea {
|
||||
box-sizing: border-box;
|
||||
border: none;
|
||||
|
@ -902,9 +879,8 @@ button::-moz-focus-inner {
|
|||
display: block;
|
||||
overflow: auto;
|
||||
resize: none;
|
||||
min-height: 40px;
|
||||
line-height: 16px;
|
||||
max-height: 300px;
|
||||
max-height: calc(100vh - 254px);
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
|
@ -1094,6 +1070,14 @@ x-peers:empty~x-instructions {
|
|||
}
|
||||
|
||||
/* Responsive Styles */
|
||||
@media screen and (max-width: 360px) {
|
||||
x-dialog x-paper {
|
||||
padding: 15px;
|
||||
}
|
||||
x-paper > div:last-child {
|
||||
margin: auto -15px -15px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-height: 800px) {
|
||||
footer {
|
||||
|
@ -1166,7 +1150,9 @@ x-dialog x-paper {
|
|||
display: none;
|
||||
}
|
||||
|
||||
.element-preview {
|
||||
.file-preview > img,
|
||||
.file-preview > audio,
|
||||
.file-preview > video {
|
||||
max-width: 100%;
|
||||
max-height: 40vh;
|
||||
margin: auto;
|
||||
|
|
|
@ -69,7 +69,7 @@
|
|||
<use xlink:href="#clear-pair-devices-icon" />
|
||||
</svg>
|
||||
</a>
|
||||
<a id="cancel-paste-mode-btn" class="button" close hidden>Done</a>
|
||||
<a id="cancel-paste-mode" class="button" hidden>Done</a>
|
||||
</header>
|
||||
<!-- Center -->
|
||||
<div id="center">
|
||||
|
@ -109,18 +109,17 @@
|
|||
<div id="pair-instructions" class="font-subheading center text-center">Input this key on another device<br>or scan the QR-Code.</div>
|
||||
<hr>
|
||||
<div id="key-input-container">
|
||||
<input id="char0" type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" autofocus contenteditable placeholder="" disabled>
|
||||
<input id="char1" type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
|
||||
<input id="char2" type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
|
||||
<input id="char3" type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
|
||||
<input id="char4" type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
|
||||
<input id="char5" type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
|
||||
<input type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" autofocus contenteditable placeholder="" disabled>
|
||||
<input type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
|
||||
<input type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
|
||||
<input type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
|
||||
<input type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
|
||||
<input type="tel" class="textarea center" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable placeholder="" disabled>
|
||||
</div>
|
||||
<div class="font-subheading center text-center">Enter key from another device to continue.</div>
|
||||
<div class="row-reverse space-between">
|
||||
<div class="center row-reverse">
|
||||
<button class="button" type="submit" disabled>Pair</button>
|
||||
<div class="separator"></div>
|
||||
<a class="button" close>Cancel</a>
|
||||
<button class="button" close>Cancel</button>
|
||||
</div>
|
||||
</x-paper>
|
||||
</x-background>
|
||||
|
@ -133,9 +132,9 @@
|
|||
<x-paper shadow="2">
|
||||
<h2 class="center">Unpair Devices</h2>
|
||||
<div class="font-subheading center text-center">Are you sure to unpair all devices?</div>
|
||||
<div class="row-reverse space-between">
|
||||
<div class="center row-reverse">
|
||||
<button class="button" type="submit">Unpair Devices</button>
|
||||
<a class="button" close>Cancel</a>
|
||||
<button class="button" close>Cancel</button>
|
||||
</div>
|
||||
</x-paper>
|
||||
</x-background>
|
||||
|
@ -145,25 +144,23 @@
|
|||
<x-dialog id="receive-request-dialog">
|
||||
<x-background class="full center">
|
||||
<x-paper shadow="2">
|
||||
<h2 class="center">PairDrop</h2>
|
||||
<div class="text-center file-description">
|
||||
<h2 class="center"></h2>
|
||||
<div class="center column file-description">
|
||||
<div>
|
||||
<span id="requesting-peer-display-name"></span>
|
||||
<span class="display-name"></span>
|
||||
<span>would like to share</span>
|
||||
</div>
|
||||
<div id="file-name" class="row" >
|
||||
<span id="file-stem"></span>
|
||||
<span id="file-extension"></span>
|
||||
<div class="row file-name" >
|
||||
<span class="file-stem"></span>
|
||||
<span class="file-extension"></span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<span id="file-other"></span>
|
||||
<div class="row file-other">
|
||||
</div>
|
||||
<div class="row font-body2 file-size"></div>
|
||||
</div>
|
||||
<div class="font-body2 text-center file-size"></div>
|
||||
<div class="center file-preview"></div>
|
||||
<div class="row-reverse space-between">
|
||||
<div class="center row-reverse">
|
||||
<button id="accept-request" class="button" title="ENTER" autofocus>Accept</button>
|
||||
<div class="separator"></div>
|
||||
<button id="decline-request" class="button" title="ESCAPE">Decline</button>
|
||||
</div>
|
||||
</x-paper>
|
||||
|
@ -173,13 +170,23 @@
|
|||
<x-dialog id="receive-file-dialog">
|
||||
<x-background class="full center">
|
||||
<x-paper shadow="2">
|
||||
<h2 id="receive-title" class="center"></h2>
|
||||
<div class="text-center file-description"></div>
|
||||
<div class="font-body2 text-center file-size"></div>
|
||||
<h2 class="center"></h2>
|
||||
<div class="center column file-description">
|
||||
<div>
|
||||
<span class="display-name"></span>
|
||||
<span>has sent</span>
|
||||
</div>
|
||||
<div class="row file-name" >
|
||||
<span class="file-stem"></span>
|
||||
<span class="file-extension"></span>
|
||||
</div>
|
||||
<div class="row file-other"></div>
|
||||
<div class="row font-body2 file-size"></div>
|
||||
</div>
|
||||
<div class="center file-preview"></div>
|
||||
<div class="row-reverse space-between">
|
||||
<a id="share-or-download" class="button" autofocus></a>
|
||||
<div class="separator"></div>
|
||||
<div class="center row-reverse">
|
||||
<button id="share-btn" class="button" autofocus hidden>Share</button>
|
||||
<button id="download-btn" class="button" autofocus>Download</button>
|
||||
<button class="button" close>Close</button>
|
||||
</div>
|
||||
</x-paper>
|
||||
|
@ -190,16 +197,16 @@
|
|||
<form action="#">
|
||||
<x-background class="full center">
|
||||
<x-paper shadow="2">
|
||||
<h2 class="text-center">PairDrop</h2>
|
||||
<div class="text-center">
|
||||
<h2 class="text-center">Send Message</h2>
|
||||
<div class="dialog-subheader text-center">
|
||||
<span>Send a Message to</span>
|
||||
<span id="text-send-peer-display-name"></span>
|
||||
<span class="display-name"></span>
|
||||
</div>
|
||||
<div class="row-separator"></div>
|
||||
<div id="text-input" class="textarea" role="textbox" autocapitalize="none" spellcheck="false" autofocus contenteditable></div>
|
||||
<div class="row-reverse">
|
||||
<div class="center row-reverse">
|
||||
<button class="button" type="submit" title="STR + ENTER" disabled close>Send</button>
|
||||
<div class="separator"></div>
|
||||
<a class="button" title="ESCAPE" close>Cancel</a>
|
||||
<button class="button" title="ESCAPE" close>Cancel</button>
|
||||
</div>
|
||||
</x-paper>
|
||||
</x-background>
|
||||
|
@ -209,16 +216,15 @@
|
|||
<x-dialog id="receive-text-dialog">
|
||||
<x-background class="full center">
|
||||
<x-paper shadow="2">
|
||||
<h2>PairDrop - Message Received</h2>
|
||||
<div id="receive-text-description-container">
|
||||
<span id="receive-text-peer-display-name"></span>
|
||||
<span>sent the following message:</span>
|
||||
<h2 class="text-center">Message Received</h2>
|
||||
<div class="text-center dialog-subheader">
|
||||
<span class="display-name"></span>
|
||||
<span>has sent:</span>
|
||||
</div>
|
||||
<div class="row-separator"></div>
|
||||
<div id="text"></div>
|
||||
<div class="row-reverse">
|
||||
<div class="center row-reverse">
|
||||
<button id="copy" class="button" title="CTRL/⌘ + C">Copy</button>
|
||||
<div class="separator"></div>
|
||||
<button id="close" class="button" title="ESCAPE">Close</button>
|
||||
</div>
|
||||
</x-paper>
|
||||
|
|
|
@ -451,7 +451,7 @@ class Peer {
|
|||
if (!this._requestAccepted.header.length) {
|
||||
this._busy = false;
|
||||
Events.fire('set-progress', {peerId: this._peerId, progress: 0, status: 'process'});
|
||||
Events.fire('files-received', {sender: this._peerId, files: this._filesReceived, request: this._requestAccepted});
|
||||
Events.fire('files-received', {sender: this._peerId, files: this._filesReceived, imagesOnly: this._requestAccepted.imagesOnly, totalSize: this._requestAccepted.totalSize});
|
||||
this._filesReceived = [];
|
||||
this._requestAccepted = null;
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ class PeersUI {
|
|||
Events.on('activate-paste-mode', e => this._activatePasteMode(e.detail.files, e.detail.text));
|
||||
this.peers = {};
|
||||
|
||||
this.$cancelPasteModeBtn = $('cancel-paste-mode-btn');
|
||||
this.$cancelPasteModeBtn = $('cancel-paste-mode');
|
||||
this.$cancelPasteModeBtn.addEventListener('click', _ => this._cancelPasteMode());
|
||||
|
||||
Events.on('dragover', e => this._onDragOver(e));
|
||||
|
@ -474,10 +474,14 @@ class Dialog {
|
|||
class ReceiveDialog extends Dialog {
|
||||
constructor(id) {
|
||||
super(id);
|
||||
|
||||
this.$fileDescriptionNode = this.$el.querySelector('.file-description');
|
||||
this.$fileSizeNode = this.$el.querySelector('.file-size');
|
||||
this.$previewBox = this.$el.querySelector('.file-preview')
|
||||
this.$fileDescription = this.$el.querySelector('.file-description');
|
||||
this.$displayName = this.$el.querySelector('.display-name');
|
||||
this.$fileStem = this.$el.querySelector('.file-stem');
|
||||
this.$fileExtension = this.$el.querySelector('.file-extension');
|
||||
this.$fileOther = this.$el.querySelector('.file-other');
|
||||
this.$fileSize = this.$el.querySelector('.file-size');
|
||||
this.$previewBox = this.$el.querySelector('.file-preview');
|
||||
this.$receiveTitle = this.$el.querySelector('h2:first-of-type');
|
||||
}
|
||||
|
||||
_formatFileSize(bytes) {
|
||||
|
@ -493,6 +497,26 @@ class ReceiveDialog extends Dialog {
|
|||
return bytes + ' Bytes';
|
||||
}
|
||||
}
|
||||
|
||||
_parseFileData(displayName, files, imagesOnly, totalSize) {
|
||||
if (files.length > 1) {
|
||||
let fileOtherText = ` and ${files.length - 1} other `;
|
||||
if (files.length === 2) {
|
||||
fileOtherText += imagesOnly ? 'image' : 'file';
|
||||
} else {
|
||||
fileOtherText += imagesOnly ? 'images' : 'files';
|
||||
}
|
||||
this.$fileOther.innerText = fileOtherText;
|
||||
}
|
||||
|
||||
const fileName = files[0].name;
|
||||
const fileNameSplit = fileName.split('.');
|
||||
const fileExtension = '.' + fileNameSplit[fileNameSplit.length - 1];
|
||||
this.$fileStem.innerText = fileName.substring(0, fileName.length - fileExtension.length);
|
||||
this.$fileExtension.innerText = fileExtension;
|
||||
this.$displayName.innerText = displayName;
|
||||
this.$fileSize.innerText = this._formatFileSize(totalSize);
|
||||
}
|
||||
}
|
||||
|
||||
class ReceiveFileDialog extends ReceiveDialog {
|
||||
|
@ -500,24 +524,25 @@ class ReceiveFileDialog extends ReceiveDialog {
|
|||
constructor() {
|
||||
super('receive-file-dialog');
|
||||
|
||||
this.$shareOrDownloadBtn = this.$el.querySelector('#share-or-download');
|
||||
this.$receiveTitleNode = this.$el.querySelector('#receive-title')
|
||||
this.$downloadBtn = this.$el.querySelector('#download-btn');
|
||||
this.$shareBtn = this.$el.querySelector('#share-btn');
|
||||
|
||||
Events.on('files-received', e => this._onFilesReceived(e.detail.sender, e.detail.files, e.detail.request));
|
||||
Events.on('files-received', e => this._onFilesReceived(e.detail.sender, e.detail.files, e.detail.imagesOnly, e.detail.totalSize));
|
||||
this._filesQueue = [];
|
||||
}
|
||||
|
||||
_onFilesReceived(sender, files, request) {
|
||||
this._nextFiles(sender, files, request);
|
||||
_onFilesReceived(sender, files, imagesOnly, totalSize) {
|
||||
const displayName = $(sender).ui._displayName()
|
||||
this._filesQueue.push({peer: sender, displayName: displayName, files: files, imagesOnly: imagesOnly, totalSize: totalSize});
|
||||
this._nextFiles();
|
||||
window.blop.play();
|
||||
}
|
||||
|
||||
_nextFiles(sender, nextFiles, nextRequest) {
|
||||
if (nextFiles) this._filesQueue.push({peerId: sender, files: nextFiles, request: nextRequest});
|
||||
_nextFiles() {
|
||||
if (this._busy) return;
|
||||
this._busy = true;
|
||||
const {peerId, files, request} = this._filesQueue.shift();
|
||||
this._displayFiles(peerId, files, request);
|
||||
const {peer, displayName, files, imagesOnly, totalSize} = this._filesQueue.shift();
|
||||
this._displayFiles(peer, displayName, files, imagesOnly, totalSize);
|
||||
}
|
||||
|
||||
_dequeueFile() {
|
||||
|
@ -548,7 +573,6 @@ class ReceiveFileDialog extends ReceiveDialog {
|
|||
let element = document.createElement(previewElement[mime]);
|
||||
element.src = URL.createObjectURL(file);
|
||||
element.controls = true;
|
||||
element.classList.add('element-preview');
|
||||
element.onload = _ => {
|
||||
this.$previewBox.appendChild(element);
|
||||
resolve(true)
|
||||
|
@ -559,30 +583,32 @@ class ReceiveFileDialog extends ReceiveDialog {
|
|||
});
|
||||
}
|
||||
|
||||
async _displayFiles(peerId, files, request) {
|
||||
if (this.continueCallback) this.$shareOrDownloadBtn.removeEventListener("click", this.continueCallback);
|
||||
|
||||
let url;
|
||||
let title;
|
||||
let filenameDownload;
|
||||
|
||||
let descriptor = request.imagesOnly ? "Image" : "File";
|
||||
|
||||
let size = this._formatFileSize(request.totalSize);
|
||||
let description = files[0].name;
|
||||
|
||||
let shareInsteadOfDownload = (window.iOS || window.android) && !!navigator.share && navigator.canShare({files});
|
||||
async _displayFiles(peerId, displayName, files, imagesOnly, totalSize) {
|
||||
this._parseFileData(displayName, files, imagesOnly, totalSize);
|
||||
|
||||
let descriptor, url, filenameDownload;
|
||||
if (files.length === 1) {
|
||||
url = URL.createObjectURL(files[0])
|
||||
title = `PairDrop - ${descriptor} Received`
|
||||
filenameDownload = files[0].name;
|
||||
descriptor = imagesOnly ? 'Image' : 'File';
|
||||
} else {
|
||||
title = `PairDrop - ${files.length} ${descriptor}s Received`
|
||||
description += ` and ${files.length-1} other ${descriptor.toLowerCase()}`;
|
||||
if(files.length>2) description += "s";
|
||||
descriptor = imagesOnly ? 'Images' : 'Files';
|
||||
}
|
||||
this.$receiveTitle.innerText = `${descriptor} Received`;
|
||||
|
||||
if(!shareInsteadOfDownload) {
|
||||
const canShare = (window.iOS || window.android) && !!navigator.share && navigator.canShare({files});
|
||||
if (canShare) {
|
||||
this.$shareBtn.removeAttribute('hidden');
|
||||
this.$shareBtn.onclick = _ => {
|
||||
navigator.share({files: files})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let downloadZipped = false;
|
||||
if (files.length > 1) {
|
||||
downloadZipped = true;
|
||||
try {
|
||||
let bytesCompleted = 0;
|
||||
zipper.createNewZipWriter();
|
||||
for (let i=0; i<files.length; i++) {
|
||||
|
@ -590,7 +616,7 @@ class ReceiveFileDialog extends ReceiveDialog {
|
|||
onprogress: (progress) => {
|
||||
Events.fire('set-progress', {
|
||||
peerId: peerId,
|
||||
progress: (bytesCompleted + progress) / request.totalSize,
|
||||
progress: (bytesCompleted + progress) / totalSize,
|
||||
status: 'process'
|
||||
})
|
||||
}
|
||||
|
@ -610,49 +636,58 @@ class ReceiveFileDialog extends ReceiveDialog {
|
|||
let minutes = now.getMinutes().toString();
|
||||
minutes = minutes.length < 2 ? "0" + minutes : minutes;
|
||||
filenameDownload = `PairDrop_files_${year+month+date}_${hours+minutes}.zip`;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
downloadZipped = false;
|
||||
}
|
||||
}
|
||||
|
||||
this.$receiveTitleNode.textContent = title;
|
||||
this.$fileDescriptionNode.textContent = description;
|
||||
this.$fileSizeNode.textContent = size;
|
||||
|
||||
if (shareInsteadOfDownload) {
|
||||
this.$shareOrDownloadBtn.innerText = "Share";
|
||||
this.continue = _ => {
|
||||
navigator.share({files: files})
|
||||
.catch(err => console.error(err));
|
||||
this.$downloadBtn.innerText = "Download";
|
||||
this.$downloadBtn.onclick = _ => {
|
||||
if (downloadZipped) {
|
||||
let tmpZipBtn = document.createElement("a");
|
||||
tmpZipBtn.download = filenameDownload;
|
||||
tmpZipBtn.href = url;
|
||||
tmpZipBtn.click();
|
||||
} else {
|
||||
this._downloadFilesIndividually(files);
|
||||
}
|
||||
this.continueCallback = _ => this.continue();
|
||||
} else {
|
||||
this.$shareOrDownloadBtn.innerText = "Download again";
|
||||
this.continue = _ => {
|
||||
let tmpBtn = document.createElement("a");
|
||||
tmpBtn.download = filenameDownload;
|
||||
tmpBtn.href = url;
|
||||
tmpBtn.click();
|
||||
};
|
||||
this.continueCallback = _ => {
|
||||
this.continue();
|
||||
this.hide();
|
||||
};
|
||||
}
|
||||
this.$shareOrDownloadBtn.addEventListener("click", this.continueCallback);
|
||||
|
||||
if (!canShare) {
|
||||
this.$downloadBtn.innerText = "Download again";
|
||||
}
|
||||
Events.fire('notify-user', `${descriptor} downloaded successfully`);
|
||||
this.$downloadBtn.style.pointerEvents = "none";
|
||||
setTimeout(_ => this.$downloadBtn.style.pointerEvents = "unset", 2000);
|
||||
};
|
||||
|
||||
this.createPreviewElement(files[0]).finally(_ => {
|
||||
document.title = files.length === 1
|
||||
? 'File received - PairDrop'
|
||||
: `(${files.length}) Files received - PairDrop`;
|
||||
document.changeFavicon("images/favicon-96x96-notification.png");
|
||||
this.show();
|
||||
Events.fire('set-progress', {peerId: peerId, progress: 1, status: 'process'})
|
||||
this.continue();
|
||||
this.show();
|
||||
|
||||
if (canShare) {
|
||||
this.$shareBtn.click();
|
||||
} else {
|
||||
this.$downloadBtn.click();
|
||||
}
|
||||
}).catch(r => console.error(r));
|
||||
}
|
||||
|
||||
_downloadFilesIndividually(files) {
|
||||
let tmpBtn = document.createElement("a");
|
||||
for (let i=0; i<files.length; i++) {
|
||||
tmpBtn.download = files[i].name;
|
||||
tmpBtn.href = URL.createObjectURL(files[i]);
|
||||
tmpBtn.click();
|
||||
}
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.$shareOrDownloadBtn.removeAttribute('href');
|
||||
this.$shareOrDownloadBtn.removeAttribute('download');
|
||||
this.$shareBtn.setAttribute('hidden', '');
|
||||
this.$previewBox.innerHTML = '';
|
||||
super.hide();
|
||||
this._dequeueFile();
|
||||
|
@ -664,11 +699,6 @@ class ReceiveRequestDialog extends ReceiveDialog {
|
|||
constructor() {
|
||||
super('receive-request-dialog');
|
||||
|
||||
this.$requestingPeerDisplayNameNode = this.$el.querySelector('#requesting-peer-display-name');
|
||||
this.$fileStemNode = this.$el.querySelector('#file-stem');
|
||||
this.$fileExtensionNode = this.$el.querySelector('#file-extension');
|
||||
this.$fileOtherNode = this.$el.querySelector('#file-other');
|
||||
|
||||
this.$acceptRequestBtn = this.$el.querySelector('#accept-request');
|
||||
this.$declineRequestBtn = this.$el.querySelector('#decline-request');
|
||||
this.$acceptRequestBtn.addEventListener('click', _ => this._respondToFileTransferRequest(true));
|
||||
|
@ -700,32 +730,18 @@ class ReceiveRequestDialog extends ReceiveDialog {
|
|||
_showRequestDialog(request, peerId) {
|
||||
this.correspondingPeerId = peerId;
|
||||
|
||||
this.$requestingPeerDisplayNameNode.innerText = $(peerId).ui._displayName();
|
||||
|
||||
const fileName = request.header[0].name;
|
||||
const fileNameSplit = fileName.split('.');
|
||||
const fileExtension = '.' + fileNameSplit[fileNameSplit.length - 1];
|
||||
this.$fileStemNode.innerText = fileName.substring(0, fileName.length - fileExtension.length);
|
||||
this.$fileExtensionNode.innerText = fileExtension
|
||||
|
||||
if (request.header.length >= 2) {
|
||||
let fileOtherText = ` and ${request.header.length - 1} other `;
|
||||
fileOtherText += request.imagesOnly ? 'image' : 'file';
|
||||
if (request.header.length > 2) fileOtherText += "s";
|
||||
this.$fileOtherNode.innerText = fileOtherText;
|
||||
}
|
||||
|
||||
this.$fileSizeNode.innerText = this._formatFileSize(request.totalSize);
|
||||
const displayName = $(peerId).ui._displayName();
|
||||
this._parseFileData(displayName, request.header, request.imagesOnly, request.totalSize);
|
||||
|
||||
if (request.thumbnailDataUrl?.substring(0, 22) === "data:image/jpeg;base64") {
|
||||
let element = document.createElement('img');
|
||||
element.src = request.thumbnailDataUrl;
|
||||
element.classList.add('element-preview');
|
||||
|
||||
this.$previewBox.appendChild(element)
|
||||
}
|
||||
|
||||
document.title = 'File Transfer Requested - PairDrop';
|
||||
this.$receiveTitle.innerText = `${request.imagesOnly ? 'Image' : 'File'} Transfer Request`
|
||||
|
||||
document.title = `${request.imagesOnly ? 'Image' : 'File'} Transfer Requested - PairDrop`;
|
||||
document.changeFavicon("images/favicon-96x96-notification.png");
|
||||
this.show();
|
||||
}
|
||||
|
@ -1000,7 +1016,7 @@ class SendTextDialog extends Dialog {
|
|||
super('send-text-dialog');
|
||||
Events.on('text-recipient', e => this._onRecipient(e.detail.peerId, e.detail.deviceName));
|
||||
this.$text = this.$el.querySelector('#text-input');
|
||||
this.$peerDisplayName = this.$el.querySelector('#text-send-peer-display-name');
|
||||
this.$peerDisplayName = this.$el.querySelector('.display-name');
|
||||
this.$form = this.$el.querySelector('form');
|
||||
this.$submit = this.$el.querySelector('button[type="submit"]');
|
||||
this.$form.addEventListener('submit', e => this._onSubmit(e));
|
||||
|
@ -1073,7 +1089,7 @@ class ReceiveTextDialog extends Dialog {
|
|||
|
||||
Events.on("keydown", e => this._onKeyDown(e));
|
||||
|
||||
this.$receiveTextPeerDisplayNameNode = this.$el.querySelector('#receive-text-peer-display-name');
|
||||
this.$displayNameNode = this.$el.querySelector('.display-name');
|
||||
this._receiveTextQueue = [];
|
||||
}
|
||||
|
||||
|
@ -1103,7 +1119,7 @@ class ReceiveTextDialog extends Dialog {
|
|||
}
|
||||
|
||||
_showReceiveTextDialog(text, peerId) {
|
||||
this.$receiveTextPeerDisplayNameNode.innerText = $(peerId).ui._displayName();
|
||||
this.$displayNameNode.innerText = $(peerId).ui._displayName();
|
||||
|
||||
if (isURL(text)) {
|
||||
const $a = document.createElement('a');
|
||||
|
@ -1199,7 +1215,7 @@ class Base64ZipDialog extends Dialog {
|
|||
}
|
||||
|
||||
_setPasteBtnToProcessing() {
|
||||
this.$pasteBtn.pointerEvents = "none";
|
||||
this.$pasteBtn.style.pointerEvents = "none";
|
||||
this.$pasteBtn.innerText = "Processing...";
|
||||
}
|
||||
|
||||
|
|
|
@ -590,7 +590,7 @@ x-dialog x-background {
|
|||
z-index: 10;
|
||||
transition: opacity 300ms;
|
||||
will-change: opacity;
|
||||
padding: 35px;
|
||||
padding: 15px;
|
||||
overflow: overlay;
|
||||
}
|
||||
|
||||
|
@ -601,19 +601,20 @@ x-dialog x-paper {
|
|||
padding: 16px 24px;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
transition: transform 300ms;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
#pair-device-dialog x-paper {
|
||||
position: absolute;
|
||||
top: max(50%, 350px);
|
||||
height: 650px;
|
||||
margin-top: -325px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
position: absolute;
|
||||
top: max(50%, 350px);
|
||||
margin-top: -328.5px;
|
||||
width: calc(100vw - 20px);
|
||||
height: 625px;
|
||||
}
|
||||
|
||||
x-dialog:not([show]) {
|
||||
|
@ -628,12 +629,6 @@ x-dialog:not([show]) x-background {
|
|||
opacity: 0;
|
||||
}
|
||||
|
||||
x-dialog .row-reverse>.button {
|
||||
margin-top: 0;
|
||||
margin-bottom: -16px;
|
||||
width: 50%;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
x-dialog a {
|
||||
color: var(--primary-color);
|
||||
|
@ -672,7 +667,7 @@ x-dialog .font-subheading {
|
|||
}
|
||||
|
||||
#key-input-container>input:nth-of-type(4) {
|
||||
margin-left: 18px;
|
||||
margin-left: 5%;
|
||||
}
|
||||
|
||||
#room-key {
|
||||
|
@ -684,16 +679,11 @@ x-dialog .font-subheading {
|
|||
}
|
||||
|
||||
#room-key-qr-code {
|
||||
padding: inherit;
|
||||
margin: auto;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
margin: 16px;
|
||||
}
|
||||
|
||||
#pair-device-dialog hr {
|
||||
margin-top: 40px;
|
||||
margin-bottom: 40px;
|
||||
width: 100%;
|
||||
margin: 40px -24px;
|
||||
}
|
||||
|
||||
#pair-device-dialog x-background {
|
||||
|
@ -707,29 +697,24 @@ x-dialog .row {
|
|||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
x-dialog h2 {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
#receive-request-dialog h2,
|
||||
#receive-file-dialog h2 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
x-dialog .row-reverse {
|
||||
margin: 40px -24px 0;
|
||||
/* button row*/
|
||||
x-paper > div:last-child {
|
||||
margin: auto -24px -15px;
|
||||
border-top: solid 2.5px var(--border-color);
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.separator {
|
||||
border: solid 1.25px var(--border-color);
|
||||
margin-bottom: -16px;
|
||||
x-paper > div:last-child > .button {
|
||||
height: 100%;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
x-paper > div:last-child > .button:not(:last-child) {
|
||||
border-left: solid 2.5px var(--border-color);
|
||||
}
|
||||
|
||||
.file-description {
|
||||
word-break: break-word;
|
||||
width: 80%;
|
||||
margin: auto;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.file-description .row {
|
||||
|
@ -741,26 +726,26 @@ x-dialog .row-reverse {
|
|||
word-break: normal;
|
||||
}
|
||||
|
||||
#file-name {
|
||||
.file-name {
|
||||
font-style: italic;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
#file-stem {
|
||||
max-width: 80%;
|
||||
.file-stem {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-all;
|
||||
max-height: 20px;
|
||||
}
|
||||
|
||||
.file-size{
|
||||
margin-bottom: 30px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Send Text Dialog */
|
||||
|
||||
x-dialog .dialog-subheader {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
#text-input {
|
||||
min-height: 120px;
|
||||
min-height: 200px;
|
||||
margin: 14px auto;
|
||||
}
|
||||
|
||||
/* Receive Text Dialog */
|
||||
|
@ -768,14 +753,14 @@ x-dialog .row-reverse {
|
|||
#receive-text-dialog #text {
|
||||
width: 100%;
|
||||
word-break: break-all;
|
||||
max-height: 300px;
|
||||
max-height: calc(100vh - 393px);
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
-webkit-user-select: all;
|
||||
-moz-user-select: all;
|
||||
user-select: all;
|
||||
white-space: pre-wrap;
|
||||
margin-top:36px;
|
||||
padding: 15px 0;
|
||||
}
|
||||
|
||||
#receive-text-dialog #text a {
|
||||
|
@ -794,11 +779,7 @@ x-dialog .row-reverse {
|
|||
|
||||
.row-separator {
|
||||
border-bottom: solid 2.5px var(--border-color);
|
||||
margin: auto -25px;
|
||||
}
|
||||
|
||||
#receive-text-description-container {
|
||||
margin-bottom: 25px;
|
||||
margin: auto -24px;
|
||||
}
|
||||
|
||||
#base64-paste-btn {
|
||||
|
@ -826,7 +807,6 @@ x-dialog .row-reverse {
|
|||
padding: 2px 16px 0;
|
||||
box-sizing: border-box;
|
||||
min-height: 36px;
|
||||
min-width: 100px;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
font-weight: 700;
|
||||
|
@ -837,6 +817,7 @@ x-dialog .row-reverse {
|
|||
user-select: none;
|
||||
background: inherit;
|
||||
color: var(--primary-color);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.button[disabled] {
|
||||
|
@ -874,7 +855,7 @@ x-dialog .row-reverse {
|
|||
opacity: 0.1;
|
||||
}
|
||||
|
||||
#cancel-paste-mode-btn {
|
||||
#cancel-paste-mode {
|
||||
z-index: 2;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
@ -901,7 +882,6 @@ button::-moz-focus-inner {
|
|||
|
||||
|
||||
/* Icon Button */
|
||||
|
||||
.icon-button {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
|
@ -911,10 +891,7 @@ button::-moz-focus-inner {
|
|||
border-radius: 50%;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Text Input */
|
||||
|
||||
.textarea {
|
||||
box-sizing: border-box;
|
||||
border: none;
|
||||
|
@ -928,9 +905,8 @@ button::-moz-focus-inner {
|
|||
display: block;
|
||||
overflow: auto;
|
||||
resize: none;
|
||||
min-height: 40px;
|
||||
line-height: 16px;
|
||||
max-height: 300px;
|
||||
max-height: calc(100vh - 254px);
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
|
@ -1120,6 +1096,14 @@ x-peers:empty~x-instructions {
|
|||
}
|
||||
|
||||
/* Responsive Styles */
|
||||
@media screen and (max-width: 360px) {
|
||||
x-dialog x-paper {
|
||||
padding: 15px;
|
||||
}
|
||||
x-paper > div:last-child {
|
||||
margin: auto -15px -15px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-height: 800px) {
|
||||
#websocket-fallback {
|
||||
|
@ -1192,7 +1176,9 @@ x-dialog x-paper {
|
|||
display: none;
|
||||
}
|
||||
|
||||
.element-preview {
|
||||
.file-preview > img,
|
||||
.file-preview > audio,
|
||||
.file-preview > video {
|
||||
max-width: 100%;
|
||||
max-height: 40vh;
|
||||
margin: auto;
|
||||
|
|
Loading…
Reference in a new issue