diff --git a/public/scripts/ui.js b/public/scripts/ui.js
index c4373ae..cd1aedd 100644
--- a/public/scripts/ui.js
+++ b/public/scripts/ui.js
@@ -28,7 +28,7 @@ class PeersUI {
Events.on('activate-paste-mode', e => this._activatePasteMode(e.detail.files, e.detail.text));
this.peers = {};
- this.$cancelPasteModeBtn = $('cancelPasteModeBtn');
+ this.$cancelPasteModeBtn = $('cancel-paste-mode-btn');
this.$cancelPasteModeBtn.addEventListener('click', _ => this._cancelPasteMode());
Events.on('dragover', e => this._onDragOver(e));
@@ -38,8 +38,12 @@ class PeersUI {
Events.on('drop', e => this._onDrop(e));
Events.on('keydown', e => this._onKeyDown(e));
+ this.$xPeers = $$('x-peers');
this.$xNoPeers = $$('x-no-peers');
this.$xInstructions = $$('x-instructions');
+
+ Events.on('peer-added', _ => this.evaluateOverflowing());
+ Events.on('bg-resize', _ => this.evaluateOverflowing());
}
_onKeyDown(e) {
@@ -53,11 +57,11 @@ class PeersUI {
}
_joinPeer(peer, roomType, roomSecret) {
- peer.roomType = roomType;
+ peer.roomTypes = [roomType];
peer.roomSecret = roomSecret;
if (this.peers[peer.id]) {
- this.peers[peer.id].roomType = peer.roomType;
- this._redrawPeer(peer);
+ if (!this.peers[peer.id].roomTypes.includes(roomType)) this.peers[peer.id].roomTypes.push(roomType);
+ this._redrawPeer(this.peers[peer.id]);
return; // peer already exists
}
this.peers[peer.id] = peer;
@@ -72,7 +76,15 @@ class PeersUI {
const peerNode = $(peer.id);
if (!peerNode) return;
peerNode.classList.remove('type-ip', 'type-secret');
- peerNode.classList.add(`type-${peer.roomType}`)
+ peer.roomTypes.forEach(roomType => peerNode.classList.add(`type-${roomType}`));
+ }
+
+ evaluateOverflowing() {
+ if (this.$xPeers.clientHeight < this.$xPeers.scrollHeight) {
+ this.$xPeers.classList.add('overflowing');
+ } else {
+ this.$xPeers.classList.remove('overflowing');
+ }
}
_onPeers(msg) {
@@ -83,6 +95,7 @@ class PeersUI {
const $peer = $(peerId);
if (!$peer) return;
$peer.remove();
+ this.evaluateOverflowing();
if ($$('x-peers:empty')) setTimeout(_ => window.animateBackground(true), 1750); // Start animation again
}
@@ -213,6 +226,18 @@ class PeersUI {
class PeerUI {
+ constructor(peer, connectionHash) {
+ this._peer = peer;
+ this._connectionHash = connectionHash;
+ this._initDom();
+ this._bindListeners();
+
+ $$('x-peers').appendChild(this.$el)
+ Events.fire('peer-added');
+ this.$xInstructions = $$('x-instructions');
+ setTimeout(_ => window.animateBackground(false), 1750); // Stop animation
+ }
+
html() {
let title;
let input = '';
@@ -225,17 +250,24 @@ class PeerUI {
this.$el.innerHTML = `
`;
this.$el.querySelector('svg use').setAttribute('xlink:href', this._icon());
@@ -245,23 +277,12 @@ class PeerUI {
this._connectionHash.substring(0, 4) + " " + this._connectionHash.substring(4, 8) + " " + this._connectionHash.substring(8, 12) + " " + this._connectionHash.substring(12, 16);
}
- constructor(peer, connectionHash) {
- this._peer = peer;
- this._roomType = peer.roomType;
- this._roomSecret = peer.roomSecret;
- this._connectionHash = connectionHash;
- this._initDom();
- this._bindListeners();
- $$('x-peers').appendChild(this.$el);
- this.$xInstructions = $$('x-instructions');
- setTimeout(_ => window.animateBackground(false), 1750); // Stop animation
- }
-
_initDom() {
this.$el = document.createElement('x-peer');
this.$el.id = this._peer.id;
this.$el.ui = this;
- this.$el.classList.add(`type-${this._roomType}`);
+ this._peer.roomTypes.forEach(roomType => this.$el.classList.add(`type-${roomType}`));
+ this.$el.classList.add('center');
this.html();
this._callbackInput = e => this._onFilesSelected(e)
@@ -272,7 +293,7 @@ class PeerUI {
this._callbackDragLeave = e => this._onDragEnd(e)
this._callbackDragOver = e => this._onDragOver(e)
this._callbackContextMenu = e => this._onRightClick(e)
- this._callbackTouchStart = _ => this._onTouchStart()
+ this._callbackTouchStart = e => this._onTouchStart(e)
this._callbackTouchEnd = e => this._onTouchEnd(e)
this._callbackPointerDown = e => this._onPointerDown(e)
// PasteMode
@@ -393,21 +414,28 @@ class PeerUI {
_onRightClick(e) {
e.preventDefault();
- Events.fire('text-recipient', this._peer.id);
+ Events.fire('text-recipient', {
+ peerId: this._peer.id,
+ deviceName: e.target.closest('x-peer').querySelector('.name').innerText
+ });
}
- _onTouchStart() {
+ _onTouchStart(e) {
this._touchStart = Date.now();
- this._touchTimer = setTimeout(_ => this._onTouchEnd(), 610);
+ this._touchTimer = setTimeout(_ => this._onTouchEnd(e), 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);
+ } else if (this._touchTimer) { // this was a long tap
+ e.preventDefault();
+ Events.fire('text-recipient', {
+ peerId: this._peer.id,
+ deviceName: e.target.closest('x-peer').querySelector('.name').innerText
+ });
}
+ this._touchTimer = null;
}
}
@@ -843,7 +871,7 @@ class PairDeviceDialog extends Dialog {
height: 150,
padding: 0,
background: "transparent",
- color: getComputedStyle(document.body).getPropertyValue('--text-color'),
+ color: `rgb(var(--text-color))`,
ecl: "L",
join: true
});
@@ -935,6 +963,7 @@ class PairDeviceDialog extends Dialog {
this.$clearSecretsBtn.setAttribute('hidden', '');
this.$footerInstructionsPairedDevices.setAttribute('hidden', '');
}
+ Events.fire('bg-resize');
}).catch(_ => PersistentStorage.logBrowserNotCapable());
}
}
@@ -960,8 +989,9 @@ class ClearDevicesDialog extends Dialog {
class SendTextDialog extends Dialog {
constructor() {
super('sendTextDialog');
- Events.on('text-recipient', e => this._onRecipient(e.detail));
+ Events.on('text-recipient', e => this._onRecipient(e.detail.peerId, e.detail.deviceName));
this.$text = this.$el.querySelector('#textInput');
+ this.$peerDisplayName = this.$el.querySelector('#textSendPeerDisplayName');
this.$form = this.$el.querySelector('form');
this.$submit = this.$el.querySelector('button[type="submit"]');
this.$form.addEventListener('submit', _ => this._send());
@@ -992,8 +1022,9 @@ class SendTextDialog extends Dialog {
}
}
- _onRecipient(peerId) {
+ _onRecipient(peerId, deviceName) {
this.correspondingPeerId = peerId;
+ this.$peerDisplayName.innerText = deviceName;
this.show();
const range = document.createRange();
@@ -1246,6 +1277,7 @@ class Notifications {
this.$button.removeAttribute('hidden');
this.$button.addEventListener('click', _ => this._requestPermission());
}
+ // Todo: fix Notifications
Events.on('text-received', e => this._messageNotification(e.detail.text, e.detail.peerId));
Events.on('files-received', e => this._downloadNotification(e.detail.files));
}
@@ -1714,19 +1746,15 @@ Events.on('load', () => {
h = window.innerHeight;
c.width = w;
c.height = h;
- offset = h > 800
- ? 116
- : h > 380
- ? 100
- : 65;
-
- if (w < 420) offset += 20;
+ offset = $$('footer').offsetHeight - 32;
+ if (h > 800) offset += 16;
x0 = w / 2;
y0 = h - offset;
dw = Math.max(w, h, 1000) / 13;
drawCircles();
}
- window.onresize = init;
+ Events.on('bg-resize', _ => init());
+ window.onresize = _ => Events.fire('bg-resize');
function drawCircle(radius) {
ctx.beginPath();
diff --git a/public/styles.css b/public/styles.css
index aa08cbc..25be4a1 100644
--- a/public/styles.css
+++ b/public/styles.css
@@ -10,28 +10,25 @@
/* Layout */
-html {
- min-height: 100%;
- height: -webkit-fill-available;
-}
-
html,
body {
margin: 0;
display: flex;
flex-direction: column;
- width: 100%;
+ width: 100vw;
overflow-x: hidden;
- overscroll-behavior-y: none;
+ overscroll-behavior: none;
+ overflow-y: hidden;
}
body {
- min-height: 100%;
+ min-height: 100vh;
+ /* mobile viewport bug fix */
min-height: -webkit-fill-available;
- flex-grow: 1;
- align-items: center;
- justify-content: center;
- overflow-y: hidden;
+}
+
+html {
+ height: -webkit-fill-available;
}
.row-reverse {
@@ -73,10 +70,7 @@ body {
}
header {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
+ position: relative;
height: 56px;
align-items: center;
padding: 16px;
@@ -119,9 +113,9 @@ h3 {
}
.font-subheading {
- font-size: 16px;
+ font-size: 14px;
font-weight: 400;
- line-height: 24px;
+ line-height: 18px;
word-break: normal;
}
@@ -199,20 +193,151 @@ body>header a {
margin-left: 8px;
}
+#center {
+ position: relative;
+ display: flex;
+ flex-direction: column-reverse;
+ flex-grow: 1;
+ --footer-height: 132px;
+ max-height: calc(100vh - 56px - var(--footer-height));
+ justify-content: space-around;
+ align-items: center;
+ overflow-x: hidden;
+ overflow-y: scroll;
+ overscroll-behavior-x: none;
+}
+
+@media screen and (max-width: 425px) {
+ header:has(#clear-pair-devices:not([hidden]))~#center {
+ --footer-height: 150px;
+ }
+}
+
/* Peers List */
+#x-peers-filler {
+ display: flex;
+ flex-grow: 1;
+}
+
x-peers {
- width: 100%;
- overflow: hidden;
+ position: relative;
+ display: flex;
flex-flow: row wrap;
+ flex-grow: 1;
+ align-items: start !important;
+ justify-content: center;
+
z-index: 2;
- transition: color 300ms;
+ transition: --bg-color 0.5s ease;
+ overflow-y: scroll;
+ overflow-x: hidden;
+ overscroll-behavior-x: none;
+ scrollbar-width: none;
+
+ --peers-per-row: 6; /* default if browser does not support :has selector */
+ --x-peers-width: min(100vw, calc(var(--peers-per-row) * (var(--peer-width) + 25px) - 16px));
+ width: var(--x-peers-width);
+ margin-right: 20px;
+ margin-left: 20px;
+}
+
+x-peers.overflowing {
+ background: /* Shadow covers */ linear-gradient(rgb(var(--bg-color)) 30%, rgba(var(--bg-color), 0)),
+ linear-gradient(rgba(var(--bg-color), 0), rgb(var(--bg-color)) 70%) 0 100%,
+ /* Shadows */ radial-gradient(farthest-side at 50% 0, rgba(var(--text-color), .2), rgba(var(--text-color), 0)),
+ radial-gradient(farthest-side at 50% 100%, rgba(var(--text-color), .2), rgba(var(--text-color), 0)) 0 100%;
+
+ background-repeat: no-repeat;
+ background-size: 100% 40px, 100% 40px, 100% 14px, 100% 14px;
+
+ /* Opera doesn't support this in the shorthand */
+ background-attachment: local, local, scroll, scroll;
+}
+
+x-peers:has(> x-peer) {
+ --peers-per-row: 10;
+}
+
+@media screen and (min-height: 505px) and (max-height: 649px) and (max-width: 426px),
+screen and (min-height: 486px) and (max-height: 631px) and (min-width: 426px) {
+ x-peers:has(> x-peer) {
+ --peers-per-row: 3;
+ }
+
+ x-peers:has(> x-peer:nth-of-type(7)) {
+ --peers-per-row: 4;
+ }
+
+ x-peers:has(> x-peer:nth-of-type(10)) {
+ --peers-per-row: 5;
+ }
+
+ x-peers:has(> x-peer:nth-of-type(13)) {
+ --peers-per-row: 6;
+ }
+
+ x-peers:has(> x-peer:nth-of-type(16)) {
+ --peers-per-row: 7;
+ }
+
+ x-peers:has(> x-peer:nth-of-type(19)) {
+ --peers-per-row: 8;
+ }
+
+ x-peers:has(> x-peer:nth-of-type(22)) {
+ --peers-per-row: 9;
+ }
+
+ x-peers:has(> x-peer:nth-of-type(25)) {
+ --peers-per-row: 10;
+ }
+}
+
+@media screen and (min-height: 649px) and (max-width: 425px),
+screen and (min-height: 631px) and (min-width: 426px) {
+ x-peers:has(> x-peer) {
+ --peers-per-row: 3;
+ }
+
+ x-peers:has(> x-peer:nth-of-type(10)) {
+ --peers-per-row: 4;
+ }
+
+ x-peers:has(> x-peer:nth-of-type(13)) {
+ --peers-per-row: 5;
+ }
+
+ x-peers:has(> x-peer:nth-of-type(16)) {
+ --peers-per-row: 6;
+ }
+
+ x-peers:has(> x-peer:nth-of-type(19)) {
+ --peers-per-row: 7;
+ }
+
+ x-peers:has(> x-peer:nth-of-type(22)) {
+ --peers-per-row: 8;
+ }
+
+ x-peers:has(> x-peer:nth-of-type(25)) {
+ --peers-per-row: 9;
+ }
+
+ x-peers:has(> x-peer:nth-of-type(28)) {
+ --peers-per-row: 10;
+ }
+}
+
+::-webkit-scrollbar {
+ display: none;
}
/* Empty Peers List */
x-no-peers {
- height: 114px;
+ display: flex;
+ flex-direction: column;
padding: 8px;
text-align: center;
/* prevent flickering on load */
@@ -254,25 +379,19 @@ x-no-peers[drop-bg] * {
x-peer {
-webkit-user-select: none;
user-select: none;
+ padding: 8px;
+ align-content: start;
+ flex-wrap: wrap;
}
x-peer label {
width: var(--peer-width);
- padding: 8px;
cursor: pointer;
touch-action: manipulation;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
position: relative;
}
-x-peer .name {
- width: var(--peer-width);
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- text-align: center;
-}
-
input[type="file"] {
visibility: hidden;
position: absolute;
@@ -280,21 +399,45 @@ input[type="file"] {
x-peer x-icon {
--icon-size: 40px;
+ margin-bottom: 4px;
+ transition: transform 150ms;
+ will-change: transform;
+ display: flex;
+ flex-direction: column;
+}
+
+x-peer .icon-wrapper {
width: var(--icon-size);
padding: 12px;
border-radius: 50%;
background: var(--primary-color);
color: white;
display: flex;
- margin-bottom: 8px;
- transition: transform 150ms;
- will-change: transform;
}
-x-peer:not(.type-ip) x-icon {
+x-peer:not(.type-ip).type-secret .icon-wrapper {
background: var(--paired-device-color);
}
+x-peer x-icon > .highlight-wrapper {
+ align-self: center;
+ align-items: center;
+ margin: 7px auto 0;
+ height: 6px;
+}
+
+x-peer x-icon > .highlight-wrapper > .highlight {
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ display: none;
+}
+
+x-peer.type-secret x-icon > .highlight-wrapper > .highlight {
+ background-color: var(--paired-device-color);
+ display: inline;
+}
+
x-peer:not([status]):hover x-icon,
x-peer:not([status]):focus x-icon {
transform: scale(1.05);
@@ -306,6 +449,18 @@ x-peer[status] x-icon {
transform: scale(1);
}
+.device-descriptor {
+ text-align: center;
+}
+
+.name {
+ width: 100%;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ text-align: center;
+}
+
.status,
.device-name,
.connection-hash {
@@ -371,10 +526,9 @@ x-peer[drop] x-icon {
/* Footer */
footer {
- position: absolute;
- bottom: 0;
- left: 0;
- right: 0;
+ position: relative;
+ margin-top: auto;
+ z-index: 2;
align-items: center;
padding: 0 0 16px 0;
text-align: center;
@@ -385,6 +539,7 @@ footer .logo {
--icon-size: 80px;
margin-bottom: 8px;
color: var(--primary-color);
+ margin-top: -10px;
}
footer .font-body2 {
@@ -430,6 +585,9 @@ x-dialog x-paper {
top: max(50%, 350px);
height: 650px;
margin-top: -325px;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
}
x-dialog:not([show]) {
@@ -509,6 +667,7 @@ x-dialog .font-subheading {
#pairDeviceDialog hr {
margin-top: 40px;
margin-bottom: 40px;
+ width: 100%;
}
#pairDeviceDialog x-background {
@@ -532,7 +691,7 @@ x-dialog h2 {
}
x-dialog .row-reverse {
- margin: 40px -24px auto;
+ margin: 40px -24px 0;
border-top: solid 2.5px var(--border-color);
}
@@ -689,16 +848,18 @@ x-dialog .row-reverse {
opacity: 0.1;
}
-#cancelPasteModeBtn {
+#cancel-paste-mode-btn {
z-index: 2;
- margin-top: 0;
+ margin: 0;
+ padding: 0;
position: absolute;
top: 0;
right: 0;
left: 0;
- width: 100%;
+ width: 100vw;
height: 56px;
- border-bottom: solid 2.5px var(--border-color);
+ background-color: var(--primary-color);
+ color: rgb(238, 238, 238);
}
.button:focus:before,
@@ -809,7 +970,7 @@ button::-moz-focus-inner {
width: 80px;
height: 80px;
position: absolute;
- top: 0;
+ top: -8px;
clip: rect(0px, 80px, 80px, 40px);
--progress: rotate(0deg);
transition: transform 200ms;
@@ -876,13 +1037,16 @@ x-toast:not([show]):not(:hover) {
/* Instructions */
x-instructions {
- position: absolute;
- top: 120px;
+ position: relative;
opacity: 0.5;
transition: opacity 300ms;
- z-index: -1;
text-align: center;
- width: 80%;
+ margin-left: 10px;
+ margin-right: 10px;
+ display: flex;
+ flex-direction: column;
+ flex-grow: 1;
+ justify-content: center;
}
x-instructions:not([drop-peer]):not([drop-bg]):before {
@@ -899,88 +1063,84 @@ x-instructions[drop-bg]:not([drop-peer]):before {
x-instructions p {
display: none;
- margin: 0 auto auto;
- max-width: 80%;
}
x-peers:empty~x-instructions {
opacity: 0;
}
+@media (hover: none) and (pointer: coarse) {
+ x-peer {
+ transform: scale(0.95);
+ padding: 4px 0;
+ }
+}
+
+#websocket-fallback {
+ margin-left: 5px;
+ margin-right: 5px;
+ padding: 5px;
+ text-align: center;
+ opacity: 0.5;
+ transition: opacity 300ms;
+}
+
+#websocket-fallback>span {
+ margin: 2px;
+}
+
+#websocket-fallback > span > span {
+ border-bottom: solid 4px var(--ws-peer-color);
+}
/* Responsive Styles */
-@media (min-height: 800px) {
+@media screen and (min-height: 800px) {
footer {
margin-bottom: 16px;
}
}
-@media screen and (min-height: 800px),
-screen and (min-width: 1100px) {
+@media (hover: hover) and (pointer: fine) {
x-instructions:not([drop-peer]):not([drop-bg]):before {
content: attr(desktop);
}
}
-@media (max-height: 420px) {
- x-instructions {
- top: 24px;
- }
-
- footer .logo {
- --icon-size: 40px;
- }
-}
-
-/*
- iOS specific styles
-*/
-@supports (-webkit-overflow-scrolling: touch) {
-
-
- html {
- position: fixed;
- }
-
- x-instructions:not([drop-peer]):not([drop-bg]):before {
- content: attr(mobile);
- }
-}
-
/*
Color Themes
*/
/* Default colors */
body {
- --text-color: #333;
- --bg-color: #fff;
+ --text-color: 51,51,51;
+ --bg-color: 250,250,250; /*rgb code*/
+ --bg-color-test: 18,18,18;
--bg-color-secondary: #f1f3f4;
--border-color: #e7e8e8;
}
/* Dark theme colors */
body.dark-theme {
- --text-color: #eee;
- --bg-color: #121212;
+ --text-color: 238,238,238;
+ --bg-color: 18,18,18; /*rgb code*/
--bg-color-secondary: #333;
--border-color: #252525;
}
/* Colored Elements */
body {
- color: var(--text-color);
- background-color: var(--bg-color);
+ color: rgb(var(--text-color));
+ background-color: rgb(var(--bg-color));
transition: background-color 0.5s ease;
}
x-dialog x-paper {
- background-color: var(--bg-color);
+ background-color: rgb(var(--bg-color));
}
.textarea {
- color: var(--text-color) !important;
+ color: rgb(var(--text-color)) !important;
background-color: var(--bg-color-secondary) !important;
}
@@ -1018,16 +1178,16 @@ x-dialog x-paper {
/* defaults to dark theme */
body {
- --text-color: #eee;
- --bg-color: #121212;
+ --text-color: 238,238,238;
+ --bg-color: 18,18,18; /*rgb code*/
--bg-color-secondary: #333;
--border-color: #252525;
}
/* Override dark mode with light mode styles if the user decides to swap */
body.light-theme {
- --text-color: #333;
- --bg-color: #fafafa;
+ --text-color: 51,51,51;
+ --bg-color: 250,250,250; /*rgb code*/
--bg-color-secondary: #f1f3f4;
--border-color: #e7e8e8;
}
@@ -1045,6 +1205,15 @@ x-dialog x-paper {
}
}
+/*
+ iOS specific styles
+*/
+@supports (-webkit-overflow-scrolling: touch) {
+ html {
+ min-height: -webkit-fill-available;
+ }
+}
+
/* webkit scrollbar style*/
::-webkit-scrollbar{
diff --git a/public_included_ws_fallback/index.html b/public_included_ws_fallback/index.html
index bc7bc22..b5042a8 100644
--- a/public_included_ws_fallback/index.html
+++ b/public_included_ws_fallback/index.html
@@ -69,20 +69,21 @@
+
Done
-
Done
-
-
-
- Open PairDrop on other devices to send files
- Pair devices to be discoverable on other networks
-
- A websocket fallback is implemented on this instance. Use only if you trust the server!
-
-
- A websocket fallback is implemented on this instance. Use only if you trust the server!
-
-
+
+
+
+
+
+
+ Open PairDrop on other devices to send files
+ Pair devices to be discoverable on other networks
+
+
+
+
+
+