diff --git a/public/index.html b/public/index.html index b71028d..06ff099 100644 --- a/public/index.html +++ b/public/index.html @@ -35,7 +35,7 @@ - + @@ -599,13 +599,8 @@ + - - - - - - diff --git a/public/scripts/main.js b/public/scripts/main.js index 4e412eb..056bb22 100644 --- a/public/scripts/main.js +++ b/public/scripts/main.js @@ -1,179 +1,3 @@ -class FooterUI { - - constructor() { - this.$displayName = $('display-name'); - this.$discoveryWrapper = $$('footer .discovery-wrapper'); - - // Show "Loading…" - this.$displayName.setAttribute('placeholder', this.$displayName.dataset.placeholder); - - this.$displayName.addEventListener('keydown', e => this._onKeyDownDisplayName(e)); - this.$displayName.addEventListener('keyup', e => this._onKeyUpDisplayName(e)); - this.$displayName.addEventListener('blur', e => this._saveDisplayName(e.target.innerText)); - - Events.on('display-name', e => this._onDisplayName(e.detail.displayName)); - Events.on('self-display-name-changed', e => this._insertDisplayName(e.detail)); - - // Load saved display name on page load - Events.on('ws-connected', _ => this._loadSavedDisplayName()); - - Events.on('evaluate-footer-badges', _ => this._evaluateFooterBadges()); - } - - _evaluateFooterBadges() { - if (this.$discoveryWrapper.querySelectorAll('div:last-of-type > span[hidden]').length < 2) { - this.$discoveryWrapper.classList.remove('row'); - this.$discoveryWrapper.classList.add('column'); - } - else { - this.$discoveryWrapper.classList.remove('column'); - this.$discoveryWrapper.classList.add('row'); - } - Events.fire('redraw-canvas'); - Events.fire('fade-in-ui'); - } - - _loadSavedDisplayName() { - this._getSavedDisplayName() - .then(displayName => { - console.log("Retrieved edited display name:", displayName) - if (displayName) { - Events.fire('self-display-name-changed', displayName); - } - }); - } - - _onDisplayName(displayName){ - console.debug(displayName) - // set display name - this.$displayName.setAttribute('placeholder', displayName); - } - - - _insertDisplayName(displayName) { - this.$displayName.textContent = displayName; - } - - _onKeyDownDisplayName(e) { - if (e.key === "Enter" || e.key === "Escape") { - e.preventDefault(); - e.target.blur(); - } - } - - _onKeyUpDisplayName(e) { - // fix for Firefox inserting a linebreak into div on edit which prevents the placeholder from showing automatically when it is empty - if (/^(\n|\r|\r\n)$/.test(e.target.innerText)) e.target.innerText = ''; - } - - async _saveDisplayName(newDisplayName) { - newDisplayName = newDisplayName.replace(/(\n|\r|\r\n)/, '') - const savedDisplayName = await this._getSavedDisplayName(); - if (newDisplayName === savedDisplayName) return; - - if (newDisplayName) { - PersistentStorage.set('editedDisplayName', newDisplayName) - .then(_ => { - Events.fire('notify-user', Localization.getTranslation("notifications.display-name-changed-permanently")); - }) - .catch(_ => { - console.log("This browser does not support IndexedDB. Use localStorage instead."); - localStorage.setItem('editedDisplayName', newDisplayName); - Events.fire('notify-user', Localization.getTranslation("notifications.display-name-changed-temporarily")); - }) - .finally(() => { - Events.fire('self-display-name-changed', newDisplayName); - Events.fire('broadcast-send', {type: 'self-display-name-changed', detail: newDisplayName}); - }); - } - else { - PersistentStorage.delete('editedDisplayName') - .catch(_ => { - console.log("This browser does not support IndexedDB. Use localStorage instead.") - localStorage.removeItem('editedDisplayName'); - }) - .finally(() => { - Events.fire('notify-user', Localization.getTranslation("notifications.display-name-random-again")); - Events.fire('self-display-name-changed', ''); - Events.fire('broadcast-send', {type: 'self-display-name-changed', detail: ''}); - }); - } - } - - _getSavedDisplayName() { - return new Promise((resolve) => { - PersistentStorage.get('editedDisplayName') - .then(displayName => { - if (!displayName) displayName = ""; - resolve(displayName); - }) - .catch(_ => { - let displayName = localStorage.getItem('editedDisplayName'); - if (!displayName) displayName = ""; - resolve(displayName); - }) - }); - } -} - -class BackgroundCanvas { - constructor() { - this.c = $$('canvas'); - this.cCtx = this.c.getContext('2d'); - this.$footer = $$('footer'); - - // fade-in on load - Events.on('fade-in-ui', _ => this._fadeIn()); - - // redraw canvas - Events.on('resize', _ => this.init()); - Events.on('redraw-canvas', _ => this.init()); - Events.on('translation-loaded', _ => this.init()); - } - - _fadeIn() { - this.c.classList.remove('opacity-0'); - } - - init() { - let oldW = this.w; - let oldH = this.h; - let oldOffset = this.offset - this.w = document.documentElement.clientWidth; - this.h = document.documentElement.clientHeight; - this.offset = this.$footer.offsetHeight - 27; - if (this.h >= 800) this.offset += 10; - - if (oldW === this.w && oldH === this.h && oldOffset === this.offset) return; // nothing has changed - - this.c.width = this.w; - this.c.height = this.h; - this.x0 = this.w / 2; - this.y0 = this.h - this.offset; - this.dw = Math.round(Math.max(this.w, this.h, 1000) / 13); - - this.drawCircles(this.cCtx); - } - - - drawCircle(ctx, radius) { - ctx.beginPath(); - ctx.lineWidth = 2; - let opacity = Math.max(0, 0.3 * (1 - 1 * radius / Math.max(this.w, this.h))); - ctx.strokeStyle = `rgba(128, 128, 128, ${opacity})`; - ctx.arc(this.x0, this.y0, radius, 0, 2 * Math.PI); - ctx.stroke(); - } - - drawCircles(ctx) { - ctx.clearRect(0, 0, this.w, this.h); - for (let i = 0; i < 13; i++) { - this.drawCircle(ctx, this.dw * i + 33 + 66); - } - } -} - - class PairDrop { constructor() { this.$header = $$('header.opacity-0'); @@ -186,6 +10,12 @@ class PairDrop { this.$head = $$('head'); + this.$installBtn = $('install'); + + this.registerServiceWorker(); + + Events.on('beforeinstallprompt', e => this.onPwaInstallable(e)); + Events.on('initial-translation-loaded', _ => { const backgroundCanvas = new BackgroundCanvas(); const footerUI = new FooterUI(); @@ -196,11 +26,34 @@ class PairDrop { // Evaluate UI elements and fade in UI this.evaluateUI(); - // Load delayed assets + // Load deferred assets this.loadDeferredAssets(); }); } + registerServiceWorker() { + if ('serviceWorker' in navigator) { + navigator.serviceWorker + .register('/service-worker.js') + .then(serviceWorker => { + console.log('Service Worker registered'); + window.serviceWorker = serviceWorker + }); + } + } + + onPwaInstallable(e) { + if (!window.matchMedia('(display-mode: minimal-ui)').matches) { + // only display install btn when not installed + this.$installBtn.removeAttribute('hidden'); + this.$installBtn.addEventListener('click', () => { + this.$installBtn.setAttribute('hidden', true); + e.prompt(); + }); + } + return e.preventDefault(); + } + evaluateUI() { // Check whether notification permissions have already been granted if ('Notification' in window && Notification.permission !== 'granted') { @@ -236,14 +89,20 @@ class PairDrop { } loadDeferredAssets() { - console.debug("Load deferred assets"); - if (document.readyState === "loading") { - // Loading hasn't finished yet - Events.on('DOMContentLoaded', _ => this.hydrate()); - } else { - // `DOMContentLoaded` has already fired - this.hydrate(); - } + console.log("Load deferred assets"); + this.deferredStyles = [ + "styles/deferred-styles.css" + ]; + this.deferredScripts = [ + "scripts/util.js", + "scripts/network.js", + "scripts/ui.js", + "scripts/qr-code.min.js", + "scripts/zip.min.js", + "scripts/no-sleep.min.js" + ]; + this.deferredStyles.forEach(url => this.loadStyleSheet(url, _ => this.onStyleLoaded(url))) + this.deferredScripts.forEach(url => this.loadScript(url, _ => this.onScriptLoaded(url))) } loadStyleSheet(url, callback) { @@ -255,53 +114,63 @@ class PairDrop { this.$head.appendChild(stylesheet); } + loadScript(url, callback) { + let script = document.createElement("script"); + script.src = url; + script.onload = callback; + document.body.appendChild(script); + } + + onStyleLoaded(url) { + // remove entry from array + const index = this.deferredStyles.indexOf(url); + if (index !== -1) { + this.deferredStyles.splice(index, 1); + } + this.onAssetLoaded(); + } + + onScriptLoaded(url) { + // remove entry from array + const index = this.deferredScripts.indexOf(url); + if (index !== -1) { + this.deferredScripts.splice(index, 1); + } + this.onAssetLoaded(); + } + + onAssetLoaded() { + if (this.deferredScripts.length || this.deferredStyles.length) return; + + console.log("Loading of deferred assets completed. Start UI hydration."); + + this.hydrate(); + } + hydrate() { - this.loadStyleSheet('styles/deferred-styles.css', _ => { - const peersUI = new PeersUI(); - const languageSelectDialog = new LanguageSelectDialog(); - const receiveFileDialog = new ReceiveFileDialog(); - const receiveRequestDialog = new ReceiveRequestDialog(); - const sendTextDialog = new SendTextDialog(); - const receiveTextDialog = new ReceiveTextDialog(); - const pairDeviceDialog = new PairDeviceDialog(); - const clearDevicesDialog = new EditPairedDevicesDialog(); - const publicRoomDialog = new PublicRoomDialog(); - const base64ZipDialog = new Base64ZipDialog(); - const toast = new Toast(); - const notifications = new Notifications(); - const networkStatusUI = new NetworkStatusUI(); - const webShareTargetUI = new WebShareTargetUI(); - const webFileHandlersUI = new WebFileHandlersUI(); - const noSleepUI = new NoSleepUI(); - const broadCast = new BrowserTabsConnector(); - const server = new ServerConnection(); - const peers = new PeersManager(server); - }); + const peersUI = new PeersUI(); + const languageSelectDialog = new LanguageSelectDialog(); + const receiveFileDialog = new ReceiveFileDialog(); + const receiveRequestDialog = new ReceiveRequestDialog(); + const sendTextDialog = new SendTextDialog(); + const receiveTextDialog = new ReceiveTextDialog(); + const pairDeviceDialog = new PairDeviceDialog(); + const clearDevicesDialog = new EditPairedDevicesDialog(); + const publicRoomDialog = new PublicRoomDialog(); + const base64ZipDialog = new Base64ZipDialog(); + const toast = new Toast(); + const notifications = new Notifications(); + const networkStatusUI = new NetworkStatusUI(); + const webShareTargetUI = new WebShareTargetUI(); + const webFileHandlersUI = new WebFileHandlersUI(); + const noSleepUI = new NoSleepUI(); + const broadCast = new BrowserTabsConnector(); + const server = new ServerConnection(); + const peers = new PeersManager(server); + console.log("UI hydrated.") } } const persistentStorage = new PersistentStorage(); const pairDrop = new PairDrop(); -const localization = new Localization(); - -if ('serviceWorker' in navigator) { - navigator.serviceWorker - .register('/service-worker.js') - .then(serviceWorker => { - console.log('Service Worker registered'); - window.serviceWorker = serviceWorker - }); -} - -window.addEventListener('beforeinstallprompt', installEvent => { - if (!window.matchMedia('(display-mode: minimal-ui)').matches) { - // only display install btn when not installed - const installBtn = $('install') - installBtn.removeAttribute('hidden'); - installBtn.addEventListener('click', () => { - installBtn.setAttribute('hidden', true); - installEvent.prompt(); - }); - } - return installEvent.preventDefault(); -}); \ No newline at end of file +const localization = new Localization(); \ No newline at end of file diff --git a/public/scripts/ui-main.js b/public/scripts/ui-main.js new file mode 100644 index 0000000..a9c9f6d --- /dev/null +++ b/public/scripts/ui-main.js @@ -0,0 +1,173 @@ +class FooterUI { + + constructor() { + this.$displayName = $('display-name'); + this.$discoveryWrapper = $$('footer .discovery-wrapper'); + + // Show "Loading…" + this.$displayName.setAttribute('placeholder', this.$displayName.dataset.placeholder); + + this.$displayName.addEventListener('keydown', e => this._onKeyDownDisplayName(e)); + this.$displayName.addEventListener('keyup', e => this._onKeyUpDisplayName(e)); + this.$displayName.addEventListener('blur', e => this._saveDisplayName(e.target.innerText)); + + Events.on('display-name', e => this._onDisplayName(e.detail.displayName)); + Events.on('self-display-name-changed', e => this._insertDisplayName(e.detail)); + + // Load saved display name on page load + Events.on('ws-connected', _ => this._loadSavedDisplayName()); + + Events.on('evaluate-footer-badges', _ => this._evaluateFooterBadges()); + } + + _evaluateFooterBadges() { + if (this.$discoveryWrapper.querySelectorAll('div:last-of-type > span[hidden]').length < 2) { + this.$discoveryWrapper.classList.remove('row'); + this.$discoveryWrapper.classList.add('column'); + } + else { + this.$discoveryWrapper.classList.remove('column'); + this.$discoveryWrapper.classList.add('row'); + } + Events.fire('redraw-canvas'); + Events.fire('fade-in-ui'); + } + + _loadSavedDisplayName() { + this._getSavedDisplayName() + .then(displayName => { + console.log("Retrieved edited display name:", displayName) + if (displayName) { + Events.fire('self-display-name-changed', displayName); + } + }); + } + + _onDisplayName(displayName){ + // set display name + this.$displayName.setAttribute('placeholder', displayName); + } + + + _insertDisplayName(displayName) { + this.$displayName.textContent = displayName; + } + + _onKeyDownDisplayName(e) { + if (e.key === "Enter" || e.key === "Escape") { + e.preventDefault(); + e.target.blur(); + } + } + + _onKeyUpDisplayName(e) { + // fix for Firefox inserting a linebreak into div on edit which prevents the placeholder from showing automatically when it is empty + if (/^(\n|\r|\r\n)$/.test(e.target.innerText)) e.target.innerText = ''; + } + + async _saveDisplayName(newDisplayName) { + newDisplayName = newDisplayName.replace(/(\n|\r|\r\n)/, '') + const savedDisplayName = await this._getSavedDisplayName(); + if (newDisplayName === savedDisplayName) return; + + if (newDisplayName) { + PersistentStorage.set('editedDisplayName', newDisplayName) + .then(_ => { + Events.fire('notify-user', Localization.getTranslation("notifications.display-name-changed-permanently")); + }) + .catch(_ => { + console.log("This browser does not support IndexedDB. Use localStorage instead."); + localStorage.setItem('editedDisplayName', newDisplayName); + Events.fire('notify-user', Localization.getTranslation("notifications.display-name-changed-temporarily")); + }) + .finally(() => { + Events.fire('self-display-name-changed', newDisplayName); + Events.fire('broadcast-send', {type: 'self-display-name-changed', detail: newDisplayName}); + }); + } + else { + PersistentStorage.delete('editedDisplayName') + .catch(_ => { + console.log("This browser does not support IndexedDB. Use localStorage instead.") + localStorage.removeItem('editedDisplayName'); + }) + .finally(() => { + Events.fire('notify-user', Localization.getTranslation("notifications.display-name-random-again")); + Events.fire('self-display-name-changed', ''); + Events.fire('broadcast-send', {type: 'self-display-name-changed', detail: ''}); + }); + } + } + + _getSavedDisplayName() { + return new Promise((resolve) => { + PersistentStorage.get('editedDisplayName') + .then(displayName => { + if (!displayName) displayName = ""; + resolve(displayName); + }) + .catch(_ => { + let displayName = localStorage.getItem('editedDisplayName'); + if (!displayName) displayName = ""; + resolve(displayName); + }) + }); + } +} + +class BackgroundCanvas { + constructor() { + this.c = $$('canvas'); + this.cCtx = this.c.getContext('2d'); + this.$footer = $$('footer'); + + // fade-in on load + Events.on('fade-in-ui', _ => this._fadeIn()); + + // redraw canvas + Events.on('resize', _ => this.init()); + Events.on('redraw-canvas', _ => this.init()); + Events.on('translation-loaded', _ => this.init()); + } + + _fadeIn() { + this.c.classList.remove('opacity-0'); + } + + init() { + let oldW = this.w; + let oldH = this.h; + let oldOffset = this.offset + this.w = document.documentElement.clientWidth; + this.h = document.documentElement.clientHeight; + this.offset = this.$footer.offsetHeight - 27; + if (this.h >= 800) this.offset += 10; + + if (oldW === this.w && oldH === this.h && oldOffset === this.offset) return; // nothing has changed + + this.c.width = this.w; + this.c.height = this.h; + this.x0 = this.w / 2; + this.y0 = this.h - this.offset; + this.dw = Math.round(Math.max(this.w, this.h, 1000) / 13); + + this.drawCircles(this.cCtx); + } + + + drawCircle(ctx, radius) { + ctx.beginPath(); + ctx.lineWidth = 2; + let opacity = Math.max(0, 0.3 * (1 - 1 * radius / Math.max(this.w, this.h))); + ctx.strokeStyle = `rgba(128, 128, 128, ${opacity})`; + ctx.arc(this.x0, this.y0, radius, 0, 2 * Math.PI); + ctx.stroke(); + } + + drawCircles(ctx) { + ctx.clearRect(0, 0, this.w, this.h); + for (let i = 0; i < 13; i++) { + this.drawCircle(ctx, this.dw * i + 33 + 66); + } + } +} \ No newline at end of file diff --git a/public/styles/main-styles.css b/public/styles/styles-main.css similarity index 100% rename from public/styles/main-styles.css rename to public/styles/styles-main.css