Save
@@ -208,9 +240,12 @@
Snapdrop
-
+
+
+
+
-
+
diff --git a/client/scripts/network.js b/client/scripts/network.js
index eb599014..7e216fc4 100644
--- a/client/scripts/network.js
+++ b/client/scripts/network.js
@@ -26,23 +26,23 @@ class ServerConnection {
msg = JSON.parse(msg);
console.log('WS:', msg);
switch (msg.type) {
- case 'peers':
- Events.fire('peers', msg.peers);
+ case Events.PEERS:
+ Events.fire(Events.PEERS, msg.peers);
break;
- case 'peer-joined':
- Events.fire('peer-joined', msg.peer);
+ case Events.PEER_JOINED:
+ Events.fire(Events.PEER_JOINED, msg.peer);
break;
- case 'peer-left':
- Events.fire('peer-left', msg.peerId);
+ case Events.PEER_LEFT:
+ Events.fire(Events.PEER_LEFT, msg.peerId);
break;
- case 'signal':
- Events.fire('signal', msg);
+ case Events.SIGNAL:
+ Events.fire(Events.SIGNAL, msg);
break;
- case 'ping':
- this.send({ type: 'pong' });
+ case Events.DISPLAY_NAME:
+ Events.fire(Events.DISPLAY_NAME, msg);
break;
- case 'display-name':
- Events.fire('display-name', msg);
+ case Events.PING:
+ this.send({ type: Events.PONG });
break;
default:
console.error('WS: unkown message type', msg);
@@ -70,7 +70,7 @@ class ServerConnection {
_onDisconnect() {
console.log('WS: server disconnected');
- Events.fire('notify-user', 'Connection lost. Retry in 5 seconds...');
+ Events.fire(Events.NOTIFY_USER, 'Connection lost. Retry in 5 seconds...');
clearTimeout(this._reconnectTimer);
this._reconnectTimer = setTimeout(_ => this._connect(), 5000);
}
@@ -91,11 +91,40 @@ class ServerConnection {
class Peer {
+ // return uuid of form xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
+ static uuid() {
+ let uuid = '',
+ ii;
+ for (ii = 0; ii < 32; ii += 1) {
+ switch (ii) {
+ case 8:
+ case 20:
+ uuid += '-';
+ uuid += (Math.random() * 16 | 0).toString(16);
+ break;
+ case 12:
+ uuid += '-';
+ uuid += '4';
+ break;
+ case 16:
+ uuid += '-';
+ uuid += (Math.random() * 4 | 8).toString(16);
+ break;
+ default:
+ uuid += (Math.random() * 16 | 0).toString(16);
+ }
+ }
+ return uuid;
+ };
+
constructor(serverConnection, peerId) {
this._server = serverConnection;
this._peerId = peerId;
this._filesQueue = [];
- this._busy = false;
+ this._files = {}
+ this._sendingSimultaneos = 0
+ this._totalBytesAccepted = 0;
+ this._totalBytesReceived = 0;
}
sendJSON(message) {
@@ -104,47 +133,64 @@ class Peer {
sendFiles(files) {
for (let i = 0; i < files.length; i++) {
- this._filesQueue.push(files[i]);
+ this._sendFile(files[i])
}
- if (this._busy) return;
- this._dequeueFile();
- }
-
- _dequeueFile() {
- if (!this._filesQueue.length) return;
- this._busy = true;
- const file = this._filesQueue.shift();
- this._sendFile(file);
}
_sendFile(file) {
+ file.uuid = Peer.uuid();
this.sendJSON({
type: 'header',
name: file.name,
mime: file.type,
- size: file.size
+ size: file.size,
+ uuid: file.uuid,
});
- this._chunker = new FileChunker(file,
- chunk => this._send(chunk),
- offset => this._onPartitionEnd(offset));
- this._chunker.nextPartition();
+ this._files[file.uuid] = {
+ header: file,
+ chunker: new FileChunker(
+ file,
+ chunk => {
+ let uuidBytes = new TextEncoder().encode(file.uuid)
+ let fileBytes = new Uint8Array(chunk);
+ let bytes = new Uint8Array(uuidBytes.byteLength + fileBytes.byteLength);
+ bytes.set(uuidBytes);
+ bytes.set(fileBytes, uuidBytes.byteLength);
+ this._send(bytes)
+ },
+ offset => this._onPartitionEnd(file.uuid, offset)
+ )
+ }
+ }
+
+ static MAX_SIMULTANEOUS_REQUEST = 5
+
+ _dequeueFile() {
+ if (
+ this._sendingSimultaneos >= Peer.MAX_SIMULTANEOUS_REQUEST |
+ !this._filesQueue.length
+ ) return;
+ this._sendingSimultaneos++
+ const uuid = this._filesQueue.shift();
+ this._sendNextPartition(uuid);
}
- _onPartitionEnd(offset) {
- this.sendJSON({ type: 'partition', offset: offset });
+ _onPartitionEnd(uuid, offset) {
+ this.sendJSON({ type: 'partition', uuid, offset });
}
- _onReceivedPartitionEnd(offset) {
- this.sendJSON({ type: 'partition-received', offset: offset });
+ _onReceivedPartitionEnd(uuid, offset) {
+ this.sendJSON({ type: 'partition-received', uuid, offset });
}
- _sendNextPartition() {
- if (!this._chunker || this._chunker.isFileEnd()) return;
- this._chunker.nextPartition();
+ _sendNextPartition(uuid) {
+ let chunker = this._files[uuid].chunker
+ if (!chunker || chunker.isFileEnd()) return;
+ chunker.nextPartition();
}
- _sendProgress(progress) {
- this.sendJSON({ type: 'progress', progress: progress });
+ _sendProgress(totalProgress, uuid, fileProgress, ) {
+ this.sendJSON({ type: 'progress', uuid, totalProgress, fileProgress });
}
_onMessage(message) {
@@ -159,13 +205,22 @@ class Peer {
this._onFileHeader(message);
break;
case 'partition':
- this._onReceivedPartitionEnd(message);
+ this._onReceivedPartitionEnd(message.uuid, message.offset);
break;
+ case Events.FILE_DENY:
+ this._dequeueFile();
+ break;
+ case Events.FILE_ACCEPT:
+ Events.fire(Events.FILE_ACCEPT, this._files[message.uuid].header)
+ this._filesQueue.push(message.uuid);
+ this._dequeueFile();
+ break
case 'partition-received':
- this._sendNextPartition();
+ this._sendNextPartition(message.uuid);
break;
case 'progress':
- this._onDownloadProgress(message.progress);
+ const {totalProgress, uuid, fileProgress} = message
+ this._onDownloadProgress(totalProgress, uuid, fileProgress);
break;
case 'transfer-complete':
this._onTransferCompleted();
@@ -177,42 +232,61 @@ class Peer {
}
_onFileHeader(header) {
- this._lastProgress = 0;
- this._digester = new FileDigester({
- name: header.name,
- mime: header.mime,
- size: header.size
- }, file => this._onFileReceived(file));
+ this._files[header.uuid] = {
+ header,
+ lastProgress: 0,
+ digester: new FileDigester({
+ name: header.name,
+ mime: header.mime,
+ size: header.size,
+ uuid: header.uuid
+ }, file => this._onFileReceived(header.uuid, file))
+ }
+ Events.fire(Events.FILE_REQUEST, {
+ file: header,
+ accept: () => {
+ this._totalBytesAccepted += header.size
+ this.sendJSON({type: Events.FILE_ACCEPT, uuid: header.uuid})
+ },
+ deny: () => this.sendJSON({type: Events.FILE_DENY, uuid: header.uuid})
+ })
}
- _onChunkReceived(chunk) {
- if(!chunk.byteLength) return;
+ _onChunkReceived(data) {
+ if(!data.byteLength) return;
+
+ let uuid = new TextDecoder().decode(data.slice(0,36));
+ var chunk = data.slice(36);
- this._digester.unchunk(chunk);
- const progress = this._digester.progress;
- this._onDownloadProgress(progress);
+ let file = this._files[uuid];
+ file.digester.unchunk(chunk);
+
+ this._totalBytesReceived += chunk.byteLength
+ const totalProgress = this._totalBytesReceived / this._totalBytesAccepted
+ const fileProgress = file.digester.progress;
+ this._onDownloadProgress(totalProgress, uuid, fileProgress);
// occasionally notify sender about our progress
- if (progress - this._lastProgress < 0.01) return;
- this._lastProgress = progress;
- this._sendProgress(progress);
+ if (fileProgress - file.lastProgress < 0.05) return;
+ file.lastProgress = fileProgress;
+
+ this._sendProgress(totalProgress, file.header.uuid, file.digester.progress);
}
- _onDownloadProgress(progress) {
- Events.fire('file-progress', { sender: this._peerId, progress: progress });
+ _onDownloadProgress(totalProgress, uuid, fileProgress) {
+ Events.fire(Events.FILE_PROGRESS, { sender: this._peerId, uuid, totalProgress, fileProgress });
}
- _onFileReceived(proxyFile) {
- Events.fire('file-received', proxyFile);
- this.sendJSON({ type: 'transfer-complete' });
+ _onFileReceived(uuid, proxyFile) {
+ Events.fire(Events.FILE_RECEIVED, { file: proxyFile, uuid });
+ this.sendJSON({ type: 'transfer-complete', uuid });
}
_onTransferCompleted() {
- this._onDownloadProgress(1);
+ this._sendingSimultaneos--
this._reader = null;
- this._busy = false;
this._dequeueFile();
- Events.fire('notify-user', 'File transfer completed.');
+ Events.fire(Events.NOTIFY_USER, 'File transfer completed.');
}
sendText(text) {
@@ -222,7 +296,7 @@ class Peer {
_onTextReceived(message) {
const escaped = decodeURIComponent(escape(atob(message.text)));
- Events.fire('text-received', { text: escaped, sender: this._peerId });
+ Events.fire(Events.TEXT_RECEIVED, { text: escaped, sender: this._peerId });
}
}
@@ -339,7 +413,7 @@ class RTCPeer extends Peer {
}
_sendSignal(signal) {
- signal.type = 'signal';
+ signal.type = Events.SIGNAL;
signal.to = this._peerId;
this._server.send(signal);
}
@@ -364,11 +438,11 @@ class PeersManager {
constructor(serverConnection) {
this.peers = {};
this._server = serverConnection;
- Events.on('signal', e => this._onMessage(e.detail));
- Events.on('peers', e => this._onPeers(e.detail));
- Events.on('files-selected', e => this._onFilesSelected(e.detail));
- Events.on('send-text', e => this._onSendText(e.detail));
- Events.on('peer-left', e => this._onPeerLeft(e.detail));
+ Events.on(Events.SIGNAL, e => this._onMessage(e.detail));
+ Events.on(Events.PEERS, e => this._onPeers(e.detail));
+ Events.on(Events.FILES_SELECTED, e => this._onFilesSelected(e.detail));
+ Events.on(Events.SEND_TEXT, e => this._onSendText(e.detail));
+ Events.on(Events.PEER_LEFT, e => this._onPeerLeft(e.detail));
}
_onMessage(message) {
@@ -423,7 +497,7 @@ class WSPeer {
class FileChunker {
constructor(file, onChunk, onPartitionEnd) {
- this._chunkSize = 64000; // 64 KB
+ this._chunkSize = 128000; // 128 KB
this._maxPartitionSize = 1e6; // 1 MB
this._offset = 0;
this._partitionSize = 0;
@@ -431,7 +505,7 @@ class FileChunker {
this._onChunk = onChunk;
this._onPartitionEnd = onPartitionEnd;
this._reader = new FileReader();
- this._reader.addEventListener('load', e => this._onChunkRead(e.target.result));
+ this._reader.addEventListener(Events.LOAD, e => this._onChunkRead(e.target.result));
}
nextPartition() {
@@ -512,6 +586,30 @@ class Events {
static on(type, callback) {
return window.addEventListener(type, callback, false);
}
+
+ static LOAD = 'load'
+
+ static PEERS = 'peers'
+ static PEER_JOINED = 'peer-joined'
+ static PEER_LEFT = 'peer-left'
+ static PING = 'ping'
+ static PONG = 'pong'
+
+ static SIGNAL = 'signal'
+ static DISPLAY_NAME = 'display-name'
+ static NOTIFY_USER = 'notify-user'
+
+ static FILES_SELECTED = 'files-selected'
+ static FILE_PROGRESS = 'file-progress'
+ static FILE_RECEIVED = 'file-received'
+ static FILE_REQUEST = 'file-request'
+ static FILE_ACCEPT = 'file-accept'
+ static FILE_DENY = 'file-deny'
+
+ static PASTE = 'paste'
+ static SEND_TEXT = 'send-text'
+ static TEXT_RECEIVED = 'text-received'
+ static TEXT_RECIPIENT = 'text-recipient'
}
@@ -520,4 +618,4 @@ RTCPeer.config = {
'iceServers': [{
urls: 'stun:stun.l.google.com:19302'
}]
-}
+}
\ No newline at end of file
diff --git a/client/scripts/ui.js b/client/scripts/ui.js
index 0c159841..234dca58 100644
--- a/client/scripts/ui.js
+++ b/client/scripts/ui.js
@@ -6,21 +6,66 @@ window.isProductionEnvironment = !window.location.host.startsWith('localhost');
window.iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
// set display name
-Events.on('display-name', e => {
+Events.on(Events.DISPLAY_NAME, e => {
const me = e.detail.message;
const $displayName = $('displayName')
$displayName.textContent = 'You are known as ' + me.displayName;
$displayName.title = me.deviceName;
});
+Number.prototype.bytesToHumanFileSize = function () {
+ let bytes = this;
+ if (bytes >= 1e12) {
+ return (Math.round(bytes / 1e11) / 10) + ' TB';
+ } else if (bytes >= 1e9) {
+ return (Math.round(bytes / 1e8) / 10) + ' GB';
+ } else if (bytes >= 1e6) {
+ return (Math.round(bytes / 1e5) / 10) + ' MB';
+ } else if (bytes > 1000) {
+ return Math.round(bytes / 1000) + ' KB';
+ } else {
+ return bytes + ' Bytes';
+ }
+}
+
+class PreviewView {
+
+ static Elements = {
+ image: 'img',
+ audio: 'audio',
+ video: 'video'
+ }
+
+ static Keys = Object.keys(PreviewView.Elements)
+
+ constructor(file, parent) {
+ let mine = file.mime.split('/')[0]
+ this.url = URL.createObjectURL(file.blob);
+ this.isPlayable = PreviewView.Keys.indexOf(mine) !== -1
+ this.$parent = parent
+
+ if(this.isPlayable){
+ console.log('the file is able to preview');
+ let element = document.createElement(PreviewView.Elements[mine]);
+ element.src = this.url;
+ element.controls = true;
+ element.classList = 'element-preview'
+
+ this.$parent.appendChild(element)
+ }
+
+ }
+
+}
+
class PeersUI {
constructor() {
- Events.on('peer-joined', e => this._onPeerJoined(e.detail));
- Events.on('peer-left', e => this._onPeerLeft(e.detail));
- Events.on('peers', e => this._onPeers(e.detail));
- Events.on('file-progress', e => this._onFileProgress(e.detail));
- Events.on('paste', e => this._onPaste(e));
+ Events.on(Events.PEER_JOINED, e => this._onPeerJoined(e.detail));
+ Events.on(Events.PEER_LEFT, e => this._onPeerLeft(e.detail));
+ Events.on(Events.PEERS, e => this._onPeers(e.detail));
+ Events.on(Events.FILE_PROGRESS, e => this._onFileProgress(e.detail));
+ Events.on(Events.PASTE, e => this._onPaste(e));
}
_onPeerJoined(peer) {
@@ -62,7 +107,7 @@ class PeersUI {
// "image data has been pasted, click the client to which to send it"
// not implemented
if (files.length > 0 && peers.length === 1) {
- Events.fire('files-selected', {
+ Events.fire(Events.FILES_SELECTED, {
files: files,
to: $$('x-peer').id
});
@@ -143,7 +188,7 @@ class PeerUI {
_onFilesSelected(e) {
const $input = e.target;
const files = $input.files;
- Events.fire('files-selected', {
+ Events.fire(Events.FILES_SELECTED, {
files: files,
to: this._peer.id
});
@@ -170,7 +215,7 @@ class PeerUI {
_onDrop(e) {
e.preventDefault();
const files = e.dataTransfer.files;
- Events.fire('files-selected', {
+ Events.fire(Events.FILES_SELECTED, {
files: files,
to: this._peer.id
});
@@ -187,7 +232,7 @@ class PeerUI {
_onRightClick(e) {
e.preventDefault();
- Events.fire('text-recipient', this._peer.id);
+ Events.fire(Events.TEXT_RECIPIENT, this._peer.id);
}
_onTouchStart(e) {
@@ -200,7 +245,7 @@ class PeerUI {
clearTimeout(this._touchTimer);
} else { // this was a long tap
if (e) e.preventDefault();
- Events.fire('text-recipient', this._peer.id);
+ Events.fire(Events.TEXT_RECIPIENT, this._peer.id);
}
}
}
@@ -225,15 +270,71 @@ class Dialog {
}
}
+class RequestDialog extends Dialog {
+
+ constructor() {
+ super('requestDialog');
+ Events.on(Events.FILE_REQUEST, e => {
+ window.blop.play();
+ this._acceptQueue.push(e.detail)
+ this._proccess()
+ });
+ this._acceptQueue = []
+ this._showing = false
+ this.$el.querySelector('.accept').addEventListener('click', () => this._accept())
+ this.$el.querySelector('.deny').addEventListener('click', () => this._deny())
+ }
+
+ _proccess () {
+ if(this._showing || this._acceptQueue.length == 0) return;
+ this.lastDetail = this._acceptQueue.shift()
+ if(this._autoDownload()) {
+ this._accept();
+ } else {
+ this._ask(this.lastDetail.file);
+ }
+ }
+
+ _ask(file) {
+ this.$el.querySelector('.fileName').textContent = file.name;
+ this.$el.querySelector('.fileSize').textContent = file.size.bytesToHumanFileSize();
+ this.show()
+ this._showing = true
+ }
+
+ _accept() {
+ this.lastDetail && this.lastDetail.accept();
+ this._hide()
+ }
+
+ _deny() {
+ this.lastDetail && this.lastDetail.deny();
+ this._hide()
+ }
+
+ _hide() {
+ this._showing = false
+ this.lastDetail = null
+ this.hide()
+ this._proccess()
+ }
+
+ _autoDownload(){
+ return !this.$el.querySelector('.autoDownload').checked
+ }
+
+}
+
class ReceiveDialog extends Dialog {
constructor() {
super('receiveDialog');
- Events.on('file-received', e => {
- this._nextFile(e.detail);
+ Events.on(Events.FILE_RECEIVED, e => {
+ this._nextFile(e.detail.file);
window.blop.play();
});
this._filesQueue = [];
+ this.$previewBox = this.$el.querySelector('.preview')
}
_nextFile(nextFile) {
@@ -257,23 +358,23 @@ class ReceiveDialog extends Dialog {
}
_displayFile(file) {
+ const preview = new PreviewView(file, this.$previewBox);
+
const $a = this.$el.querySelector('#download');
- const url = URL.createObjectURL(file.blob);
- $a.href = url;
+ $a.href = preview.url;
$a.download = file.name;
if(this._autoDownload()){
$a.click()
return
}
- if(file.mime.split('/')[0] === 'image'){
- console.log('the file is image');
- this.$el.querySelector('.preview').style.visibility = 'inherit';
- this.$el.querySelector("#img-preview").src = url;
+
+ if(preview.isPlayable) {
+ this.$previewBox.style.visibility = 'inherit';
}
- this.$el.querySelector('#fileName').textContent = file.name;
- this.$el.querySelector('#fileSize').textContent = this._formatFileSize(file.size);
+ this.$el.querySelector('.fileName').textContent = file.name;
+ this.$el.querySelector('.fileSize').textContent = file.size.bytesToHumanFileSize();
this.show();
if (window.isDownloadSupported) return;
@@ -284,28 +385,16 @@ class ReceiveDialog extends Dialog {
reader.readAsDataURL(file.blob);
}
- _formatFileSize(bytes) {
- if (bytes >= 1e9) {
- return (Math.round(bytes / 1e8) / 10) + ' GB';
- } else if (bytes >= 1e6) {
- return (Math.round(bytes / 1e5) / 10) + ' MB';
- } else if (bytes > 1000) {
- return Math.round(bytes / 1000) + ' KB';
- } else {
- return bytes + ' Bytes';
- }
- }
-
hide() {
- this.$el.querySelector('.preview').style.visibility = 'hidden';
- this.$el.querySelector("#img-preview").src = "";
+ this.$previewBox.style.visibility = 'hidden';
+ this.$previewBox.innerHTML = '';
super.hide();
this._dequeueFile();
}
_autoDownload(){
- return !this.$el.querySelector('#autoDownload').checked
+ return !this.$el.querySelector('.autoDownload').checked
}
}
@@ -313,7 +402,7 @@ class ReceiveDialog extends Dialog {
class SendTextDialog extends Dialog {
constructor() {
super('sendTextDialog');
- Events.on('text-recipient', e => this._onRecipient(e.detail))
+ Events.on(Events.TEXT_RECIPIENT, e => this._onRecipient(e.detail))
this.$text = this.$el.querySelector('#textInput');
const button = this.$el.querySelector('form');
button.addEventListener('submit', e => this._send(e));
@@ -341,7 +430,7 @@ class SendTextDialog extends Dialog {
_send(e) {
e.preventDefault();
- Events.fire('send-text', {
+ Events.fire(Events.SEND_TEXT, {
to: this._recipient,
text: this.$text.innerText
});
@@ -351,7 +440,7 @@ class SendTextDialog extends Dialog {
class ReceiveTextDialog extends Dialog {
constructor() {
super('receiveTextDialog');
- Events.on('text-received', e => this._onText(e.detail))
+ Events.on(Events.TEXT_RECEIVED, e => this._onText(e.detail))
this.$text = this.$el.querySelector('#text');
const $copy = this.$el.querySelector('#copy');
copy.addEventListener('click', _ => this._onCopy());
@@ -375,14 +464,14 @@ class ReceiveTextDialog extends Dialog {
async _onCopy() {
await navigator.clipboard.writeText(this.$text.textContent);
- Events.fire('notify-user', 'Copied to clipboard');
+ Events.fire(Events.NOTIFY_USER, 'Copied to clipboard');
}
}
class Toast extends Dialog {
constructor() {
super('toast');
- Events.on('notify-user', e => this._onNotfiy(e.detail));
+ Events.on(Events.NOTIFY_USER, e => this._onNotfiy(e.detail));
}
_onNotfiy(message) {
@@ -405,14 +494,14 @@ class Notifications {
this.$button.removeAttribute('hidden');
this.$button.addEventListener('click', e => this._requestPermission());
}
- Events.on('text-received', e => this._messageNotification(e.detail.text));
- Events.on('file-received', e => this._downloadNotification(e.detail.name));
+ Events.on(Events.TEXT_RECEIVED, e => this._messageNotification(e.detail.text));
+ Events.on(Events.FILE_RECEIVED, e => this._downloadNotification(e.detail.file.name));
}
_requestPermission() {
Notification.requestPermission(permission => {
if (permission !== 'granted') {
- Events.fire('notify-user', Notifications.PERMISSION_ERROR || 'Error');
+ Events.fire(Events.NOTIFY_USER, Notifications.PERMISSION_ERROR || 'Error');
return;
}
this._notify('Even more snappy sharing!');
@@ -490,11 +579,11 @@ class NetworkStatusUI {
}
_showOfflineMessage() {
- Events.fire('notify-user', 'You are offline');
+ Events.fire(Events.NOTIFY_USER, 'You are offline');
}
_showOnlineMessage() {
- Events.fire('notify-user', 'You are back online');
+ Events.fire(Events.NOTIFY_USER, 'You are back online');
}
}
@@ -517,14 +606,86 @@ class WebShareTargetUI {
}
}
+class FilesListView {
+ constructor() {
+ this.$el = $('filesListView');
+ this.$list = this.$el.querySelector('ul');
+ this.$el.querySelectorAll('[toggle]').forEach(el => el.addEventListener('click', e => this.toggle()))
+ this.$autoFocus = this.$el.querySelector('[autofocus]');
+ this._visible = false
+ this._files = {}
+
+ Events.on(Events.FILE_ACCEPT, ({detail}) => this._addFile(detail))
+ Events.on(Events.FILE_REQUEST, ({detail}) => this._addFile(detail.file))
+ Events.on(Events.FILE_PROGRESS, ({detail}) => this._files[detail.uuid].setProgress(detail.fileProgress))
+ Events.on(Events.FILE_RECEIVED, ({detail}) => this._files[detail.uuid].preview(detail.file) );
+
+ }
+
+ _addFile(file) {
+ this._files[file.uuid] = new FilesListItemView(file).root(this.$list)
+ }
+
+ toggle() {
+ this._visible = !this._visible
+ if (this._visible) {
+ this.$el.setAttribute('show', 1);
+ } else {
+ this.$el.removeAttribute('show');
+ }
+ }
+
+}
+
+class FilesListItemView {
+ constructor(file) {
+ this._file = file
+ this.$el = document.createElement('li')
+ this.$el.innerHTML = `
+
+
+
${file.name}
+
+ ${(0).bytesToHumanFileSize()} / ${file.size.bytesToHumanFileSize()}
+
+
+
+
+
+ `
+ this.$progress = this.$el.querySelector('progress')
+ this.$progressLabel = this.$el.querySelector('.progressLabel')
+ this.$previewBox = this.$el.querySelector('.preview')
+ }
+
+ setProgress(value) {
+ this.$progress.value = value
+ this.$progressLabel.innerHTML = (this._file.size * value).bytesToHumanFileSize()
+ }
+
+ root(root) {
+ root.appendChild(this.$el)
+ return this
+ }
+
+ preview(file) {
+ let preview = new PreviewView(file, this.$previewBox);
+ if(preview.isPlayable) {
+ this.$previewBox.setAttribute('preview','loaded')
+ }
+ }
+
+}
class Snapdrop {
constructor() {
const server = new ServerConnection();
const peers = new PeersManager(server);
const peersUI = new PeersUI();
- Events.on('load', e => {
+ Events.on(Events.LOAD, e => {
+ const filesListView = new FilesListView();
const receiveDialog = new ReceiveDialog();
+ const requestDialog = new RequestDialog();
const sendTextDialog = new SendTextDialog();
const receiveTextDialog = new ReceiveTextDialog();
const toast = new Toast();
@@ -560,7 +721,7 @@ window.addEventListener('beforeinstallprompt', e => {
});
// Background Animation
-Events.on('load', () => {
+Events.on(Events.LOAD, () => {
let c = document.createElement('canvas');
document.body.appendChild(c);
let style = c.style;
diff --git a/client/styles.css b/client/styles.css
index 0a9aca98..679bbdaa 100644
--- a/client/styles.css
+++ b/client/styles.css
@@ -322,6 +322,109 @@ footer .font-body2 {
}
}
+/*
+ Files Listing
+*/
+
+#filesListView {
+ box-sizing: border-box;
+ border-radius: 20px;
+ position: fixed;
+ left: 2vw;
+ top: 2.5vh;
+ width: 90px;
+ height: 45px;
+ max-width: 600px;
+ padding: 10px;
+ background-color: var(--bg-color-secondary);
+ transition-duration: 0.3s;
+ transition-property: width, height, overflow-y;
+ overflow-x: hidden;
+ overflow-y: hidden;
+ z-index: 15;
+}
+
+#filesListView [toggle] {
+ display: block;
+ padding-bottom: 15px;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);
+ text-align: center;
+ cursor: pointer;
+}
+
+#filesListView[show] {
+ width: 98vw;
+ height: 95vh;
+ overflow-y: scroll;
+ z-index: 3;
+}
+
+#filesListView > div {
+ width: 98vw;
+ max-width: 100%;
+}
+
+#filesListView ul {
+ margin: 0;
+ padding: 0;
+ list-style: none;
+}
+
+#filesListView li {
+ display: flex;
+ align-items: center;
+ margin: 10px 0;
+ padding: 10px 12px;
+ background-color: rgba(255, 255, 255, 0.08);
+ border-radius: 8px;
+}
+
+#filesListView li .preview {
+ display: flex;
+ align-items: end;
+ max-width: 0px;
+ max-height: 100px;
+ border-radius: 8px;
+ transition-duration: 0.3s;
+ transition-property: max-width, opacity, margin;
+ opacity: 0;
+ margin-left: 0;
+ overflow: hidden;
+}
+
+#filesListView li .preview > * {
+ min-width: 100%;
+ min-height: 100%;
+}
+
+#filesListView li .preview[preview] {
+ margin-left: 15px;
+ max-width: 100px;
+ max-height: 100px;
+ opacity: 1;
+}
+
+#filesListView li .content {
+ flex: 1;
+}
+
+#filesListView li .content > div {
+ display: flex;
+ align-items: flex-end;
+}
+
+
+#filesListView .details {
+ text-align: right;
+ font-size: 0.7em;
+}
+
+#filesListView progress {
+ display: block;
+ box-sizing: border-box;
+ width: 100%;
+}
+
/* Dialog */
@@ -717,8 +820,9 @@ x-dialog x-paper {
color: var(--text-color);
background-color: var(--bg-color-secondary);
}
-/* Image Preview */
-#img-preview{
+/* Image/Video/Audio Preview */
+.element-preview {
+ max-width: 100%;
max-height: 50vh;
margin: auto;
display: block;
@@ -754,4 +858,3 @@ x-dialog x-paper {
overflow: hidden;
}
}
-