Speed up initial load while still preventing css layout shift; Tidy up ui.js

This commit is contained in:
schlagmichdoch 2023-10-11 23:22:10 +02:00
parent 2578803a78
commit ed2f1b0c61
8 changed files with 353 additions and 312 deletions

View file

@ -38,7 +38,7 @@
</head>
<body translate="no">
<header class="row-reverse">
<header class="row-reverse opacity-0">
<a href="#about" class="icon-button" data-i18n-key="header.about" data-i18n-attrs="title aria-label">
<svg class="icon">
<use xlink:href="#info-outline"></use>
@ -96,11 +96,11 @@
<div id="cancel-paste-mode" class="button" data-i18n-key="header.cancel-paste-mode" data-i18n-attrs="text" hidden></div>
</header>
<!-- Center -->
<div id="center">
<div id="center" class="opacity-0">
<!-- Peers -->
<div class="x-peers-filler"></div>
<x-peers class="center"></x-peers>
<x-no-peers data-i18n-key="instructions.no-peers" data-i18n-attrs="data-drop-bg">
<x-no-peers class="no-animation-on-load" data-i18n-key="instructions.no-peers" data-i18n-attrs="data-drop-bg">
<h2 data-i18n-key="instructions.no-peers-title" data-i18n-attrs="text"></h2>
<div data-i18n-key="instructions.no-peers-subtitle" data-i18n-attrs="text"></div>
</x-no-peers>
@ -109,7 +109,7 @@
</x-instructions>
</div>
<!-- Footer -->
<footer class="column">
<footer class="column opacity-0">
<svg class="icon logo">
<use xlink:href="#wifi-tethering"></use>
</svg>
@ -466,14 +466,14 @@
</div>
<!-- About Page -->
<x-about id="about" class="full center column">
<header class="row-reverse fade-in">
<header class="row-reverse">
<a href="#" class="close icon-button" data-i18n-key="about.close-about" data-i18n-attrs="aria-label">
<svg class="icon">
<use xlink:href="#close-icon"></use>
</svg>
</a>
</header>
<section class="center column fade-in">
<section class="center column">
<svg class="icon logo">
<use xlink:href="#wifi-tethering"></use>
</svg>
@ -507,7 +507,7 @@
</section>
<x-background></x-background>
</x-about>
<canvas class="circles"></canvas>
<canvas class="circles opacity-0"></canvas>
<!-- SVG Icon Library -->
<svg style="display: none;">
<symbol id="wifi-tethering" viewBox="0 0 24 24">
@ -587,7 +587,7 @@
<script src="scripts/theme.js"></script>
<script src="scripts/network.js"></script>
<script src="scripts/ui.js"></script>
<script src="scripts/util.js" async></script>
<script src="scripts/util.js"></script>
<script src="scripts/QRCode.min.js" async></script>
<script src="scripts/zip.min.js" async></script>
<script src="scripts/NoSleep.min.js" async></script>

View file

