diff --git a/public/index.html b/public/index.html index 59a257a..4171562 100644 --- a/public/index.html +++ b/public/index.html @@ -69,7 +69,7 @@ - +
@@ -106,18 +106,17 @@
Input this key on another device
or scan the QR-Code.

- - - - - - + + + + + +
Enter key from another device to continue.
-
+
-
- Cancel +
@@ -130,9 +129,9 @@

Unpair Devices

Are you sure to unpair all devices?
-
+
- Cancel +
@@ -142,25 +141,23 @@ -

PairDrop

-
+

+
- + would like to share
-
- - +
+ +
-
- +
+
-
-
+
-
@@ -170,13 +167,23 @@ -

-
-
+

+
+
+ + has sent +
+
+ + +
+
+
+
-
- -
+
+ +
@@ -187,16 +194,16 @@
-

PairDrop

-
+

Send Message

+
Send a Message to - +
+
-
+
-
- Cancel +
@@ -206,16 +213,15 @@ -

PairDrop - Message Received

-
- - sent the following message: +

Message Received

+
+ + has sent:
-
+
-
diff --git a/public/scripts/network.js b/public/scripts/network.js index be1389f..e2ad3cc 100644 --- a/public/scripts/network.js +++ b/public/scripts/network.js @@ -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; } diff --git a/public/scripts/ui.js b/public/scripts/ui.js index 9b22ca1..4f2c167 100644 --- a/public/scripts/ui.js +++ b/public/scripts/ui.js @@ -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 { 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 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..."; } diff --git a/public/styles.css b/public/styles.css index d3c05ac..c88d9d5 100644 --- a/public/styles.css +++ b/public/styles.css @@ -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; diff --git a/public_included_ws_fallback/index.html b/public_included_ws_fallback/index.html index 8227434..7591cf1 100644 --- a/public_included_ws_fallback/index.html +++ b/public_included_ws_fallback/index.html @@ -69,7 +69,7 @@ - +
@@ -109,18 +109,17 @@
Input this key on another device
or scan the QR-Code.

- - - - - - + + + + + +
Enter key from another device to continue.
-
+
-
- Cancel +
@@ -133,9 +132,9 @@

Unpair Devices

Are you sure to unpair all devices?
-
+
- Cancel +
@@ -145,25 +144,23 @@ -

PairDrop

-
+

+
- + would like to share
-
- - +
+ +
-
- +
+
-
-
+
-
@@ -173,13 +170,23 @@ -

-
-
+

+
+
+ + has sent +
+
+ + +
+
+
+
-
- -
+
+ +
@@ -190,16 +197,16 @@ -

PairDrop

-
+

Send Message

+
Send a Message to - +
+
-
+
-
- Cancel +
@@ -209,16 +216,15 @@ -

PairDrop - Message Received

-
- - sent the following message: +

Message Received

+
+ + has sent:
-
+
-
diff --git a/public_included_ws_fallback/scripts/network.js b/public_included_ws_fallback/scripts/network.js index f739465..c5f3b2f 100644 --- a/public_included_ws_fallback/scripts/network.js +++ b/public_included_ws_fallback/scripts/network.js @@ -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; } diff --git a/public_included_ws_fallback/scripts/ui.js b/public_included_ws_fallback/scripts/ui.js index 8fca2ba..0df8e81 100644 --- a/public_included_ws_fallback/scripts/ui.js +++ b/public_included_ws_fallback/scripts/ui.js @@ -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 { 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 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..."; } diff --git a/public_included_ws_fallback/styles.css b/public_included_ws_fallback/styles.css index f153398..0026356 100644 --- a/public_included_ws_fallback/styles.css +++ b/public_included_ws_fallback/styles.css @@ -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;