From b6238b05aeec7be2ba8a749da5cfe1072242e078 Mon Sep 17 00:00:00 2001 From: kylethedeveloper <8023096+kylethedeveloper@users.noreply.github.com> Date: Tue, 21 Feb 2023 22:42:41 -0600 Subject: [PATCH 01/15] add docker-compose instructions to docs --- docs/host-your-own.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/host-your-own.md b/docs/host-your-own.md index e2cf6fe..2be4952 100644 --- a/docs/host-your-own.md +++ b/docs/host-your-own.md @@ -41,6 +41,31 @@ Set options by using the following flags in the `docker run` command:
+## Deployment with Docker Compose + +Here's an example docker-compose file: + +```yaml +version: "2" +services: + pairdrop: + image: lscr.io/linuxserver/pairdrop:latest + container_name: pairdrop + restart: unless-stopped + environment: + - PUID=1000 # UID to run the application as + - PGID=1000 # GID to run the application as + - WS_FALLBACK=false # Set to true to enable websocket fallback if the peer to peer WebRTC connection is not available to the client. + - RATE_LIMIT=false # Set to true to limit clients to 100 requests per 5 min. + - TZ=Etc/UTC # Time Zone + ports: + - 3000:3000 # Web UI +``` + +Run the compose file with `docker compose up -d`. + +
+ ## Deployment with Docker with self-built image ### Build the image ```bash From f9e214a1e53ff4897a988979fb001668fc666c84 Mon Sep 17 00:00:00 2001 From: Xstar97TheNoob <9399967+xstar97@users.noreply.github.com> Date: Thu, 23 Feb 2023 12:53:04 -0500 Subject: [PATCH 02/15] docs(ghcr) add deployment notes for ghcr --- docs/host-your-own.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/host-your-own.md b/docs/host-your-own.md index e2cf6fe..2d0e83f 100644 --- a/docs/host-your-own.md +++ b/docs/host-your-own.md @@ -6,6 +6,17 @@ The easiest way to get PairDrop up and running is by using Docker. ```bash docker run -d --restart=unless-stopped --name=pairdrop -p 127.0.0.1:3000:3000 lscr.io/linuxserver/pairdrop ``` + +## Deployment with Docker from GHCR + +```bash +docker run -d --restart=unless-stopped --name=pairdrop -p 127.0.0.1:3000:3000 ghcr.io/schlagmichdoch/pairdrop:v0.0.1 +``` + +```bash +docker run -d --restart=unless-stopped --name=pairdrop -p 127.0.0.1:3000:3000 ghcr.io/schlagmichdoch/pairdrop:latest +``` + > You must use a server proxy to set the X-Forwarded-For to prevent all clients from discovering each other (See [#HTTP-Server](#http-server)). > > To prevent bypassing the proxy and reach the docker container directly, `127.0.0.1` is specified in the run command. From 0ba1bd71133d17646699ab9f2d23c0bc81bdff3c Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Fri, 24 Feb 2023 16:15:29 +0100 Subject: [PATCH 03/15] tidy up Docker deployment notes --- docs/host-your-own.md | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/docs/host-your-own.md b/docs/host-your-own.md index 2d0e83f..aafc06e 100644 --- a/docs/host-your-own.md +++ b/docs/host-your-own.md @@ -1,26 +1,23 @@ # Deployment Notes The easiest way to get PairDrop up and running is by using Docker. -## Deployment with Docker from Docker Hub +## Deployment with Docker +> You must use a server proxy to set the X-Forwarded-For to prevent all clients from discovering each other (See [#HTTP-Server](#http-server)). +> +> To prevent bypassing the proxy and reach the docker container directly, `127.0.0.1` is specified in the run command. + +### Image from Docker Hub ```bash docker run -d --restart=unless-stopped --name=pairdrop -p 127.0.0.1:3000:3000 lscr.io/linuxserver/pairdrop ``` -## Deployment with Docker from GHCR +### Image from GHCR ```bash -docker run -d --restart=unless-stopped --name=pairdrop -p 127.0.0.1:3000:3000 ghcr.io/schlagmichdoch/pairdrop:v0.0.1 +docker run -d --restart=unless-stopped --name=pairdrop -p 127.0.0.1:3000:3000 ghcr.io/schlagmichdoch/pairdrop ``` -```bash -docker run -d --restart=unless-stopped --name=pairdrop -p 127.0.0.1:3000:3000 ghcr.io/schlagmichdoch/pairdrop:latest -``` - -> You must use a server proxy to set the X-Forwarded-For to prevent all clients from discovering each other (See [#HTTP-Server](#http-server)). -> -> To prevent bypassing the proxy and reach the docker container directly, `127.0.0.1` is specified in the run command. - ### Options / Flags Set options by using the following flags in the `docker run` command: From 2a3d1d410549d386ce1cae8644f9ada81c777f72 Mon Sep 17 00:00:00 2001 From: Xstar97TheNoob <9399967+xstar97@users.noreply.github.com> Date: Fri, 24 Feb 2023 10:40:30 -0500 Subject: [PATCH 04/15] Fix variable to a static lowercase name --- .github/workflows/github-image.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/github-image.yml b/.github/workflows/github-image.yml index 5e04e6f..dd21810 100644 --- a/.github/workflows/github-image.yml +++ b/.github/workflows/github-image.yml @@ -16,7 +16,7 @@ on: env: REGISTRY: ghcr.io - IMAGE_NAME: ${{ github.repository | downcase }} + IMAGE_NAME: pairdrop jobs: build-and-push-image: @@ -48,4 +48,4 @@ jobs: context: . push: true tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} \ No newline at end of file + labels: ${{ steps.meta.outputs.labels }} From 591c76c15a2c020ba07052c6270f8079f7ae65b2 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Fri, 24 Feb 2023 16:53:13 +0100 Subject: [PATCH 05/15] fix dialog heights --- public/styles.css | 3 +++ public_included_ws_fallback/styles.css | 3 +++ 2 files changed, 6 insertions(+) diff --git a/public/styles.css b/public/styles.css index 0b089bf..aa08cbc 100644 --- a/public/styles.css +++ b/public/styles.css @@ -423,6 +423,9 @@ x-dialog x-paper { box-sizing: border-box; transition: transform 300ms; will-change: transform; +} + +#pairDeviceDialog x-paper { position: absolute; top: max(50%, 350px); height: 650px; diff --git a/public_included_ws_fallback/styles.css b/public_included_ws_fallback/styles.css index c415cdd..ab61629 100644 --- a/public_included_ws_fallback/styles.css +++ b/public_included_ws_fallback/styles.css @@ -432,6 +432,9 @@ x-dialog x-paper { box-sizing: border-box; transition: transform 300ms; will-change: transform; +} + +#pairDeviceDialog x-paper { position: absolute; top: max(50%, 350px); height: 650px; From 8ecec5c1bf49cf3ba594efafd6a413015cd859f3 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Fri, 24 Feb 2023 18:18:17 +0100 Subject: [PATCH 06/15] increase version to v1.1.2 --- package-lock.json | 4 ++-- package.json | 2 +- public/service-worker.js | 2 +- public_included_ws_fallback/service-worker.js | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index f1dbbca..00c8c5d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pairdrop", - "version": "1.1.1", + "version": "1.1.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "pairdrop", - "version": "1.1.1", + "version": "1.1.2", "license": "ISC", "dependencies": { "express": "^4.18.2", diff --git a/package.json b/package.json index ff66482..34bc9ac 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pairdrop", - "version": "1.1.1", + "version": "1.1.2", "description": "", "main": "index.js", "scripts": { diff --git a/public/service-worker.js b/public/service-worker.js index 8230bfe..10b4d32 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -1,4 +1,4 @@ -const cacheVersion = 'v1.1.1'; +const cacheVersion = 'v1.1.2'; const cacheTitle = `pairdrop-cache-${cacheVersion}`; const urlsToCache = [ 'index.html', diff --git a/public_included_ws_fallback/service-worker.js b/public_included_ws_fallback/service-worker.js index fe52427..c204361 100644 --- a/public_included_ws_fallback/service-worker.js +++ b/public_included_ws_fallback/service-worker.js @@ -1,4 +1,4 @@ -const cacheVersion = 'v1.1.1'; +const cacheVersion = 'v1.1.2'; const cacheTitle = `pairdrop-included-ws-fallback-cache-${cacheVersion}`; const urlsToCache = [ 'index.html', From 0de92864eb8a6868b6222d44ea1dc144894a7021 Mon Sep 17 00:00:00 2001 From: Xstar97TheNoob <9399967+xstar97@users.noreply.github.com> Date: Fri, 24 Feb 2023 13:44:14 -0500 Subject: [PATCH 07/15] fix IMAGE_NAME Just tested this on my fork, works. --- .github/workflows/github-image.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/github-image.yml b/.github/workflows/github-image.yml index dd21810..c240bf8 100644 --- a/.github/workflows/github-image.yml +++ b/.github/workflows/github-image.yml @@ -16,7 +16,7 @@ on: env: REGISTRY: ghcr.io - IMAGE_NAME: pairdrop + IMAGE_NAME: ${{ github.repository }} jobs: build-and-push-image: From e77f8565153f1204c3e9e72af4b8805a4ef3ff33 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Sat, 25 Feb 2023 17:03:44 +0100 Subject: [PATCH 08/15] increase version to v1.1.3 --- package-lock.json | 4 ++-- package.json | 2 +- public/service-worker.js | 2 +- public_included_ws_fallback/service-worker.js | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 00c8c5d..c9ddb2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pairdrop", - "version": "1.1.2", + "version": "1.1.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "pairdrop", - "version": "1.1.2", + "version": "1.1.3", "license": "ISC", "dependencies": { "express": "^4.18.2", diff --git a/package.json b/package.json index 34bc9ac..30444eb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pairdrop", - "version": "1.1.2", + "version": "1.1.3", "description": "", "main": "index.js", "scripts": { diff --git a/public/service-worker.js b/public/service-worker.js index 10b4d32..1f1adc6 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -1,4 +1,4 @@ -const cacheVersion = 'v1.1.2'; +const cacheVersion = 'v1.1.3'; const cacheTitle = `pairdrop-cache-${cacheVersion}`; const urlsToCache = [ 'index.html', diff --git a/public_included_ws_fallback/service-worker.js b/public_included_ws_fallback/service-worker.js index c204361..9e968ec 100644 --- a/public_included_ws_fallback/service-worker.js +++ b/public_included_ws_fallback/service-worker.js @@ -1,4 +1,4 @@ -const cacheVersion = 'v1.1.2'; +const cacheVersion = 'v1.1.3'; const cacheTitle = `pairdrop-included-ws-fallback-cache-${cacheVersion}`; const urlsToCache = [ 'index.html', From 765b4e65b1bb5a3df2d1d51b4c700f30f6d5bafe Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Sat, 25 Feb 2023 17:32:19 +0100 Subject: [PATCH 09/15] Update GHCR docker docs --- docs/host-your-own.md | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/docs/host-your-own.md b/docs/host-your-own.md index aafc06e..9d10f0c 100644 --- a/docs/host-your-own.md +++ b/docs/host-your-own.md @@ -2,21 +2,16 @@ The easiest way to get PairDrop up and running is by using Docker. ## Deployment with Docker -> You must use a server proxy to set the X-Forwarded-For to prevent all clients from discovering each other (See [#HTTP-Server](#http-server)). -> -> To prevent bypassing the proxy and reach the docker container directly, `127.0.0.1` is specified in the run command. -### Image from Docker Hub +### Docker Image from Docker Hub ```bash docker run -d --restart=unless-stopped --name=pairdrop -p 127.0.0.1:3000:3000 lscr.io/linuxserver/pairdrop ``` -### Image from GHCR - -```bash -docker run -d --restart=unless-stopped --name=pairdrop -p 127.0.0.1:3000:3000 ghcr.io/schlagmichdoch/pairdrop -``` +> You must use a server proxy to set the X-Forwarded-For to prevent all clients from discovering each other (See [#HTTP-Server](#http-server)). +> +> To prevent bypassing the proxy by reaching the docker container directly, `127.0.0.1` is specified in the run command. ### Options / Flags Set options by using the following flags in the `docker run` command: @@ -49,8 +44,18 @@ Set options by using the following flags in the `docker run` command:
-## Deployment with Docker with self-built image -### Build the image +### Docker Image from GHCR +```bash +docker run -d --restart=unless-stopped --name=pairdrop -p 127.0.0.1:3000:3000 ghcr.io/schlagmichdoch/pairdrop npm run start:prod +``` +> You must use a server proxy to set the X-Forwarded-For to prevent all clients from discovering each other (See [#HTTP-Server](#http-server)). +> +> To prevent bypassing the proxy by reaching the docker container directly, `127.0.0.1` is specified in the run command. +> +> To specify options replace `npm run start:prod` according to [the documentation below.](#options--flags-1) + +### Docker Image self-built +#### Build the image ```bash docker build --pull . -f Dockerfile -t pairdrop ``` @@ -58,15 +63,15 @@ docker build --pull . -f Dockerfile -t pairdrop > > `--pull` ensures always the latest node image is used. -### Run the image +#### Run the image ```bash docker run -d --restart=unless-stopped --name=pairdrop -p 127.0.0.1:3000:3000 -it pairdrop npm run start:prod ``` > You must use a server proxy to set the X-Forwarded-For to prevent all clients from discovering each other (See [#HTTP-Server](#http-server)). > -> To prevent bypassing the proxy and reach the docker container directly, `127.0.0.1` is specified in the run command. +> To prevent bypassing the proxy by reaching the docker container directly, `127.0.0.1` is specified in the run command. > -> To specify options replace `npm run start:prod` according to [the documentation above.](#options--flags) +> To specify options replace `npm run start:prod` according to [the documentation below.](#options--flags-1) ## Deployment with node @@ -117,7 +122,7 @@ npm start -- --localhost-only > > You must use a server proxy to set the X-Forwarded-For to prevent all clients from discovering each other (See [#HTTP-Server](#http-server)). > -> Use this when deploying PairDrop with node to prevent bypassing the proxy and reach the docker container directly. +> Use this when deploying PairDrop with node to prevent bypassing the proxy by reaching the docker container directly. #### Automatic restart on error ```bash From 80dc36c00ab44d62673904110f5d8187fa329fcb Mon Sep 17 00:00:00 2001 From: kylethedeveloper <8023096+kylethedeveloper@users.noreply.github.com> Date: Sun, 26 Feb 2023 01:34:37 -0600 Subject: [PATCH 10/15] merge commit --- .github/workflows/github-image.yml | 51 +++++++++++++++++++ package-lock.json | 4 +- package.json | 2 +- public/scripts/ui.js | 4 +- public/service-worker.js | 2 +- public/styles.css | 20 ++++++-- public_included_ws_fallback/scripts/ui.js | 4 +- public_included_ws_fallback/service-worker.js | 2 +- public_included_ws_fallback/styles.css | 20 ++++++-- 9 files changed, 92 insertions(+), 17 deletions(-) create mode 100644 .github/workflows/github-image.yml diff --git a/.github/workflows/github-image.yml b/.github/workflows/github-image.yml new file mode 100644 index 0000000..c240bf8 --- /dev/null +++ b/.github/workflows/github-image.yml @@ -0,0 +1,51 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +# GitHub recommends pinning actions to a commit SHA. +# To get a newer version, you will need to update the SHA. +# You can also reference a tag or branch, but the action may change without warning. + +name: GHCR Image CI + +on: + push: + tags: + - "v*.*.*" + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push-image: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Log in to the Container registry + uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + + - name: Build and push Docker image + uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/package-lock.json b/package-lock.json index 3fb3b38..c9ddb2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "pairdrop", - "version": "1.1.0", + "version": "1.1.3", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "pairdrop", - "version": "1.1.0", + "version": "1.1.3", "license": "ISC", "dependencies": { "express": "^4.18.2", diff --git a/package.json b/package.json index 0196f9f..30444eb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pairdrop", - "version": "1.1.0", + "version": "1.1.3", "description": "", "main": "index.js", "scripts": { diff --git a/public/scripts/ui.js b/public/scripts/ui.js index 161f7b3..e864781 100644 --- a/public/scripts/ui.js +++ b/public/scripts/ui.js @@ -839,8 +839,8 @@ class PairDeviceDialog extends Dialog { // Display the QR code for the url const qr = new QRCode({ content: this._getShareRoomURL(), - width: 80, - height: 80, + width: 150, + height: 150, padding: 0, background: "transparent", color: getComputedStyle(document.body).getPropertyValue('--text-color'), diff --git a/public/service-worker.js b/public/service-worker.js index e05a378..1f1adc6 100644 --- a/public/service-worker.js +++ b/public/service-worker.js @@ -1,4 +1,4 @@ -const cacheVersion = 'v1.1.0'; +const cacheVersion = 'v1.1.3'; const cacheTitle = `pairdrop-cache-${cacheVersion}`; const urlsToCache = [ 'index.html', diff --git a/public/styles.css b/public/styles.css index dcec033..aa08cbc 100644 --- a/public/styles.css +++ b/public/styles.css @@ -11,7 +11,8 @@ /* Layout */ html { - height: 100%; + min-height: 100%; + height: -webkit-fill-available; } html, @@ -25,6 +26,8 @@ body { } body { + min-height: 100%; + min-height: -webkit-fill-available; flex-grow: 1; align-items: center; justify-content: center; @@ -407,6 +410,7 @@ x-dialog x-background { transition: opacity 300ms; will-change: opacity; padding: 35px; + overflow: overlay; } x-dialog x-paper { @@ -421,6 +425,13 @@ x-dialog x-paper { will-change: transform; } +#pairDeviceDialog x-paper { + position: absolute; + top: max(50%, 350px); + height: 650px; + margin-top: -325px; +} + x-dialog:not([show]) { pointer-events: none; } @@ -491,8 +502,8 @@ x-dialog .font-subheading { #roomKeyQrCode { padding: inherit; margin: auto; - width: 80px; - height: 80px; + width: 150px; + height: 150px; } #pairDeviceDialog hr { @@ -619,6 +630,7 @@ x-dialog .row-reverse { #base64PasteDialog button[close] { margin-top: 20px; } + #base64PasteDialog button[close]:before { border-radius: 8px; } @@ -931,7 +943,7 @@ screen and (min-width: 1100px) { position: fixed; } - x-instructions:before { + x-instructions:not([drop-peer]):not([drop-bg]):before { content: attr(mobile); } } diff --git a/public_included_ws_fallback/scripts/ui.js b/public_included_ws_fallback/scripts/ui.js index 8e1a306..da103ed 100644 --- a/public_included_ws_fallback/scripts/ui.js +++ b/public_included_ws_fallback/scripts/ui.js @@ -840,8 +840,8 @@ class PairDeviceDialog extends Dialog { // Display the QR code for the url const qr = new QRCode({ content: this._getShareRoomURL(), - width: 80, - height: 80, + width: 150, + height: 150, padding: 0, background: "transparent", color: getComputedStyle(document.body).getPropertyValue('--text-color'), diff --git a/public_included_ws_fallback/service-worker.js b/public_included_ws_fallback/service-worker.js index b510684..9e968ec 100644 --- a/public_included_ws_fallback/service-worker.js +++ b/public_included_ws_fallback/service-worker.js @@ -1,4 +1,4 @@ -const cacheVersion = 'v1.1.0'; +const cacheVersion = 'v1.1.3'; const cacheTitle = `pairdrop-included-ws-fallback-cache-${cacheVersion}`; const urlsToCache = [ 'index.html', diff --git a/public_included_ws_fallback/styles.css b/public_included_ws_fallback/styles.css index 9df5852..ab61629 100644 --- a/public_included_ws_fallback/styles.css +++ b/public_included_ws_fallback/styles.css @@ -12,7 +12,8 @@ /* Layout */ html { - height: 100%; + min-height: 100%; + height: -webkit-fill-available; } html, @@ -26,6 +27,8 @@ body { } body { + min-height: 100%; + min-height: -webkit-fill-available; flex-grow: 1; align-items: center; justify-content: center; @@ -416,6 +419,7 @@ x-dialog x-background { transition: opacity 300ms; will-change: opacity; padding: 35px; + overflow: overlay; } x-dialog x-paper { @@ -430,6 +434,13 @@ x-dialog x-paper { will-change: transform; } +#pairDeviceDialog x-paper { + position: absolute; + top: max(50%, 350px); + height: 650px; + margin-top: -325px; +} + x-dialog:not([show]) { pointer-events: none; } @@ -500,8 +511,8 @@ x-dialog .font-subheading { #roomKeyQrCode { padding: inherit; margin: auto; - width: 80px; - height: 80px; + width: 150px; + height: 150px; } #pairDeviceDialog hr { @@ -628,6 +639,7 @@ x-dialog .row-reverse { #base64PasteDialog button[close] { margin-top: 20px; } + #base64PasteDialog button[close]:before { border-radius: 8px; } @@ -944,7 +956,7 @@ screen and (min-width: 1100px) { position: fixed; } - x-instructions:before { + x-instructions:not([drop-peer]):not([drop-bg]):before { content: attr(mobile); } } From 75726ae5f4e3abfe0806695d2b2ec62d4f2c2f3c Mon Sep 17 00:00:00 2001 From: kylethedeveloper <8023096+kylethedeveloper@users.noreply.github.com> Date: Sun, 26 Feb 2023 01:35:19 -0600 Subject: [PATCH 11/15] resolve comments on documentation --- docs/host-your-own.md | 63 ++++++++++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 22 deletions(-) diff --git a/docs/host-your-own.md b/docs/host-your-own.md index 2be4952..88529ea 100644 --- a/docs/host-your-own.md +++ b/docs/host-your-own.md @@ -1,14 +1,17 @@ # Deployment Notes The easiest way to get PairDrop up and running is by using Docker. -## Deployment with Docker from Docker Hub +## Deployment with Docker + +### Docker Image from Docker Hub ```bash docker run -d --restart=unless-stopped --name=pairdrop -p 127.0.0.1:3000:3000 lscr.io/linuxserver/pairdrop ``` + > You must use a server proxy to set the X-Forwarded-For to prevent all clients from discovering each other (See [#HTTP-Server](#http-server)). > -> To prevent bypassing the proxy and reach the docker container directly, `127.0.0.1` is specified in the run command. +> To prevent bypassing the proxy by reaching the docker container directly, `127.0.0.1` is specified in the run command. ### Options / Flags Set options by using the following flags in the `docker run` command: @@ -41,6 +44,37 @@ Set options by using the following flags in the `docker run` command:
+### Docker Image from GHCR +```bash +docker run -d --restart=unless-stopped --name=pairdrop -p 127.0.0.1:3000:3000 ghcr.io/schlagmichdoch/pairdrop npm run start:prod +``` +> You must use a server proxy to set the X-Forwarded-For to prevent all clients from discovering each other (See [#HTTP-Server](#http-server)). +> +> To prevent bypassing the proxy by reaching the docker container directly, `127.0.0.1` is specified in the run command. +> +> To specify options replace `npm run start:prod` according to [the documentation below.](#options--flags-1) + +### Docker Image self-built +#### Build the image +```bash +docker build --pull . -f Dockerfile -t pairdrop +``` +> A GitHub action is set up to do this step automatically. +> +> `--pull` ensures always the latest node image is used. + +#### Run the image +```bash +docker run -d --restart=unless-stopped --name=pairdrop -p 127.0.0.1:3000:3000 -it pairdrop npm run start:prod +``` +> You must use a server proxy to set the X-Forwarded-For to prevent all clients from discovering each other (See [#HTTP-Server](#http-server)). +> +> To prevent bypassing the proxy by reaching the docker container directly, `127.0.0.1` is specified in the run command. +> +> To specify options replace `npm run start:prod` according to [the documentation below.](#options--flags-1) + +
+ ## Deployment with Docker Compose Here's an example docker-compose file: @@ -59,31 +93,16 @@ services: - RATE_LIMIT=false # Set to true to limit clients to 100 requests per 5 min. - TZ=Etc/UTC # Time Zone ports: - - 3000:3000 # Web UI + - 127.0.0.1:3000:3000 # Web UI ``` Run the compose file with `docker compose up -d`. -
- -## Deployment with Docker with self-built image -### Build the image -```bash -docker build --pull . -f Dockerfile -t pairdrop -``` -> A GitHub action is set up to do this step automatically. -> -> `--pull` ensures always the latest node image is used. - -### Run the image -```bash -docker run -d --restart=unless-stopped --name=pairdrop -p 127.0.0.1:3000:3000 -it pairdrop npm run start:prod -``` > You must use a server proxy to set the X-Forwarded-For to prevent all clients from discovering each other (See [#HTTP-Server](#http-server)). > -> To prevent bypassing the proxy and reach the docker container directly, `127.0.0.1` is specified in the run command. -> -> To specify options replace `npm run start:prod` according to [the documentation above.](#options--flags) +> To prevent bypassing the proxy by reaching the docker container directly, `127.0.0.1` is specified in the run command. + +
## Deployment with node @@ -134,7 +153,7 @@ npm start -- --localhost-only > > You must use a server proxy to set the X-Forwarded-For to prevent all clients from discovering each other (See [#HTTP-Server](#http-server)). > -> Use this when deploying PairDrop with node to prevent bypassing the proxy and reach the docker container directly. +> Use this when deploying PairDrop with node to prevent bypassing the proxy by reaching the docker container directly. #### Automatic restart on error ```bash From e96ca53aa4d4c5614a052bac6d8ecfa9c161b436 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Sun, 26 Feb 2023 21:28:17 +0100 Subject: [PATCH 12/15] Fix rate limit docs and set header hierarchy correctly --- docs/host-your-own.md | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/docs/host-your-own.md b/docs/host-your-own.md index 88529ea..9306fb7 100644 --- a/docs/host-your-own.md +++ b/docs/host-your-own.md @@ -13,23 +13,23 @@ docker run -d --restart=unless-stopped --name=pairdrop -p 127.0.0.1:3000:3000 ls > > To prevent bypassing the proxy by reaching the docker container directly, `127.0.0.1` is specified in the run command. -### Options / Flags +#### Options / Flags Set options by using the following flags in the `docker run` command: -#### Port +##### Port ``` -p 127.0.0.1:8080:3000 ``` > Specify the port used by the docker image > - 3000 -> `-p 127.0.0.1:3000:3000` > - 8080 -> `-p 127.0.0.1:8080:3000` -#### Rate limiting requests +##### Rate limiting requests ``` -e RATE_LIMIT=true ``` -> Limits clients to 100 requests per 5 min +> Limits clients to 1000 requests per 5 min -#### Websocket Fallback (for VPN) +##### Websocket Fallback (for VPN) ``` -e WS_FALLBACK=true ``` @@ -42,8 +42,6 @@ Set options by using the following flags in the `docker run` command: > Beware that the traffic routed via this fallback is readable by the server. Only ever use this on instances you can trust. > Additionally, beware that all traffic using this fallback debits the servers data plan. -
- ### Docker Image from GHCR ```bash docker run -d --restart=unless-stopped --name=pairdrop -p 127.0.0.1:3000:3000 ghcr.io/schlagmichdoch/pairdrop npm run start:prod @@ -76,7 +74,6 @@ docker run -d --restart=unless-stopped --name=pairdrop -p 127.0.0.1:3000:3000 -i
## Deployment with Docker Compose - Here's an example docker-compose file: ```yaml @@ -90,7 +87,7 @@ services: - PUID=1000 # UID to run the application as - PGID=1000 # GID to run the application as - WS_FALLBACK=false # Set to true to enable websocket fallback if the peer to peer WebRTC connection is not available to the client. - - RATE_LIMIT=false # Set to true to limit clients to 100 requests per 5 min. + - RATE_LIMIT=false # Set to true to limit clients to 1000 requests per 5 min. - TZ=Etc/UTC # Time Zone ports: - 127.0.0.1:3000:3000 # Web UI @@ -167,7 +164,7 @@ npm start -- --auto-restart ```bash npm start -- --rate-limit ``` -> Limits clients to 100 requests per 5 min +> Limits clients to 1000 requests per 5 min
From 7b08973cef1758e3e7c4c5f291237dc3b779d6c6 Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Wed, 1 Mar 2023 03:15:56 +0100 Subject: [PATCH 13/15] remove safari audio blop "hack" as it should not completely stop music that is playing in the background --- public/scripts/ui.js | 6 ------ public_included_ws_fallback/scripts/ui.js | 6 ------ 2 files changed, 12 deletions(-) diff --git a/public/scripts/ui.js b/public/scripts/ui.js index e864781..c4373ae 100644 --- a/public/scripts/ui.js +++ b/public/scripts/ui.js @@ -1791,9 +1791,3 @@ Notifications permission has been blocked 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 = _ => { // safari hack to fix audio - document.body.onclick = null; - if (!(/.*Version.*Safari.*/.test(navigator.userAgent))) return; - blop.play(); -} diff --git a/public_included_ws_fallback/scripts/ui.js b/public_included_ws_fallback/scripts/ui.js index da103ed..e33dddb 100644 --- a/public_included_ws_fallback/scripts/ui.js +++ b/public_included_ws_fallback/scripts/ui.js @@ -1792,9 +1792,3 @@ Notifications permission has been blocked 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 = _ => { // safari hack to fix audio - document.body.onclick = null; - if (!(/.*Version.*Safari.*/.test(navigator.userAgent))) return; - blop.play(); -} From 4566528179a73e4295f3a8fa4351311b20f8f2ed Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Wed, 1 Mar 2023 10:04:37 +0100 Subject: [PATCH 14/15] - restructure UI to use flexbox everywhere - structure peers on desktop responsively - make peer box scrollable when peers are overflowing + shadow - add highlight badge to differentiate local peers into paired and not paired - change websocket fallback warning and move to the bottom --- public/index.html | 32 +- public/scripts/ui.js | 112 ++++--- public/styles.css | 351 ++++++++++++++------ public_included_ws_fallback/index.html | 38 ++- public_included_ws_fallback/scripts/ui.js | 111 ++++--- public_included_ws_fallback/styles.css | 382 ++++++++++++++++------ 6 files changed, 724 insertions(+), 302 deletions(-) diff --git a/public/index.html b/public/index.html index 14d50cf..2c88850 100644 --- a/public/index.html +++ b/public/index.html @@ -69,17 +69,21 @@ + - - - - -

Open PairDrop on other devices to send files

-
Pair devices to be discoverable on other networks
-
- -

-
+ +
+ +
+ + +

Open PairDrop on other devices to send files

+
Pair devices to be discoverable on other networks
+
+ +

+
+

PairDrop - Send a Message

-
+

PairDrop

+
+ Send a Message to + +
+
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 @@ + - - - - -

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
+
+ +

+
+
on this network
+
+ Traffic is routed through the server if WebRTC is not available. +
@@ -186,8 +190,12 @@
-

PairDrop - Send a Message

-
+

PairDrop

+
+ Send a Message to + +
+
diff --git a/public_included_ws_fallback/scripts/ui.js b/public_included_ws_fallback/scripts/ui.js index e33dddb..d368173 100644 --- a/public_included_ws_fallback/scripts/ui.js +++ b/public_included_ws_fallback/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'); if (!this._peer.rtcSupported || !window.isRtcSupported) this.$el.classList.add('ws-peer') this.html(); @@ -273,7 +294,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 @@ -394,21 +415,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; } } @@ -844,7 +872,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 }); @@ -936,6 +964,7 @@ class PairDeviceDialog extends Dialog { this.$clearSecretsBtn.setAttribute('hidden', ''); this.$footerInstructionsPairedDevices.setAttribute('hidden', ''); } + Events.fire('bg-resize'); }).catch(_ => PersistentStorage.logBrowserNotCapable()); } } @@ -961,8 +990,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()); @@ -993,8 +1023,9 @@ class SendTextDialog extends Dialog { } } - _onRecipient(peerId) { + _onRecipient(peerId, deviceName) { this.correspondingPeerId = peerId; + this.$peerDisplayName.innerText = deviceName; this.show(); const range = document.createRange(); @@ -1247,6 +1278,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)); } @@ -1715,19 +1747,14 @@ 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; 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_included_ws_fallback/styles.css b/public_included_ws_fallback/styles.css index ab61629..60bb6a6 100644 --- a/public_included_ws_fallback/styles.css +++ b/public_included_ws_fallback/styles.css @@ -11,28 +11,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 { @@ -74,10 +71,7 @@ body { } header { - position: absolute; - top: 0; - left: 0; - right: 0; + position: relative; height: 56px; align-items: center; padding: 16px; @@ -120,9 +114,9 @@ h3 { } .font-subheading { - font-size: 16px; + font-size: 14px; font-weight: 400; - line-height: 24px; + line-height: 18px; word-break: normal; } @@ -200,20 +194,160 @@ body>header a { margin-left: 8px; } +#center { + position: relative; + display: flex; + flex-direction: column-reverse; + flex-grow: 1; + --footer-height: 146px; + 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 (min-width: 402px) and (max-width: 425px) { + header:has(#clear-pair-devices:not([hidden]))~#center { + --footer-height: 164px; + } +} + +@media screen and (max-width: 402px) { + #center { + --footer-height: 184px; + } +} /* 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; +} + +/* peers-per-row if height is too small for 2 rows */ +@media screen and (min-height: 538px) and (max-height: 683px) and (max-width: 402px), +screen and (min-height: 517px) and (max-height: 664px) and (max-width: 426px), +screen and (min-height: 501px) and (max-height: 647px) 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; + } +} + +/* peers-per-row if height is too small for 3 rows */ +@media screen and (min-height: 683px) and (max-width: 402px), +screen and (min-height: 664px) and (max-width: 426px), +screen and (min-height: 647px) 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 */ @@ -255,25 +389,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; @@ -281,27 +409,43 @@ 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.ws-peer x-icon { - border: solid 4px var(--ws-peer-color); +x-peer x-icon > .highlight-wrapper { + align-self: center; + align-items: center; + margin: 7px auto 0; + height: 6px; } -x-peer.ws-peer .progress { - margin-top: 4px; +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, @@ -315,6 +459,35 @@ x-peer[status] x-icon { transform: scale(1); } + +x-peer.ws-peer { + margin-top: -1.5px; +} + +x-peer.ws-peer .progress { + margin-top: 3px; +} + +x-peer.ws-peer .icon-wrapper{ + border: solid 3px var(--ws-peer-color); +} + +x-peer.ws-peer .highlight-wrapper { + margin-top: 3px; +} + +.device-descriptor { + text-align: center; +} + +.name { + width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + text-align: center; +} + .status, .device-name, .connection-hash { @@ -380,12 +553,10 @@ 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; transition: color 300ms; } @@ -394,6 +565,7 @@ footer .logo { --icon-size: 80px; margin-bottom: 8px; color: var(--primary-color); + margin-top: -10px; } footer .font-body2 { @@ -439,6 +611,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]) { @@ -518,6 +693,7 @@ x-dialog .font-subheading { #pairDeviceDialog hr { margin-top: 40px; margin-bottom: 40px; + width: 100%; } #pairDeviceDialog x-background { @@ -541,7 +717,7 @@ x-dialog h2 { } x-dialog .row-reverse { - margin: 40px -24px auto; + margin: 40px -24px 0; border-top: solid 2.5px var(--border-color); } @@ -698,16 +874,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, @@ -818,7 +996,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; @@ -885,13 +1063,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 { @@ -908,92 +1089,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; } -.websocket-fallback { +@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); - padding-bottom: 1px; } /* Responsive Styles */ -@media (min-height: 800px) { - footer { - margin-bottom: 16px; +@media screen and (min-height: 800px) { + #websocket-fallback { + padding-bottom: 15px; } } -@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; } @@ -1031,16 +1204,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; } @@ -1058,6 +1231,15 @@ x-dialog x-paper { } } +/* + iOS specific styles +*/ +@supports (-webkit-overflow-scrolling: touch) { + html { + min-height: -webkit-fill-available; + } +} + /* webkit scrollbar style*/ ::-webkit-scrollbar{ From a3b348d9b6294e2bd11ae0a581716dd7eb4f707b Mon Sep 17 00:00:00 2001 From: schlagmichdoch Date: Wed, 1 Mar 2023 10:44:57 +0100 Subject: [PATCH 15/15] refactor all missing html ids to kebap-case --- public/index.html | 74 +++++++++++------------ public/scripts/ui.js | 50 +++++++-------- public/styles.css | 46 +++++++------- public_included_ws_fallback/index.html | 74 +++++++++++------------ public_included_ws_fallback/scripts/ui.js | 50 +++++++-------- public_included_ws_fallback/styles.css | 46 +++++++------- 6 files changed, 170 insertions(+), 170 deletions(-) diff --git a/public/index.html b/public/index.html index 2c88850..59a257a 100644 --- a/public/index.html +++ b/public/index.html @@ -81,7 +81,7 @@
Pair devices to be discoverable on other networks
-

+

@@ -89,29 +89,29 @@ -
+
You can be discovered by everyone on this network
- +

Pair Devices

-
-

000 000

-
Input this key on another device
or scan the QR-Code.
+
+

000 000

+
Input this key on another device
or scan the QR-Code.

-
- - - - - - +
+ + + + + +
Enter key from another device to continue.
@@ -124,7 +124,7 @@ - +
@@ -139,43 +139,43 @@
- +

PairDrop

- + would like to share
-
- - +
+ +
- +
- +
- +
- + -

+

- +
@@ -183,16 +183,16 @@
- +

PairDrop

Send a Message to - +
-
+
@@ -203,36 +203,36 @@ - +

PairDrop - Message Received

-
- +
+ sent the following message:
- +
- +
- - + + - +
- +
diff --git a/public/scripts/ui.js b/public/scripts/ui.js index cd1aedd..25733b8 100644 --- a/public/scripts/ui.js +++ b/public/scripts/ui.js @@ -10,7 +10,7 @@ window.pasteMode.activated = false; // set display name Events.on('display-name', e => { const me = e.detail.message; - const $displayName = $('displayName') + const $displayName = $('display-name') $displayName.textContent = 'You are known as ' + me.displayName; $displayName.title = me.deviceName; }); @@ -497,10 +497,10 @@ class ReceiveDialog extends Dialog { class ReceiveFileDialog extends ReceiveDialog { constructor() { - super('receiveFileDialog'); + super('receive-file-dialog'); - this.$shareOrDownloadBtn = this.$el.querySelector('#shareOrDownload'); - this.$receiveTitleNode = this.$el.querySelector('#receiveTitle') + this.$shareOrDownloadBtn = this.$el.querySelector('#share-or-download'); + this.$receiveTitleNode = this.$el.querySelector('#receive-title') Events.on('files-received', e => this._onFilesReceived(e.detail.sender, e.detail.files, e.detail.request)); this._filesQueue = []; @@ -659,15 +659,15 @@ class ReceiveFileDialog extends ReceiveDialog { class ReceiveRequestDialog extends ReceiveDialog { constructor() { - super('receiveRequestDialog'); + super('receive-request-dialog'); - this.$requestingPeerDisplayNameNode = this.$el.querySelector('#requestingPeerDisplayName'); - this.$fileStemNode = this.$el.querySelector('#fileStem'); - this.$fileExtensionNode = this.$el.querySelector('#fileExtension'); - this.$fileOtherNode = this.$el.querySelector('#fileOther'); + this.$requestingPeerDisplayNameNode = this.$el.querySelector('#requesting-peer-display-name'); + this.$fileStemNode = this.$el.querySelector('#file-stem'); + this.$fileExtensionNode = this.$el.querySelector('#file-extension'); + this.$fileOtherNode = this.$el.querySelector('#file-other'); - this.$acceptRequestBtn = this.$el.querySelector('#acceptRequest'); - this.$declineRequestBtn = this.$el.querySelector('#declineRequest'); + this.$acceptRequestBtn = this.$el.querySelector('#accept-request'); + this.$declineRequestBtn = this.$el.querySelector('#decline-request'); this.$acceptRequestBtn.addEventListener('click', _ => this._respondToFileTransferRequest(true)); this.$declineRequestBtn.addEventListener('click', _ => this._respondToFileTransferRequest(false)); @@ -748,12 +748,12 @@ class ReceiveRequestDialog extends ReceiveDialog { class PairDeviceDialog extends Dialog { constructor() { - super('pairDeviceDialog'); + super('pair-device-dialog'); $('pair-device').addEventListener('click', _ => this._pairDeviceInitiate()); - this.$inputRoomKeyChars = this.$el.querySelectorAll('#keyInputContainer>input'); + this.$inputRoomKeyChars = this.$el.querySelectorAll('#key-input-container>input'); this.$submitBtn = this.$el.querySelector('button[type="submit"]'); - this.$roomKey = this.$el.querySelector('#roomKey'); - this.$qrCode = this.$el.querySelector('#roomKeyQrCode'); + this.$roomKey = this.$el.querySelector('#room-key'); + this.$qrCode = this.$el.querySelector('#room-key-qr-code'); this.$clearSecretsBtn = $('clear-pair-devices'); this.$footerInstructionsPairedDevices = $('and-by-paired-devices'); let createJoinForm = this.$el.querySelector('form'); @@ -827,7 +827,7 @@ class PairDeviceDialog extends Dialog { } evaluateRoomKeyChars() { - if (this.$el.querySelectorAll('#keyInputContainer>input:placeholder-shown').length > 0) { + if (this.$el.querySelectorAll('#key-input-container>input:placeholder-shown').length > 0) { this.$submitBtn.setAttribute("disabled", ""); } else { this.inputRoomKey = ""; @@ -970,7 +970,7 @@ class PairDeviceDialog extends Dialog { class ClearDevicesDialog extends Dialog { constructor() { - super('clearDevicesDialog'); + super('clear-devices-dialog'); $('clear-pair-devices').addEventListener('click', _ => this._onClearPairDevices()); let clearDevicesForm = this.$el.querySelector('form'); clearDevicesForm.addEventListener('submit', _ => this._onSubmit()); @@ -988,10 +988,10 @@ class ClearDevicesDialog extends Dialog { class SendTextDialog extends Dialog { constructor() { - super('sendTextDialog'); + super('send-text-dialog'); 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.$text = this.$el.querySelector('#text-input'); + this.$peerDisplayName = this.$el.querySelector('#text-send-peer-display-name'); this.$form = this.$el.querySelector('form'); this.$submit = this.$el.querySelector('button[type="submit"]'); this.$form.addEventListener('submit', _ => this._send()); @@ -1048,7 +1048,7 @@ class SendTextDialog extends Dialog { class ReceiveTextDialog extends Dialog { constructor() { - super('receiveTextDialog'); + super('receive-text-dialog'); Events.on('text-received', e => this._onText(e.detail.text, e.detail.peerId)); this.$text = this.$el.querySelector('#text'); this.$copy = this.$el.querySelector('#copy'); @@ -1059,7 +1059,7 @@ class ReceiveTextDialog extends Dialog { Events.on("keydown", e => this._onKeyDown(e)); - this.$receiveTextPeerDisplayNameNode = this.$el.querySelector('#receiveTextPeerDisplayName'); + this.$receiveTextPeerDisplayNameNode = this.$el.querySelector('#receive-text-peer-display-name'); this._receiveTextQueue = []; } @@ -1120,13 +1120,13 @@ class ReceiveTextDialog extends Dialog { class Base64ZipDialog extends Dialog { constructor() { - super('base64PasteDialog'); + super('base64-paste-dialog'); const urlParams = new URL(window.location).searchParams; const base64Text = urlParams.get('base64text'); const base64Zip = urlParams.get('base64zip'); const base64Hash = window.location.hash.substring(1); - this.$pasteBtn = this.$el.querySelector('#base64PasteBtn'); + this.$pasteBtn = this.$el.querySelector('#base64-paste-btn'); if (base64Text) { this.show(); @@ -1353,7 +1353,7 @@ class Notifications { } _download(notification) { - $('shareOrDownload').click(); + $('share-or-download').click(); notification.close(); } diff --git a/public/styles.css b/public/styles.css index 25be4a1..d3c05ac 100644 --- a/public/styles.css +++ b/public/styles.css @@ -580,7 +580,7 @@ x-dialog x-paper { will-change: transform; } -#pairDeviceDialog x-paper { +#pair-device-dialog x-paper { position: absolute; top: max(50%, 350px); height: 650px; @@ -619,13 +619,13 @@ x-dialog .font-subheading { /* PairDevicesDialog */ -#keyInputContainer { +#key-input-container { width: 100%; display: flex; justify-content: center; } -#keyInputContainer>input { +#key-input-container>input { width: 45px; height: 45px; font-size: 30px; @@ -641,15 +641,15 @@ x-dialog .font-subheading { justify-content: center; } -#keyInputContainer>input + * { +#key-input-container>input + * { margin-left: 6px; } -#keyInputContainer>input:nth-of-type(4) { +#key-input-container>input:nth-of-type(4) { margin-left: 18px; } -#roomKey { +#room-key { font-size: 50px; letter-spacing: min(calc((100vw - 80px - 99px) / 100 * 7), 23px); display: inline-block; @@ -657,20 +657,20 @@ x-dialog .font-subheading { margin: 15px -15px; } -#roomKeyQrCode { +#room-key-qr-code { padding: inherit; margin: auto; width: 150px; height: 150px; } -#pairDeviceDialog hr { +#pair-device-dialog hr { margin-top: 40px; margin-bottom: 40px; width: 100%; } -#pairDeviceDialog x-background { +#pair-device-dialog x-background { padding: 16px!important; } @@ -685,8 +685,8 @@ x-dialog h2 { margin-top: 1rem; } -#receiveRequestDialog h2, -#receiveFileDialog h2 { +#receive-request-dialog h2, +#receive-file-dialog h2 { margin-bottom: 0.5rem; } @@ -715,11 +715,11 @@ x-dialog .row-reverse { word-break: normal; } -#fileName { +#file-name { font-style: italic; } -#fileStem { +#file-stem { max-width: 80%; overflow: hidden; text-overflow: ellipsis; @@ -733,13 +733,13 @@ x-dialog .row-reverse { /* Send Text Dialog */ -#textInput { +#text-input { min-height: 120px; } /* Receive Text Dialog */ -#receiveTextDialog #text { +#receive-text-dialog #text { width: 100%; word-break: break-all; max-height: 300px; @@ -752,15 +752,15 @@ x-dialog .row-reverse { margin-top:36px; } -#receiveTextDialog #text a { +#receive-text-dialog #text a { cursor: pointer; } -#receiveTextDialog #text a:hover { +#receive-text-dialog #text a:hover { text-decoration: underline; } -#receiveTextDialog h3 { +#receive-text-dialog h3 { /* Select the received text when double-clicking the dialog */ user-select: none; pointer-events: none; @@ -771,26 +771,26 @@ x-dialog .row-reverse { margin: auto -25px; } -#receiveTextDescriptionContainer { +#receive-text-description-container { margin-bottom: 25px; } -#base64PasteBtn { +#base64-paste-btn { width: 100%; height: 40vh; border: solid 12px #438cff; } -#base64PasteDialog button { +#base64-paste-dialog button { margin: auto; border-radius: 8px; } -#base64PasteDialog button[close] { +#base64-paste-dialog button[close] { margin-top: 20px; } -#base64PasteDialog button[close]:before { +#base64-paste-dialog button[close]:before { border-radius: 8px; } diff --git a/public_included_ws_fallback/index.html b/public_included_ws_fallback/index.html index b5042a8..8227434 100644 --- a/public_included_ws_fallback/index.html +++ b/public_included_ws_fallback/index.html @@ -81,7 +81,7 @@
Pair devices to be discoverable on other networks
-

+

@@ -89,7 +89,7 @@ -
+
You can be discovered by everyone on this network @@ -99,22 +99,22 @@
- +

Pair Devices

-
-

000 000

-
Input this key on another device
or scan the QR-Code.
+
+

000 000

+
Input this key on another device
or scan the QR-Code.

-
- - - - - - +
+ + + + + +
Enter key from another device to continue.
@@ -127,7 +127,7 @@ - +
@@ -142,43 +142,43 @@
- +

PairDrop

- + would like to share
-
- - +
+ +
- +
- +
- +
- + -

+

- +
@@ -186,16 +186,16 @@
- +

PairDrop

Send a Message to - +
-
+
@@ -206,36 +206,36 @@ - +

PairDrop - Message Received

-
- +
+ sent the following message:
- +
- +
- - + + - +
- +
diff --git a/public_included_ws_fallback/scripts/ui.js b/public_included_ws_fallback/scripts/ui.js index d368173..dadfb02 100644 --- a/public_included_ws_fallback/scripts/ui.js +++ b/public_included_ws_fallback/scripts/ui.js @@ -10,7 +10,7 @@ window.pasteMode.activated = false; // set display name Events.on('display-name', e => { const me = e.detail.message; - const $displayName = $('displayName') + const $displayName = $('display-name') $displayName.textContent = 'You are known as ' + me.displayName; $displayName.title = me.deviceName; }); @@ -498,10 +498,10 @@ class ReceiveDialog extends Dialog { class ReceiveFileDialog extends ReceiveDialog { constructor() { - super('receiveFileDialog'); + super('receive-file-dialog'); - this.$shareOrDownloadBtn = this.$el.querySelector('#shareOrDownload'); - this.$receiveTitleNode = this.$el.querySelector('#receiveTitle') + this.$shareOrDownloadBtn = this.$el.querySelector('#share-or-download'); + this.$receiveTitleNode = this.$el.querySelector('#receive-title') Events.on('files-received', e => this._onFilesReceived(e.detail.sender, e.detail.files, e.detail.request)); this._filesQueue = []; @@ -660,15 +660,15 @@ class ReceiveFileDialog extends ReceiveDialog { class ReceiveRequestDialog extends ReceiveDialog { constructor() { - super('receiveRequestDialog'); + super('receive-request-dialog'); - this.$requestingPeerDisplayNameNode = this.$el.querySelector('#requestingPeerDisplayName'); - this.$fileStemNode = this.$el.querySelector('#fileStem'); - this.$fileExtensionNode = this.$el.querySelector('#fileExtension'); - this.$fileOtherNode = this.$el.querySelector('#fileOther'); + this.$requestingPeerDisplayNameNode = this.$el.querySelector('#requesting-peer-display-name'); + this.$fileStemNode = this.$el.querySelector('#file-stem'); + this.$fileExtensionNode = this.$el.querySelector('#file-extension'); + this.$fileOtherNode = this.$el.querySelector('#file-other'); - this.$acceptRequestBtn = this.$el.querySelector('#acceptRequest'); - this.$declineRequestBtn = this.$el.querySelector('#declineRequest'); + this.$acceptRequestBtn = this.$el.querySelector('#accept-request'); + this.$declineRequestBtn = this.$el.querySelector('#decline-request'); this.$acceptRequestBtn.addEventListener('click', _ => this._respondToFileTransferRequest(true)); this.$declineRequestBtn.addEventListener('click', _ => this._respondToFileTransferRequest(false)); @@ -749,12 +749,12 @@ class ReceiveRequestDialog extends ReceiveDialog { class PairDeviceDialog extends Dialog { constructor() { - super('pairDeviceDialog'); + super('pair-device-dialog'); $('pair-device').addEventListener('click', _ => this._pairDeviceInitiate()); - this.$inputRoomKeyChars = this.$el.querySelectorAll('#keyInputContainer>input'); + this.$inputRoomKeyChars = this.$el.querySelectorAll('#key-input-container>input'); this.$submitBtn = this.$el.querySelector('button[type="submit"]'); - this.$roomKey = this.$el.querySelector('#roomKey'); - this.$qrCode = this.$el.querySelector('#roomKeyQrCode'); + this.$roomKey = this.$el.querySelector('#room-key'); + this.$qrCode = this.$el.querySelector('#room-key-qr-code'); this.$clearSecretsBtn = $('clear-pair-devices'); this.$footerInstructionsPairedDevices = $('and-by-paired-devices'); let createJoinForm = this.$el.querySelector('form'); @@ -828,7 +828,7 @@ class PairDeviceDialog extends Dialog { } evaluateRoomKeyChars() { - if (this.$el.querySelectorAll('#keyInputContainer>input:placeholder-shown').length > 0) { + if (this.$el.querySelectorAll('#key-input-container>input:placeholder-shown').length > 0) { this.$submitBtn.setAttribute("disabled", ""); } else { this.inputRoomKey = ""; @@ -971,7 +971,7 @@ class PairDeviceDialog extends Dialog { class ClearDevicesDialog extends Dialog { constructor() { - super('clearDevicesDialog'); + super('clear-devices-dialog'); $('clear-pair-devices').addEventListener('click', _ => this._onClearPairDevices()); let clearDevicesForm = this.$el.querySelector('form'); clearDevicesForm.addEventListener('submit', _ => this._onSubmit()); @@ -989,10 +989,10 @@ class ClearDevicesDialog extends Dialog { class SendTextDialog extends Dialog { constructor() { - super('sendTextDialog'); + super('send-text-dialog'); 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.$text = this.$el.querySelector('#text-input'); + this.$peerDisplayName = this.$el.querySelector('#text-send-peer-display-name'); this.$form = this.$el.querySelector('form'); this.$submit = this.$el.querySelector('button[type="submit"]'); this.$form.addEventListener('submit', _ => this._send()); @@ -1049,7 +1049,7 @@ class SendTextDialog extends Dialog { class ReceiveTextDialog extends Dialog { constructor() { - super('receiveTextDialog'); + super('receive-text-dialog'); Events.on('text-received', e => this._onText(e.detail.text, e.detail.peerId)); this.$text = this.$el.querySelector('#text'); this.$copy = this.$el.querySelector('#copy'); @@ -1060,7 +1060,7 @@ class ReceiveTextDialog extends Dialog { Events.on("keydown", e => this._onKeyDown(e)); - this.$receiveTextPeerDisplayNameNode = this.$el.querySelector('#receiveTextPeerDisplayName'); + this.$receiveTextPeerDisplayNameNode = this.$el.querySelector('#receive-text-peer-display-name'); this._receiveTextQueue = []; } @@ -1121,13 +1121,13 @@ class ReceiveTextDialog extends Dialog { class Base64ZipDialog extends Dialog { constructor() { - super('base64PasteDialog'); + super('base64-paste-dialog'); const urlParams = new URL(window.location).searchParams; const base64Text = urlParams.get('base64text'); const base64Zip = urlParams.get('base64zip'); const base64Hash = window.location.hash.substring(1); - this.$pasteBtn = this.$el.querySelector('#base64PasteBtn'); + this.$pasteBtn = this.$el.querySelector('#base64-paste-btn'); if (base64Text) { this.show(); @@ -1354,7 +1354,7 @@ class Notifications { } _download(notification) { - $('shareOrDownload').click(); + $('share-or-download').click(); notification.close(); } diff --git a/public_included_ws_fallback/styles.css b/public_included_ws_fallback/styles.css index 60bb6a6..f153398 100644 --- a/public_included_ws_fallback/styles.css +++ b/public_included_ws_fallback/styles.css @@ -606,7 +606,7 @@ x-dialog x-paper { will-change: transform; } -#pairDeviceDialog x-paper { +#pair-device-dialog x-paper { position: absolute; top: max(50%, 350px); height: 650px; @@ -645,13 +645,13 @@ x-dialog .font-subheading { /* PairDevicesDialog */ -#keyInputContainer { +#key-input-container { width: 100%; display: flex; justify-content: center; } -#keyInputContainer>input { +#key-input-container>input { width: 45px; height: 45px; font-size: 30px; @@ -667,15 +667,15 @@ x-dialog .font-subheading { justify-content: center; } -#keyInputContainer>input + * { +#key-input-container>input + * { margin-left: 6px; } -#keyInputContainer>input:nth-of-type(4) { +#key-input-container>input:nth-of-type(4) { margin-left: 18px; } -#roomKey { +#room-key { font-size: 50px; letter-spacing: min(calc((100vw - 80px - 99px) / 100 * 7), 23px); display: inline-block; @@ -683,20 +683,20 @@ x-dialog .font-subheading { margin: 15px -15px; } -#roomKeyQrCode { +#room-key-qr-code { padding: inherit; margin: auto; width: 150px; height: 150px; } -#pairDeviceDialog hr { +#pair-device-dialog hr { margin-top: 40px; margin-bottom: 40px; width: 100%; } -#pairDeviceDialog x-background { +#pair-device-dialog x-background { padding: 16px!important; } @@ -711,8 +711,8 @@ x-dialog h2 { margin-top: 1rem; } -#receiveRequestDialog h2, -#receiveFileDialog h2 { +#receive-request-dialog h2, +#receive-file-dialog h2 { margin-bottom: 0.5rem; } @@ -741,11 +741,11 @@ x-dialog .row-reverse { word-break: normal; } -#fileName { +#file-name { font-style: italic; } -#fileStem { +#file-stem { max-width: 80%; overflow: hidden; text-overflow: ellipsis; @@ -759,13 +759,13 @@ x-dialog .row-reverse { /* Send Text Dialog */ -#textInput { +#text-input { min-height: 120px; } /* Receive Text Dialog */ -#receiveTextDialog #text { +#receive-text-dialog #text { width: 100%; word-break: break-all; max-height: 300px; @@ -778,15 +778,15 @@ x-dialog .row-reverse { margin-top:36px; } -#receiveTextDialog #text a { +#receive-text-dialog #text a { cursor: pointer; } -#receiveTextDialog #text a:hover { +#receive-text-dialog #text a:hover { text-decoration: underline; } -#receiveTextDialog h3 { +#receive-text-dialog h3 { /* Select the received text when double-clicking the dialog */ user-select: none; pointer-events: none; @@ -797,26 +797,26 @@ x-dialog .row-reverse { margin: auto -25px; } -#receiveTextDescriptionContainer { +#receive-text-description-container { margin-bottom: 25px; } -#base64PasteBtn { +#base64-paste-btn { width: 100%; height: 40vh; border: solid 12px #438cff; } -#base64PasteDialog button { +#base64-paste-dialog button { margin: auto; border-radius: 8px; } -#base64PasteDialog button[close] { +#base64-paste-dialog button[close] { margin-top: 20px; } -#base64PasteDialog button[close]:before { +#base64-paste-dialog button[close]:before { border-radius: 8px; }