@ -36,27 +36,15 @@ class PeersUI {
Events.on('drop', e => this._onDrop(e));
Events.on('keydown', e => this._onKeyDown(e));
this.$header = document.querySelector('body > header')
this.$xPeers = $$('x-peers');
this.$xNoPeers = $$('x-no-peers');
this.$xInstructions = $$('x-instructions');
this.$center = $$('#center');
this.$logo = $$('footer .icon.logo');
this.$footer = $$('footer');
this.$discoveryWrapper = $$('footer .discovery-wrapper');
this.$knownAsWrapper = $$('footer .known-as-wrapper');
this.$header.style.opacity = "1";
this.$xPeers.style.opacity = "1";
this.$xNoPeers.style.opacity = "1";
this.$xInstructions.style.opacity = "0.5";
this.$center.style.opacity = "1";
this.$logo.style.opacity = "1";
this.$discoveryWrapper.style.opacity = "1";
this.$knownAsWrapper.style.opacity = "1";
Events.on('peer-added', _ => this.evaluateOverflowing());
Events.on('bg-resize', _ => this.evaluateOverflowing());
Events.on('peer-added', _ => this._evaluateOverflowing());
Events.on('bg-resize', _ => this._evaluateOverflowing());
this.$displayName = $('display-name');
@ -75,11 +63,45 @@ class PeersUI {
if (displayName) Events.fire('self-display-name-changed', displayName);
});
Events.on('evaluate-footer-badges', _ => this._evaluateFooterBadges())
/* prevent animation on load */
this.fadedIn = false;
this.$header = document.querySelector('header.opacity-0');
Events.on('header-evaluated', () => this._fadeInHeader());
}
_fadeInHeader() {
//prevent flickering
setTimeout(() => this.$header.classList.remove('opacity-0'), 50);
}
_fadeInUI() {
if (this.fadedIn) return;
this.fadedIn = true;
this.$center.classList.remove('opacity-0');
this.$footer.classList.remove('opacity-0');
// Prevent flickering on load
setTimeout(_ => {
this.$xNoPeers.style.animationIterationCount = "1";
}, 300);
this.$xNoPeers.classList.remove('no-animation-on-load');
}, 600);
Events.fire('ui-faded-in');
}
_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');
this._fadeInUI();
}
_insertDisplayName(displayName) {
@ -162,6 +184,11 @@ class PeersUI {
if (document.querySelectorAll('x-dialog[show]').length === 0 && window.pasteMode.activated && e.code === "Escape") {
Events.fire('deactivate-paste-mode');
}
// close About PairDrop page on Escape
if (e.key === "Escape") {
window.location.hash = '#';
}
}
_onPeerJoined(msg) {
@ -207,7 +234,7 @@ class PeersUI {
Object.keys(peer._roomIds).forEach(roomType => peerNode.classList.add(`type-${roomType}`));
}
evaluateOverflowing() {
_evaluateOverflowing() {
if (this.$xPeers.clientHeight < this.$xPeers.scrollHeight) {
this.$xPeers.classList.add('overflowing');
} else {
@ -223,7 +250,7 @@ class PeersUI {
const $peer = $(peerId);
if (!$peer) return;
$peer.remove();
this.evaluateOverflowing();
this._evaluateOverflowing();
}
_onRoomTypeRemoved(peerId, roomType) {
@ -608,7 +635,6 @@ class Dialog {
this.$el = $(id);
this.$el.querySelectorAll('[close]').forEach(el => el.addEventListener('click', _ => this.hide()));
this.$autoFocus = this.$el.querySelector('[autofocus]');
this.$discoveryWrapper = $$('footer .discovery-wrapper');
Events.on('peer-disconnected', e => this._onPeerDisconnected(e.detail));
}
@ -629,7 +655,7 @@ class Dialog {
window.blur();
}
document.title = 'PairDrop';
document.changeFavicon("images/favicon-96x96.png");
changeFavicon("images/favicon-96x96.png");
this.correspondingPeerId = undefined;
}
@ -639,17 +665,6 @@ class Dialog {
Events.fire('notify-user', Localization.getTranslation("notifications.selected-peer-left"));
}
}
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('bg-resize');
}
}
class LanguageSelectDialog extends Dialog {
@ -929,7 +944,7 @@ class ReceiveFileDialog extends ReceiveDialog {
document.title = files.length === 1
? `${ Localization.getTranslation("document-titles.file-received") } - PairDrop`
: `${ Localization.getTranslation("document-titles.file-received-plural", null, {count: files.length}) } - PairDrop`;
document.changeFavicon("images/favicon-96x96-notification.png");
changeFavicon("images/favicon-96x96-notification.png");
Events.fire('set-progress', {peerId: peerId, progress: 1, status: 'process'})
this.show();
@ -1026,7 +1041,7 @@ class ReceiveRequestDialog extends ReceiveDialog {
this.$receiveTitle.innerText = transferRequestTitle;
document.title = `${transferRequestTitle} - PairDrop`;
document.changeFavicon("images/favicon-96x96-notification.png");
changeFavicon("images/favicon-96x96-notification.png");
this.show();
}
@ -1190,7 +1205,6 @@ class PairDeviceDialog extends Dialog {
this.$closeBtn.addEventListener('click', _ => this._close());
Events.on('keydown', e => this._onKeyDown(e));
Events.on('ws-connected', _ => this._onWsConnected());
Events.on('ws-disconnected', _ => this.hide());
Events.on('pair-device-initiated', e => this._onPairDeviceInitiated(e.detail));
Events.on('pair-device-joined', e => this._onPairDeviceJoined(e.detail.peerId, e.detail.roomSecret));
@ -1205,6 +1219,8 @@ class PairDeviceDialog extends Dialog {
this.evaluateUrlAttributes();
this.pairPeer = {};
this._evaluateNumberRoomSecrets();
}
_onKeyDown(e) {
@ -1229,10 +1245,6 @@ class PairDeviceDialog extends Dialog {
}
}
_onWsConnected() {
this._evaluateNumberRoomSecrets();
}
_pairDeviceInitiate() {
Events.fire('pair-device-initiate');
}
@ -1380,16 +1392,17 @@ class PairDeviceDialog extends Dialog {
}
_evaluateNumberRoomSecrets() {
PersistentStorage.getAllRoomSecrets().then(roomSecrets => {
if (roomSecrets.length > 0) {
this.$editPairedDevicesHeaderBtn.removeAttribute('hidden');
this.$footerInstructionsPairedDevices.removeAttribute('hidden');
} else {
this.$editPairedDevicesHeaderBtn.setAttribute('hidden', '');
this.$footerInstructionsPairedDevices.setAttribute('hidden', '');
}
super.evaluateFooterBadges();
});
PersistentStorage.getAllRoomSecrets()
.then(roomSecrets => {
if (roomSecrets.length > 0) {
this.$editPairedDevicesHeaderBtn.removeAttribute('hidden');
this.$footerInstructionsPairedDevices.removeAttribute('hidden');
} else {
this.$editPairedDevicesHeaderBtn.setAttribute('hidden', '');
this.$footerInstructionsPairedDevices.setAttribute('hidden', '');
}
Events.fire('evaluate-footer-badges');
});
}
}
@ -1397,10 +1410,10 @@ class EditPairedDevicesDialog extends Dialog {
constructor() {
super('edit-paired-devices-dialog');
this.$pairedDevicesWrapper = this.$el.querySelector('.paired-devices-wrapper');
this.$footerInstructionsPairedDevices = $$('.discovery-wrapper .badge-room-secret');
this.$footerBadgePairedDevices = $$('.discovery-wrapper .badge-room-secret');
$('edit-paired-devices').addEventListener('click', _ => this._onEditPairedDevices());
this.$footerInstructionsPairedDevices.addEventListener('click', _ => this._onEditPairedDevices());
this.$footerBadgePairedDevices.addEventListener('click', _ => this._onEditPairedDevices());
Events.on('peer-display-name-changed', e => this._onPeerDisplayNameChanged(e));
Events.on('keydown', e => this._onKeyDown(e));
@ -1508,7 +1521,7 @@ class PublicRoomDialog extends Dialog {
this.$leaveBtn = this.$el.querySelector('.leave-room');
this.$joinSubmitBtn = this.$el.querySelector('button[type="submit"]');
this.$headerBtnJoinPublicRoom = $('join-public-room');
this.$footerInstructionsPublicRoomDevices = $$('.discovery-wrapper .badge-room-public-id');
this.$footerBadgePublicRoomDevices = $$('.discovery-wrapper .badge-room-public-id');
this.$form.addEventListener('submit', e => this._onSubmit(e));
@ -1516,7 +1529,7 @@ class PublicRoomDialog extends Dialog {
this.$leaveBtn.addEventListener('click', _ => this._leavePublicRoom())
this.$headerBtnJoinPublicRoom.addEventListener('click', _ => this._onHeaderBtnClick());
this.$footerInstructionsPublicRoomDevices.addEventListener('click', _ => this._onHeaderBtnClick());
this.$footerBadgePublicRoomDevices.addEventListener('click', _ => this._onHeaderBtnClick());
this.inputKeyContainer = new InputKeyContainer(
this.$el.querySelector('.input-key-container'),
@ -1598,12 +1611,12 @@ class PublicRoomDialog extends Dialog {
setFooterBadge() {
if (!this.roomId) return;
this.$footerInstructionsPublicRoomDevices.innerText = Localization.getTranslation("footer.public-room-devices", null, {
this.$footerBadgePublicRoomDevices.innerText = Localization.getTranslation("footer.public-room-devices", null, {
roomId: this.roomId.toUpperCase()
});
this.$footerInstructionsPublicRoomDevices.removeAttribute('hidden');
this.$footerBadgePublicRoomDevices.removeAttribute('hidden');
super.evaluateFooterBadges();
Events.fire('evaluate-footer-badges');
}
_getShareRoomURL() {
@ -1715,8 +1728,8 @@ class PublicRoomDialog extends Dialog {
this.roomId = null;
this.inputKeyContainer._cleanUp();
sessionStorage.removeItem('public_room_id');
this.$footerInstructionsPublicRoomDevices.setAttribute('hidden', '');
super.evaluateFooterBadges();
this.$footerBadgePublicRoomDevices.setAttribute('hidden', '');
Events.fire('evaluate-footer-badges');
}
}
@ -1847,7 +1860,7 @@ class ReceiveTextDialog extends Dialog {
this._setDocumentTitleMessages();
document.changeFavicon("images/favicon-96x96-notification.png");
changeFavicon("images/favicon-96x96-notification.png");
this.show();
}
@ -2057,15 +2070,20 @@ class Notifications {
constructor() {
// Check if the browser supports notifications
if (!('Notification' in window)) return;
if (!('Notification' in window)) {
Events.fire('header-evaluated');
return;
}
// Check whether notification permissions have already been granted
if (Notification.permission !== 'granted') {
this.$button = $('notification');
this.$button.removeAttribute('hidden');
this.$button.addEventListener('click', _ => this._requestPermission());
this.$headerNotificationButton = $('notification');
this.$headerNotificationButton.removeAttribute('hidden');
this.$headerNotificationButton.addEventListener('click', _ => this._requestPermission());
}
Events.fire('header-evaluated');
Events.on('text-received', e => this._messageNotification(e.detail.text, e.detail.peerId));
Events.on('files-received', e => this._downloadNotification(e.detail.files));
Events.on('files-transfer-request', e => this._requestNotification(e.detail.request, e.detail.peerId));
@ -2078,7 +2096,7 @@ class Notifications {
return;
}
Events.fire('notify-user', Localization.getTranslation("notifications.notifications-enabled"));
this.$button.setAttribute('hidden', "");
this.$headerNotificationButton.setAttribute('hidden', "");
});
}
@ -2460,7 +2478,7 @@ class PersistentStorage {
return(secrets);
} catch (e) {
this.logBrowserNotCapable();
return false;
return 0;
}
}
@ -2678,12 +2696,71 @@ class BrowserTabsConnector {
}
}
class BackgroundCanvas {
constructor() {
this.c = $$('canvas');
this.cCtx = this.c.getContext('2d');
this.$footer = $$('footer');
Events.on('bg-resize', _ => this.init());
Events.on('redraw-canvas', _ => this.init());
Events.on('translation-loaded', _ => this.init());
//fade-in on load
Events.on('ui-faded-in', _ => this._fadeIn());
window.onresize = _ => Events.fire('bg-resize');
}
_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() {
Events.on('initial-translation-loaded', _ => {
const server = new ServerConnection();
const peers = new PeersManager(server);
const peersUI = new PeersUI();
const backgroundCanvas = new BackgroundCanvas();
const languageSelectDialog = new LanguageSelectDialog();
const receiveFileDialog = new ReceiveFileDialog();
const receiveRequestDialog = new ReceiveRequestDialog();
@ -2708,7 +2785,6 @@ const persistentStorage = new PersistentStorage();
const pairDrop = new PairDrop();
const localization = new Localization();
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(serviceWorker => {
@ -2726,63 +2802,3 @@ window.addEventListener('beforeinstallprompt', e => {
}
return e.preventDefault();
});
// Background Circles
Events.on('load', () => {
let c = $$('canvas');
let cCtx = c.getContext('2d');
let x0, y0, w, h, dw, offset;
function init() {
let oldW = w;
let oldH = h;
let oldOffset = offset
w = document.documentElement.clientWidth;
h = document.documentElement.clientHeight;
offset = $$('footer').offsetHeight - 27;
if (h > 800) offset += 10;
if (oldW === w && oldH === h && oldOffset === offset) return; // nothing has changed
c.width = w;
c.height = h;
x0 = w / 2;
y0 = h - offset;
dw = Math.round(Math.max(w, h, 1000) / 13);
drawCircles(cCtx, dw);
c.style.opacity = "1";
}
Events.on('translation-loaded', _ => init());
Events.on('bg-resize', _ => init());
window.onresize = _ => Events.fire('bg-resize');
function drawCircle(ctx, radius) {
ctx.beginPath();
ctx.lineWidth = 2;
let opacity = 0.3 * (1 - 1.2 * radius / Math.max(w, h));
ctx.strokeStyle = `rgba(128, 128, 128, ${opacity})`;
ctx.arc(x0, y0, radius, 0, 2 * Math.PI);
ctx.stroke();
}
function drawCircles(ctx, frame) {
ctx.clearRect(0, 0, w, h);
for (let i = 0; i < 13; i++) {
drawCircle(ctx, dw * i + frame + 33);
}
}
});
document.changeFavicon = function (src) {
document.querySelector('[rel="icon"]').href = src;
document.querySelector('[rel="shortcut icon"]').href = src;
}
// close About PairDrop page on Escape
window.addEventListener("keydown", (e) => {
if (e.key === "Escape") {
window.location.hash = '#';
}
});

View file

@ -406,3 +406,8 @@ function onlyUnique (value, index, array) {
function getUrlWithoutArguments() {
return `${window.location.protocol}//${window.location.host}${window.location.pathname}`;
}
function changeFavicon(src) {
document.querySelector('[rel="icon"]').href = src;
document.querySelector('[rel="shortcut icon"]').href = src;
}

View file

@ -273,6 +273,9 @@ x-noscript {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
#center {
@ -417,10 +420,6 @@ x-no-peers {
padding: 8px;
height: 137px;
text-align: center;
animation: fade-in 600ms;
animation-fill-mode: backwards;
/* prevent flickering on load */
animation-iteration-count: 0;
}
x-no-peers h2,
@ -613,7 +612,6 @@ footer .logo {
margin-bottom: 8px;
color: var(--primary-color);
margin-top: -10px;
animation: ease-in;
}
.discovery-wrapper {
@ -1228,22 +1226,23 @@ button::-moz-focus-inner {
z-index: 1;
}
#about:not(:target) header.fade-in {
#about:not(:target) header {
transition-delay: 400ms;
}
#about:target header.fade-in {
#about:target header {
transition-delay: 100ms;
}
#about .fade-in {
#about > * {
transition: opacity 300ms ease 300ms;
will-change: opacity;
pointer-events: all;
}
#about:not(:target) .fade-in {
opacity: 0 !important;
#about:not(:target) > header,
#about:not(:target) > section {
opacity: 0;
pointer-events: none;
transition-delay: 0s;
}
@ -1422,17 +1421,17 @@ x-peers:empty~x-instructions {
/* Prevent Cumulative Layout Shift */
body > header,
canvas,
#center,
x-no-peers,
x-peers,
x-instructions,
footer > .icon.logo,
.discovery-wrapper,
.known-as-wrapper {
transition: opacity 0.5s ease 0.1s;
opacity: 0; /* will be set to 1 after initial translation is loaded */
.fade-in {
animation: fade-in 600ms;
animation-fill-mode: backwards;
}
.no-animation-on-load {
animation-iteration-count: 0;
}
.opacity-0 {
opacity: 0;
}
/* Responsive Styles */

View file

@ -38,7 +38,7 @@
</head>
<body translate="no">
<header class="row-reverse">
<header class="row-reverse opacity-0">
<a href="#about" class="icon-button" data-i18n-key="header.about" data-i18n-attrs="title aria-label">
<svg class="icon">
<use xlink:href="#info-outline"></use>
@ -96,11 +96,11 @@
<div id="cancel-paste-mode" class="button" data-i18n-key="header.cancel-paste-mode" data-i18n-attrs="text" hidden></div>
</header>
<!-- Center -->
<div id="center">
<div id="center" class="opacity-0">
<!-- Peers -->
<div class="x-peers-filler"></div>
<x-peers class="center"></x-peers>
<x-no-peers data-i18n-key="instructions.no-peers" data-i18n-attrs="data-drop-bg">
<x-no-peers class="no-animation-on-load" data-i18n-key="instructions.no-peers" data-i18n-attrs="data-drop-bg">
<h2 data-i18n-key="instructions.no-peers-title" data-i18n-attrs="text"></h2>
<div data-i18n-key="instructions.no-peers-subtitle" data-i18n-attrs="text"></div>
</x-no-peers>
@ -114,7 +114,7 @@
</div>
</div>
<!-- Footer -->
<footer class="column">
<footer class="column opacity-0">
<svg class="icon logo">
<use xlink:href="#wifi-tethering"></use>
</svg>
@ -471,14 +471,14 @@
</div>
<!-- About Page -->
<x-about id="about" class="full center column">
<header class="row-reverse fade-in">
<header class="row-reverse">
<a href="#" class="close icon-button" data-i18n-key="about.close-about" data-i18n-attrs="aria-label">
<svg class="icon">
<use xlink:href="#close-icon"></use>
</svg>
</a>
</header>
<section class="center column fade-in">
<section class="center column">
<svg class="icon logo">
<use xlink:href="#wifi-tethering"></use>
</svg>
@ -512,7 +512,7 @@
</section>
<x-background></x-background>
</x-about>
<canvas class="circles"></canvas>
<canvas class="circles opacity-0"></canvas>
<!-- SVG Icon Library -->
<svg style="display: none;">
<symbol id="wifi-tethering" viewBox="0 0 24 24">
@ -592,7 +592,7 @@
<script src="scripts/theme.js"></script>
<script src="scripts/network.js"></script>
<script src="scripts/ui.js"></script>
<script src="scripts/util.js" async></script>
<script src="scripts/util.js"></script>
<script src="scripts/QRCode.min.js" async></script>
<script src="scripts/zip.min.js" async></script>
<script src="scripts/NoSleep.min.js" async></script>

View file

@ -36,27 +36,15 @@ class PeersUI {
Events.on('drop', e => this._onDrop(e));
Events.on('keydown', e => this._onKeyDown(e));
this.$header = document.querySelector('body > header')
this.$xPeers = $$('x-peers');
this.$xNoPeers = $$('x-no-peers');
this.$xInstructions = $$('x-instructions');
this.$center = $$('#center');
this.$logo = $$('footer .icon.logo');
this.$footer = $$('footer');
this.$discoveryWrapper = $$('footer .discovery-wrapper');
this.$knownAsWrapper = $$('footer .known-as-wrapper');
this.$header.style.opacity = "1";
this.$xPeers.style.opacity = "1";
this.$xNoPeers.style.opacity = "1";
this.$xInstructions.style.opacity = "0.5";
this.$center.style.opacity = "1";
this.$logo.style.opacity = "1";
this.$discoveryWrapper.style.opacity = "1";
this.$knownAsWrapper.style.opacity = "1";
Events.on('peer-added', _ => this.evaluateOverflowing());
Events.on('bg-resize', _ => this.evaluateOverflowing());
Events.on('peer-added', _ => this._evaluateOverflowing());
Events.on('bg-resize', _ => this._evaluateOverflowing());
this.$displayName = $('display-name');
@ -75,11 +63,45 @@ class PeersUI {
if (displayName) Events.fire('self-display-name-changed', displayName);
});
Events.on('evaluate-footer-badges', _ => this._evaluateFooterBadges())
/* prevent animation on load */
this.fadedIn = false;
this.$header = document.querySelector('header.opacity-0');
Events.on('header-evaluated', () => this._fadeInHeader());
}
_fadeInHeader() {
//prevent flickering
setTimeout(() => this.$header.classList.remove('opacity-0'), 50);
}
_fadeInUI() {
if (this.fadedIn) return;
this.fadedIn = true;
this.$center.classList.remove('opacity-0');
this.$footer.classList.remove('opacity-0');
// Prevent flickering on load
setTimeout(_ => {
this.$xNoPeers.style.animationIterationCount = "1";
}, 300);
this.$xNoPeers.classList.remove('no-animation-on-load');
}, 600);
Events.fire('ui-faded-in');
}
_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');
this._fadeInUI();
}
_insertDisplayName(displayName) {
@ -162,6 +184,11 @@ class PeersUI {
if (document.querySelectorAll('x-dialog[show]').length === 0 && window.pasteMode.activated && e.code === "Escape") {
Events.fire('deactivate-paste-mode');
}
// close About PairDrop page on Escape
if (e.key === "Escape") {
window.location.hash = '#';
}
}
_onPeerJoined(msg) {
@ -207,7 +234,7 @@ class PeersUI {
Object.keys(peer._roomIds).forEach(roomType => peerNode.classList.add(`type-${roomType}`));
}
evaluateOverflowing() {
_evaluateOverflowing() {
if (this.$xPeers.clientHeight < this.$xPeers.scrollHeight) {
this.$xPeers.classList.add('overflowing');
} else {
@ -223,7 +250,7 @@ class PeersUI {
const $peer = $(peerId);
if (!$peer) return;
$peer.remove();
this.evaluateOverflowing();
this._evaluateOverflowing();
}
_onRoomTypeRemoved(peerId, roomType) {
@ -610,7 +637,6 @@ class Dialog {
this.$el = $(id);
this.$el.querySelectorAll('[close]').forEach(el => el.addEventListener('click', _ => this.hide()));
this.$autoFocus = this.$el.querySelector('[autofocus]');
this.$discoveryWrapper = $$('footer .discovery-wrapper');
Events.on('peer-disconnected', e => this._onPeerDisconnected(e.detail));
}
@ -631,7 +657,7 @@ class Dialog {
window.blur();
}
document.title = 'PairDrop';
document.changeFavicon("images/favicon-96x96.png");
changeFavicon("images/favicon-96x96.png");
this.correspondingPeerId = undefined;
}
@ -641,17 +667,6 @@ class Dialog {
Events.fire('notify-user', Localization.getTranslation("notifications.selected-peer-left"));
}
}
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('bg-resize');
}
}
class LanguageSelectDialog extends Dialog {
@ -931,7 +946,7 @@ class ReceiveFileDialog extends ReceiveDialog {
document.title = files.length === 1
? `${ Localization.getTranslation("document-titles.file-received") } - PairDrop`
: `${ Localization.getTranslation("document-titles.file-received-plural", null, {count: files.length}) } - PairDrop`;
document.changeFavicon("images/favicon-96x96-notification.png");
changeFavicon("images/favicon-96x96-notification.png");
Events.fire('set-progress', {peerId: peerId, progress: 1, status: 'process'})
this.show();
@ -1028,7 +1043,7 @@ class ReceiveRequestDialog extends ReceiveDialog {
this.$receiveTitle.innerText = transferRequestTitle;
document.title = `${transferRequestTitle} - PairDrop`;
document.changeFavicon("images/favicon-96x96-notification.png");
changeFavicon("images/favicon-96x96-notification.png");
this.show();
}
@ -1192,7 +1207,6 @@ class PairDeviceDialog extends Dialog {
this.$closeBtn.addEventListener('click', _ => this._close());
Events.on('keydown', e => this._onKeyDown(e));
Events.on('ws-connected', _ => this._onWsConnected());
Events.on('ws-disconnected', _ => this.hide());
Events.on('pair-device-initiated', e => this._onPairDeviceInitiated(e.detail));
Events.on('pair-device-joined', e => this._onPairDeviceJoined(e.detail.peerId, e.detail.roomSecret));
@ -1207,6 +1221,8 @@ class PairDeviceDialog extends Dialog {
this.evaluateUrlAttributes();
this.pairPeer = {};
this._evaluateNumberRoomSecrets();
}
_onKeyDown(e) {
@ -1231,10 +1247,6 @@ class PairDeviceDialog extends Dialog {
}
}
_onWsConnected() {
this._evaluateNumberRoomSecrets();
}
_pairDeviceInitiate() {
Events.fire('pair-device-initiate');
}
@ -1382,16 +1394,17 @@ class PairDeviceDialog extends Dialog {
}
_evaluateNumberRoomSecrets() {
PersistentStorage.getAllRoomSecrets().then(roomSecrets => {
if (roomSecrets.length > 0) {
this.$editPairedDevicesHeaderBtn.removeAttribute('hidden');
this.$footerInstructionsPairedDevices.removeAttribute('hidden');
} else {
this.$editPairedDevicesHeaderBtn.setAttribute('hidden', '');
this.$footerInstructionsPairedDevices.setAttribute('hidden', '');
}
super.evaluateFooterBadges();
});
PersistentStorage.getAllRoomSecrets()
.then(roomSecrets => {
if (roomSecrets.length > 0) {
this.$editPairedDevicesHeaderBtn.removeAttribute('hidden');
this.$footerInstructionsPairedDevices.removeAttribute('hidden');
} else {
this.$editPairedDevicesHeaderBtn.setAttribute('hidden', '');
this.$footerInstructionsPairedDevices.setAttribute('hidden', '');
}
Events.fire('evaluate-footer-badges');
});
}
}
@ -1399,10 +1412,10 @@ class EditPairedDevicesDialog extends Dialog {
constructor() {
super('edit-paired-devices-dialog');
this.$pairedDevicesWrapper = this.$el.querySelector('.paired-devices-wrapper');
this.$footerInstructionsPairedDevices = $$('.discovery-wrapper .badge-room-secret');
this.$footerBadgePairedDevices = $$('.discovery-wrapper .badge-room-secret');
$('edit-paired-devices').addEventListener('click', _ => this._onEditPairedDevices());
this.$footerInstructionsPairedDevices.addEventListener('click', _ => this._onEditPairedDevices());
this.$footerBadgePairedDevices.addEventListener('click', _ => this._onEditPairedDevices());
Events.on('peer-display-name-changed', e => this._onPeerDisplayNameChanged(e));
Events.on('keydown', e => this._onKeyDown(e));
@ -1510,7 +1523,7 @@ class PublicRoomDialog extends Dialog {
this.$leaveBtn = this.$el.querySelector('.leave-room');
this.$joinSubmitBtn = this.$el.querySelector('button[type="submit"]');
this.$headerBtnJoinPublicRoom = $('join-public-room');
this.$footerInstructionsPublicRoomDevices = $$('.discovery-wrapper .badge-room-public-id');
this.$footerBadgePublicRoomDevices = $$('.discovery-wrapper .badge-room-public-id');
this.$form.addEventListener('submit', e => this._onSubmit(e));
@ -1518,7 +1531,7 @@ class PublicRoomDialog extends Dialog {
this.$leaveBtn.addEventListener('click', _ => this._leavePublicRoom())
this.$headerBtnJoinPublicRoom.addEventListener('click', _ => this._onHeaderBtnClick());
this.$footerInstructionsPublicRoomDevices.addEventListener('click', _ => this._onHeaderBtnClick());
this.$footerBadgePublicRoomDevices.addEventListener('click', _ => this._onHeaderBtnClick());
this.inputKeyContainer = new InputKeyContainer(
this.$el.querySelector('.input-key-container'),
@ -1600,12 +1613,12 @@ class PublicRoomDialog extends Dialog {
setFooterBadge() {
if (!this.roomId) return;
this.$footerInstructionsPublicRoomDevices.innerText = Localization.getTranslation("footer.public-room-devices", null, {
this.$footerBadgePublicRoomDevices.innerText = Localization.getTranslation("footer.public-room-devices", null, {
roomId: this.roomId.toUpperCase()
});
this.$footerInstructionsPublicRoomDevices.removeAttribute('hidden');
this.$footerBadgePublicRoomDevices.removeAttribute('hidden');
super.evaluateFooterBadges();
Events.fire('evaluate-footer-badges');
}
_getShareRoomURL() {
@ -1717,8 +1730,8 @@ class PublicRoomDialog extends Dialog {
this.roomId = null;
this.inputKeyContainer._cleanUp();
sessionStorage.removeItem('public_room_id');
this.$footerInstructionsPublicRoomDevices.setAttribute('hidden', '');
super.evaluateFooterBadges();
this.$footerBadgePublicRoomDevices.setAttribute('hidden', '');
Events.fire('evaluate-footer-badges');
}
}
@ -1849,7 +1862,7 @@ class ReceiveTextDialog extends Dialog {
this._setDocumentTitleMessages();
document.changeFavicon("images/favicon-96x96-notification.png");
changeFavicon("images/favicon-96x96-notification.png");
this.show();
}
@ -2059,15 +2072,20 @@ class Notifications {
constructor() {
// Check if the browser supports notifications
if (!('Notification' in window)) return;
if (!('Notification' in window)) {
Events.fire('header-evaluated');
return;
}
// Check whether notification permissions have already been granted
if (Notification.permission !== 'granted') {
this.$button = $('notification');
this.$button.removeAttribute('hidden');
this.$button.addEventListener('click', _ => this._requestPermission());
this.$headerNotificationButton = $('notification');
this.$headerNotificationButton.removeAttribute('hidden');
this.$headerNotificationButton.addEventListener('click', _ => this._requestPermission());
}
Events.fire('header-evaluated');
Events.on('text-received', e => this._messageNotification(e.detail.text, e.detail.peerId));
Events.on('files-received', e => this._downloadNotification(e.detail.files));
Events.on('files-transfer-request', e => this._requestNotification(e.detail.request, e.detail.peerId));
@ -2080,7 +2098,7 @@ class Notifications {
return;
}
Events.fire('notify-user', Localization.getTranslation("notifications.notifications-enabled"));
this.$button.setAttribute('hidden', "");
this.$headerNotificationButton.setAttribute('hidden', "");
});
}
@ -2462,7 +2480,7 @@ class PersistentStorage {
return(secrets);
} catch (e) {
this.logBrowserNotCapable();
return false;
return 0;
}
}
@ -2680,12 +2698,71 @@ class BrowserTabsConnector {
}
}
class BackgroundCanvas {
constructor() {
this.c = $$('canvas');
this.cCtx = this.c.getContext('2d');
this.$footer = $$('footer');
Events.on('bg-resize', _ => this.init());
Events.on('redraw-canvas', _ => this.init());
Events.on('translation-loaded', _ => this.init());
//fade-in on load
Events.on('ui-faded-in', _ => this._fadeIn());
window.onresize = _ => Events.fire('bg-resize');
}
_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() {
Events.on('initial-translation-loaded', _ => {
const server = new ServerConnection();
const peers = new PeersManager(server);
const peersUI = new PeersUI();
const backgroundCanvas = new BackgroundCanvas();
const languageSelectDialog = new LanguageSelectDialog();
const receiveFileDialog = new ReceiveFileDialog();
const receiveRequestDialog = new ReceiveRequestDialog();
@ -2710,7 +2787,6 @@ const persistentStorage = new PersistentStorage();
const pairDrop = new PairDrop();
const localization = new Localization();
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(serviceWorker => {
@ -2728,62 +2804,3 @@ window.addEventListener('beforeinstallprompt', e => {
}
return e.preventDefault();
});
// Background Circles
Events.on('load', () => {
let c = $$('canvas');
let cCtx = c.getContext('2d');
let x0, y0, w, h, dw, offset;
function init() {
let oldW = w;
let oldH = h;
let oldOffset = offset
w = document.documentElement.clientWidth;
h = document.documentElement.clientHeight;
offset = $$('footer').offsetHeight - 27;
if (oldW === w && oldH === h && oldOffset === offset) return; // nothing has changed
c.width = w;
c.height = h;
x0 = w / 2;
y0 = h - offset;
dw = Math.round(Math.max(w, h, 1000) / 13);
drawCircles(cCtx, dw);
c.style.opacity = "1";
}
Events.on('translation-loaded', _ => init());
Events.on('bg-resize', _ => init());
window.onresize = _ => Events.fire('bg-resize');
function drawCircle(ctx, radius) {
ctx.beginPath();
ctx.lineWidth = 2;
let opacity = 0.3 * (1 - 1.2 * radius / Math.max(w, h));
ctx.strokeStyle = `rgba(128, 128, 128, ${opacity})`;
ctx.arc(x0, y0, radius, 0, 2 * Math.PI);
ctx.stroke();
}
function drawCircles(ctx, frame) {
ctx.clearRect(0, 0, w, h);
for (let i = 0; i < 13; i++) {
drawCircle(ctx, dw * i + frame + 33);
}
}
});
document.changeFavicon = function (src) {
document.querySelector('[rel="icon"]').href = src;
document.querySelector('[rel="shortcut icon"]').href = src;
}
// close About PairDrop page on Escape
window.addEventListener("keydown", (e) => {
if (e.key === "Escape") {
window.location.hash = '#';
}
});

View file

@ -407,6 +407,11 @@ function getUrlWithoutArguments() {
return `${window.location.protocol}//${window.location.host}${window.location.pathname}`;
}
function changeFavicon(src) {
document.querySelector('[rel="icon"]').href = src;
document.querySelector('[rel="shortcut icon"]').href = src;
}
function arrayBufferToBase64(buffer) {
var binary = '';
var bytes = new Uint8Array(buffer);
@ -425,4 +430,4 @@ function base64ToArrayBuffer(base64) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
}
}

View file

@ -274,6 +274,9 @@ x-noscript {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
#center {
@ -422,10 +425,6 @@ x-no-peers {
padding: 8px;
height: 137px;
text-align: center;
animation: fade-in 600ms;
animation-fill-mode: backwards;
/* prevent flickering on load */
animation-iteration-count: 0;
}
x-no-peers h2,
@ -643,7 +642,6 @@ footer .logo {
margin-bottom: 8px;
color: var(--primary-color);
margin-top: -10px;
animation: ease-in;
}
.discovery-wrapper {
@ -1258,22 +1256,23 @@ button::-moz-focus-inner {
z-index: 1;
}
#about:not(:target) header.fade-in {
#about:not(:target) header {
transition-delay: 400ms;
}
#about:target header.fade-in {
#about:target header {
transition-delay: 100ms;
}
#about .fade-in {
#about > * {
transition: opacity 300ms ease 300ms;
will-change: opacity;
pointer-events: all;
}
#about:not(:target) .fade-in {
opacity: 0 !important;
#about:not(:target) > header,
#about:not(:target) > section {
opacity: 0;
pointer-events: none;
transition-delay: 0s;
}
@ -1452,17 +1451,17 @@ x-peers:empty~x-instructions {
/* Prevent Cumulative Layout Shift */
body > header,
canvas,
#center,
x-no-peers,
x-peers,
x-instructions,
footer > .icon.logo,
.discovery-wrapper,
.known-as-wrapper {
transition: opacity 0.5s ease 0.1s;
opacity: 0; /* will be set to 1 after initial translation is loaded */
.fade-in {
animation: fade-in 600ms;
animation-fill-mode: backwards;
}
.no-animation-on-load {
animation-iteration-count: 0;
}
.opacity-0 {
opacity: 0;
}
/* Responsive Styles */