From 8809ea666761129a3e52f4bb06f180a412ef9a3b Mon Sep 17 00:00:00 2001 From: MoPaMo Date: Wed, 16 Jun 2021 07:13:19 +0200 Subject: [PATCH 1/3] add event listener to textbox --- client/scripts/ui.js | 1028 +++++++++++++++++++++--------------------- 1 file changed, 520 insertions(+), 508 deletions(-) diff --git a/client/scripts/ui.js b/client/scripts/ui.js index 0c15984..93dd77e 100644 --- a/client/scripts/ui.js +++ b/client/scripts/ui.js @@ -1,79 +1,80 @@ -const $ = query => document.getElementById(query); -const $$ = query => document.body.querySelector(query); -const isURL = text => /^((https?:\/\/|www)[^\s]+)/g.test(text.toLowerCase()); -window.isDownloadSupported = (typeof document.createElement('a').download !== 'undefined'); -window.isProductionEnvironment = !window.location.host.startsWith('localhost'); +const $ = (query) => document.getElementById(query); +const $$ = (query) => document.body.querySelector(query); +const isURL = (text) => /^((https?:\/\/|www)[^\s]+)/g.test(text.toLowerCase()); +window.isDownloadSupported = + typeof document.createElement("a").download !== "undefined"; +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 => { - const me = e.detail.message; - const $displayName = $('displayName') - $displayName.textContent = 'You are known as ' + me.displayName; - $displayName.title = me.deviceName; +Events.on("display-name", (e) => { + const me = e.detail.message; + const $displayName = $("displayName"); + $displayName.textContent = "You are known as " + me.displayName; + $displayName.title = me.deviceName; }); 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)); + } - 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)); - } + _onPeerJoined(peer) { + if ($(peer.id)) return; // peer already exists + const peerUI = new PeerUI(peer); + $$("x-peers").appendChild(peerUI.$el); + setTimeout((e) => window.animateBackground(false), 1750); // Stop animation + } - _onPeerJoined(peer) { - if ($(peer.id)) return; // peer already exists - const peerUI = new PeerUI(peer); - $$('x-peers').appendChild(peerUI.$el); - setTimeout(e => window.animateBackground(false), 1750); // Stop animation - } + _onPeers(peers) { + this._clearPeers(); + peers.forEach((peer) => this._onPeerJoined(peer)); + } - _onPeers(peers) { - this._clearPeers(); - peers.forEach(peer => this._onPeerJoined(peer)); - } + _onPeerLeft(peerId) { + const $peer = $(peerId); + if (!$peer) return; + $peer.remove(); + } - _onPeerLeft(peerId) { - const $peer = $(peerId); - if (!$peer) return; - $peer.remove(); - } + _onFileProgress(progress) { + const peerId = progress.sender || progress.recipient; + const $peer = $(peerId); + if (!$peer) return; + $peer.ui.setProgress(progress.progress); + } - _onFileProgress(progress) { - const peerId = progress.sender || progress.recipient; - const $peer = $(peerId); - if (!$peer) return; - $peer.ui.setProgress(progress.progress); - } + _clearPeers() { + const $peers = ($$("x-peers").innerHTML = ""); + } - _clearPeers() { - const $peers = $$('x-peers').innerHTML = ''; - } - - _onPaste(e) { - const files = e.clipboardData.files || e.clipboardData.items - .filter(i => i.type.indexOf('image') > -1) - .map(i => i.getAsFile()); - const peers = document.querySelectorAll('x-peer'); - // send the pasted image content to the only peer if there is one - // otherwise, select the peer somehow by notifying the client that - // "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', { - files: files, - to: $$('x-peer').id - }); - } + _onPaste(e) { + const files = + e.clipboardData.files || + e.clipboardData.items + .filter((i) => i.type.indexOf("image") > -1) + .map((i) => i.getAsFile()); + const peers = document.querySelectorAll("x-peer"); + // send the pasted image content to the only peer if there is one + // otherwise, select the peer somehow by notifying the client that + // "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", { + files: files, + to: $$("x-peer").id, + }); } + } } class PeerUI { - - html() { - return ` + html() { + return ` ` - } + `; + } - constructor(peer) { - this._peer = peer; - this._initDom(); - this._bindListeners(this.$el); - } + constructor(peer) { + this._peer = peer; + this._initDom(); + this._bindListeners(this.$el); + } - _initDom() { - const el = document.createElement('x-peer'); - el.id = this._peer.id; - el.innerHTML = this.html(); - el.ui = this; - el.querySelector('svg use').setAttribute('xlink:href', this._icon()); - el.querySelector('.name').textContent = this._displayName(); - el.querySelector('.device-name').textContent = this._deviceName(); - this.$el = el; - this.$progress = el.querySelector('.progress'); - } + _initDom() { + const el = document.createElement("x-peer"); + el.id = this._peer.id; + el.innerHTML = this.html(); + el.ui = this; + el.querySelector("svg use").setAttribute("xlink:href", this._icon()); + el.querySelector(".name").textContent = this._displayName(); + el.querySelector(".device-name").textContent = this._deviceName(); + this.$el = el; + this.$progress = el.querySelector(".progress"); + } - _bindListeners(el) { - el.querySelector('input').addEventListener('change', e => this._onFilesSelected(e)); - el.addEventListener('drop', e => this._onDrop(e)); - el.addEventListener('dragend', e => this._onDragEnd(e)); - el.addEventListener('dragleave', e => this._onDragEnd(e)); - el.addEventListener('dragover', e => this._onDragOver(e)); - el.addEventListener('contextmenu', e => this._onRightClick(e)); - el.addEventListener('touchstart', e => this._onTouchStart(e)); - el.addEventListener('touchend', e => this._onTouchEnd(e)); - // prevent browser's default file drop behavior - Events.on('dragover', e => e.preventDefault()); - Events.on('drop', e => e.preventDefault()); - } + _bindListeners(el) { + el.querySelector("input").addEventListener("change", (e) => + this._onFilesSelected(e) + ); + el.addEventListener("drop", (e) => this._onDrop(e)); + el.addEventListener("dragend", (e) => this._onDragEnd(e)); + el.addEventListener("dragleave", (e) => this._onDragEnd(e)); + el.addEventListener("dragover", (e) => this._onDragOver(e)); + el.addEventListener("contextmenu", (e) => this._onRightClick(e)); + el.addEventListener("touchstart", (e) => this._onTouchStart(e)); + el.addEventListener("touchend", (e) => this._onTouchEnd(e)); + // prevent browser's default file drop behavior + Events.on("dragover", (e) => e.preventDefault()); + Events.on("drop", (e) => e.preventDefault()); + } - _displayName() { - return this._peer.name.displayName; - } + _displayName() { + return this._peer.name.displayName; + } - _deviceName() { - return this._peer.name.deviceName; - } + _deviceName() { + return this._peer.name.deviceName; + } - _icon() { - const device = this._peer.name.device || this._peer.name; - if (device.type === 'mobile') { - return '#phone-iphone'; - } - if (device.type === 'tablet') { - return '#tablet-mac'; - } - return '#desktop-mac'; + _icon() { + const device = this._peer.name.device || this._peer.name; + if (device.type === "mobile") { + return "#phone-iphone"; } + if (device.type === "tablet") { + return "#tablet-mac"; + } + return "#desktop-mac"; + } - _onFilesSelected(e) { - const $input = e.target; - const files = $input.files; - Events.fire('files-selected', { - files: files, - to: this._peer.id - }); - $input.value = null; // reset input - } + _onFilesSelected(e) { + const $input = e.target; + const files = $input.files; + Events.fire("files-selected", { + files: files, + to: this._peer.id, + }); + $input.value = null; // reset input + } - setProgress(progress) { - if (progress > 0) { - this.$el.setAttribute('transfer', '1'); - } - if (progress > 0.5) { - this.$progress.classList.add('over50'); - } else { - this.$progress.classList.remove('over50'); - } - const degrees = `rotate(${360 * progress}deg)`; - this.$progress.style.setProperty('--progress', degrees); - if (progress >= 1) { - this.setProgress(0); - this.$el.removeAttribute('transfer'); - } + setProgress(progress) { + if (progress > 0) { + this.$el.setAttribute("transfer", "1"); } + if (progress > 0.5) { + this.$progress.classList.add("over50"); + } else { + this.$progress.classList.remove("over50"); + } + const degrees = `rotate(${360 * progress}deg)`; + this.$progress.style.setProperty("--progress", degrees); + if (progress >= 1) { + this.setProgress(0); + this.$el.removeAttribute("transfer"); + } + } - _onDrop(e) { - e.preventDefault(); - const files = e.dataTransfer.files; - Events.fire('files-selected', { - files: files, - to: this._peer.id - }); - this._onDragEnd(); - } + _onDrop(e) { + e.preventDefault(); + const files = e.dataTransfer.files; + Events.fire("files-selected", { + files: files, + to: this._peer.id, + }); + this._onDragEnd(); + } - _onDragOver() { - this.$el.setAttribute('drop', 1); - } + _onDragOver() { + this.$el.setAttribute("drop", 1); + } - _onDragEnd() { - this.$el.removeAttribute('drop'); - } + _onDragEnd() { + this.$el.removeAttribute("drop"); + } - _onRightClick(e) { - e.preventDefault(); - Events.fire('text-recipient', this._peer.id); - } + _onRightClick(e) { + e.preventDefault(); + Events.fire("text-recipient", this._peer.id); + } - _onTouchStart(e) { - this._touchStart = Date.now(); - this._touchTimer = setTimeout(_ => this._onTouchEnd(), 610); - } + _onTouchStart(e) { + this._touchStart = Date.now(); + this._touchTimer = setTimeout((_) => this._onTouchEnd(), 610); + } - _onTouchEnd(e) { - if (Date.now() - this._touchStart < 500) { - clearTimeout(this._touchTimer); - } else { // this was a long tap - if (e) e.preventDefault(); - Events.fire('text-recipient', this._peer.id); - } + _onTouchEnd(e) { + if (Date.now() - this._touchStart < 500) { + clearTimeout(this._touchTimer); + } else { + // this was a long tap + if (e) e.preventDefault(); + Events.fire("text-recipient", this._peer.id); } + } } - class Dialog { - constructor(id) { - this.$el = $(id); - this.$el.querySelectorAll('[close]').forEach(el => el.addEventListener('click', e => this.hide())) - this.$autoFocus = this.$el.querySelector('[autofocus]'); - } + constructor(id) { + this.$el = $(id); + this.$el.querySelectorAll("[close]").forEach((el) => { + el.addEventListener("click", (e) => this.hide()); + }); + this.$el.querySelectorAll("[role=\"textbox\"]").forEach((el) => { + el.addEventListener("click", (e) => this.hide()); + }); + this.$autoFocus = this.$el.querySelector("[autofocus]"); + } - show() { - this.$el.setAttribute('show', 1); - if (this.$autoFocus) this.$autoFocus.focus(); - } + show() { + this.$el.setAttribute("show", 1); + if (this.$autoFocus) this.$autoFocus.focus(); + } - hide() { - this.$el.removeAttribute('show'); - document.activeElement.blur(); - window.blur(); - } + hide() { + this.$el.removeAttribute("show"); + document.activeElement.blur(); + window.blur(); + } } class ReceiveDialog extends Dialog { + constructor() { + super("receiveDialog"); + Events.on("file-received", (e) => { + this._nextFile(e.detail); + window.blop.play(); + }); + this._filesQueue = []; + } - constructor() { - super('receiveDialog'); - Events.on('file-received', e => { - this._nextFile(e.detail); - window.blop.play(); - }); - this._filesQueue = []; + _nextFile(nextFile) { + if (nextFile) this._filesQueue.push(nextFile); + if (this._busy) return; + this._busy = true; + const file = this._filesQueue.shift(); + this._displayFile(file); + } + + _dequeueFile() { + if (!this._filesQueue.length) { + // nothing to do + this._busy = false; + return; + } + // dequeue next file + setTimeout((_) => { + this._busy = false; + this._nextFile(); + }, 300); + } + + _displayFile(file) { + const $a = this.$el.querySelector("#download"); + const url = URL.createObjectURL(file.blob); + $a.href = 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; } - _nextFile(nextFile) { - if (nextFile) this._filesQueue.push(nextFile); - if (this._busy) return; - this._busy = true; - const file = this._filesQueue.shift(); - this._displayFile(file); + this.$el.querySelector("#fileName").textContent = file.name; + this.$el.querySelector("#fileSize").textContent = this._formatFileSize( + file.size + ); + this.show(); + + if (window.isDownloadSupported) return; + // fallback for iOS + $a.target = "_blank"; + const reader = new FileReader(); + reader.onload = (e) => ($a.href = reader.result); + 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"; } + } - _dequeueFile() { - if (!this._filesQueue.length) { // nothing to do - this._busy = false; - return; - } - // dequeue next file - setTimeout(_ => { - this._busy = false; - this._nextFile(); - }, 300); - } + hide() { + this.$el.querySelector(".preview").style.visibility = "hidden"; + this.$el.querySelector("#img-preview").src = ""; + super.hide(); + this._dequeueFile(); + } - _displayFile(file) { - const $a = this.$el.querySelector('#download'); - const url = URL.createObjectURL(file.blob); - $a.href = 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; - } - - this.$el.querySelector('#fileName').textContent = file.name; - this.$el.querySelector('#fileSize').textContent = this._formatFileSize(file.size); - this.show(); - - if (window.isDownloadSupported) return; - // fallback for iOS - $a.target = '_blank'; - const reader = new FileReader(); - reader.onload = e => $a.href = reader.result; - 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 = ""; - super.hide(); - this._dequeueFile(); - } - - - _autoDownload(){ - return !this.$el.querySelector('#autoDownload').checked - } + _autoDownload() { + return !this.$el.querySelector("#autoDownload").checked; + } } - class SendTextDialog extends Dialog { - constructor() { - super('sendTextDialog'); - Events.on('text-recipient', e => this._onRecipient(e.detail)) - this.$text = this.$el.querySelector('#textInput'); - const button = this.$el.querySelector('form'); - button.addEventListener('submit', e => this._send(e)); - } + constructor() { + super("sendTextDialog"); + Events.on("text-recipient", (e) => this._onRecipient(e.detail)); + this.$text = this.$el.querySelector("#textInput"); + const button = this.$el.querySelector("form"); + button.addEventListener("submit", (e) => this._send(e)); + } - _onRecipient(recipient) { - this._recipient = recipient; - this._handleShareTargetText(); - this.show(); + _onRecipient(recipient) { + this._recipient = recipient; + this._handleShareTargetText(); + this.show(); - const range = document.createRange(); - const sel = window.getSelection(); + const range = document.createRange(); + const sel = window.getSelection(); - range.selectNodeContents(this.$text); - sel.removeAllRanges(); - sel.addRange(range); + range.selectNodeContents(this.$text); + sel.removeAllRanges(); + sel.addRange(range); + } - } + _handleShareTargetText() { + if (!window.shareTargetText) return; + this.$text.textContent = window.shareTargetText; + window.shareTargetText = ""; + } - _handleShareTargetText() { - if (!window.shareTargetText) return; - this.$text.textContent = window.shareTargetText; - window.shareTargetText = ''; - } - - _send(e) { - e.preventDefault(); - Events.fire('send-text', { - to: this._recipient, - text: this.$text.innerText - }); - } + _send(e) { + e.preventDefault(); + Events.fire("send-text", { + to: this._recipient, + text: this.$text.innerText, + }); + } } class ReceiveTextDialog extends Dialog { - constructor() { - super('receiveTextDialog'); - Events.on('text-received', e => this._onText(e.detail)) - this.$text = this.$el.querySelector('#text'); - const $copy = this.$el.querySelector('#copy'); - copy.addEventListener('click', _ => this._onCopy()); - } + constructor() { + super("receiveTextDialog"); + Events.on("text-received", (e) => this._onText(e.detail)); + this.$text = this.$el.querySelector("#text"); + const $copy = this.$el.querySelector("#copy"); + copy.addEventListener("click", (_) => this._onCopy()); + } - _onText(e) { - this.$text.innerHTML = ''; - const text = e.text; - if (isURL(text)) { - const $a = document.createElement('a'); - $a.href = text; - $a.target = '_blank'; - $a.textContent = text; - this.$text.appendChild($a); - } else { - this.$text.textContent = text; - } - this.show(); - window.blop.play(); + _onText(e) { + this.$text.innerHTML = ""; + const text = e.text; + if (isURL(text)) { + const $a = document.createElement("a"); + $a.href = text; + $a.target = "_blank"; + $a.textContent = text; + this.$text.appendChild($a); + } else { + this.$text.textContent = text; } + this.show(); + window.blop.play(); + } - async _onCopy() { - await navigator.clipboard.writeText(this.$text.textContent); - Events.fire('notify-user', 'Copied to clipboard'); - } + async _onCopy() { + await navigator.clipboard.writeText(this.$text.textContent); + Events.fire("notify-user", "Copied to clipboard"); + } } class Toast extends Dialog { - constructor() { - super('toast'); - Events.on('notify-user', e => this._onNotfiy(e.detail)); - } + constructor() { + super("toast"); + Events.on("notify-user", (e) => this._onNotfiy(e.detail)); + } - _onNotfiy(message) { - this.$el.textContent = message; - this.show(); - setTimeout(_ => this.hide(), 3000); - } + _onNotfiy(message) { + this.$el.textContent = message; + this.show(); + setTimeout((_) => this.hide(), 3000); + } } - class Notifications { + constructor() { + // Check if the browser supports notifications + if (!("Notification" in window)) return; - constructor() { - // Check if the browser supports notifications - if (!('Notification' in window)) return; + // Check whether notification permissions have already been granted + if (Notification.permission !== "granted") { + this.$button = $("notification"); + 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) + ); + } - // Check whether notification permissions have already been granted - if (Notification.permission !== 'granted') { - this.$button = $('notification'); - 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)); + _requestPermission() { + Notification.requestPermission((permission) => { + if (permission !== "granted") { + Events.fire("notify-user", Notifications.PERMISSION_ERROR || "Error"); + return; + } + this._notify("Even more snappy sharing!"); + this.$button.setAttribute("hidden", 1); + }); + } + + _notify(message, body, closeTimeout = 20000) { + const config = { + body: body, + icon: "/images/logo_transparent_128x128.png", + }; + let notification; + try { + notification = new Notification(message, config); + } catch (e) { + // Android doesn't support "new Notification" if service worker is installed + if (!serviceWorker || !serviceWorker.showNotification) return; + notification = serviceWorker.showNotification(message, config); } - _requestPermission() { - Notification.requestPermission(permission => { - if (permission !== 'granted') { - Events.fire('notify-user', Notifications.PERMISSION_ERROR || 'Error'); - return; - } - this._notify('Even more snappy sharing!'); - this.$button.setAttribute('hidden', 1); - }); + // Notification is persistent on Android. We have to close it manually + if (closeTimeout) { + setTimeout((_) => notification.close(), closeTimeout); } - _notify(message, body, closeTimeout = 20000) { - const config = { - body: body, - icon: '/images/logo_transparent_128x128.png', - } - let notification; - try { - notification = new Notification(message, config); - } catch (e) { - // Android doesn't support "new Notification" if service worker is installed - if (!serviceWorker || !serviceWorker.showNotification) return; - notification = serviceWorker.showNotification(message, config); - } + return notification; + } - // Notification is persistent on Android. We have to close it manually - if (closeTimeout) { - setTimeout(_ => notification.close(), closeTimeout); - } - - return notification; + _messageNotification(message) { + if (isURL(message)) { + const notification = this._notify(message, "Click to open link"); + this._bind(notification, (e) => + window.open(message, "_blank", null, true) + ); + } else { + const notification = this._notify(message, "Click to copy text"); + this._bind(notification, (e) => this._copyText(message, notification)); } + } - _messageNotification(message) { - if (isURL(message)) { - const notification = this._notify(message, 'Click to open link'); - this._bind(notification, e => window.open(message, '_blank', null, true)); - } else { - const notification = this._notify(message, 'Click to copy text'); - this._bind(notification, e => this._copyText(message, notification)); - } - } + _downloadNotification(message) { + const notification = this._notify(message, "Click to download"); + if (!window.isDownloadSupported) return; + this._bind(notification, (e) => this._download(notification)); + } - _downloadNotification(message) { - const notification = this._notify(message, 'Click to download'); - if (!window.isDownloadSupported) return; - this._bind(notification, e => this._download(notification)); - } + _download(notification) { + document.querySelector("x-dialog [download]").click(); + notification.close(); + } - _download(notification) { - document.querySelector('x-dialog [download]').click(); - notification.close(); - } + _copyText(message, notification) { + notification.close(); + if (!navigator.clipboard.writeText(message)) return; + this._notify("Copied text to clipboard"); + } - _copyText(message, notification) { - notification.close(); - if (!navigator.clipboard.writeText(message)) return; - this._notify('Copied text to clipboard'); - } - - _bind(notification, handler) { - if (notification.then) { - notification.then(e => serviceWorker.getNotifications().then(notifications => { - serviceWorker.addEventListener('notificationclick', handler); - })); - } else { - notification.onclick = handler; - } + _bind(notification, handler) { + if (notification.then) { + notification.then((e) => + serviceWorker.getNotifications().then((notifications) => { + serviceWorker.addEventListener("notificationclick", handler); + }) + ); + } else { + notification.onclick = handler; } + } } - class NetworkStatusUI { + constructor() { + window.addEventListener( + "offline", + (e) => this._showOfflineMessage(), + false + ); + window.addEventListener("online", (e) => this._showOnlineMessage(), false); + if (!navigator.onLine) this._showOfflineMessage(); + } - constructor() { - window.addEventListener('offline', e => this._showOfflineMessage(), false); - window.addEventListener('online', e => this._showOnlineMessage(), false); - if (!navigator.onLine) this._showOfflineMessage(); - } + _showOfflineMessage() { + Events.fire("notify-user", "You are offline"); + } - _showOfflineMessage() { - Events.fire('notify-user', 'You are offline'); - } - - _showOnlineMessage() { - Events.fire('notify-user', 'You are back online'); - } + _showOnlineMessage() { + Events.fire("notify-user", "You are back online"); + } } class WebShareTargetUI { - constructor() { - const parsedUrl = new URL(window.location); - const title = parsedUrl.searchParams.get('title'); - const text = parsedUrl.searchParams.get('text'); - const url = parsedUrl.searchParams.get('url'); + constructor() { + const parsedUrl = new URL(window.location); + const title = parsedUrl.searchParams.get("title"); + const text = parsedUrl.searchParams.get("text"); + const url = parsedUrl.searchParams.get("url"); - let shareTargetText = title ? title : ''; - shareTargetText += text ? shareTargetText ? ' ' + text : text : ''; + let shareTargetText = title ? title : ""; + shareTargetText += text ? (shareTargetText ? " " + text : text) : ""; - if(url) shareTargetText = url; // We share only the Link - no text. Because link-only text becomes clickable. + if (url) shareTargetText = url; // We share only the Link - no text. Because link-only text becomes clickable. - if (!shareTargetText) return; - window.shareTargetText = shareTargetText; - history.pushState({}, 'URL Rewrite', '/'); - console.log('Shared Target Text:', '"' + shareTargetText + '"'); - } + if (!shareTargetText) return; + window.shareTargetText = shareTargetText; + history.pushState({}, "URL Rewrite", "/"); + console.log("Shared Target Text:", '"' + shareTargetText + '"'); + } } - class Snapdrop { - constructor() { - const server = new ServerConnection(); - const peers = new PeersManager(server); - const peersUI = new PeersUI(); - Events.on('load', e => { - const receiveDialog = new ReceiveDialog(); - const sendTextDialog = new SendTextDialog(); - const receiveTextDialog = new ReceiveTextDialog(); - const toast = new Toast(); - const notifications = new Notifications(); - const networkStatusUI = new NetworkStatusUI(); - const webShareTargetUI = new WebShareTargetUI(); - }); - } + constructor() { + const server = new ServerConnection(); + const peers = new PeersManager(server); + const peersUI = new PeersUI(); + Events.on("load", (e) => { + const receiveDialog = new ReceiveDialog(); + const sendTextDialog = new SendTextDialog(); + const receiveTextDialog = new ReceiveTextDialog(); + const toast = new Toast(); + const notifications = new Notifications(); + const networkStatusUI = new NetworkStatusUI(); + const webShareTargetUI = new WebShareTargetUI(); + }); + } } const snapdrop = new Snapdrop(); - - -if ('serviceWorker' in navigator) { - navigator.serviceWorker.register('/service-worker.js') - .then(serviceWorker => { - console.log('Service Worker registered'); - window.serviceWorker = serviceWorker - }); +if ("serviceWorker" in navigator) { + navigator.serviceWorker + .register("/service-worker.js") + .then((serviceWorker) => { + console.log("Service Worker registered"); + window.serviceWorker = serviceWorker; + }); } -window.addEventListener('beforeinstallprompt', e => { - if (window.matchMedia('(display-mode: standalone)').matches) { - // don't display install banner when installed - return e.preventDefault(); - } else { - const btn = document.querySelector('#install') - btn.hidden = false; - btn.onclick = _ => e.prompt(); - return e.preventDefault(); - } +window.addEventListener("beforeinstallprompt", (e) => { + if (window.matchMedia("(display-mode: standalone)").matches) { + // don't display install banner when installed + return e.preventDefault(); + } else { + const btn = document.querySelector("#install"); + btn.hidden = false; + btn.onclick = (_) => e.prompt(); + return e.preventDefault(); + } }); // Background Animation -Events.on('load', () => { - let c = document.createElement('canvas'); - document.body.appendChild(c); - let style = c.style; - style.width = '100%'; - style.position = 'absolute'; - style.zIndex = -1; - style.top = 0; - style.left = 0; - let ctx = c.getContext('2d'); - let x0, y0, w, h, dw; +Events.on("load", () => { + let c = document.createElement("canvas"); + document.body.appendChild(c); + let style = c.style; + style.width = "100%"; + style.position = "absolute"; + style.zIndex = -1; + style.top = 0; + style.left = 0; + let ctx = c.getContext("2d"); + let x0, y0, w, h, dw; - function init() { - w = window.innerWidth; - h = window.innerHeight; - c.width = w; - c.height = h; - let offset = h > 380 ? 100 : 65; - offset = h > 800 ? 116 : offset; - x0 = w / 2; - y0 = h - offset; - dw = Math.max(w, h, 1000) / 13; + function init() { + w = window.innerWidth; + h = window.innerHeight; + c.width = w; + c.height = h; + let offset = h > 380 ? 100 : 65; + offset = h > 800 ? 116 : offset; + x0 = w / 2; + y0 = h - offset; + dw = Math.max(w, h, 1000) / 13; + drawCircles(); + } + window.onresize = init; + + function drawCircle(radius) { + ctx.beginPath(); + let color = Math.round(255 * (1 - radius / Math.max(w, h))); + ctx.strokeStyle = "rgba(" + color + "," + color + "," + color + ",0.1)"; + ctx.arc(x0, y0, radius, 0, 2 * Math.PI); + ctx.stroke(); + ctx.lineWidth = 2; + } + + let step = 0; + + function drawCircles() { + ctx.clearRect(0, 0, w, h); + for (let i = 0; i < 8; i++) { + drawCircle(dw * i + (step % dw)); + } + step += 1; + } + + let loading = true; + + function animate() { + if (loading || step % dw < dw - 5) { + requestAnimationFrame(function () { drawCircles(); - } - window.onresize = init; - - function drawCircle(radius) { - ctx.beginPath(); - let color = Math.round(255 * (1 - radius / Math.max(w, h))); - ctx.strokeStyle = 'rgba(' + color + ',' + color + ',' + color + ',0.1)'; - ctx.arc(x0, y0, radius, 0, 2 * Math.PI); - ctx.stroke(); - ctx.lineWidth = 2; - } - - let step = 0; - - function drawCircles() { - ctx.clearRect(0, 0, w, h); - for (let i = 0; i < 8; i++) { - drawCircle(dw * i + step % dw); - } - step += 1; - } - - let loading = true; - - function animate() { - if (loading || step % dw < dw - 5) { - requestAnimationFrame(function() { - drawCircles(); - animate(); - }); - } - } - window.animateBackground = function(l) { - loading = l; animate(); - }; - init(); + }); + } + } + window.animateBackground = function (l) { + loading = l; animate(); + }; + init(); + animate(); }); Notifications.PERMISSION_ERROR = ` @@ -629,8 +640,9 @@ as the user has dismissed the permission prompt several times. This can be reset in Page Info which can be accessed by clicking the lock icon next to the URL.`; -document.body.onclick = e => { // safari hack to fix audio - document.body.onclick = null; - if (!(/.*Version.*Safari.*/.test(navigator.userAgent))) return; - blop.play(); -} +document.body.onclick = (e) => { + // safari hack to fix audio + document.body.onclick = null; + if (!/.*Version.*Safari.*/.test(navigator.userAgent)) return; + blop.play(); +}; From f71b025db67f325d9a9b27ba551ae17b0cf81620 Mon Sep 17 00:00:00 2001 From: MoPaMo Date: Wed, 16 Jun 2021 07:15:03 +0200 Subject: [PATCH 2/3] close text modal on escape press --- client/scripts/ui.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/client/scripts/ui.js b/client/scripts/ui.js index 93dd77e..3e2af9c 100644 --- a/client/scripts/ui.js +++ b/client/scripts/ui.js @@ -215,9 +215,13 @@ class Dialog { this.$el.querySelectorAll("[close]").forEach((el) => { el.addEventListener("click", (e) => this.hide()); }); - this.$el.querySelectorAll("[role=\"textbox\"]").forEach((el) => { - el.addEventListener("click", (e) => this.hide()); + this.$el.querySelectorAll('[role="textbox"]').forEach((el) => { + el.addEventListener("keypress", (e) => { + if (e.key == "Escape") { + this.hide(); + } }); + }); this.$autoFocus = this.$el.querySelector("[autofocus]"); } From 36c9289280201385cae999292b2d5282f0d8241c Mon Sep 17 00:00:00 2001 From: MoPaMo Date: Wed, 16 Jun 2021 15:26:49 +0200 Subject: [PATCH 3/3] remove whitespace --- client/scripts/ui.js | 1075 +++++++++++++++++++++--------------------- 1 file changed, 533 insertions(+), 542 deletions(-) diff --git a/client/scripts/ui.js b/client/scripts/ui.js index 3e2af9c..b3d5a79 100644 --- a/client/scripts/ui.js +++ b/client/scripts/ui.js @@ -1,80 +1,79 @@ -const $ = (query) => document.getElementById(query); -const $$ = (query) => document.body.querySelector(query); -const isURL = (text) => /^((https?:\/\/|www)[^\s]+)/g.test(text.toLowerCase()); -window.isDownloadSupported = - typeof document.createElement("a").download !== "undefined"; -window.isProductionEnvironment = !window.location.host.startsWith("localhost"); +const $ = query => document.getElementById(query); +const $$ = query => document.body.querySelector(query); +const isURL = text => /^((https?:\/\/|www)[^\s]+)/g.test(text.toLowerCase()); +window.isDownloadSupported = (typeof document.createElement('a').download !== 'undefined'); +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) => { - const me = e.detail.message; - const $displayName = $("displayName"); - $displayName.textContent = "You are known as " + me.displayName; - $displayName.title = me.deviceName; +Events.on('display-name', e => { + const me = e.detail.message; + const $displayName = $('displayName') + $displayName.textContent = 'You are known as ' + me.displayName; + $displayName.title = me.deviceName; }); 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)); - } - _onPeerJoined(peer) { - if ($(peer.id)) return; // peer already exists - const peerUI = new PeerUI(peer); - $$("x-peers").appendChild(peerUI.$el); - setTimeout((e) => window.animateBackground(false), 1750); // Stop animation - } - - _onPeers(peers) { - this._clearPeers(); - peers.forEach((peer) => this._onPeerJoined(peer)); - } - - _onPeerLeft(peerId) { - const $peer = $(peerId); - if (!$peer) return; - $peer.remove(); - } - - _onFileProgress(progress) { - const peerId = progress.sender || progress.recipient; - const $peer = $(peerId); - if (!$peer) return; - $peer.ui.setProgress(progress.progress); - } - - _clearPeers() { - const $peers = ($$("x-peers").innerHTML = ""); - } - - _onPaste(e) { - const files = - e.clipboardData.files || - e.clipboardData.items - .filter((i) => i.type.indexOf("image") > -1) - .map((i) => i.getAsFile()); - const peers = document.querySelectorAll("x-peer"); - // send the pasted image content to the only peer if there is one - // otherwise, select the peer somehow by notifying the client that - // "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", { - files: files, - to: $$("x-peer").id, - }); + 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)); + } + + _onPeerJoined(peer) { + if ($(peer.id)) return; // peer already exists + const peerUI = new PeerUI(peer); + $$('x-peers').appendChild(peerUI.$el); + setTimeout(e => window.animateBackground(false), 1750); // Stop animation + } + + _onPeers(peers) { + this._clearPeers(); + peers.forEach(peer => this._onPeerJoined(peer)); + } + + _onPeerLeft(peerId) { + const $peer = $(peerId); + if (!$peer) return; + $peer.remove(); + } + + _onFileProgress(progress) { + const peerId = progress.sender || progress.recipient; + const $peer = $(peerId); + if (!$peer) return; + $peer.ui.setProgress(progress.progress); + } + + _clearPeers() { + const $peers = $$('x-peers').innerHTML = ''; + } + + _onPaste(e) { + const files = e.clipboardData.files || e.clipboardData.items + .filter(i => i.type.indexOf('image') > -1) + .map(i => i.getAsFile()); + const peers = document.querySelectorAll('x-peer'); + // send the pasted image content to the only peer if there is one + // otherwise, select the peer somehow by notifying the client that + // "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', { + files: files, + to: $$('x-peer').id + }); + } } - } } class PeerUI { - html() { - return ` + + html() { + return ` `; - } - - constructor(peer) { - this._peer = peer; - this._initDom(); - this._bindListeners(this.$el); - } - - _initDom() { - const el = document.createElement("x-peer"); - el.id = this._peer.id; - el.innerHTML = this.html(); - el.ui = this; - el.querySelector("svg use").setAttribute("xlink:href", this._icon()); - el.querySelector(".name").textContent = this._displayName(); - el.querySelector(".device-name").textContent = this._deviceName(); - this.$el = el; - this.$progress = el.querySelector(".progress"); - } - - _bindListeners(el) { - el.querySelector("input").addEventListener("change", (e) => - this._onFilesSelected(e) - ); - el.addEventListener("drop", (e) => this._onDrop(e)); - el.addEventListener("dragend", (e) => this._onDragEnd(e)); - el.addEventListener("dragleave", (e) => this._onDragEnd(e)); - el.addEventListener("dragover", (e) => this._onDragOver(e)); - el.addEventListener("contextmenu", (e) => this._onRightClick(e)); - el.addEventListener("touchstart", (e) => this._onTouchStart(e)); - el.addEventListener("touchend", (e) => this._onTouchEnd(e)); - // prevent browser's default file drop behavior - Events.on("dragover", (e) => e.preventDefault()); - Events.on("drop", (e) => e.preventDefault()); - } - - _displayName() { - return this._peer.name.displayName; - } - - _deviceName() { - return this._peer.name.deviceName; - } - - _icon() { - const device = this._peer.name.device || this._peer.name; - if (device.type === "mobile") { - return "#phone-iphone"; + ` } - if (device.type === "tablet") { - return "#tablet-mac"; + + constructor(peer) { + this._peer = peer; + this._initDom(); + this._bindListeners(this.$el); } - return "#desktop-mac"; - } - _onFilesSelected(e) { - const $input = e.target; - const files = $input.files; - Events.fire("files-selected", { - files: files, - to: this._peer.id, - }); - $input.value = null; // reset input - } - - setProgress(progress) { - if (progress > 0) { - this.$el.setAttribute("transfer", "1"); + _initDom() { + const el = document.createElement('x-peer'); + el.id = this._peer.id; + el.innerHTML = this.html(); + el.ui = this; + el.querySelector('svg use').setAttribute('xlink:href', this._icon()); + el.querySelector('.name').textContent = this._displayName(); + el.querySelector('.device-name').textContent = this._deviceName(); + this.$el = el; + this.$progress = el.querySelector('.progress'); } - if (progress > 0.5) { - this.$progress.classList.add("over50"); - } else { - this.$progress.classList.remove("over50"); + + _bindListeners(el) { + el.querySelector('input').addEventListener('change', e => this._onFilesSelected(e)); + el.addEventListener('drop', e => this._onDrop(e)); + el.addEventListener('dragend', e => this._onDragEnd(e)); + el.addEventListener('dragleave', e => this._onDragEnd(e)); + el.addEventListener('dragover', e => this._onDragOver(e)); + el.addEventListener('contextmenu', e => this._onRightClick(e)); + el.addEventListener('touchstart', e => this._onTouchStart(e)); + el.addEventListener('touchend', e => this._onTouchEnd(e)); + // prevent browser's default file drop behavior + Events.on('dragover', e => e.preventDefault()); + Events.on('drop', e => e.preventDefault()); } - const degrees = `rotate(${360 * progress}deg)`; - this.$progress.style.setProperty("--progress", degrees); - if (progress >= 1) { - this.setProgress(0); - this.$el.removeAttribute("transfer"); + + _displayName() { + return this._peer.name.displayName; } - } - _onDrop(e) { - e.preventDefault(); - const files = e.dataTransfer.files; - Events.fire("files-selected", { - files: files, - to: this._peer.id, - }); - this._onDragEnd(); - } - - _onDragOver() { - this.$el.setAttribute("drop", 1); - } - - _onDragEnd() { - this.$el.removeAttribute("drop"); - } - - _onRightClick(e) { - e.preventDefault(); - Events.fire("text-recipient", this._peer.id); - } - - _onTouchStart(e) { - this._touchStart = Date.now(); - this._touchTimer = setTimeout((_) => this._onTouchEnd(), 610); - } - - _onTouchEnd(e) { - if (Date.now() - this._touchStart < 500) { - clearTimeout(this._touchTimer); - } else { - // this was a long tap - if (e) e.preventDefault(); - Events.fire("text-recipient", this._peer.id); + _deviceName() { + return this._peer.name.deviceName; + } + + _icon() { + const device = this._peer.name.device || this._peer.name; + if (device.type === 'mobile') { + return '#phone-iphone'; + } + if (device.type === 'tablet') { + return '#tablet-mac'; + } + return '#desktop-mac'; + } + + _onFilesSelected(e) { + const $input = e.target; + const files = $input.files; + Events.fire('files-selected', { + files: files, + to: this._peer.id + }); + $input.value = null; // reset input + } + + setProgress(progress) { + if (progress > 0) { + this.$el.setAttribute('transfer', '1'); + } + if (progress > 0.5) { + this.$progress.classList.add('over50'); + } else { + this.$progress.classList.remove('over50'); + } + const degrees = `rotate(${360 * progress}deg)`; + this.$progress.style.setProperty('--progress', degrees); + if (progress >= 1) { + this.setProgress(0); + this.$el.removeAttribute('transfer'); + } + } + + _onDrop(e) { + e.preventDefault(); + const files = e.dataTransfer.files; + Events.fire('files-selected', { + files: files, + to: this._peer.id + }); + this._onDragEnd(); + } + + _onDragOver() { + this.$el.setAttribute('drop', 1); + } + + _onDragEnd() { + this.$el.removeAttribute('drop'); + } + + _onRightClick(e) { + e.preventDefault(); + Events.fire('text-recipient', this._peer.id); + } + + _onTouchStart(e) { + this._touchStart = Date.now(); + this._touchTimer = setTimeout(_ => this._onTouchEnd(), 610); + } + + _onTouchEnd(e) { + if (Date.now() - this._touchStart < 500) { + clearTimeout(this._touchTimer); + } else { // this was a long tap + if (e) e.preventDefault(); + Events.fire('text-recipient', this._peer.id); + } } - } } + class Dialog { - constructor(id) { - this.$el = $(id); - this.$el.querySelectorAll("[close]").forEach((el) => { - el.addEventListener("click", (e) => this.hide()); - }); - this.$el.querySelectorAll('[role="textbox"]').forEach((el) => { - el.addEventListener("keypress", (e) => { - if (e.key == "Escape") { - this.hide(); - } - }); - }); - this.$autoFocus = this.$el.querySelector("[autofocus]"); - } + constructor(id) { + this.$el = $(id); + this.$el.querySelectorAll('[close]').forEach(el => el.addEventListener('click', e => this.hide())) + this.$el.querySelectorAll('[role="textbox"]').forEach((el) => { + el.addEventListener("keypress", (e) => { + if (e.key == "Escape") { + this.hide(); + } + }); + }) + this.$autoFocus = this.$el.querySelector('[autofocus]'); + } - show() { - this.$el.setAttribute("show", 1); - if (this.$autoFocus) this.$autoFocus.focus(); - } + show() { + this.$el.setAttribute('show', 1); + if (this.$autoFocus) this.$autoFocus.focus(); + } - hide() { - this.$el.removeAttribute("show"); - document.activeElement.blur(); - window.blur(); - } + hide() { + this.$el.removeAttribute('show'); + document.activeElement.blur(); + window.blur(); + } } class ReceiveDialog extends Dialog { - constructor() { - super("receiveDialog"); - Events.on("file-received", (e) => { - this._nextFile(e.detail); - window.blop.play(); - }); - this._filesQueue = []; - } - _nextFile(nextFile) { - if (nextFile) this._filesQueue.push(nextFile); - if (this._busy) return; - this._busy = true; - const file = this._filesQueue.shift(); - this._displayFile(file); - } - - _dequeueFile() { - if (!this._filesQueue.length) { - // nothing to do - this._busy = false; - return; - } - // dequeue next file - setTimeout((_) => { - this._busy = false; - this._nextFile(); - }, 300); - } - - _displayFile(file) { - const $a = this.$el.querySelector("#download"); - const url = URL.createObjectURL(file.blob); - $a.href = 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; + constructor() { + super('receiveDialog'); + Events.on('file-received', e => { + this._nextFile(e.detail); + window.blop.play(); + }); + this._filesQueue = []; } - this.$el.querySelector("#fileName").textContent = file.name; - this.$el.querySelector("#fileSize").textContent = this._formatFileSize( - file.size - ); - this.show(); - - if (window.isDownloadSupported) return; - // fallback for iOS - $a.target = "_blank"; - const reader = new FileReader(); - reader.onload = (e) => ($a.href = reader.result); - 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"; + _nextFile(nextFile) { + if (nextFile) this._filesQueue.push(nextFile); + if (this._busy) return; + this._busy = true; + const file = this._filesQueue.shift(); + this._displayFile(file); } - } - hide() { - this.$el.querySelector(".preview").style.visibility = "hidden"; - this.$el.querySelector("#img-preview").src = ""; - super.hide(); - this._dequeueFile(); - } + _dequeueFile() { + if (!this._filesQueue.length) { // nothing to do + this._busy = false; + return; + } + // dequeue next file + setTimeout(_ => { + this._busy = false; + this._nextFile(); + }, 300); + } - _autoDownload() { - return !this.$el.querySelector("#autoDownload").checked; - } + _displayFile(file) { + const $a = this.$el.querySelector('#download'); + const url = URL.createObjectURL(file.blob); + $a.href = 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; + } + + this.$el.querySelector('#fileName').textContent = file.name; + this.$el.querySelector('#fileSize').textContent = this._formatFileSize(file.size); + this.show(); + + if (window.isDownloadSupported) return; + // fallback for iOS + $a.target = '_blank'; + const reader = new FileReader(); + reader.onload = e => $a.href = reader.result; + 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 = ""; + super.hide(); + this._dequeueFile(); + } + + + _autoDownload(){ + return !this.$el.querySelector('#autoDownload').checked + } } + class SendTextDialog extends Dialog { - constructor() { - super("sendTextDialog"); - Events.on("text-recipient", (e) => this._onRecipient(e.detail)); - this.$text = this.$el.querySelector("#textInput"); - const button = this.$el.querySelector("form"); - button.addEventListener("submit", (e) => this._send(e)); - } + constructor() { + super('sendTextDialog'); + Events.on('text-recipient', e => this._onRecipient(e.detail)) + this.$text = this.$el.querySelector('#textInput'); + const button = this.$el.querySelector('form'); + button.addEventListener('submit', e => this._send(e)); + } - _onRecipient(recipient) { - this._recipient = recipient; - this._handleShareTargetText(); - this.show(); + _onRecipient(recipient) { + this._recipient = recipient; + this._handleShareTargetText(); + this.show(); - const range = document.createRange(); - const sel = window.getSelection(); + const range = document.createRange(); + const sel = window.getSelection(); - range.selectNodeContents(this.$text); - sel.removeAllRanges(); - sel.addRange(range); - } + range.selectNodeContents(this.$text); + sel.removeAllRanges(); + sel.addRange(range); - _handleShareTargetText() { - if (!window.shareTargetText) return; - this.$text.textContent = window.shareTargetText; - window.shareTargetText = ""; - } + } - _send(e) { - e.preventDefault(); - Events.fire("send-text", { - to: this._recipient, - text: this.$text.innerText, - }); - } + _handleShareTargetText() { + if (!window.shareTargetText) return; + this.$text.textContent = window.shareTargetText; + window.shareTargetText = ''; + } + + _send(e) { + e.preventDefault(); + Events.fire('send-text', { + to: this._recipient, + text: this.$text.innerText + }); + } } class ReceiveTextDialog extends Dialog { - constructor() { - super("receiveTextDialog"); - Events.on("text-received", (e) => this._onText(e.detail)); - this.$text = this.$el.querySelector("#text"); - const $copy = this.$el.querySelector("#copy"); - copy.addEventListener("click", (_) => this._onCopy()); - } - - _onText(e) { - this.$text.innerHTML = ""; - const text = e.text; - if (isURL(text)) { - const $a = document.createElement("a"); - $a.href = text; - $a.target = "_blank"; - $a.textContent = text; - this.$text.appendChild($a); - } else { - this.$text.textContent = text; + constructor() { + super('receiveTextDialog'); + Events.on('text-received', e => this._onText(e.detail)) + this.$text = this.$el.querySelector('#text'); + const $copy = this.$el.querySelector('#copy'); + copy.addEventListener('click', _ => this._onCopy()); } - this.show(); - window.blop.play(); - } - async _onCopy() { - await navigator.clipboard.writeText(this.$text.textContent); - Events.fire("notify-user", "Copied to clipboard"); - } + _onText(e) { + this.$text.innerHTML = ''; + const text = e.text; + if (isURL(text)) { + const $a = document.createElement('a'); + $a.href = text; + $a.target = '_blank'; + $a.textContent = text; + this.$text.appendChild($a); + } else { + this.$text.textContent = text; + } + this.show(); + window.blop.play(); + } + + async _onCopy() { + await navigator.clipboard.writeText(this.$text.textContent); + Events.fire('notify-user', 'Copied to clipboard'); + } } class Toast extends Dialog { - constructor() { - super("toast"); - Events.on("notify-user", (e) => this._onNotfiy(e.detail)); - } + constructor() { + super('toast'); + Events.on('notify-user', e => this._onNotfiy(e.detail)); + } - _onNotfiy(message) { - this.$el.textContent = message; - this.show(); - setTimeout((_) => this.hide(), 3000); - } + _onNotfiy(message) { + this.$el.textContent = message; + this.show(); + setTimeout(_ => this.hide(), 3000); + } } + class Notifications { - constructor() { - // Check if the browser supports notifications - if (!("Notification" in window)) return; - // Check whether notification permissions have already been granted - if (Notification.permission !== "granted") { - this.$button = $("notification"); - 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) - ); - } + constructor() { + // Check if the browser supports notifications + if (!('Notification' in window)) return; - _requestPermission() { - Notification.requestPermission((permission) => { - if (permission !== "granted") { - Events.fire("notify-user", Notifications.PERMISSION_ERROR || "Error"); - return; - } - this._notify("Even more snappy sharing!"); - this.$button.setAttribute("hidden", 1); - }); - } - - _notify(message, body, closeTimeout = 20000) { - const config = { - body: body, - icon: "/images/logo_transparent_128x128.png", - }; - let notification; - try { - notification = new Notification(message, config); - } catch (e) { - // Android doesn't support "new Notification" if service worker is installed - if (!serviceWorker || !serviceWorker.showNotification) return; - notification = serviceWorker.showNotification(message, config); + // Check whether notification permissions have already been granted + if (Notification.permission !== 'granted') { + this.$button = $('notification'); + 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)); } - // Notification is persistent on Android. We have to close it manually - if (closeTimeout) { - setTimeout((_) => notification.close(), closeTimeout); + _requestPermission() { + Notification.requestPermission(permission => { + if (permission !== 'granted') { + Events.fire('notify-user', Notifications.PERMISSION_ERROR || 'Error'); + return; + } + this._notify('Even more snappy sharing!'); + this.$button.setAttribute('hidden', 1); + }); } - return notification; - } + _notify(message, body, closeTimeout = 20000) { + const config = { + body: body, + icon: '/images/logo_transparent_128x128.png', + } + let notification; + try { + notification = new Notification(message, config); + } catch (e) { + // Android doesn't support "new Notification" if service worker is installed + if (!serviceWorker || !serviceWorker.showNotification) return; + notification = serviceWorker.showNotification(message, config); + } - _messageNotification(message) { - if (isURL(message)) { - const notification = this._notify(message, "Click to open link"); - this._bind(notification, (e) => - window.open(message, "_blank", null, true) - ); - } else { - const notification = this._notify(message, "Click to copy text"); - this._bind(notification, (e) => this._copyText(message, notification)); + // Notification is persistent on Android. We have to close it manually + if (closeTimeout) { + setTimeout(_ => notification.close(), closeTimeout); + } + + return notification; } - } - _downloadNotification(message) { - const notification = this._notify(message, "Click to download"); - if (!window.isDownloadSupported) return; - this._bind(notification, (e) => this._download(notification)); - } - - _download(notification) { - document.querySelector("x-dialog [download]").click(); - notification.close(); - } - - _copyText(message, notification) { - notification.close(); - if (!navigator.clipboard.writeText(message)) return; - this._notify("Copied text to clipboard"); - } - - _bind(notification, handler) { - if (notification.then) { - notification.then((e) => - serviceWorker.getNotifications().then((notifications) => { - serviceWorker.addEventListener("notificationclick", handler); - }) - ); - } else { - notification.onclick = handler; + _messageNotification(message) { + if (isURL(message)) { + const notification = this._notify(message, 'Click to open link'); + this._bind(notification, e => window.open(message, '_blank', null, true)); + } else { + const notification = this._notify(message, 'Click to copy text'); + this._bind(notification, e => this._copyText(message, notification)); + } + } + + _downloadNotification(message) { + const notification = this._notify(message, 'Click to download'); + if (!window.isDownloadSupported) return; + this._bind(notification, e => this._download(notification)); + } + + _download(notification) { + document.querySelector('x-dialog [download]').click(); + notification.close(); + } + + _copyText(message, notification) { + notification.close(); + if (!navigator.clipboard.writeText(message)) return; + this._notify('Copied text to clipboard'); + } + + _bind(notification, handler) { + if (notification.then) { + notification.then(e => serviceWorker.getNotifications().then(notifications => { + serviceWorker.addEventListener('notificationclick', handler); + })); + } else { + notification.onclick = handler; + } } - } } + class NetworkStatusUI { - constructor() { - window.addEventListener( - "offline", - (e) => this._showOfflineMessage(), - false - ); - window.addEventListener("online", (e) => this._showOnlineMessage(), false); - if (!navigator.onLine) this._showOfflineMessage(); - } - _showOfflineMessage() { - Events.fire("notify-user", "You are offline"); - } + constructor() { + window.addEventListener('offline', e => this._showOfflineMessage(), false); + window.addEventListener('online', e => this._showOnlineMessage(), false); + if (!navigator.onLine) this._showOfflineMessage(); + } - _showOnlineMessage() { - Events.fire("notify-user", "You are back online"); - } + _showOfflineMessage() { + Events.fire('notify-user', 'You are offline'); + } + + _showOnlineMessage() { + Events.fire('notify-user', 'You are back online'); + } } class WebShareTargetUI { - constructor() { - const parsedUrl = new URL(window.location); - const title = parsedUrl.searchParams.get("title"); - const text = parsedUrl.searchParams.get("text"); - const url = parsedUrl.searchParams.get("url"); + constructor() { + const parsedUrl = new URL(window.location); + const title = parsedUrl.searchParams.get('title'); + const text = parsedUrl.searchParams.get('text'); + const url = parsedUrl.searchParams.get('url'); - let shareTargetText = title ? title : ""; - shareTargetText += text ? (shareTargetText ? " " + text : text) : ""; + let shareTargetText = title ? title : ''; + shareTargetText += text ? shareTargetText ? ' ' + text : text : ''; - if (url) shareTargetText = url; // We share only the Link - no text. Because link-only text becomes clickable. + if(url) shareTargetText = url; // We share only the Link - no text. Because link-only text becomes clickable. - if (!shareTargetText) return; - window.shareTargetText = shareTargetText; - history.pushState({}, "URL Rewrite", "/"); - console.log("Shared Target Text:", '"' + shareTargetText + '"'); - } + if (!shareTargetText) return; + window.shareTargetText = shareTargetText; + history.pushState({}, 'URL Rewrite', '/'); + console.log('Shared Target Text:', '"' + shareTargetText + '"'); + } } + class Snapdrop { - constructor() { - const server = new ServerConnection(); - const peers = new PeersManager(server); - const peersUI = new PeersUI(); - Events.on("load", (e) => { - const receiveDialog = new ReceiveDialog(); - const sendTextDialog = new SendTextDialog(); - const receiveTextDialog = new ReceiveTextDialog(); - const toast = new Toast(); - const notifications = new Notifications(); - const networkStatusUI = new NetworkStatusUI(); - const webShareTargetUI = new WebShareTargetUI(); - }); - } + constructor() { + const server = new ServerConnection(); + const peers = new PeersManager(server); + const peersUI = new PeersUI(); + Events.on('load', e => { + const receiveDialog = new ReceiveDialog(); + const sendTextDialog = new SendTextDialog(); + const receiveTextDialog = new ReceiveTextDialog(); + const toast = new Toast(); + const notifications = new Notifications(); + const networkStatusUI = new NetworkStatusUI(); + const webShareTargetUI = new WebShareTargetUI(); + }); + } } const snapdrop = new Snapdrop(); -if ("serviceWorker" in navigator) { - navigator.serviceWorker - .register("/service-worker.js") - .then((serviceWorker) => { - console.log("Service Worker registered"); - window.serviceWorker = serviceWorker; - }); + + +if ('serviceWorker' in navigator) { + navigator.serviceWorker.register('/service-worker.js') + .then(serviceWorker => { + console.log('Service Worker registered'); + window.serviceWorker = serviceWorker + }); } -window.addEventListener("beforeinstallprompt", (e) => { - if (window.matchMedia("(display-mode: standalone)").matches) { - // don't display install banner when installed - return e.preventDefault(); - } else { - const btn = document.querySelector("#install"); - btn.hidden = false; - btn.onclick = (_) => e.prompt(); - return e.preventDefault(); - } +window.addEventListener('beforeinstallprompt', e => { + if (window.matchMedia('(display-mode: standalone)').matches) { + // don't display install banner when installed + return e.preventDefault(); + } else { + const btn = document.querySelector('#install') + btn.hidden = false; + btn.onclick = _ => e.prompt(); + return e.preventDefault(); + } }); // Background Animation -Events.on("load", () => { - let c = document.createElement("canvas"); - document.body.appendChild(c); - let style = c.style; - style.width = "100%"; - style.position = "absolute"; - style.zIndex = -1; - style.top = 0; - style.left = 0; - let ctx = c.getContext("2d"); - let x0, y0, w, h, dw; +Events.on('load', () => { + let c = document.createElement('canvas'); + document.body.appendChild(c); + let style = c.style; + style.width = '100%'; + style.position = 'absolute'; + style.zIndex = -1; + style.top = 0; + style.left = 0; + let ctx = c.getContext('2d'); + let x0, y0, w, h, dw; - function init() { - w = window.innerWidth; - h = window.innerHeight; - c.width = w; - c.height = h; - let offset = h > 380 ? 100 : 65; - offset = h > 800 ? 116 : offset; - x0 = w / 2; - y0 = h - offset; - dw = Math.max(w, h, 1000) / 13; - drawCircles(); - } - window.onresize = init; - - function drawCircle(radius) { - ctx.beginPath(); - let color = Math.round(255 * (1 - radius / Math.max(w, h))); - ctx.strokeStyle = "rgba(" + color + "," + color + "," + color + ",0.1)"; - ctx.arc(x0, y0, radius, 0, 2 * Math.PI); - ctx.stroke(); - ctx.lineWidth = 2; - } - - let step = 0; - - function drawCircles() { - ctx.clearRect(0, 0, w, h); - for (let i = 0; i < 8; i++) { - drawCircle(dw * i + (step % dw)); - } - step += 1; - } - - let loading = true; - - function animate() { - if (loading || step % dw < dw - 5) { - requestAnimationFrame(function () { + function init() { + w = window.innerWidth; + h = window.innerHeight; + c.width = w; + c.height = h; + let offset = h > 380 ? 100 : 65; + offset = h > 800 ? 116 : offset; + x0 = w / 2; + y0 = h - offset; + dw = Math.max(w, h, 1000) / 13; drawCircles(); - animate(); - }); } - } - window.animateBackground = function (l) { - loading = l; + window.onresize = init; + + function drawCircle(radius) { + ctx.beginPath(); + let color = Math.round(255 * (1 - radius / Math.max(w, h))); + ctx.strokeStyle = 'rgba(' + color + ',' + color + ',' + color + ',0.1)'; + ctx.arc(x0, y0, radius, 0, 2 * Math.PI); + ctx.stroke(); + ctx.lineWidth = 2; + } + + let step = 0; + + function drawCircles() { + ctx.clearRect(0, 0, w, h); + for (let i = 0; i < 8; i++) { + drawCircle(dw * i + step % dw); + } + step += 1; + } + + let loading = true; + + function animate() { + if (loading || step % dw < dw - 5) { + requestAnimationFrame(function() { + drawCircles(); + animate(); + }); + } + } + window.animateBackground = function(l) { + loading = l; + animate(); + }; + init(); animate(); - }; - init(); - animate(); }); Notifications.PERMISSION_ERROR = ` @@ -644,9 +636,8 @@ as the user has dismissed the permission prompt several times. This can be reset in Page Info which can be accessed by clicking the lock icon next to the URL.`; -document.body.onclick = (e) => { - // safari hack to fix audio - document.body.onclick = null; - if (!/.*Version.*Safari.*/.test(navigator.userAgent)) return; - blop.play(); -}; +document.body.onclick = e => { // safari hack to fix audio + document.body.onclick = null; + if (!(/.*Version.*Safari.*/.test(navigator.userAgent))) return; + blop.play(); +}