Merge branch 'next' into translate

This commit is contained in:
schlagmichdoch 2023-12-11 20:10:38 +01:00
commit da56a7b6bc
60 changed files with 2586 additions and 1677 deletions

View file

@ -1,3 +1,14 @@
# 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.
# Build a Docker image whenever it is pushed to master
name: Docker Image CI name: Docker Image CI
on: on:
@ -13,6 +24,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Build the Docker image - name: Build the Docker image
run: docker build --pull . -f Dockerfile -t pairdrop run: docker build --pull . -f Dockerfile -t pairdrop

View file

@ -7,6 +7,8 @@
# To get a newer version, you will need to update the 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. # You can also reference a tag or branch, but the action may change without warning.
# Create a Docker image and push it to ghcr.io whenever a new version tag is pushed
name: GHCR Image CI name: GHCR Image CI
on: on:
@ -27,16 +29,16 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Setup qemu - name: Setup qemu
uses: docker/setup-qemu-action@v2.1.0 uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
- name: Setup Docker Buildx - name: Setup Docker Buildx
uses: docker/setup-buildx-action@v2.5.0 uses: docker/setup-buildx-action@f95db51fddba0c2d1ec667646a06c2ce06100226 # v3.0.0
- name: Log in to the Container registry - name: Log in to the Container registry
uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9 uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
@ -44,12 +46,12 @@ jobs:
- name: Extract metadata (tags, labels) for Docker - name: Extract metadata (tags, labels) for Docker
id: meta id: meta
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 uses: docker/metadata-action@31cebacef4805868f9ce9a0cb03ee36c32df2ac4 # v5.3.0
with: with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Build and push Docker image - name: Build and push Docker image
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5.1.0
with: with:
context: . context: .
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64

35
.github/workflows/zip-release.yml vendored Normal file
View file

@ -0,0 +1,35 @@
# 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.
# Create a new zip file from pairdrop-cli whenever a new version tag is pushed
name: Zip Release
on:
push:
tags:
- "v*.*.*"
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- name: Archive Release
uses: thedoctor0/zip-release@b57d897cb5d60cb78b51a507f63fa184cfe35554 # v0.7.6
with:
type: 'zip'
filename: 'pairdrop-cli.zip'
path: 'pairdrop-cli'
exclusions: '*.git* /*node_modules/* .editorconfig'
- name: Upload Release
uses: ncipollo/release-action@6c75be85e571768fa31b40abf38de58ba0397db5 # v1.13.0
with:
artifacts: "pairdrop-cli.zip"
token: ${{ secrets.GITHUB_TOKEN }}

View file

@ -67,10 +67,11 @@ Developed based on [Snapdrop](https://github.com/RobinLinus/snapdrop)
* Multiple files are transferred at once with an overall progress indicator * Multiple files are transferred at once with an overall progress indicator
### Send Files or Text Directly From Share Menu, Context Menu or CLI ### Send Files or Text Directly From Share Menu, Context Menu or CLI
* [Send files directly from context menu on Windows](/docs/how-to.md#send-files-directly-from-context-menu-on-windows) * [Send files directly from context menu on Windows](/docs/how-to.md#send-multiple-files-and-directories-directly-from-context-menu-on-windows)
* [Send directly from share menu on iOS](/docs/how-to.md#send-directly-from-share-menu-on-ios) * [Send files directly from context menu on Ubuntu (using Nautilus)](/docs/how-to.md#send-multiple-files-and-directories-directly-from-context-menu-on-ubuntu-using-nautilus)
* [Send directly from share menu on Android](/docs/how-to.md#send-directly-from-share-menu-on-android) * [Send files directly from share menu on iOS](/docs/how-to.md#send-directly-from-share-menu-on-ios)
* [Send directly via command-line interface](/docs/how-to.md#send-directly-via-command-line-interface) * [Send files directly from share menu on Android](/docs/how-to.md#send-directly-from-share-menu-on-android)
* [Send files directly via command-line interface](/docs/how-to.md#send-directly-via-command-line-interface)
### Other changes ### Other changes
* Change your display name permanently to easily differentiate your devices * Change your display name permanently to easily differentiate your devices
@ -99,9 +100,11 @@ Developed based on [Snapdrop](https://github.com/RobinLinus/snapdrop)
* [NodeJS](https://nodejs.org/en/) backend * [NodeJS](https://nodejs.org/en/) backend
* [Progressive Web App](https://wikipedia.org/wiki/Progressive_Web_App) * [Progressive Web App](https://wikipedia.org/wiki/Progressive_Web_App)
* [IndexedDB API](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) * [IndexedDB API](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API)
* [zip.js](https://gildas-lormeau.github.io/zip.js/)
* [cyrb53](https://github.com/bryc) super fast hash function
* [Weblate](https://weblate.org/) Web based localization tool * [Weblate](https://weblate.org/) Web based localization tool
* [zip.js](https://github.com/gildas-lormeau/zip.js) JavaScript library to zip and unzip files ([BSD 3-Clause License](licenses/BSD_3-Clause-zip-js))
* [NoSleep](https://github.com/richtr/NoSleep.js) JavaScript library to prevent display sleep and enable wake lock in any Android or iOS web browser ([MIT License](licenses/MIT-NoSleep))
* [heic2any](https://github.com/alexcorvi/heic2any) JavaScript library to convert HEIC/HEIF images to PNG/GIF/JPEG ([MIT License](licenses/MIT-heic2any))
* [cyrb53](https://github.com/bryc) Super fast hash function
Have any questions? Read our [FAQ](/docs/faq.md). Have any questions? Read our [FAQ](/docs/faq.md).

View file

@ -1,84 +1,120 @@
# How-To # How-To
## Send files directly from context menu on Windows
### Registering to open files with PairDrop
The [File Handling API](https://learn.microsoft.com/en-us/microsoft-edge/progressive-web-apps-chromium/how-to/handle-files) is implemented
This is still experimental and must be enabled via a flag **before** the PWA is installed to Windows.
1. [Enabled feature in Edge](https://learn.microsoft.com/en-us/microsoft-edge/progressive-web-apps-chromium/how-to/handle-files#enable-the-file-handling-api)
2. Install PairDrop by visiting https://pairdrop.net/ with the Edge web browser and install it as described [here](faq.md#help--i-cant-install-the-pwa-).
3. You are done! You can now send most files one at a time via PairDrop:
_context menu > Open with > PairDrop_
[//]: # (Todo: add screenshots)
### Sending multiple files to PairDrop
Outstandingly, it is also possible to send multiple files to PairDrop \
via the context menu by adding PairDrop to the `Send to` menu:
1. [Register PairDrop as file handler](#registering-to-open-files-with-pairdrop)
2. Hit Windows Key+R, type: `shell:programs` and hit Enter.
3. Copy the PairDrop shortcut from the directory
4. Hit Windows Key+R, type: `shell:sendto` and hit Enter.
5. Paste the copied shortcut into the directory
6. You are done! You can now send multiple files (but no directories) directly via PairDrop:
_context menu > Send to > PairDrop_
[//]: # (Todo: add screenshots)
## Send directly from share menu on iOS ## Send directly from share menu on iOS
I created an iOS shortcut to send images, files, folder, URLs \ I created an iOS shortcut to send images, files, folder, URLs \
or text directly from the share-menu or text directly from the share-menu
https://routinehub.co/shortcut/13990/ https://routinehub.co/shortcut/13990/
[//]: # (Todo: add doku with screenshots) [//]: # (Todo: Add screenshots)
<br>
## Send directly from share menu on Android ## Send directly from share menu on Android
The [Web Share Target API](https://developer.mozilla.org/en-US/docs/Web/Manifest/share_target) is implemented. The [Web Share Target API](https://developer.mozilla.org/en-US/docs/Web/Manifest/share_target) is implemented.
When the PWA is installed, it will register itself to the share-menu of the device automatically. When the PWA is installed, it will register itself to the share-menu of the device automatically.
<br>
## Send directly via command-line interface ## Send directly via command-line interface
Send files or text with PairDrop via command-line interface. Send files or text with PairDrop via command-line interface. \
This opens PairDrop in the default browser where you can choose the receiver. This opens PairDrop in the default browser where you can choose the receiver.
### Usage ### Usage
```bash ```bash
$ pairdrop -h pairdrop -h
Current domain: https://pairdrop.net/ ```
```bash
Send files or text with PairDrop via command-line interface.
Current domain: https://pairdrop-dev.onrender.com/
Usage: Usage:
Open PairDrop: pairdrop Open PairDrop: pairdrop
Send files: pairdrop file/directory Send files: pairdrop file1/directory1 (file2/directory2 file3/directory3 ...)
Send text: pairdrop -t "text" Send text: pairdrop -t "text"
Specify domain: pairdrop -d "https://pairdrop.net/" Specify domain: pairdrop -d "https://pairdrop.net/"
Show this help text: pairdrop (-h|--help) Show this help text: pairdrop (-h|--help)
This pairdrop-cli version was released alongside v1.10.0
``` ```
On Windows Command Prompt you need to use bash: `bash pairdrop -h` <br>
### Setup ### Setup
Download the bash file: [pairdrop-cli/pairdrop](/pairdrop-cli/pairdrop). Download the bash file: [pairdrop-cli/pairdrop](/pairdrop-cli/pairdrop).
#### Linux #### Linux
1. Put the file in a preferred folder e.g. `/usr/local/bin` 1. Download the latest _pairdrop-cli.zip_ from the [releases page](https://github.com/schlagmichdoch/PairDrop/releases)
2. Make sure the bash file is executable. Otherwise, use `chmod +x pairdrop` 2. Unzip the archive to a folder of your choice e.g. `/usr/local/bin/pairdrop-cli/`
3. Add absolute path of the folder to PATH variable to make `pairdrop` available globally by executing 3. Make sure the bash file `/usr/local/bin/pairdrop-cli/pairdrop` is executable. Otherwise, use `chmod +x pairdrop`
`export PATH=$PATH:/opt/pairdrop-cli` 4. Add absolute path of the folder to PATH variable to make `pairdrop` available globally by executing
`export PATH=$PATH:/usr/local/bin/pairdrop-cli/`
<br>
#### Mac #### Mac
1. add bash file to `/usr/local/bin` 1. add bash file to `/usr/local/bin`
<br>
#### Windows #### Windows
1. Put file in a preferred folder e.g. `C:\Users\Public\pairdrop-cli` 1. Download the latest _pairdrop-cli.zip_ from the [releases page](https://github.com/schlagmichdoch/PairDrop/releases)
2. Search for and open `Edit environment variables for your account` 2. Put file in a preferred folder e.g. `C:\Program Files\pairdrop-cli`
3. Click `Environment Variables…` 3. Search for and open `Edit environment variables for your account`
4. Under *System Variables* select `Path` and click *Edit...* 4. Click `Environment Variables…`
5. Click *New*, insert the preferred folder (`C:\Users\Public\pairdrop-cli`), click *OK* until all windows are closed 5. Under *System Variables* select `Path` and click *Edit...*
6. Reopen Command prompt window 6. Click *New*, insert the preferred folder (`C:\Program Files\pairdrop-cli`), click *OK* until all windows are closed
7. Reopen Command prompt window
<br>
### Requirements
As Windows cannot execute bash scripts natively, you need to install [Git Bash](https://gitforwindows.org/).
Then, you can also use pairdrop-cli from the default Windows Command Prompt \
by using the shell file instead of the bash file: `pairdrop.sh -h` which then itself executes \
pairdrop-cli (the bash file) via the Git Bash.
<br>
## Send multiple files and directories directly from context menu on Windows
### Registering to open files with PairDrop
It is possible to send multiple files with PairDrop via the context menu by adding pairdrop-cli to Windows `Send to` menu:
1. Download the latest _pairdrop-cli.zip_ from the [releases page](https://github.com/schlagmichdoch/PairDrop/releases)
2. Unzip the archive to a folder of your choice e.g. `C:\Program Files\pairdrop-cli\`
3. Copy the shortcut _send with PairDrop.lnk_
4. Hit Windows Key+R, type: `shell:sendto` and hit Enter.
5. Paste the copied shortcut into the directory
6. Open the properties window of the shortcut and edit the link field to point to _send-with-pairdrop.ps1_ located in the folder you used in step 2: \
`"C:\Program Files\PowerShell\7\pwsh.exe" -File "C:\Program Files\pairdrop-cli\send-with-pairdrop.ps1"`
7. You are done! You can now send multiple files and directories directly via PairDrop:
> _context menu > Send to > PairDrop_
##### Requirements
As Windows cannot execute bash scripts natively, you need to install [Git Bash](https://gitforwindows.org/).
<br>
## Send multiple files and directories directly from context menu on Ubuntu using Nautilus
### Registering to open files with PairDrop
It is possible to send multiple files with PairDrop via the context menu by adding pairdrop-cli to Nautilus `Scripts` menu:
1. Download the latest _pairdrop-cli.zip_ from the [releases page](https://github.com/schlagmichdoch/PairDrop/releases)
2. Unzip the archive to a folder of your choice e.g. `/usr/local/bin/pairdrop-cli/`
3. Copy the shell file _send-with-pairdrop.sh_ to `/home/<user>/.local/share/nautilus/scripts/`
4. Edit the shell file and edit the variable `pathToPairDropCli` to point to the pairdrop-cli executable from step 2 (e.g. `/usr/local/bin/pairdrop-cli/pairdrop`)
5. Make sure the shell file `/home/<user>/.local/share/nautilus/scripts/send-with-pairdrop.sh` is executable. Otherwise, use `chmod +x send-with-pairdrop.sh`
6. You are done! You can now send multiple files and directories directly via PairDrop:
> _context menu > Scripts > send-with-pairdrop.sh_
<br>
## File Handling API
The [File Handling API](https://learn.microsoft.com/en-us/microsoft-edge/progressive-web-apps-chromium/how-to/handle-files)
was implemented, but it was removed as default file associations were overwritten ([#17](https://github.com/schlagmichdoch/PairDrop/issues/17),
[#116](https://github.com/schlagmichdoch/PairDrop/issues/116) [#190](https://github.com/schlagmichdoch/PairDrop/issues/190))
and it only worked with explicitly specified file types and not with directories at all.
[< Back](/README.md) [< Back](/README.md)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 326 KiB

After

Width:  |  Height:  |  Size: 1.5 MiB

Before After
Before After

View file

@ -0,0 +1,28 @@
BSD 3-Clause License
Copyright (c) 2023, Gildas Lormeau
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

22
licenses/MIT-NoSleep Normal file
View file

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) Rich Tibbett
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

22
licenses/MIT-heic2any Normal file
View file

@ -0,0 +1,22 @@
MIT License
Copyright (c) 2020 Alex Corvi
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

1
pairdrop-cli/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
.pairdrop-cli-config

View file

@ -12,10 +12,12 @@ help()
echo echo
echo "Usage:" echo "Usage:"
echo -e "Open PairDrop:\t\t$(basename "$0")" echo -e "Open PairDrop:\t\t$(basename "$0")"
echo -e "Send files:\t\t$(basename "$0") file/directory" echo -e "Send files:\t\t$(basename "$0") file1/directory1 (file2/directory2 file3/directory3 ...)"
echo -e "Send text:\t\t$(basename "$0") -t \"text\"" echo -e "Send text:\t\t$(basename "$0") -t \"text\""
echo -e "Specify domain:\t\t$(basename "$0") -d \"https://pairdrop.net/\"" echo -e "Specify domain:\t\t$(basename "$0") -d \"https://pairdrop.net/\""
echo -e "Show this help text:\t$(basename "$0") (-h|--help)" echo -e "Show this help text:\t$(basename "$0") (-h|--help)"
echo
echo "This pairdrop-cli version was released alongside v1.10.0"
} }
openPairDrop() openPairDrop()
@ -36,7 +38,7 @@ openPairDrop()
elif [[ $OS == "WSL" || $OS == "WSL2" ]];then elif [[ $OS == "WSL" || $OS == "WSL2" ]];then
powershell.exe /c "Start-Process ${url}" powershell.exe /c "Start-Process ${url}"
else else
xdg-open "$url" xdg-open "$url" > /dev/null 2>&1
fi fi
@ -62,7 +64,7 @@ setOs()
specifyDomain() specifyDomain()
{ {
[[ ! $1 = http* ]] || [[ ! $1 = */ ]] && echo "Incorrect format. Specify domain like https://pairdrop.net/" && exit [[ ! $1 = http* ]] || [[ ! $1 = */ ]] && echo "Incorrect format. Specify domain like https://pairdrop.net/" && exit
echo "DOMAIN=${1}" > "$CONFIGPATH" echo "DOMAIN=${1}" > "$config_path"
echo -e "Domain is now set to:\n$1\n" echo -e "Domain is now set to:\n$1\n"
} }
@ -87,75 +89,228 @@ sendText()
exit exit
} }
escapePSPath()
{
local path=$1
# escape '[' and ']' with grave accent (`) character
pathPS=${path//[/\`[}
pathPS=${pathPS//]/\`]}
# escape single quote (') with another single quote (')
pathPS=${pathPS//\'/\'\'}
# Convert GitHub bash path "/i/path" to Windows path "I:/path"
if [[ $pathPS == /* ]]; then
# Remove preceding slash
pathPS="${pathPS#/}"
# Convert drive letter to uppercase
driveLetter=$(echo "${pathPS::1}" | tr '[:lower:]' '[:upper:]')
# Put together absolute path as used in Windows
pathPS="${driveLetter}:${pathPS:1}"
fi
echo "$pathPS"
}
sendFiles() sendFiles()
{ {
params="base64zip=hash" params="base64zip=hash"
if [[ $1 == */ ]]; then workingDir="$(pwd)"
path="${1::-1}" tmpDir="/tmp/pairdrop-cli-temp/"
else tmpDirPS="\$env:TEMP/pairdrop-cli-temp/"
path=$1
fi
zipPath="${path}_pairdrop.zip"
zipPath=${zipPath// /_}
[[ -a "$zipPath" ]] && echo "Cannot overwrite $zipPath. Please remove first." && exit index=0
directoryBaseNamesUnix=()
directoryPathsUnix=()
filePathsUnix=()
directoryCount=0
fileCount=0
pathsPS=""
if [[ -d $path ]]; then #create tmp folder if it does not exist already
zipPathTemp="${path}_pairdrop_temp.zip" if [[ ! -d "$tmpDir" ]]; then
[[ -a "$zipPathTemp" ]] && echo "Cannot overwrite $zipPathTemp. Please remove first." && exit mkdir "$tmpDir"
echo "Processing directory..."
# Create zip files temporarily to send directory
if [[ $OS == "Windows" ]];then
powershell.exe -Command "Compress-Archive -Path ${path} -DestinationPath ${zipPath}"
echo "Compress-Archive -Path ${zipPath} -DestinationPath ${zipPathTemp}"
powershell.exe -Command "Compress-Archive -Path ${zipPath} -DestinationPath ${zipPathTemp}"
else
zip -q -b /tmp/ -r "$zipPath" "$path"
zip -q -b /tmp/ "$zipPathTemp" "$zipPath"
fi
if [[ $OS == "Mac" ]];then
hash=$(base64 -i "$zipPathTemp")
else
hash=$(base64 -w 0 "$zipPathTemp")
fi
# remove temporary temp file
rm "$zipPathTemp"
else
echo "Processing file..."
# Create zip file temporarily to send file
if [[ $OS == "Windows" ]];then
powershell.exe -Command "Compress-Archive -Path ${path} -DestinationPath ${zipPath} -CompressionLevel Optimal"
else
zip -q -b /tmp/ "$zipPath" "$path"
fi
if [[ $OS == "Mac" ]];then
hash=$(base64 -i "$zipPath")
else
hash=$(base64 -w 0 "$zipPath")
fi
fi fi
# remove temporary temp file for arg in "$@"; do
rm "$zipPath" echo "$arg"
[[ ! -e "$arg" ]] && echo "The given path $arg does not exist." && exit
if [[ $(echo -n "$hash" | wc -m) -gt 32600 ]];then # Remove trailing slash from directory
arg="${arg%/}"
# get absolute path and basename of file/directory
absolutePath=$(realpath "$arg")
baseName=$(basename "$absolutePath")
directoryPath=$(dirname "$absolutePath")
if [[ -d $absolutePath ]]; then
# is directory
((directoryCount+=1))
# add basename and directory path to arrays
directoryBaseNamesUnix+=("$baseName")
directoryPathsUnix+=("$directoryPath")
else
# is file
((fileCount+=1))
absolutePathUnix=$absolutePath
# append new path and separate paths with space
filePathsUnix+=("$absolutePathUnix")
fi
# Prepare paths for PowerShell on Windows
if [[ $OS == "Windows" ]];then
absolutePathPS=$(escapePSPath "$absolutePath")
# append new path and separate paths with commas
pathsPS+="'${absolutePathPS}', "
fi
# set fileNames on first loop
if [[ $index == 0 ]]; then
baseNameU=${baseName// /_}
# Prevent baseNameU being empty for hidden files by removing the preceding dot
if [[ $baseNameU == .* ]]; then
baseNameU=${baseNameU#.*}
fi
# only use trunk of basename "document.txt" -> "document"
baseNameTrunk=${baseNameU%.*}
# remove all special characters
zipName=${baseNameTrunk//[^a-zA-Z0-9_]/}
zipToSendAbs="${tmpDir}${zipName}_pairdrop.zip"
wrapperZipAbs="${tmpDir}${zipName}_pairdrop_wrapper.zip"
if [[ $OS == "Windows" ]];then
zipToSendAbsPS="${tmpDirPS}${zipName}_pairdrop.zip"
wrapperZipAbsPS="${tmpDirPS}${zipName}_pairdrop_wrapper.zip"
fi
fi
((index+=1)) # somehow ((index++)) stops the script
done
# Prepare paths for PowerShell on Windows
if [[ $OS == "Windows" ]];then
# remove trailing comma
pathsPS=${pathsPS%??}
fi
echo "Preparing ${fileCount} files and ${directoryCount} directories..."
# if arguments include files only -> zip files once so files it is unzipped by sending browser
# if arguments include directories -> wrap first zip in a second wrapper zip so that after unzip by sending browser a zip file is sent to receiver
#
# Preferred zip structure:
# pairdrop "d1/d2/d3/f1" "../../d4/d5/d6/f2" "d7/" "../d8/" "f5"
# zip structure: pairdrop.zip
# |-f1
# |-f2
# |-d7/
# |-d8/
# |-f5
# -> truncate (relative) paths but keep directories
[[ -e "$zipToSendAbs" ]] && echo "Cannot overwrite $zipToSendAbs. Please remove first." && exit
if [[ $OS == "Windows" ]];then
# Powershell does preferred zip structure natively
powershell.exe -Command "Compress-Archive -Path ${pathsPS} -DestinationPath ${zipToSendAbsPS}"
else
# Workaround needed to create preferred zip structure on unix systems
# Create zip file with all single files by junking the path
if [[ $fileCount != 0 ]]; then
zip -q -b /tmp/ -j -0 -r "$zipToSendAbs" "${filePathsUnix[@]}"
fi
# Add directories recursively to zip file
index=0
while [[ $index < $directoryCount ]]; do
# workaround to keep directory name but junk the rest of the paths
# cd to path above directory
cd "${directoryPathsUnix[index]}"
# add directory to zip without junking the path
zip -q -b /tmp/ -0 -u -r "$zipToSendAbs" "${directoryBaseNamesUnix[index]}"
# cd back to working directory
cd "$workingDir"
((index+=1)) # somehow ((index++)) stops the script
done
fi
# If directories are included send as zip
# -> Create additional zip wrapper which will be unzipped by the sending browser
if [[ "$directoryCount" != 0 ]]; then
echo "Bundle as ZIP file..."
# Prevent filename from being absolute zip path by "cd"ing to directory before zipping
zipToSendDirectory=$(dirname "$zipToSendAbs")
zipToSendBaseName=$(basename "$zipToSendAbs")
cd "$zipToSendDirectory"
[[ -e "$wrapperZipAbs" ]] && echo "Cannot overwrite $wrapperZipAbs. Please remove first." && exit
if [[ $OS == "Windows" ]];then
powershell.exe -Command "Compress-Archive -Path ${zipToSendBaseName} -DestinationPath ${wrapperZipAbsPS} -CompressionLevel Optimal"
else
zip -q -b /tmp/ -0 "$wrapperZipAbs" "$zipToSendBaseName"
fi
cd "$workingDir"
# remove inner zip file and set wrapper as zipToSend (do not differentiate between OS as this is done via Git Bash on Windows)
rm "$zipToSendAbs"
zipToSendAbs=$wrapperZipAbs
fi
# base64 encode zip file
if [[ $OS == "Mac" ]];then
hash=$(base64 -i "$zipToSendAbs")
else
hash=$(base64 -w 0 "$zipToSendAbs")
fi
# remove zip file (do not differentiate between OS as this is done via Git Bash on Windows)
rm "$zipToSendAbs"
if [[ $(echo -n "$hash" | wc -m) -gt 1000 ]];then
params="base64zip=paste" params="base64zip=paste"
# Copy $hash to clipboard
if [[ $OS == "Windows" || $OS == "WSL" || $OS == "WSL2" ]];then if [[ $OS == "Windows" || $OS == "WSL" || $OS == "WSL2" ]];then
echo -n "$hash" | clip.exe echo -n "$hash" | clip.exe
elif [[ $OS == "Mac" ]];then elif [[ $OS == "Mac" ]];then
echo -n "$hash" | pbcopy echo -n "$hash" | pbcopy
elif [ -n "$WAYLAND_DISPLAY" ]; then
# Wayland
if ! command -v wl-copy &> /dev/null; then
echo -e "You need to install 'wl-copy' to send bigger filePathsUnix from cli"
echo "Try: sudo apt install wl-clipboard"
exit 1
fi
# Workaround to prevent use of Pipe which has a max letter limit
echo -n "$hash" > /tmp/pairdrop-cli-temp/pairdrop_hash_temp
wl-copy < /tmp/pairdrop-cli-temp/pairdrop_hash_temp
rm /tmp/pairdrop-cli-temp/pairdrop_hash_temp
else else
(echo -n "$hash" | xclip) || echo "You need to install xclip for sending bigger files from cli" # X11
if ! command -v xclip &> /dev/null; then
echo -e "You need to install 'xclip' to send bigger filePathsUnix from cli"
echo "Try: sudo apt install xclip"
exit 1
fi
echo -n "$hash" | xclip -sel c
fi fi
hash= hash=
fi fi
openPairDrop openPairDrop
exit exit
} }
@ -165,31 +320,32 @@ sendFiles()
# Main program # # Main program #
############################################################ ############################################################
############################################################ ############################################################
SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" script_path="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
pushd . > '/dev/null'; pushd . > '/dev/null';
SCRIPTPATH="${BASH_SOURCE[0]:-$0}"; script_path="${BASH_SOURCE[0]:-$0}";
while [ -h "$SCRIPTPATH" ]; while [ -h "$script_path" ];
do do
cd "$( dirname -- "$SCRIPTPATH"; )"; cd "$( dirname -- "$script_path"; )";
SCRIPTPATH="$( readlink -f -- "$SCRIPTPATH"; )"; script_path="$( readlink -f -- "$script_path"; )";
done done
cd "$( dirname -- "$SCRIPTPATH"; )" > '/dev/null'; cd "$( dirname -- "$script_path"; )" > '/dev/null';
SCRIPTPATH="$( pwd; )"; script_path="$( pwd; )";
popd > '/dev/null'; popd > '/dev/null';
CONFIGPATH="${SCRIPTPATH}/.pairdrop-cli-config" config_path="${script_path}/.pairdrop-cli-config"
[ ! -f "$CONFIGPATH" ] && [ ! -f "$config_path" ] &&
specifyDomain "https://pairdrop.net/" && specifyDomain "https://pairdrop.net/" &&
[ ! -f "$CONFIGPATH" ] && [ ! -f "$config_path" ] &&
echo "Could not create config file. Add 'DOMAIN=https://pairdrop.net/' to a file called .pairdrop-cli-config in the same file as this 'pairdrop' bash file" echo "Could not create config file. Add 'DOMAIN=https://pairdrop.net/' to a file called .pairdrop-cli-config in the same file as this 'pairdrop' bash file"
[ ! -f "$CONFIGPATH" ] || export "$(grep -v '^#' "$CONFIGPATH" | xargs)" [ ! -f "$config_path" ] || export "$(grep -v '^#' "$config_path" | xargs)"
setOs setOs
############################################################ ############################################################
# Process the input options. Add options as needed. # # Process the input options. Add options as needed. #
############################################################ ############################################################
@ -198,24 +354,23 @@ setOs
[[ $# -eq 0 ]] && openPairDrop && exit [[ $# -eq 0 ]] && openPairDrop && exit
# display help and exit if first argument is "--help" or more than 2 arguments are given # display help and exit if first argument is "--help" or more than 2 arguments are given
[ "$1" == "--help" ] || [[ $# -gt 2 ]] && help && exit [ "$1" == "--help" ] && help && exit
while getopts "d:ht:*" option; do while getopts "d:ht:*" option; do
case $option in case $option in
d) # specify domain d) # specify domain - show help and exit if too many arguments
specifyDomain "$2" [[ $# -gt 2 ]] && help && exit
exit;; specifyDomain "$2"
t) # Send text exit;;
sendText t) # Send text - show help and exit if too many arguments
exit;; [[ $# -gt 2 ]] && help && exit
h | ?) # display help and exit sendText
help exit;;
exit;; h | ?) # display help and exit
esac help
exit;;
esac
done done
# Send file(s) # Send file(s)
# display help and exit if 2 arguments are given or if file does not exist sendFiles "$@"
[[ $# -eq 2 ]] || [[ ! -a $1 ]] && help && exit
sendFiles "$1"

6
pairdrop-cli/pairdrop.sh Normal file
View file

@ -0,0 +1,6 @@
#!/bin/bash
parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" || exit ; pwd -P )
cd "$parent_path" || exit
./pairdrop "$@"

Binary file not shown.

View file

@ -0,0 +1,3 @@
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
& "$scriptDir\pairdrop.sh" $args

View file

@ -0,0 +1,20 @@
#!/bin/bash
# edit this to point to the pairdrop-cli executable
pathToPairDropCli="/usr/local/bin/pairdrop-cli/pairdrop"
# Initialize an array
lines=()
# Read each line into the array
while IFS= read -r line; do
lines+=("$line")
done <<< "$NAUTILUS_SCRIPT_SELECTED_FILE_PATHS"
# Get the length of the array
length=${#lines[@]}
# Remove the last entry
unset 'lines[length-1]'
$pathToPairDropCli "${lines[@]}"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 23 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.5 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

After

Width:  |  Height:  |  Size: 269 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

After

Width:  |  Height:  |  Size: 271 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 232 KiB

After

Width:  |  Height:  |  Size: 271 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

After

Width:  |  Height:  |  Size: 2.5 MiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

After

Width:  |  Height:  |  Size: 190 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 KiB

After

Width:  |  Height:  |  Size: 231 KiB

Before After
Before After

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB

View file

@ -40,7 +40,7 @@
</head> </head>
<body translate="no"> <body translate="no">
<header class="row-reverse opacity-0"> <header class="row-reverse wrap opacity-0">
<a href="#about" class="icon-button" data-i18n-key="header.about" data-i18n-attrs="title aria-label"> <a href="#about" class="icon-button" data-i18n-key="header.about" data-i18n-attrs="title aria-label">
<svg class="icon"> <svg class="icon">
<use xlink:href="#info-outline"></use> <use xlink:href="#info-outline"></use>
@ -95,20 +95,49 @@
<use xlink:href="#public-room-icon"></use> <use xlink:href="#public-room-icon"></use>
</svg> </svg>
</div> </div>
<div id="cancel-paste-mode" class="btn" data-i18n-key="header.cancel-paste-mode" data-i18n-attrs="text" hidden></div> <div id="expand" class="icon-button" data-i18n-key="header.expand" data-i18n-attrs="title" hidden>
<svg class="icon">
<use xlink:href="#caret"></use>
</svg>
</div>
</header> </header>
<!-- Center --> <!-- Center -->
<div id="center" class="opacity-0"> <div id="center" class="opacity-0">
<!-- Peers --> <!-- Peers -->
<div class="x-peers-filler"></div> <x-peers class="center grow"></x-peers>
<x-peers class="center"></x-peers> <x-no-peers class="center grow fade-in no-animation-on-load" data-i18n-key="instructions.no-peers" data-i18n-attrs="data-drop-bg">
<x-no-peers class="no-animation-on-load" data-i18n-key="instructions.no-peers" data-i18n-attrs="data-drop-bg">
<h2 data-i18n-key="instructions.no-peers-title" data-i18n-attrs="text"></h2> <h2 data-i18n-key="instructions.no-peers-title" data-i18n-attrs="text"></h2>
<div data-i18n-key="instructions.no-peers-subtitle" data-i18n-attrs="text"></div> <div data-i18n-key="instructions.no-peers-subtitle" data-i18n-attrs="text"></div>
</x-no-peers> </x-no-peers>
<x-instructions data-i18n-key="instructions.x-instructions" data-i18n-attrs="desktop mobile data-drop-peer data-drop-bg"> <x-instructions class="fade-in" data-i18n-key="instructions.x-instructions" data-i18n-attrs="desktop mobile data-drop-peer data-drop-bg"></x-instructions>
<p id="paste-filename"></p> <div class="shr-panel panel column" hidden>
</x-instructions> <div class="row">
<div class="thumb center">
<div class="text-thumb row" hidden>
<svg>
<use xlink:href="#font"></use>
</svg>
<svg>
<use xlink:href="#i-cursor"></use>
</svg>
</div>
<div class="file-thumb" hidden>
<svg>
<use xlink:href="#file"></use>
</svg>
</div>
<div class="image-thumb" hidden></div>
</div>
<div class="share-descriptor column p1">
<span class="descriptor-item"></span>
<span class="descriptor-other" hidden></span>
</div>
</div>
<div class="center btn-row wrap">
<div class="cancel-btn btn btn-small btn-rounded btn-dark text-white" data-i18n-key="header.cancel-share-mode" data-i18n-attrs="text"></div>
<div class="edit-btn btn btn-small btn-rounded btn-dark text-white" data-i18n-key="header.edit-share-mode" data-i18n-attrs="text" hidden></div>
</div>
</div>
<div id="websocket-fallback" hidden> <div id="websocket-fallback" hidden>
<span data-i18n-key="footer.traffic" data-i18n-attrs="text"></span> <span data-i18n-key="footer.traffic" data-i18n-attrs="text"></span>
<span data-i18n-key="footer.routed" data-i18n-attrs="text"></span> <span data-i18n-key="footer.routed" data-i18n-attrs="text"></span>
@ -130,18 +159,18 @@
<div class="known-as-wrapper"> <div class="known-as-wrapper">
<span data-i18n-key="footer.known-as" data-i18n-attrs="text"></span> <span data-i18n-key="footer.known-as" data-i18n-attrs="text"></span>
<div id="display-name" class="badge" data-i18n-key="footer.display-name" data-i18n-attrs="data-placeholder title" placeholder="Loading..." autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable></div> <div id="display-name" class="badge" data-i18n-key="footer.display-name" data-i18n-attrs="data-placeholder title" placeholder="Loading..." autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" contenteditable></div>
<svg id="edit-pen" class="icon"> <svg class="icon edit-pen">
<use xlink:href="#edit-pen-icon"></use> <use xlink:href="#edit-pen-icon"></use>
</svg> </svg>
</div> </div>
<div class="discovery-wrapper row"> <div class="discovery-wrapper panel border row">
<div class="row center"> <div class="row center">
<span data-i18n-key="footer.discovery" data-i18n-attrs="text"></span> <span data-i18n-key="footer.discovery" data-i18n-attrs="text"></span>
</div> </div>
<div class="row center"> <div class="row center wrap">
<span class="badge badge-gradient badge-room-ip" data-i18n-key="footer.on-this-network" data-i18n-attrs="text title"></span> <span class="badge badge-room-ip" data-i18n-key="footer.on-this-network" data-i18n-attrs="text title"></span>
<span class="badge badge-gradient badge-room-secret pointer" data-i18n-key="footer.paired-devices" data-i18n-attrs="text title" hidden></span> <span class="badge badge-room-secret pointer" data-i18n-key="footer.paired-devices" data-i18n-attrs="text title" hidden></span>
<span class="badge badge-gradient badge-room-public-id pointer" data-i18n-key="footer.public-room-devices" data-i18n-attrs="title" hidden>in room IAIAI</span> <span class="badge badge-room-public-id pointer" data-i18n-key="footer.public-room-devices" data-i18n-attrs="title" hidden>in room IAIAI</span>
</div> </div>
</div> </div>
</div> </div>
@ -150,8 +179,8 @@
<x-dialog id="language-select-dialog"> <x-dialog id="language-select-dialog">
<x-background class="full center"> <x-background class="full center">
<x-paper shadow="2"> <x-paper shadow="2">
<div class="row center"> <div class="row center p2">
<h2 class="center" data-i18n-key="dialogs.language-selector-title" data-i18n-attrs="text"></h2> <h2 class="dialog-title" data-i18n-key="dialogs.language-selector-title" data-i18n-attrs="text"></h2>
</div> </div>
<div class="language-buttons"> <div class="language-buttons">
<button class="btn fw" data-i18n-key="dialogs.system-language" data-i18n-attrs="text"></button> <button class="btn fw" data-i18n-key="dialogs.system-language" data-i18n-attrs="text"></button>
@ -229,7 +258,7 @@
<span>(Japanese)</span> <span>(Japanese)</span>
</button> </button>
</div> </div>
<div class="center row-reverse button-row"> <div class="center row-reverse btn-row wrap">
<button class="btn btn-rounded btn-grey" type="button" data-i18n-key="dialogs.close" data-i18n-attrs="text" close></button> <button class="btn btn-rounded btn-grey" type="button" data-i18n-key="dialogs.close" data-i18n-attrs="text" close></button>
</div> </div>
</x-paper> </x-paper>
@ -240,12 +269,12 @@
<form action="#"> <form action="#">
<x-background class="full center text-center"> <x-background class="full center text-center">
<x-paper shadow="2"> <x-paper shadow="2">
<div class="row center"> <div class="row center p2">
<h2 class="center" data-i18n-key="dialogs.pair-devices-title" data-i18n-attrs="text"></h2> <h2 class="dialog-title" data-i18n-key="dialogs.pair-devices-title" data-i18n-attrs="text"></h2>
</div> </div>
<div class="row center"> <div class="row center p2">
<div class="column"> <div class="column">
<div class="center key-qr-code" data-i18n-key="dialogs.pair-devices-qr-code" data-i18n-attrs="title"></div> <div class="center key-qr-code pointer" data-i18n-key="dialogs.pair-devices-qr-code" data-i18n-attrs="title"></div>
<h1 class="center key" dir="ltr">000 000</h1> <h1 class="center key" dir="ltr">000 000</h1>
<p class="center text-center key-instructions"> <p class="center text-center key-instructions">
<span class="font-subheading" data-i18n-key="dialogs.input-key-on-this-device" data-i18n-attrs="text"></span> <span class="font-subheading" data-i18n-key="dialogs.input-key-on-this-device" data-i18n-attrs="text"></span>
@ -259,7 +288,7 @@
<span data-i18n-key="dialogs.hr-or" data-i18n-attrs="text"></span> <span data-i18n-key="dialogs.hr-or" data-i18n-attrs="text"></span>
</div> </div>
</div> </div>
<div class="row center"> <div class="row center p2">
<div class="column fw"> <div class="column fw">
<div class="input-key-container six-chars" dir="ltr"> <div class="input-key-container six-chars" dir="ltr">
<input type="tel" class="textarea center" aria-label="pair-key-char-1" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" autofocus contenteditable placeholder disabled> <input type="tel" class="textarea center" aria-label="pair-key-char-1" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" autofocus contenteditable placeholder disabled>
@ -272,7 +301,7 @@
<p class="font-subheading center text-center" data-i18n-key="dialogs.enter-key-from-another-device" data-i18n-attrs="text"></p> <p class="font-subheading center text-center" data-i18n-key="dialogs.enter-key-from-another-device" data-i18n-attrs="text"></p>
</div> </div>
</div> </div>
<div class="button-row row-reverse"> <div class="btn-row row-reverse wrap">
<button class="btn btn-rounded btn-grey" type="submit" data-i18n-key="dialogs.pair" data-i18n-attrs="text" disabled></button> <button class="btn btn-rounded btn-grey" type="submit" data-i18n-key="dialogs.pair" data-i18n-attrs="text" disabled></button>
<button class="btn btn-rounded btn-grey" type="button" data-i18n-key="dialogs.cancel" data-i18n-attrs="text" close></button> <button class="btn btn-rounded btn-grey" type="button" data-i18n-key="dialogs.cancel" data-i18n-attrs="text" close></button>
</div> </div>
@ -285,8 +314,8 @@
<form action="#"> <form action="#">
<x-background class="full center text-center"> <x-background class="full center text-center">
<x-paper shadow="2"> <x-paper shadow="2">
<div class="row center"> <div class="row center p2">
<h2 class="center" data-i18n-key="dialogs.edit-paired-devices-title" data-i18n-attrs="text"></h2> <h2 class="dialog-title" data-i18n-key="dialogs.edit-paired-devices-title" data-i18n-attrs="text"></h2>
</div> </div>
<div class="paired-devices-wrapper" data-i18n-key="dialogs.paired-devices-wrapper" data-i18n-attrs="data-empty"></div> <div class="paired-devices-wrapper" data-i18n-key="dialogs.paired-devices-wrapper" data-i18n-attrs="data-empty"></div>
<div class="font-subheading center"> <div class="font-subheading center">
@ -296,7 +325,7 @@
<span data-i18n-key="dialogs.auto-accept-instructions-2" data-i18n-attrs="text"></span> <span data-i18n-key="dialogs.auto-accept-instructions-2" data-i18n-attrs="text"></span>
</p> </p>
</div> </div>
<div class="center row-reverse button-row"> <div class="center row-reverse btn-row wrap">
<button class="btn btn-rounded btn-grey" type="button" data-i18n-key="dialogs.close" data-i18n-attrs="text" close></button> <button class="btn btn-rounded btn-grey" type="button" data-i18n-key="dialogs.close" data-i18n-attrs="text" close></button>
</div> </div>
</x-paper> </x-paper>
@ -308,14 +337,12 @@
<form action="#"> <form action="#">
<x-background class="full center text-center"> <x-background class="full center text-center">
<x-paper shadow="2"> <x-paper shadow="2">
<div class="row center"> <div class="row center p2">
<div class="column"> <h2 class="dialog-title" data-i18n-key="dialogs.temporary-public-room-title" data-i18n-attrs="text"></h2>
<h2 class="center" data-i18n-key="dialogs.temporary-public-room-title" data-i18n-attrs="text"></h2>
</div>
</div> </div>
<div class="row center"> <div class="row center p2">
<div class="column"> <div class="column">
<div class="center key-qr-code" data-i18n-key="dialogs.public-room-qr-code" data-i18n-attrs="title"></div> <div class="center key-qr-code pointer" data-i18n-key="dialogs.public-room-qr-code" data-i18n-attrs="title"></div>
<h1 class="center key" dir="ltr"></h1> <h1 class="center key" dir="ltr"></h1>
<p class="center text-center key-instructions"> <p class="center text-center key-instructions">
<span class="font-subheading" data-i18n-key="dialogs.input-room-id-on-another-device" data-i18n-attrs="text"></span> <span class="font-subheading" data-i18n-key="dialogs.input-room-id-on-another-device" data-i18n-attrs="text"></span>
@ -329,7 +356,7 @@
<span data-i18n-key="dialogs.hr-or" data-i18n-attrs="text"></span> <span data-i18n-key="dialogs.hr-or" data-i18n-attrs="text"></span>
</div> </div>
</div> </div>
<div class="row center"> <div class="row center p2">
<div class="column fw"> <div class="column fw">
<div class="input-key-container" dir="ltr"> <div class="input-key-container" dir="ltr">
<input type="text" class="textarea center" aria-label="room-id-char-1" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" autofocus contenteditable placeholder disabled> <input type="text" class="textarea center" aria-label="room-id-char-1" maxlength="1" autocorrect="off" autocomplete="off" autocapitalize="none" spellcheck="false" autofocus contenteditable placeholder disabled>
@ -341,10 +368,12 @@
<p class="font-subheading center text-center" data-i18n-key="dialogs.enter-room-id-from-another-device" data-i18n-attrs="text"></p> <p class="font-subheading center text-center" data-i18n-key="dialogs.enter-room-id-from-another-device" data-i18n-attrs="text"></p>
</div> </div>
</div> </div>
<div class="center row-reverse button-row"> <div class="center row-reverse btn-row wrap">
<button class="btn btn-rounded btn-grey" type="submit" data-i18n-key="dialogs.join" data-i18n-attrs="text" disabled></button> <div class="row-reverse wrap grow-2">
<button class="btn btn-rounded btn-grey" type="submit" data-i18n-key="dialogs.join" data-i18n-attrs="text" disabled></button>
<button class="btn btn-rounded btn-grey leave-room" type="button" data-i18n-key="dialogs.leave" data-i18n-attrs="text"></button>
</div>
<button class="btn btn-rounded btn-grey" type="button" data-i18n-key="dialogs.close" data-i18n-attrs="text" close></button> <button class="btn btn-rounded btn-grey" type="button" data-i18n-key="dialogs.close" data-i18n-attrs="text" close></button>
<button class="btn btn-rounded btn-grey leave-room" type="button" data-i18n-key="dialogs.leave" data-i18n-attrs="text"></button>
</div> </div>
</x-paper> </x-paper>
</x-background> </x-background>
@ -354,15 +383,13 @@
<x-dialog id="receive-request-dialog"> <x-dialog id="receive-request-dialog">
<x-background class="full center"> <x-background class="full center">
<x-paper shadow="2"> <x-paper shadow="2">
<div class="row center"> <div class="row center p2">
<div class="column"> <h2 class="dialog-title"></h2>
<h2 class="center"></h2>
</div>
</div> </div>
<div class="row center p1"> <div class="row center p2">
<div class="column center file-description"> <div class="column center file-description">
<div> <div>
<span class="display-name badge badge-gradient"></span> <span class="display-name badge"></span>
<span data-i18n-key="dialogs.would-like-to-share" data-i18n-attrs="text"></span> <span data-i18n-key="dialogs.would-like-to-share" data-i18n-attrs="text"></span>
</div> </div>
<div class="row file-name"> <div class="row file-name">
@ -375,8 +402,8 @@
</div> </div>
</div> </div>
<div class="center file-preview"></div> <div class="center file-preview"></div>
<div class="row-reverse center button-row"> <div class="row-reverse center btn-row wrap">
<button id="accept-request" class="btn btn-rounded btn-grey" title="ENTER" data-i18n-key="dialogs.accept" data-i18n-attrs="text" autofocus></button> <button id="accept-request" class="btn btn-rounded btn-grey" title="ENTER" data-i18n-key="dialogs.accept" data-i18n-attrs="text" autofocus disabled></button>
<button id="decline-request" class="btn btn-rounded btn-grey" title="ESCAPE" data-i18n-key="dialogs.decline" data-i18n-attrs="text"></button> <button id="decline-request" class="btn btn-rounded btn-grey" title="ESCAPE" data-i18n-key="dialogs.decline" data-i18n-attrs="text"></button>
</div> </div>
</x-paper> </x-paper>
@ -386,15 +413,13 @@
<x-dialog id="receive-file-dialog"> <x-dialog id="receive-file-dialog">
<x-background class="full center"> <x-background class="full center">
<x-paper shadow="2"> <x-paper shadow="2">
<div class="row center"> <div class="row center p2">
<div class="column"> <h2 class="dialog-title"></h2>
<h2 class="center"></h2>
</div>
</div> </div>
<div class="row center p1"> <div class="row center p2">
<div class="column center file-description"> <div class="column center file-description">
<div> <div>
<span class="display-name badge badge-gradient"></span> <span class="display-name badge"></span>
<span data-i18n-key="dialogs.has-sent" data-i18n-attrs="text"></span> <span data-i18n-key="dialogs.has-sent" data-i18n-attrs="text"></span>
</div> </div>
<div class="row file-name"> <div class="row file-name">
@ -407,9 +432,9 @@
</div> </div>
</div> </div>
<div class="center file-preview"></div> <div class="center file-preview"></div>
<div class="row-reverse center button-row"> <div class="row-reverse center btn-row wrap">
<button id="share-btn" class="btn btn-rounded btn-grey" data-i18n-key="dialogs.share" data-i18n-attrs="text" hidden></button> <button id="share-btn" class="btn btn-rounded btn-grey" data-i18n-key="dialogs.share" data-i18n-attrs="text" hidden></button>
<button id="download-btn" class="btn btn-rounded btn-grey" data-i18n-key="dialogs.download" data-i18n-attrs="text" autofocus></button> <button id="download-btn" class="btn btn-rounded btn-grey" data-i18n-key="dialogs.download" data-i18n-attrs="text" autofocus disabled></button>
<button class="btn btn-rounded btn-grey" data-i18n-key="dialogs.close" data-i18n-attrs="text" close></button> <button class="btn btn-rounded btn-grey" data-i18n-key="dialogs.close" data-i18n-attrs="text" close></button>
</div> </div>
</x-paper> </x-paper>
@ -420,25 +445,23 @@
<form action="#"> <form action="#">
<x-background class="full center"> <x-background class="full center">
<x-paper shadow="2"> <x-paper shadow="2">
<div class="row center"> <div class="row center p2">
<div class="column"> <h2 class="dialog-title" data-i18n-key="dialogs.send-message-title" data-i18n-attrs="text"></h2>
<h2 class="center" data-i18n-key="dialogs.send-message-title" data-i18n-attrs="text"></h2>
</div>
</div> </div>
<div class="row center p1 display-name-wrapper"> <div class="row center p2 display-name-wrapper">
<div class="column"> <div class="column">
<div class="text-center"> <div class="text-center">
<span data-i18n-key="dialogs.send-message-to" data-i18n-attrs="text"></span> <span data-i18n-key="dialogs.send-message-to" data-i18n-attrs="text"></span>
<span class="display-name badge badge-gradient"></span> <span class="display-name badge"></span>
</div> </div>
</div> </div>
</div> </div>
<div class="row p1"> <div class="row p2">
<div class="column fw"> <div class="column fw">
<div id="text-input" class="fw textarea" role="textbox" data-i18n-key="dialogs.message" data-i18n-attrs="title placeholder" autofocus contenteditable></div> <div class="fw textarea" role="textbox" data-i18n-key="dialogs.message" data-i18n-attrs="title placeholder" autofocus contenteditable></div>
</div> </div>
</div> </div>
<div class="button-row row-reverse"> <div class="btn-row row-reverse wrap">
<button class="btn btn-rounded btn-grey" type="submit" title="CTRL/⌘ + ENTER" data-i18n-key="dialogs.send" data-i18n-attrs="text" disabled></button> <button class="btn btn-rounded btn-grey" type="submit" title="CTRL/⌘ + ENTER" data-i18n-key="dialogs.send" data-i18n-attrs="text" disabled></button>
<button class="btn btn-rounded btn-grey" type="button" title="ESCAPE" data-i18n-key="dialogs.cancel" data-i18n-attrs="text" close></button> <button class="btn btn-rounded btn-grey" type="button" title="ESCAPE" data-i18n-key="dialogs.cancel" data-i18n-attrs="text" close></button>
</div> </div>
@ -450,34 +473,71 @@
<x-dialog id="receive-text-dialog"> <x-dialog id="receive-text-dialog">
<x-background class="full center"> <x-background class="full center">
<x-paper shadow="2"> <x-paper shadow="2">
<div class="row center"> <div class="row center p2">
<h2 class="text-center" data-i18n-key="dialogs.receive-text-title" data-i18n-attrs="text"></h2> <h2 class="dialog-title" class="text-center" data-i18n-key="dialogs.receive-text-title" data-i18n-attrs="text"></h2>
</div> </div>
<div class="row center p1 display-name-wrapper"> <div class="row center p2 display-name-wrapper">
<div class="text-center"> <div class="text-center">
<span class="display-name badge badge-gradient"></span> <span class="display-name badge"></span>
<span data-i18n-key="dialogs.has-sent" data-i18n-attrs="text"></span> <span data-i18n-key="dialogs.has-sent" data-i18n-attrs="text"></span>
</div> </div>
</div> </div>
<div class="row center p1"> <div class="row center p2">
<div class="column fw"> <div class="column fw">
<div id="text" class="textarea"></div> <div id="text" class="textarea"></div>
</div> </div>
</div> </div>
<div class="row-reverse center button-row"> <div class="row-reverse center btn-row wrap">
<button id="copy" class="btn btn-rounded btn-grey" title="CTRL/⌘ + C" data-i18n-key="dialogs.copy" data-i18n-attrs="text"></button> <button id="copy" class="btn btn-rounded btn-grey" title="CTRL/⌘ + C" data-i18n-key="dialogs.copy" data-i18n-attrs="text"></button>
<button id="close" class="btn btn-rounded btn-grey" title="ESCAPE" data-i18n-key="dialogs.close" data-i18n-attrs="text"></button> <button id="close" class="btn btn-rounded btn-grey" title="ESCAPE" data-i18n-key="dialogs.close" data-i18n-attrs="text"></button>
</div> </div>
</x-paper> </x-paper>
</x-background> </x-background>
</x-dialog> </x-dialog>
<!-- Share Text Dialog -->
<x-dialog id="share-text-dialog">
<x-background class="full center">
<x-paper shadow="2">
<div class="row center p2">
<h2 class="dialog-title" data-i18n-key="dialogs.share-text-title" data-i18n-attrs="text"></h2>
</div>
<div class="row center p2 pb0">
<div class="column">
<div class="text-center">
<span data-i18n-key="dialogs.share-text-subtitle" data-i18n-attrs="text"></span>
</div>
</div>
</div>
<div class="row p2">
<div class="column fw">
<div class="fw textarea" role="textbox" data-i18n-key="dialogs.message" data-i18n-attrs="title placeholder" contenteditable></div>
</div>
</div>
<div class="row p2 center">
<span class="mx1" data-i18n-key="dialogs.share-text-checkbox" data-i18n-attrs="text"></span>
<label class="pointer switch mx1">
<input type="checkbox">
<div class="slider round"></div>
</label>
</div>
<div class="btn-row row-reverse wrap">
<button class="btn btn-rounded btn-grey" type="submit" title="CTRL/⌘ + ENTER" data-i18n-key="dialogs.approve" data-i18n-attrs="text" autofocus disabled></button>
</div>
</x-paper>
</x-background>
</x-dialog>
<!-- base64 Paste Dialog --> <!-- base64 Paste Dialog -->
<x-dialog id="base64-paste-dialog"> <x-dialog id="base64-paste-dialog">
<x-background class="full center"> <x-background class="full center">
<x-paper shadow="2"> <x-paper shadow="2">
<button class="btn btn-rounded btn-grey center" id="base64-paste-btn" title="Paste"></button> <div class="row center p2">
<div class="textarea" placeholder="Paste here to send files" title="CMD/⌘ + V" contenteditable hidden></div> <h2 class="dialog-title"></h2>
<div class="row-reverse center button-row"> </div>
<div class="row p2">
<button class="btn btn-rounded btn-grey center" id="base64-paste-btn" title="Paste"></button>
<div class="textarea" title="CMD/⌘ + V" contenteditable hidden></div>
</div>
<div class="row-reverse center btn-row wrap">
<button class="btn btn-rounded btn-grey" data-i18n-key="dialogs.close" data-i18n-attrs="text" close></button> <button class="btn btn-rounded btn-grey" data-i18n-key="dialogs.close" data-i18n-attrs="text" close></button>
</div> </div>
</x-paper> </x-paper>
@ -485,7 +545,14 @@
</x-dialog> </x-dialog>
<!-- Toast --> <!-- Toast -->
<div class="toast-container full center"> <div class="toast-container full center">
<x-toast id="toast" class="row center" shadow="1"></x-toast> <x-toast id="toast" shadow="1">
<span class="center text-center"></span>
<div class="icon-button" data-i18n-key="dialogs.close-toast" data-i18n-attrs="title">
<svg class="icon">
<use xlink:href="#close-icon"></use>
</svg>
</div>
</x-toast>
</div> </div>
<!-- About Page --> <!-- About Page -->
<x-about id="about" class="full center column"> <x-about id="about" class="full center column">
@ -560,9 +627,9 @@
<symbol id="github"> <symbol id="github">
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"></path> <path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"></path>
</symbol> </symbol>
<g id="notifications"> <symbol id="notifications">
<path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.89 2 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z"></path> <path d="M12 22c1.1 0 2-.9 2-2h-4c0 1.1.89 2 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z"></path>
</g> </symbol>
<symbol id="homescreen"> <symbol id="homescreen">
<path fill="none" d="M0 0h24v24H0V0z"></path> <path fill="none" d="M0 0h24v24H0V0z"></path>
<path d="M18 1.01L8 1c-1.1 0-2 .9-2 2v3h2V5h10v14H8v-1H6v3c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V3c0-1.1-.9-1.99-2-1.99zM10 15h2V8H5v2h3.59L3 15.59 4.41 17 10 11.41z"></path> <path d="M18 1.01L8 1c-1.1 0-2 .9-2 2v3h2V5h10v14H8v-1H6v3c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V3c0-1.1-.9-1.99-2-1.99zM10 15h2V8H5v2h3.59L3 15.59 4.41 17 10 11.41z"></path>
@ -572,11 +639,13 @@
<path d="M0 0h24v24H0z" fill="none"></path> <path d="M0 0h24v24H0z" fill="none"></path>
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1.41 16.09V20h-2.67v-1.93c-1.71-.36-3.16-1.46-3.27-3.4h1.96c.1 1.05.82 1.87 2.65 1.87 1.96 0 2.4-.98 2.4-1.59 0-.83-.44-1.61-2.67-2.14-2.48-.6-4.18-1.62-4.18-3.67 0-1.72 1.39-2.84 3.11-3.21V4h2.67v1.95c1.86.45 2.79 1.86 2.85 3.39H14.3c-.05-1.11-.64-1.87-2.22-1.87-1.5 0-2.4.68-2.4 1.64 0 .84.65 1.39 2.67 1.91s4.18 1.39 4.18 3.91c-.01 1.83-1.38 2.83-3.12 3.16z"></path> <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1.41 16.09V20h-2.67v-1.93c-1.71-.36-3.16-1.46-3.27-3.4h1.96c.1 1.05.82 1.87 2.65 1.87 1.96 0 2.4-.98 2.4-1.59 0-.83-.44-1.61-2.67-2.14-2.48-.6-4.18-1.62-4.18-3.67 0-1.72 1.39-2.84 3.11-3.21V4h2.67v1.95c1.86.45 2.79 1.86 2.85 3.39H14.3c-.05-1.11-.64-1.87-2.22-1.87-1.5 0-2.4.68-2.4 1.64 0 .84.65 1.39 2.67 1.91s4.18 1.39 4.18 3.91c-.01 1.83-1.38 2.83-3.12 3.16z"></path>
</symbol> </symbol>
<symbol id="icon-theme-auto" viewBox="0 0 24 24"> <symbol id="icon-theme-auto" viewBox="-54 -54 620 620">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-54 -54 620 620"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M448 256c0-106-86-192-192-192V448c106 0 192-86 192-192zM0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256z"></path></svg> <!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
<path d="M448 256c0-106-86-192-192-192V448c106 0 192-86 192-192zM0 256a256 256 0 1 1 512 0A256 256 0 1 1 0 256z"></path>
</symbol> </symbol>
<symbol id="icon-theme-light" viewBox="0 0 24 24"> <symbol id="icon-theme-light" viewBox="-54 -54 620 620">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-54 -54 620 620"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M361.5 1.2c5 2.1 8.6 6.6 9.6 11.9L391 121l107.9 19.8c5.3 1 9.8 4.6 11.9 9.6s1.5 10.7-1.6 15.2L446.9 256l62.3 90.3c3.1 4.5 3.7 10.2 1.6 15.2s-6.6 8.6-11.9 9.6L391 391 371.1 498.9c-1 5.3-4.6 9.8-9.6 11.9s-10.7 1.5-15.2-1.6L256 446.9l-90.3 62.3c-4.5 3.1-10.2 3.7-15.2 1.6s-8.6-6.6-9.6-11.9L121 391 13.1 371.1c-5.3-1-9.8-4.6-11.9-9.6s-1.5-10.7 1.6-15.2L65.1 256 2.8 165.7c-3.1-4.5-3.7-10.2-1.6-15.2s6.6-8.6 11.9-9.6L121 121 140.9 13.1c1-5.3 4.6-9.8 9.6-11.9s10.7-1.5 15.2 1.6L256 65.1 346.3 2.8c4.5-3.1 10.2-3.7 15.2-1.6zM160 256a96 96 0 1 1 192 0 96 96 0 1 1 -192 0zm224 0a128 128 0 1 0 -256 0 128 128 0 1 0 256 0z"></path></svg> <!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
<path d="M361.5 1.2c5 2.1 8.6 6.6 9.6 11.9L391 121l107.9 19.8c5.3 1 9.8 4.6 11.9 9.6s1.5 10.7-1.6 15.2L446.9 256l62.3 90.3c3.1 4.5 3.7 10.2 1.6 15.2s-6.6 8.6-11.9 9.6L391 391 371.1 498.9c-1 5.3-4.6 9.8-9.6 11.9s-10.7 1.5-15.2-1.6L256 446.9l-90.3 62.3c-4.5 3.1-10.2 3.7-15.2 1.6s-8.6-6.6-9.6-11.9L121 391 13.1 371.1c-5.3-1-9.8-4.6-11.9-9.6s-1.5-10.7 1.6-15.2L65.1 256 2.8 165.7c-3.1-4.5-3.7-10.2-1.6-15.2s6.6-8.6 11.9-9.6L121 121 140.9 13.1c1-5.3 4.6-9.8 9.6-11.9s10.7-1.5 15.2 1.6L256 65.1 346.3 2.8c4.5-3.1 10.2-3.7 15.2-1.6zM160 256a96 96 0 1 1 192 0 96 96 0 1 1 -192 0zm224 0a128 128 0 1 0 -256 0 128 128 0 1 0 256 0z"></path>
</symbol> </symbol>
<symbol id="icon-theme-dark" viewBox="0 0 24 24"> <symbol id="icon-theme-dark" viewBox="0 0 24 24">
<rect fill="none" height="24" width="24"></rect><path d="M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36c-0.98,1.37-2.58,2.26-4.4,2.26 c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"></path> <rect fill="none" height="24" width="24"></rect><path d="M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36c-0.98,1.37-2.58,2.26-4.4,2.26 c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"></path>
@ -603,6 +672,21 @@
<!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --> <!--! Font Awesome Free 6.4.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. -->
<path d="M0 128C0 92.7 28.7 64 64 64H256h48 16H576c35.3 0 64 28.7 64 64V384c0 35.3-28.7 64-64 64H320 304 256 64c-35.3 0-64-28.7-64-64V128zm320 0V384H576V128H320zM178.3 175.9c-3.2-7.2-10.4-11.9-18.3-11.9s-15.1 4.7-18.3 11.9l-64 144c-4.5 10.1 .1 21.9 10.2 26.4s21.9-.1 26.4-10.2l8.9-20.1h73.6l8.9 20.1c4.5 10.1 16.3 14.6 26.4 10.2s14.6-16.3 10.2-26.4l-64-144zM160 233.2L179 276H141l19-42.8zM448 164c11 0 20 9 20 20v4h44 16c11 0 20 9 20 20s-9 20-20 20h-2l-1.6 4.5c-8.9 24.4-22.4 46.6-39.6 65.4c.9 .6 1.8 1.1 2.7 1.6l18.9 11.3c9.5 5.7 12.5 18 6.9 27.4s-18 12.5-27.4 6.9l-18.9-11.3c-4.5-2.7-8.8-5.5-13.1-8.5c-10.6 7.5-21.9 14-34 19.4l-3.6 1.6c-10.1 4.5-21.9-.1-26.4-10.2s.1-21.9 10.2-26.4l3.6-1.6c6.4-2.9 12.6-6.1 18.5-9.8l-12.2-12.2c-7.8-7.8-7.8-20.5 0-28.3s20.5-7.8 28.3 0l14.6 14.6 .5 .5c12.4-13.1 22.5-28.3 29.8-45H448 376c-11 0-20-9-20-20s9-20 20-20h52v-4c0-11 9-20 20-20z"></path> <path d="M0 128C0 92.7 28.7 64 64 64H256h48 16H576c35.3 0 64 28.7 64 64V384c0 35.3-28.7 64-64 64H320 304 256 64c-35.3 0-64-28.7-64-64V128zm320 0V384H576V128H320zM178.3 175.9c-3.2-7.2-10.4-11.9-18.3-11.9s-15.1 4.7-18.3 11.9l-64 144c-4.5 10.1 .1 21.9 10.2 26.4s21.9-.1 26.4-10.2l8.9-20.1h73.6l8.9 20.1c4.5 10.1 16.3 14.6 26.4 10.2s14.6-16.3 10.2-26.4l-64-144zM160 233.2L179 276H141l19-42.8zM448 164c11 0 20 9 20 20v4h44 16c11 0 20 9 20 20s-9 20-20 20h-2l-1.6 4.5c-8.9 24.4-22.4 46.6-39.6 65.4c.9 .6 1.8 1.1 2.7 1.6l18.9 11.3c9.5 5.7 12.5 18 6.9 27.4s-18 12.5-27.4 6.9l-18.9-11.3c-4.5-2.7-8.8-5.5-13.1-8.5c-10.6 7.5-21.9 14-34 19.4l-3.6 1.6c-10.1 4.5-21.9-.1-26.4-10.2s.1-21.9 10.2-26.4l3.6-1.6c6.4-2.9 12.6-6.1 18.5-9.8l-12.2-12.2c-7.8-7.8-7.8-20.5 0-28.3s20.5-7.8 28.3 0l14.6 14.6 .5 .5c12.4-13.1 22.5-28.3 29.8-45H448 376c-11 0-20-9-20-20s9-20 20-20h52v-4c0-11 9-20 20-20z"></path>
</symbol> </symbol>
<symbol id="i-cursor" viewBox="-180 0 640 512">
<!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.-->
<path d="M.1 29.3C-1.4 47 11.7 62.4 29.3 63.9l8 .7C70.5 67.3 96 95 96 128.3V224H64c-17.7 0-32 14.3-32 32s14.3 32 32 32H96v95.7c0 33.3-25.5 61-58.7 63.8l-8 .7C11.7 449.6-1.4 465 .1 482.7s16.9 30.7 34.5 29.2l8-.7c34.1-2.8 64.2-18.9 85.4-42.9c21.2 24 51.2 40.1 85.4 42.9l8 .7c17.6 1.5 33.1-11.6 34.5-29.2s-11.6-33.1-29.2-34.5l-8-.7C185.5 444.7 160 417 160 383.7V288h32c17.7 0 32-14.3 32-32s-14.3-32-32-32H160V128.3c0-33.3 25.5-61 58.7-63.8l8-.7c17.6-1.5 30.7-16.9 29.2-34.5S239-1.4 221.3 .1l-8 .7C179.2 3.6 149.2 19.7 128 43.7c-21.2-24-51.2-40-85.4-42.9l-8-.7C17-1.4 1.6 11.7 .1 29.3z"></path>
</symbol>
<symbol id="font" viewBox="-100 0 640 512">
<!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.-->
<path d="M254 52.8C249.3 40.3 237.3 32 224 32s-25.3 8.3-30 20.8L57.8 416H32c-17.7 0-32 14.3-32 32s14.3 32 32 32h96c17.7 0 32-14.3 32-32s-14.3-32-32-32h-1.8l18-48H303.8l18 48H320c-17.7 0-32 14.3-32 32s14.3 32 32 32h96c17.7 0 32-14.3 32-32s-14.3-32-32-32H390.2L254 52.8zM279.8 304H168.2L224 155.1 279.8 304z"></path>
</symbol>
<symbol id="file" viewBox="-130 0 650 530">
<!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.-->
<path d="M320 464c8.8 0 16-7.2 16-16V160H256c-17.7 0-32-14.3-32-32V48H64c-8.8 0-16 7.2-16 16V448c0 8.8 7.2 16 16 16H320zM0 64C0 28.7 28.7 0 64 0H229.5c17 0 33.3 6.7 45.3 18.7l90.5 90.5c12 12 18.7 28.3 18.7 45.3V448c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V64z"></path>
</symbol>
<symbol id="caret" viewBox="0 0 320 512">
<!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2023 Fonticons, Inc.--><path d="M137.4 374.6c12.5 12.5 32.8 12.5 45.3 0l128-128c9.2-9.2 11.9-22.9 6.9-34.9s-16.6-19.8-29.6-19.8L32 192c-12.9 0-24.6 7.8-29.6 19.8s-2.2 25.7 6.9 34.9l128 128z"></path>
</symbol>
</svg> </svg>
<!-- Scripts --> <!-- Scripts -->

View file

@ -55,7 +55,7 @@
"click-to-show": "اضغط للعرض" "click-to-show": "اضغط للعرض"
}, },
"header": { "header": {
"cancel-paste-mode": "تمّ", "cancel-share-mode": "تمّ",
"theme-auto_title": "تكيٌف المظهر مع النظام", "theme-auto_title": "تكيٌف المظهر مع النظام",
"install_title": "تثبيت PairDrop", "install_title": "تثبيت PairDrop",
"theme-dark_title": "إستخدام دائما المظهر المظلم", "theme-dark_title": "إستخدام دائما المظهر المظلم",
@ -70,12 +70,12 @@
}, },
"instructions": { "instructions": {
"x-instructions_mobile": "انقر لإرسال الملفات أو انقر لفترة طويلة لإرسال رسالة", "x-instructions_mobile": "انقر لإرسال الملفات أو انقر لفترة طويلة لإرسال رسالة",
"click-to-send": "انقر للإرسال", "x-instructions-share-mode_desktop": "انقر للإرسال",
"activate-paste-mode-and-other-files": "و{{count}} ملفات أخرى", "activate-share-mode-and-other-files-plural": "و{{count}} ملفات أخرى",
"tap-to-send": "انقر للإرسال", "x-instructions-share-mode_mobile": "انقر للإرسال",
"activate-paste-mode-base": "افتح PairDrop على الأجهزة الأخرى للإرسال", "activate-share-mode-base": "افتح PairDrop على الأجهزة الأخرى للإرسال",
"no-peers-subtitle": "قم بإقران الأجهزة أو ادخل إلى غرفة عامة لتتمكن من إكتشافها على الشبكات الأخرى", "no-peers-subtitle": "قم بإقران الأجهزة أو ادخل إلى غرفة عامة لتتمكن من إكتشافها على الشبكات الأخرى",
"activate-paste-mode-shared-text": "النص المشترك", "activate-share-mode-shared-text": "النص المشترك",
"x-instructions_desktop": "انقر لإرسال الملفات أو انقر بزر الماوس الأيمن لإرسال رسالة", "x-instructions_desktop": "انقر لإرسال الملفات أو انقر بزر الماوس الأيمن لإرسال رسالة",
"no-peers-title": "افتح PairDrop على الأجهزة الأخرى لإرسال الملفات", "no-peers-title": "افتح PairDrop على الأجهزة الأخرى لإرسال الملفات",
"x-instructions_data-drop-bg": "حرر لتحديد المستلم", "x-instructions_data-drop-bg": "حرر لتحديد المستلم",
@ -84,7 +84,7 @@
}, },
"peer-ui": { "peer-ui": {
"processing": "مُعالجة …", "processing": "مُعالجة …",
"click-to-send-paste-mode": "انقر للإرسال {{descriptor}}", "click-to-send-share-mode": "انقر للإرسال {{descriptor}}",
"click-to-send": "انقر لإرسال الملفات أو انقر بزر الماوس الأيمن لإرسال رسالة", "click-to-send": "انقر لإرسال الملفات أو انقر بزر الماوس الأيمن لإرسال رسالة",
"waiting": "يُرجى الإنتظار…", "waiting": "يُرجى الإنتظار…",
"connection-hash": "للتحقق من أمان التشفير الشامل، قم بمقارنة رقم الأمان هذا على كلا الجهازين", "connection-hash": "للتحقق من أمان التشفير الشامل، قم بمقارنة رقم الأمان هذا على كلا الجهازين",

View file

@ -9,7 +9,7 @@
"theme-auto_title": "Systemstil verwenden", "theme-auto_title": "Systemstil verwenden",
"theme-dark_title": "Immer dunklen Stil verwenden", "theme-dark_title": "Immer dunklen Stil verwenden",
"theme-light_title": "Immer hellen Stil verwenden", "theme-light_title": "Immer hellen Stil verwenden",
"cancel-paste-mode": "Fertig", "cancel-share-mode": "Fertig",
"language-selector_title": "Sprache Wählen", "language-selector_title": "Sprache Wählen",
"join-public-room_title": "Öffentlichen Raum temporär betreten" "join-public-room_title": "Öffentlichen Raum temporär betreten"
}, },
@ -138,14 +138,14 @@
"no-peers-title": "Öffne PairDrop auf anderen Geräten, um Dateien zu senden", "no-peers-title": "Öffne PairDrop auf anderen Geräten, um Dateien zu senden",
"no-peers_data-drop-bg": "Hier ablegen, um Empfänger auszuwählen", "no-peers_data-drop-bg": "Hier ablegen, um Empfänger auszuwählen",
"no-peers-subtitle": "Kopple Geräte oder besuche einen öffentlichen Raum, damit du in anderen Netzwerken sichtbar bist", "no-peers-subtitle": "Kopple Geräte oder besuche einen öffentlichen Raum, damit du in anderen Netzwerken sichtbar bist",
"click-to-send": "Klicke zum Senden von", "x-instructions-share-mode_desktop": "Klicke zum Senden von",
"tap-to-send": "Tippe zum Senden von", "x-instructions-share-mode_mobile": "Tippe zum Senden von",
"x-instructions_data-drop-peer": "Hier ablegen, um an Peer zu senden", "x-instructions_data-drop-peer": "Hier ablegen, um an Peer zu senden",
"x-instructions_data-drop-bg": "Loslassen um Empfänger auszuwählen", "x-instructions_data-drop-bg": "Loslassen um Empfänger auszuwählen",
"x-instructions_mobile": "Tippe, um Dateien zu senden oder tippe lange, um Nachrichten zu senden", "x-instructions_mobile": "Tippe, um Dateien zu senden oder tippe lange, um Nachrichten zu senden",
"activate-paste-mode-base": "Öffne PairDrop auf anderen Geräten zum Senden von", "activate-share-mode-base": "Öffne PairDrop auf anderen Geräten zum Senden von",
"activate-paste-mode-and-other-files": "und {{count}} anderen Dateien", "activate-share-mode-and-other-files-plural": "und {{count}} anderen Dateien",
"activate-paste-mode-shared-text": "freigegebenem Text" "activate-share-mode-shared-text": "freigegebenem Text"
}, },
"document-titles": { "document-titles": {
"file-transfer-requested": "Dateiübertragung angefordert", "file-transfer-requested": "Dateiübertragung angefordert",
@ -159,7 +159,7 @@
"click-to-send": "Klicke, um Dateien zu senden oder benutze einen Rechtsklick, um eine Textnachricht zu senden", "click-to-send": "Klicke, um Dateien zu senden oder benutze einen Rechtsklick, um eine Textnachricht zu senden",
"connection-hash": "Um die Ende-zu-Ende Verschlüsselung zu verifizieren, vergleiche die Sicherheitsnummer auf beiden Geräten", "connection-hash": "Um die Ende-zu-Ende Verschlüsselung zu verifizieren, vergleiche die Sicherheitsnummer auf beiden Geräten",
"waiting": "Warte…", "waiting": "Warte…",
"click-to-send-paste-mode": "Klicken um {{descriptor}} zu senden", "click-to-send-share-mode": "Klicken um {{descriptor}} zu senden",
"transferring": "Übertragung läuft…", "transferring": "Übertragung läuft…",
"processing": "Bearbeitung läuft…", "processing": "Bearbeitung läuft…",
"preparing": "Vorbereitung läuft…" "preparing": "Vorbereitung läuft…"

View file

@ -11,7 +11,8 @@
"pair-device_title": "Pair your devices permanently", "pair-device_title": "Pair your devices permanently",
"edit-paired-devices_title": "Edit paired devices", "edit-paired-devices_title": "Edit paired devices",
"join-public-room_title": "Join public room temporarily", "join-public-room_title": "Join public room temporarily",
"cancel-paste-mode": "Done" "cancel-share-mode": "Cancel",
"edit-share-mode": "Edit"
}, },
"instructions": { "instructions": {
"no-peers_data-drop-bg": "Release to select recipient", "no-peers_data-drop-bg": "Release to select recipient",
@ -21,11 +22,14 @@
"x-instructions_mobile": "Tap to send files or long tap to send a message", "x-instructions_mobile": "Tap to send files or long tap to send a message",
"x-instructions_data-drop-peer": "Release to send to peer", "x-instructions_data-drop-peer": "Release to send to peer",
"x-instructions_data-drop-bg": "Release to select recipient", "x-instructions_data-drop-bg": "Release to select recipient",
"click-to-send": "Click to send", "x-instructions-share-mode_desktop": "Click to send {{descriptor}}",
"tap-to-send": "Tap to send", "x-instructions-share-mode_mobile": "Tap to send {{descriptor}}",
"activate-paste-mode-base": "Open PairDrop on other devices to send", "activate-share-mode-base": "Open PairDrop on other devices to send",
"activate-paste-mode-and-other-files": "and {{count}} other files", "activate-share-mode-and-other-file": "and 1 other file",
"activate-paste-mode-shared-text": "shared text", "activate-share-mode-and-other-files-plural": "and {{count}} other files",
"activate-share-mode-shared-text": "shared text",
"activate-share-mode-shared-file": "shared file",
"activate-share-mode-shared-files-plural": "{{count}} shared files",
"webrtc-requirement": "To use PairDrop on this instance, WebRTC must be enabled!" "webrtc-requirement": "To use PairDrop on this instance, WebRTC must be enabled!"
}, },
"footer": { "footer": {
@ -56,6 +60,7 @@
"cancel": "Cancel", "cancel": "Cancel",
"edit-paired-devices-title": "Edit Paired Devices", "edit-paired-devices-title": "Edit Paired Devices",
"unpair": "Unpair", "unpair": "Unpair",
"paired-device-removed": "Paired device removed.",
"paired-devices-wrapper_data-empty": "No paired devices.", "paired-devices-wrapper_data-empty": "No paired devices.",
"auto-accept-instructions-1": "Activate", "auto-accept-instructions-1": "Activate",
"auto-accept": "auto-accept", "auto-accept": "auto-accept",
@ -76,9 +81,11 @@
"send": "Send", "send": "Send",
"receive-text-title": "Message Received", "receive-text-title": "Message Received",
"copy": "Copy", "copy": "Copy",
"base64-title-files": "Share Files",
"base64-title-text": "Share Text",
"base64-processing": "Processing…", "base64-processing": "Processing…",
"base64-tap-to-paste": "Tap here to paste {{type}}", "base64-tap-to-paste": "Tap here to share {{type}}",
"base64-paste-to-send": "Paste here to send {{type}}", "base64-paste-to-send": "Paste clipboard here to share {{type}}",
"base64-text": "text", "base64-text": "text",
"base64-files": "files", "base64-files": "files",
"file-other-description-image": "and 1 other image", "file-other-description-image": "and 1 other image",
@ -94,7 +101,12 @@
"language-selector-title": "Set Language", "language-selector-title": "Set Language",
"system-language": "System Language", "system-language": "System Language",
"public-room-qr-code_title": "Click to copy link to public room", "public-room-qr-code_title": "Click to copy link to public room",
"pair-devices-qr-code_title": "Click to copy link to pair this device" "pair-devices-qr-code_title": "Click to copy link to pair this device",
"approve": "approve",
"share-text-title": "Share Text Message",
"share-text-subtitle": "Edit message before sending:",
"share-text-checkbox": "Always show this dialog when sharing text",
"close-toast_title": "Close notification"
}, },
"about": { "about": {
"close-about_aria-label": "Close About PairDrop", "close-about_aria-label": "Close About PairDrop",
@ -156,7 +168,7 @@
"message-received-plural": "{{count}} Messages Received" "message-received-plural": "{{count}} Messages Received"
}, },
"peer-ui": { "peer-ui": {
"click-to-send-paste-mode": "Click to send {{descriptor}}", "click-to-send-share-mode": "Click to send {{descriptor}}",
"click-to-send": "Click to send files or right click to send a message", "click-to-send": "Click to send files or right click to send a message",
"connection-hash": "To verify the security of the end-to-end encryption, compare this security number on both devices", "connection-hash": "To verify the security of the end-to-end encryption, compare this security number on both devices",
"preparing": "Preparing…", "preparing": "Preparing…",

View file

@ -4,7 +4,7 @@
"language-selector_title": "Configurar Idioma", "language-selector_title": "Configurar Idioma",
"about_title": "Sobre PairDrop", "about_title": "Sobre PairDrop",
"about_aria-label": "Abrir Sobre PairDrop", "about_aria-label": "Abrir Sobre PairDrop",
"cancel-paste-mode": "Listo", "cancel-share-mode": "Listo",
"install_title": "Instalar PairDrop", "install_title": "Instalar PairDrop",
"theme-dark_title": "Siempre usar tema oscuro", "theme-dark_title": "Siempre usar tema oscuro",
"pair-device_title": "Empareja tus dispositivos permanentemente", "pair-device_title": "Empareja tus dispositivos permanentemente",
@ -73,12 +73,12 @@
}, },
"instructions": { "instructions": {
"x-instructions_mobile": "Toque para enviar archivos o toque prologádamente para enviar un mensaje", "x-instructions_mobile": "Toque para enviar archivos o toque prologádamente para enviar un mensaje",
"click-to-send": "Haga clic para enviar", "x-instructions-share-mode_desktop": "Haga clic para enviar",
"activate-paste-mode-and-other-files": "y {{count}} archivos diferentes", "activate-share-mode-and-other-files-plural": "y {{count}} archivos diferentes",
"tap-to-send": "Toca para enviar", "x-instructions-share-mode_mobile": "Toca para enviar",
"activate-paste-mode-base": "Abra PairDrop en otros dispositivos para enviar", "activate-share-mode-base": "Abra PairDrop en otros dispositivos para enviar",
"no-peers-subtitle": "Empareje dispositivos o ingrese a una sala pública para que lo puedan encontrar en otras redes", "no-peers-subtitle": "Empareje dispositivos o ingrese a una sala pública para que lo puedan encontrar en otras redes",
"activate-paste-mode-shared-text": "texto compartido", "activate-share-mode-shared-text": "texto compartido",
"x-instructions_desktop": "Haga clic para enviar archivos o haga clic derecho para enviar un mensaje", "x-instructions_desktop": "Haga clic para enviar archivos o haga clic derecho para enviar un mensaje",
"no-peers-title": "Abra PairDrop en otros dispositivos para enviar archivos", "no-peers-title": "Abra PairDrop en otros dispositivos para enviar archivos",
"x-instructions_data-drop-peer": "Liberar para enviar a un par", "x-instructions_data-drop-peer": "Liberar para enviar a un par",
@ -87,7 +87,7 @@
}, },
"peer-ui": { "peer-ui": {
"processing": "Procesando…", "processing": "Procesando…",
"click-to-send-paste-mode": "Haga clic para enviar {{descriptor}}", "click-to-send-share-mode": "Haga clic para enviar {{descriptor}}",
"click-to-send": "Haga clic para enviar archivos o haga clic derecho para enviar un mensaje", "click-to-send": "Haga clic para enviar archivos o haga clic derecho para enviar un mensaje",
"waiting": "Esperando…", "waiting": "Esperando…",
"connection-hash": "Para verificar la seguridad del cifrado de extremo a extremo, compare este número de seguridad en ambos dispositivos", "connection-hash": "Para verificar la seguridad del cifrado de extremo a extremo, compare este número de seguridad en ambos dispositivos",

View file

@ -11,7 +11,7 @@
"pair-device_title": "Associez vos appareils de manière permanente", "pair-device_title": "Associez vos appareils de manière permanente",
"edit-paired-devices_title": "Gérer les appareils couplés", "edit-paired-devices_title": "Gérer les appareils couplés",
"join-public-room_title": "Rejoindre temporairement la salle publique", "join-public-room_title": "Rejoindre temporairement la salle publique",
"cancel-paste-mode": "Terminé" "cancel-share-mode": "Terminé"
}, },
"instructions": { "instructions": {
"no-peers_data-drop-bg": "Déposer pour choisir le destinataire", "no-peers_data-drop-bg": "Déposer pour choisir le destinataire",
@ -21,11 +21,11 @@
"x-instructions_mobile": "Appuyez pour envoyer des fichiers ou appuyez longuement pour envoyer un message", "x-instructions_mobile": "Appuyez pour envoyer des fichiers ou appuyez longuement pour envoyer un message",
"x-instructions_data-drop-peer": "Déposer pour envoyer au destinataire", "x-instructions_data-drop-peer": "Déposer pour envoyer au destinataire",
"x-instructions_data-drop-bg": "Lâcher pour choisir le destinataire", "x-instructions_data-drop-bg": "Lâcher pour choisir le destinataire",
"click-to-send": "Cliquez pour envoyer", "x-instructions-share-mode_desktop": "Cliquez pour envoyer",
"tap-to-send": "Appuyez pour envoyer", "x-instructions-share-mode_mobile": "Appuyez pour envoyer",
"activate-paste-mode-base": "Ouvrez PairDrop sur d'autres appareils pour envoyer", "activate-share-mode-base": "Ouvrez PairDrop sur d'autres appareils pour envoyer",
"activate-paste-mode-and-other-files": "et {{count}} autres fichiers", "activate-share-mode-and-other-files-plural": "et {{count}} autres fichiers",
"activate-paste-mode-shared-text": "texte partagé" "activate-share-mode-shared-text": "texte partagé"
}, },
"footer": { "footer": {
"known-as": "Vous êtes connu comme :", "known-as": "Vous êtes connu comme :",
@ -149,7 +149,7 @@
"message-received-plural": "{{count}} Messages reçus" "message-received-plural": "{{count}} Messages reçus"
}, },
"peer-ui": { "peer-ui": {
"click-to-send-paste-mode": "Cliquez pour envoyer {{descriptor}}", "click-to-send-share-mode": "Cliquez pour envoyer {{descriptor}}",
"click-to-send": "Cliquez pour envoyer des fichiers ou faites un clic droit pour envoyer un message", "click-to-send": "Cliquez pour envoyer des fichiers ou faites un clic droit pour envoyer un message",
"connection-hash": "Pour vérifier la sécurité du chiffrement de bout en bout, comparez ce numéro de sécurité sur les deux appareils", "connection-hash": "Pour vérifier la sécurité du chiffrement de bout en bout, comparez ce numéro de sécurité sur les deux appareils",
"preparing": "Préparation…", "preparing": "Préparation…",

View file

@ -58,7 +58,7 @@
"room-url-copied-to-clipboard": "Tautan ke ruang publik disalin ke papan klip" "room-url-copied-to-clipboard": "Tautan ke ruang publik disalin ke papan klip"
}, },
"header": { "header": {
"cancel-paste-mode": "Selesai", "cancel-share-mode": "Selesai",
"theme-auto_title": "Sesuaikan tema dengan sistem", "theme-auto_title": "Sesuaikan tema dengan sistem",
"install_title": "Instal PairDrop", "install_title": "Instal PairDrop",
"theme-dark_title": "Selalu gunakan tema gelap", "theme-dark_title": "Selalu gunakan tema gelap",
@ -73,12 +73,12 @@
}, },
"instructions": { "instructions": {
"x-instructions_mobile": "Ketuk untuk mengirim file atau ketuk lama untuk mengirim pesan", "x-instructions_mobile": "Ketuk untuk mengirim file atau ketuk lama untuk mengirim pesan",
"click-to-send": "Klik untuk mengirim", "x-instructions-share-mode_desktop": "Klik untuk mengirim",
"activate-paste-mode-and-other-files": "dan {{count}} file lainnya", "activate-share-mode-and-other-files-plural": "dan {{count}} file lainnya",
"tap-to-send": "Ketuk untuk mengirim", "x-instructions-share-mode_mobile": "Ketuk untuk mengirim",
"activate-paste-mode-base": "Buka PairDrop di perangkat lain untuk berkirim", "activate-share-mode-base": "Buka PairDrop di perangkat lain untuk berkirim",
"no-peers-subtitle": "Pasangkan perangkat atau masuk ke ruang publik agar dapat terdeteksi di jaringan lain", "no-peers-subtitle": "Pasangkan perangkat atau masuk ke ruang publik agar dapat terdeteksi di jaringan lain",
"activate-paste-mode-shared-text": "teks bersama", "activate-share-mode-shared-text": "teks bersama",
"x-instructions_desktop": "Klik untuk mengirim file atau klik kanan untuk mengirim pesan", "x-instructions_desktop": "Klik untuk mengirim file atau klik kanan untuk mengirim pesan",
"no-peers-title": "Buka PairDrop di perangkat lain untuk berkirim file", "no-peers-title": "Buka PairDrop di perangkat lain untuk berkirim file",
"x-instructions_data-drop-peer": "Lepaskan untuk mengirim ke rekan", "x-instructions_data-drop-peer": "Lepaskan untuk mengirim ke rekan",
@ -87,7 +87,7 @@
}, },
"peer-ui": { "peer-ui": {
"processing": "Memproses…", "processing": "Memproses…",
"click-to-send-paste-mode": "Klik untuk mengirim {{descriptor}}", "click-to-send-share-mode": "Klik untuk mengirim {{descriptor}}",
"click-to-send": "Klik untuk mengirim file atau klik kanan untuk mengirim pesan", "click-to-send": "Klik untuk mengirim file atau klik kanan untuk mengirim pesan",
"waiting": "Menunggu…", "waiting": "Menunggu…",
"connection-hash": "Untuk memverifikasi keamanan enkripsi end-to-end, bandingkan nomor keamanan ini pada kedua perangkat", "connection-hash": "Untuk memverifikasi keamanan enkripsi end-to-end, bandingkan nomor keamanan ini pada kedua perangkat",

View file

@ -15,7 +15,7 @@
"known-as": "Sei visibile come:" "known-as": "Sei visibile come:"
}, },
"header": { "header": {
"cancel-paste-mode": "Fatto", "cancel-share-mode": "Fatto",
"theme-auto_title": "Adatta il tema al sistema automaticamente", "theme-auto_title": "Adatta il tema al sistema automaticamente",
"install_title": "Installa PairDrop", "install_title": "Installa PairDrop",
"theme-dark_title": "Usa sempre il tema scuro", "theme-dark_title": "Usa sempre il tema scuro",
@ -30,12 +30,12 @@
}, },
"instructions": { "instructions": {
"x-instructions_mobile": "Tocca per inviare file o tocco prolungato per inviare un messaggio", "x-instructions_mobile": "Tocca per inviare file o tocco prolungato per inviare un messaggio",
"click-to-send": "Clicca per inviare", "x-instructions-share-mode_desktop": "Clicca per inviare",
"activate-paste-mode-and-other-files": "e altri {{count}} files", "activate-share-mode-and-other-files-plural": "e altri {{count}} files",
"tap-to-send": "Tocca per inviare", "x-instructions-share-mode_mobile": "Tocca per inviare",
"activate-paste-mode-base": "Apri PairDrop su altri dispositivi per inviare", "activate-share-mode-base": "Apri PairDrop su altri dispositivi per inviare",
"no-peers-subtitle": "Abbina dispositivi o entra in una stanza pubblica per essere rilevabile su altre reti", "no-peers-subtitle": "Abbina dispositivi o entra in una stanza pubblica per essere rilevabile su altre reti",
"activate-paste-mode-shared-text": "testo condiviso", "activate-share-mode-shared-text": "testo condiviso",
"x-instructions_desktop": "Clicca per inviare files o usa il tasto destro per inviare un messaggio", "x-instructions_desktop": "Clicca per inviare files o usa il tasto destro per inviare un messaggio",
"no-peers-title": "Apri PairDrop su altri dispositivi per inviare files", "no-peers-title": "Apri PairDrop su altri dispositivi per inviare files",
"x-instructions_data-drop-peer": "Rilascia per inviare al peer", "x-instructions_data-drop-peer": "Rilascia per inviare al peer",
@ -139,7 +139,7 @@
}, },
"peer-ui": { "peer-ui": {
"processing": "Elaborazione…", "processing": "Elaborazione…",
"click-to-send-paste-mode": "Clicca per inviare {{descriptor}}", "click-to-send-share-mode": "Clicca per inviare {{descriptor}}",
"click-to-send": "Clicca per inviare files o tasto destro per inviare un messaggio", "click-to-send": "Clicca per inviare files o tasto destro per inviare un messaggio",
"waiting": "In attesa…", "waiting": "In attesa…",
"connection-hash": "Per verificare la sicurezza della crittografia end-to-end, confronta questo numero di sicurezza su entrambi i dispositivi", "connection-hash": "Per verificare la sicurezza della crittografia end-to-end, confronta questo numero di sicurezza su entrambi i dispositivi",

View file

@ -58,7 +58,7 @@
"room-url-copied-to-clipboard": "パブリックルームへのリンクをクリップボードにコピーしました" "room-url-copied-to-clipboard": "パブリックルームへのリンクをクリップボードにコピーしました"
}, },
"header": { "header": {
"cancel-paste-mode": "完了", "cancel-share-mode": "完了",
"theme-auto_title": "テーマをシステムの設定に自動的に合わせる", "theme-auto_title": "テーマをシステムの設定に自動的に合わせる",
"install_title": "PairDropをインストール", "install_title": "PairDropをインストール",
"theme-dark_title": "常にダークテーマを使用する", "theme-dark_title": "常にダークテーマを使用する",
@ -73,12 +73,12 @@
}, },
"instructions": { "instructions": {
"x-instructions_mobile": "タップしてファイルを送信または長押ししてメッセージを送信します", "x-instructions_mobile": "タップしてファイルを送信または長押ししてメッセージを送信します",
"click-to-send": "クリックして送信", "x-instructions-share-mode_desktop": "クリックして送信",
"activate-paste-mode-and-other-files": "とその他{{count}}個のファイル", "activate-share-mode-and-other-files-plural": "とその他{{count}}個のファイル",
"tap-to-send": "タップして送信", "x-instructions-share-mode_mobile": "タップして送信",
"activate-paste-mode-base": "他のデバイスでPairDropを開いて送信します", "activate-share-mode-base": "他のデバイスでPairDropを開いて送信します",
"no-peers-subtitle": "デバイスをペア設定するかパブリックルームに参加すると、他のネットワーク上からも見つけられるようになります", "no-peers-subtitle": "デバイスをペア設定するかパブリックルームに参加すると、他のネットワーク上からも見つけられるようになります",
"activate-paste-mode-shared-text": "共有されたテキスト", "activate-share-mode-shared-text": "共有されたテキスト",
"x-instructions_desktop": "左クリックしてファイルを送信または右クリックしてメッセージを送信します", "x-instructions_desktop": "左クリックしてファイルを送信または右クリックしてメッセージを送信します",
"no-peers-title": "他のデバイスでPairDropを開いてファイルを送信します", "no-peers-title": "他のデバイスでPairDropを開いてファイルを送信します",
"x-instructions_data-drop-peer": "離すとこの相手に送信します", "x-instructions_data-drop-peer": "離すとこの相手に送信します",
@ -87,7 +87,7 @@
}, },
"peer-ui": { "peer-ui": {
"processing": "処理中…", "processing": "処理中…",
"click-to-send-paste-mode": "クリックして{{descriptor}}を送信", "click-to-send-share-mode": "クリックして{{descriptor}}を送信",
"click-to-send": "クリックしてファイルを送信または右クリックしてメッセージを送信します", "click-to-send": "クリックしてファイルを送信または右クリックしてメッセージを送信します",
"waiting": "待機中…", "waiting": "待機中…",
"connection-hash": "エンドツーエンド暗号化のセキュリティを確認するには、両方のデバイスのセキュリティナンバーを確認します", "connection-hash": "エンドツーエンド暗号化のセキュリティを確認するには、両方のデバイスのセキュリティナンバーを確認します",

View file

@ -7,7 +7,7 @@
"theme-light_title": "Alltid bruk lys drakt", "theme-light_title": "Alltid bruk lys drakt",
"theme-dark_title": "Alltid bruk mørk drakt", "theme-dark_title": "Alltid bruk mørk drakt",
"notification_title": "Skru på merknader", "notification_title": "Skru på merknader",
"cancel-paste-mode": "Ferdig", "cancel-share-mode": "Ferdig",
"install_title": "Installer PairDrop", "install_title": "Installer PairDrop",
"pair-device_title": "Sammenkoble enhet" "pair-device_title": "Sammenkoble enhet"
}, },
@ -25,15 +25,15 @@
"x-instructions_desktop": "Klikk for å sende filer, eller høyreklikk for å sende en melding", "x-instructions_desktop": "Klikk for å sende filer, eller høyreklikk for å sende en melding",
"x-instructions_mobile": "Trykk for å sende filer, eller lang-trykk for å sende en melding", "x-instructions_mobile": "Trykk for å sende filer, eller lang-trykk for å sende en melding",
"x-instructions_data-drop-bg": "Slipp for å velge mottager", "x-instructions_data-drop-bg": "Slipp for å velge mottager",
"click-to-send": "Klikk for å sende", "x-instructions-share-mode_desktop": "Klikk for å sende",
"no-peers_data-drop-bg": "Slipp for å velge mottager", "no-peers_data-drop-bg": "Slipp for å velge mottager",
"no-peers-title": "Åpne PairDrop på andre enheter for å sende filer", "no-peers-title": "Åpne PairDrop på andre enheter for å sende filer",
"no-peers-subtitle": "Sammenkoble enheter for å kunne oppdages på andre nettverk", "no-peers-subtitle": "Sammenkoble enheter for å kunne oppdages på andre nettverk",
"x-instructions_data-drop-peer": "Slipp for å sende til likemann", "x-instructions_data-drop-peer": "Slipp for å sende til likemann",
"tap-to-send": "Trykk for å sende", "x-instructions-share-mode_mobile": "Trykk for å sende",
"activate-paste-mode-base": "Åpne PairDrop på andre enheter for å sende", "activate-share-mode-base": "Åpne PairDrop på andre enheter for å sende",
"activate-paste-mode-and-other-files": "og {{count}} andre filer", "activate-share-mode-and-other-files-plural": "og {{count}} andre filer",
"activate-paste-mode-shared-text": "delt tekst" "activate-share-mode-shared-text": "delt tekst"
}, },
"dialogs": { "dialogs": {
"input-key-on-this-device": "Skriv inn denne nøkkelen på en annen enhet", "input-key-on-this-device": "Skriv inn denne nøkkelen på en annen enhet",
@ -132,7 +132,7 @@
"processing": "Behandler …", "processing": "Behandler …",
"transferring": "Overfører …", "transferring": "Overfører …",
"click-to-send": "Klikk for å sende filer, eller høyreklikk for å sende en melding", "click-to-send": "Klikk for å sende filer, eller høyreklikk for å sende en melding",
"click-to-send-paste-mode": "Klikk for å sende {{descriptor}}", "click-to-send-share-mode": "Klikk for å sende {{descriptor}}",
"connection-hash": "Sammenlign dette sikkerhetsnummeret på begge enhetene for å bekrefte ende-til-ende -krypteringen." "connection-hash": "Sammenlign dette sikkerhetsnummeret på begge enhetene for å bekrefte ende-til-ende -krypteringen."
} }
} }

View file

@ -55,7 +55,7 @@
"click-to-show": "Klik om te tonen" "click-to-show": "Klik om te tonen"
}, },
"header": { "header": {
"cancel-paste-mode": "Klaar", "cancel-share-mode": "Klaar",
"theme-auto_title": "Gebruik systeemstijl", "theme-auto_title": "Gebruik systeemstijl",
"install_title": "PairDrop installeren", "install_title": "PairDrop installeren",
"theme-dark_title": "Altijd donkere modus gebruiken", "theme-dark_title": "Altijd donkere modus gebruiken",
@ -70,12 +70,12 @@
}, },
"instructions": { "instructions": {
"x-instructions_mobile": "Tik om bestanden te versturen of houdt vast om een bericht te sturen", "x-instructions_mobile": "Tik om bestanden te versturen of houdt vast om een bericht te sturen",
"click-to-send": "Klik om te verzenden", "x-instructions-share-mode_desktop": "Klik om te verzenden",
"activate-paste-mode-and-other-files": "en {{count}} andere bestanden", "activate-share-mode-and-other-files-plural": "en {{count}} andere bestanden",
"tap-to-send": "Tik om te verzenden", "x-instructions-share-mode_mobile": "Tik om te verzenden",
"activate-paste-mode-base": "Open PairDrop op andere apparaten om te verzenden", "activate-share-mode-base": "Open PairDrop op andere apparaten om te verzenden",
"no-peers-subtitle": "Koppel apparaten of betreed een openbare ruimte om op andere netwerken zichtbaar te worden", "no-peers-subtitle": "Koppel apparaten of betreed een openbare ruimte om op andere netwerken zichtbaar te worden",
"activate-paste-mode-shared-text": "gedeelde tekst", "activate-share-mode-shared-text": "gedeelde tekst",
"x-instructions_desktop": "Klik om bestanden te versturen of rechtsklik om een bericht te sturen", "x-instructions_desktop": "Klik om bestanden te versturen of rechtsklik om een bericht te sturen",
"no-peers-title": "Open PairDrop op andere apparaten om bestanden te versturen", "no-peers-title": "Open PairDrop op andere apparaten om bestanden te versturen",
"x-instructions_data-drop-peer": "Laat los om naar peer te versturen", "x-instructions_data-drop-peer": "Laat los om naar peer te versturen",
@ -84,7 +84,7 @@
}, },
"peer-ui": { "peer-ui": {
"processing": "Verwerken…", "processing": "Verwerken…",
"click-to-send-paste-mode": "Klik om {{descriptor}} te versturen", "click-to-send-share-mode": "Klik om {{descriptor}} te versturen",
"click-to-send": "Klik om bestanden te versturen of rechtsklik om een bericht te versturen", "click-to-send": "Klik om bestanden te versturen of rechtsklik om een bericht te versturen",
"waiting": "Wachten…", "waiting": "Wachten…",
"connection-hash": "Vergelijk dit veiligheidsnummer op beide apparaten, om de beveiliging van de eind-tot-eind versleuteling te verifiëren", "connection-hash": "Vergelijk dit veiligheidsnummer op beide apparaten, om de beveiliging van de eind-tot-eind versleuteling te verifiëren",

View file

@ -58,7 +58,7 @@
"room-url-copied-to-clipboard": "Link către sala publică copiat în clipboard" "room-url-copied-to-clipboard": "Link către sala publică copiat în clipboard"
}, },
"header": { "header": {
"cancel-paste-mode": "Gata", "cancel-share-mode": "Gata",
"theme-auto_title": "Adaptează tema la sistem", "theme-auto_title": "Adaptează tema la sistem",
"install_title": "Instalează PairDrop", "install_title": "Instalează PairDrop",
"theme-dark_title": "Utilizați mereu tema întunecoasă", "theme-dark_title": "Utilizați mereu tema întunecoasă",
@ -73,12 +73,12 @@
}, },
"instructions": { "instructions": {
"x-instructions_mobile": "Atingeți pentru a trimite fișiere sau atingeți lung pentru a trimite un mesaj", "x-instructions_mobile": "Atingeți pentru a trimite fișiere sau atingeți lung pentru a trimite un mesaj",
"click-to-send": "Clic pentru a trimite", "x-instructions-share-mode_desktop": "Clic pentru a trimite",
"activate-paste-mode-and-other-files": "și {{count}} alte fișiere", "activate-share-mode-and-other-files-plural": "și {{count}} alte fișiere",
"tap-to-send": "Atinge pentru a trimite", "x-instructions-share-mode_mobile": "Atinge pentru a trimite",
"activate-paste-mode-base": "Deschideți PairDrop pe alte dispozitive pentru a trimite", "activate-share-mode-base": "Deschideți PairDrop pe alte dispozitive pentru a trimite",
"no-peers-subtitle": "Împerecheați dispozitive sau intrați într-o cameră publică pentru a fi descoperit în alte rețele", "no-peers-subtitle": "Împerecheați dispozitive sau intrați într-o cameră publică pentru a fi descoperit în alte rețele",
"activate-paste-mode-shared-text": "text partajat", "activate-share-mode-shared-text": "text partajat",
"x-instructions_desktop": "Dați clic pentru a trimite fișiere sau dați clic dreapta pentru a trimite un mesaj", "x-instructions_desktop": "Dați clic pentru a trimite fișiere sau dați clic dreapta pentru a trimite un mesaj",
"no-peers-title": "Deschideți PairDrop pe alte dispozitive pentru a trimite fișiere", "no-peers-title": "Deschideți PairDrop pe alte dispozitive pentru a trimite fișiere",
"x-instructions_data-drop-peer": "Eliberare pentru a trimite la peer", "x-instructions_data-drop-peer": "Eliberare pentru a trimite la peer",
@ -87,7 +87,7 @@
}, },
"peer-ui": { "peer-ui": {
"processing": "Procesarea…", "processing": "Procesarea…",
"click-to-send-paste-mode": "Apasă pentru a trimite {{descriptor}}", "click-to-send-share-mode": "Apasă pentru a trimite {{descriptor}}",
"click-to-send": "Apasă pentru a trimite fișiere sau apasă cu butonul din dreapta pentru a trimite un mesaj", "click-to-send": "Apasă pentru a trimite fișiere sau apasă cu butonul din dreapta pentru a trimite un mesaj",
"waiting": "Așteptând…", "waiting": "Așteptând…",
"connection-hash": "Pentru a verifica securitatea criptării end-to-end, comparați acest număr de securitate pe ambele dispozitive", "connection-hash": "Pentru a verifica securitatea criptării end-to-end, comparați acest număr de securitate pe ambele dispozitive",

View file

@ -3,7 +3,7 @@
"about_aria-label": "Открыть страницу \"О сервисе\"", "about_aria-label": "Открыть страницу \"О сервисе\"",
"pair-device_title": "Связать ваши устройства навсегда", "pair-device_title": "Связать ваши устройства навсегда",
"install_title": "Установить PairDrop", "install_title": "Установить PairDrop",
"cancel-paste-mode": "Выполнено", "cancel-share-mode": "Выполнено",
"edit-paired-devices_title": "Редактировать связанные устройства", "edit-paired-devices_title": "Редактировать связанные устройства",
"notification_title": "Включить уведомления", "notification_title": "Включить уведомления",
"about_title": "О сервисе", "about_title": "О сервисе",
@ -16,16 +16,16 @@
"instructions": { "instructions": {
"x-instructions_desktop": "Нажмите, чтобы отправить файлы, или щелкните правой кнопкой мыши, чтобы отправить сообщение", "x-instructions_desktop": "Нажмите, чтобы отправить файлы, или щелкните правой кнопкой мыши, чтобы отправить сообщение",
"no-peers_data-drop-bg": "Отпустите, чтобы выбрать получателя", "no-peers_data-drop-bg": "Отпустите, чтобы выбрать получателя",
"click-to-send": "Нажмите, чтобы отправить", "x-instructions-share-mode_desktop": "Нажмите, чтобы отправить",
"x-instructions_data-drop-bg": "Отпустите, чтобы выбрать получателя", "x-instructions_data-drop-bg": "Отпустите, чтобы выбрать получателя",
"tap-to-send": "Прикоснитесь, чтобы отправить", "x-instructions-share-mode_mobile": "Прикоснитесь, чтобы отправить",
"x-instructions_data-drop-peer": "Отпустите, чтобы послать узлу", "x-instructions_data-drop-peer": "Отпустите, чтобы послать узлу",
"x-instructions_mobile": "Прикоснитесь коротко, чтобы отправить файлы, или долго, чтобы отправить сообщение", "x-instructions_mobile": "Прикоснитесь коротко, чтобы отправить файлы, или долго, чтобы отправить сообщение",
"no-peers-title": "Откройте PairDrop на других устройствах, чтобы отправить файлы", "no-peers-title": "Откройте PairDrop на других устройствах, чтобы отправить файлы",
"no-peers-subtitle": "Свяжите устройства или войдите в публичную комнату, чтобы вас могли обнаружить из других сетей", "no-peers-subtitle": "Свяжите устройства или войдите в публичную комнату, чтобы вас могли обнаружить из других сетей",
"activate-paste-mode-and-other-files": "и {{count}} других файлов", "activate-share-mode-and-other-files-plural": "и {{count}} других файлов",
"activate-paste-mode-base": "Откройте PairDrop на других устройствах, чтобы отправить", "activate-share-mode-base": "Откройте PairDrop на других устройствах, чтобы отправить",
"activate-paste-mode-shared-text": "общий текст" "activate-share-mode-shared-text": "общий текст"
}, },
"footer": { "footer": {
"display-name_data-placeholder": "Загрузка…", "display-name_data-placeholder": "Загрузка…",
@ -148,7 +148,7 @@
"room-url-copied-to-clipboard": "Ссылка на публичную комнату была скопирована в буфер обмена" "room-url-copied-to-clipboard": "Ссылка на публичную комнату была скопирована в буфер обмена"
}, },
"peer-ui": { "peer-ui": {
"click-to-send-paste-mode": "Нажмите, чтобы отправить {{descriptor}}", "click-to-send-share-mode": "Нажмите, чтобы отправить {{descriptor}}",
"preparing": "Подготовка…", "preparing": "Подготовка…",
"transferring": "Передача…", "transferring": "Передача…",
"processing": "Обработка…", "processing": "Обработка…",

View file

@ -9,19 +9,19 @@
"install_title": "PairDrop'u Yükle", "install_title": "PairDrop'u Yükle",
"pair-device_title": "Cihazı kalıcı olarak eşle", "pair-device_title": "Cihazı kalıcı olarak eşle",
"edit-paired-devices_title": "Eşleştirilmiş cihazları düzenle", "edit-paired-devices_title": "Eşleştirilmiş cihazları düzenle",
"cancel-paste-mode": "Bitti", "cancel-share-mode": "Bitti",
"join-public-room_title": "Geçici olarak genel odaya katılın", "join-public-room_title": "Geçici olarak genel odaya katılın",
"language-selector_title": "Dili Seç" "language-selector_title": "Dili Seç"
}, },
"instructions": { "instructions": {
"no-peers_data-drop-bg": "Alıcıyı seçmek için bırakın", "no-peers_data-drop-bg": "Alıcıyı seçmek için bırakın",
"x-instructions_mobile": "Dosya göndermek için dokun veya mesaj göndermek için uzun dokun", "x-instructions_mobile": "Dosya göndermek için dokun veya mesaj göndermek için uzun dokun",
"click-to-send": "Göndermek için tıkla", "x-instructions-share-mode_desktop": "Göndermek için tıkla",
"activate-paste-mode-and-other-files": "ve {{count}} diğer dosya", "activate-share-mode-and-other-files-plural": "ve {{count}} diğer dosya",
"tap-to-send": "Göndermek için dokun", "x-instructions-share-mode_mobile": "Göndermek için dokun",
"activate-paste-mode-base": "Göndermek için diğer cihazlarda PairDrop'u açın", "activate-share-mode-base": "Göndermek için diğer cihazlarda PairDrop'u açın",
"no-peers-subtitle": "Diğer ağlarda keşfedilebilir olmak için cihazları eşleştirin veya ortak bir odaya girin", "no-peers-subtitle": "Diğer ağlarda keşfedilebilir olmak için cihazları eşleştirin veya ortak bir odaya girin",
"activate-paste-mode-shared-text": "paylaşılan metin", "activate-share-mode-shared-text": "paylaşılan metin",
"x-instructions_desktop": "Dosya göndermek için tıkla ya da mesaj göndermek için sağ tıkla", "x-instructions_desktop": "Dosya göndermek için tıkla ya da mesaj göndermek için sağ tıkla",
"no-peers-title": "Dosya göndermek için diğer cihazlarda PairDrop'u açın", "no-peers-title": "Dosya göndermek için diğer cihazlarda PairDrop'u açın",
"x-instructions_data-drop-peer": "Göndermek için serbest bırak", "x-instructions_data-drop-peer": "Göndermek için serbest bırak",
@ -139,7 +139,7 @@
}, },
"peer-ui": { "peer-ui": {
"processing": "İşleniyor…", "processing": "İşleniyor…",
"click-to-send-paste-mode": "{{descriptor}} göndermek için tıkla", "click-to-send-share-mode": "{{descriptor}} göndermek için tıkla",
"click-to-send": "Dosya göndermek için tıkla veya mesaj göndermek için sağ tıkla", "click-to-send": "Dosya göndermek için tıkla veya mesaj göndermek için sağ tıkla",
"waiting": "Bekleniyor…", "waiting": "Bekleniyor…",
"connection-hash": "Uçtan uca şifrelemenin güvenliğini doğrulamak için her iki cihazda da bu güvenlik numarasını karşılaştırın", "connection-hash": "Uçtan uca şifrelemenin güvenliğini doğrulamak için her iki cihazda da bu güvenlik numarasını karşılaştırın",

View file

@ -9,7 +9,7 @@
"theme-dark_title": "总是使用暗黑主题", "theme-dark_title": "总是使用暗黑主题",
"notification_title": "开启通知", "notification_title": "开启通知",
"edit-paired-devices_title": "管理已配对设备", "edit-paired-devices_title": "管理已配对设备",
"cancel-paste-mode": "完成", "cancel-share-mode": "完成",
"join-public-room_title": "暂时加入公共房间", "join-public-room_title": "暂时加入公共房间",
"language-selector_title": "设置语言" "language-selector_title": "设置语言"
}, },
@ -21,11 +21,11 @@
"x-instructions_desktop": "点击以发送文件 或 右键来发送信息", "x-instructions_desktop": "点击以发送文件 或 右键来发送信息",
"x-instructions_mobile": "轻触以发送文件 或 长按来发送信息", "x-instructions_mobile": "轻触以发送文件 或 长按来发送信息",
"x-instructions_data-drop-bg": "释放来选择接收者", "x-instructions_data-drop-bg": "释放来选择接收者",
"click-to-send": "点击发送", "x-instructions-share-mode_desktop": "点击发送",
"tap-to-send": "轻触发送", "x-instructions-share-mode_mobile": "轻触发送",
"activate-paste-mode-base": "在其他设备上打开 PairDrop 来发送", "activate-share-mode-base": "在其他设备上打开 PairDrop 来发送",
"activate-paste-mode-and-other-files": "和 {{count}} 个其他的文件", "activate-share-mode-and-other-files-plural": "和 {{count}} 个其他的文件",
"activate-paste-mode-shared-text": "分享文本" "activate-share-mode-shared-text": "分享文本"
}, },
"footer": { "footer": {
"routed": "途径服务器", "routed": "途径服务器",
@ -155,7 +155,7 @@
"image-transfer-requested": "图片传输请求" "image-transfer-requested": "图片传输请求"
}, },
"peer-ui": { "peer-ui": {
"click-to-send-paste-mode": "点击发送 {{descriptor}}", "click-to-send-share-mode": "点击发送 {{descriptor}}",
"click-to-send": "点击以发送文件 或 右键来发送信息", "click-to-send": "点击以发送文件 或 右键来发送信息",
"connection-hash": "若要验证端到端加密的安全性,请在两个设备上比较此安全编号", "connection-hash": "若要验证端到端加密的安全性,请在两个设备上比较此安全编号",
"preparing": "准备中…", "preparing": "准备中…",

View file

@ -65,6 +65,11 @@
"src": "images/pairdrop_screenshot_mobile_7.png", "src": "images/pairdrop_screenshot_mobile_7.png",
"sizes": "1170x2532", "sizes": "1170x2532",
"type": "image/png" "type": "image/png"
},
{
"src": "images/pairdrop_screenshot_mobile_8.png",
"sizes": "1170x2532",
"type": "image/png"
} }
], ],
"share_target": { "share_target": {
@ -81,201 +86,6 @@
}] }]
} }
}, },
"file_handlers": [
{
"action": "/?file_handler",
"name": "All Files",
"accept": {
"application/cpl+xml": [".cpl"],
"application/gpx+xml": [".gpx"],
"application/gzip": [".gz"],
"application/java-archive": [".jar", ".war", ".ear"],
"application/java-vm": [".class"],
"application/javascript": [".js", ".mjs"],
"application/json": [".json", ".map"],
"application/manifest+json": [".webmanifest"],
"application/msword": [".doc", ".dot", ".wiz"],
"application/octet-stream": [".bin", ".dms", ".lrf", ".mar", ".so", ".dist", ".distz", ".pkg", ".bpk", ".dump", ".elc", ".deploy", ".exe", ".dll", ".deb", ".dmg", ".iso", ".img", ".msi", ".msp", ".msm", ".buffer"],
"application/oda": [".oda"],
"application/oxps": [".oxps"],
"application/pdf": [".pdf"],
"application/pgp-signature": [".asc", ".sig"],
"application/pics-rules": [".prf"],
"application/pkcs7-mime": [".p7c"],
"application/pkix-cert": [".cer"],
"application/postscript": [".ai", ".eps", ".ps"],
"application/rtf": [".rtf"],
"application/vnd.android.package-archive": [".apk"],
"application/vnd.apple.mpegurl": [".m3u", ".m3u8"],
"application/vnd.apple.pkpass": [".pkpass"],
"application/vnd.google-earth.kml+xml": [".kml"],
"application/vnd.google-earth.kmz": [".kmz"],
"application/vnd.ms-cab-compressed": [".cab"],
"application/vnd.ms-excel": [".xls", ".xlm", ".xla", ".xlc", ".xlt", ".xlw"],
"application/vnd.ms-outlook": [".msg"],
"application/vnd.ms-powerpoint": [".ppt", ".pot", ".ppa", ".pps", ".pwz"],
"application/vnd.ms-project": [".mpp", ".mpt"],
"application/vnd.ms-xpsdocument": [".xps"],
"application/vnd.oasis.opendocument.database": [".odb"],
"application/vnd.oasis.opendocument.spreadsheet": [".ods"],
"application/vnd.oasis.opendocument.text": [".odt"],
"application/vnd.openstreetmap.data+xml": [".osm"],
"application/vnd.openxmlformats-officedocument.presentationml.presentation": [".pptx"],
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": [".xlsx"],
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": [".docx"],
"application/vnd.tcpdump.pcap": [".pcap", ".cap", ".dmp"],
"application/vnd.wordperfect": [".wpd"],
"application/wasm": [".wasm"],
"application/x-7z-compressed": [".7z"],
"application/x-apple-diskimage": [".dmg"],
"application/x-bcpio": [".bcpio"],
"application/x-bittorrent": [".torrent"],
"application/x-cbr": [".cbr", ".cba", ".cbt", ".cbz", ".cb7"],
"application/x-cdlink": [".vcd"],
"application/x-chrome-extension": [".crx"],
"application/x-cpio": [".cpio"],
"application/x-csh": [".csh"],
"application/x-debian-package": [".deb", ".udeb"],
"application/x-dvi": [".dvi"],
"application/x-freearc": [".arc"],
"application/x-gtar": [".gtar"],
"application/x-hdf": [".hdf"],
"application/x-hdf5": [".h5"],
"application/x-httpd-php": [".php"],
"application/x-iso9660-image": [".iso"],
"application/x-iwork-keynote-sffkey": [".key"],
"application/x-iwork-numbers-sffnumbers": [".numbers"],
"application/x-iwork-pages-sffpages": [".pages"],
"application/x-latex": [".latex"],
"application/x-makeself": [".run"],
"application/x-mif": [".mif"],
"application/x-ms-shortcut": [".lnk"],
"application/x-msaccess": [".mdb"],
"application/x-msdownload": [".exe", ".dll", ".com", ".bat", ".msi"],
"application/x-mspublisher": [".pub"],
"application/x-netcdf": [".cdf", ".nc"],
"application/x-perl": [".pl", ".pm"],
"application/x-pilot": [".prc", ".pdb"],
"application/x-pkcs12": [".p12", ".pfx"],
"application/x-pn-realaudio": [".ram"],
"application/x-python-code": [".pyc", ".pyo"],
"application/x-rar-compressed": [".rar"],
"application/x-redhat-package-manager": [".rpm"],
"application/x-sh": [".sh"],
"application/x-shar": [".shar"],
"application/x-shockwave-flash": [".swf"],
"application/x-sql": [".sql"],
"application/x-subrip": [".srt"],
"application/x-sv4cpio": [".sv4cpio"],
"application/x-sv4crc": [".sv4crc"],
"application/x-tads": [".gam"],
"application/x-tar": [".tar"],
"application/x-tcl": [".tcl"],
"application/x-tex": [".tex"],
"application/x-troff": [".roff", ".t", ".tr"],
"application/x-troff-man": [".man"],
"application/x-troff-me": [".me"],
"application/x-troff-ms": [".ms"],
"application/x-ustar": [".ustar"],
"application/x-wais-source": [".src"],
"application/x-xpinstall": [".xpi"],
"application/xhtml+xml": [".xhtml", ".xht"],
"application/xml": [".xsl", ".rdf", ".wsdl", ".xpdl"],
"application/zip": [".zip"],
"audio/3gpp": [".3gp", ".3gpp"],
"audio/3gpp2": [".3g2", ".3gpp2"],
"audio/aac": [".aac", ".adts", ".loas", ".ass"],
"audio/basic": [".au", ".snd"],
"audio/midi": [".mid", ".midi", ".kar", ".rmi"],
"audio/mpeg": [".mpga", ".mp2", ".mp2a", ".mp3", ".m2a", ".m3a"],
"audio/ogg": [".oga", ".ogg", ".spx", ".opus"],
"audio/opus": [".opus"],
"audio/x-aiff": [".aif", ".aifc", ".aiff"],
"audio/x-flac": [".flac"],
"audio/x-m4a": [".m4a"],
"audio/x-mpegurl": [".m3u"],
"audio/x-ms-wma": [".wma"],
"audio/x-pn-realaudio": [".ra"],
"audio/x-wav": [".wav"],
"font/otf": [".otf"],
"font/ttf": [".ttf"],
"font/woff": [".woff"],
"font/woff2": [".woff2"],
"image/emf": [".emf"],
"image/gif": [".gif"],
"image/heic": [".heic"],
"image/heif": [".heif"],
"image/ief": [".ief"],
"image/jpeg": [".jpeg", ".jpg"],
"image/jpg": [".jpg"],
"image/pict": [".pict", ".pct", ".pic"],
"image/png": [".png"],
"image/svg+xml": [".svg", ".svgz"],
"image/tiff": [".tif", ".tiff"],
"image/vnd.adobe.photoshop": [".psd"],
"image/vnd.djvu": [".djvu", ".djv"],
"image/vnd.dwg": [".dwg"],
"image/vnd.dxf": [".dxf"],
"image/vnd.microsoft.icon": [".ico"],
"image/vnd.ms-dds": [".dds"],
"image/x-3ds": [".3ds"],
"image/x-cmu-raster": [".ras"],
"image/x-icon": [".ico"],
"image/x-ms-bmp": [".bmp"],
"image/x-portable-anymap": [".pnm"],
"image/x-portable-bitmap": [".pbm"],
"image/x-portable-graymap": [".pgm"],
"image/x-portable-pixmap": [".ppm"],
"image/x-rgb": [".rgb"],
"image/x-tga": [".tga"],
"image/x-xbitmap": [".xbm"],
"image/x-xpixmap": [".xpm"],
"image/x-xwindowdump": [".xwd"],
"message/rfc822": [".eml", ".mht", ".mhtml", ".nws"],
"model/obj": [".obj"],
"model/stl": [".stl"],
"model/vnd.collada+xml": [".dae"],
"text/calendar": [".ics", ".ifb"],
"text/css": [".css"],
"text/csv": [".csv"],
"text/html": [".html", ".htm", ".shtml"],
"text/markdown": [".markdown", ".md"],
"text/plain": [".txt", ".text", ".conf", ".def", ".list", ".log", ".in", ".ini"],
"text/richtext": [".rtx"],
"text/rtf": [".rtf"],
"text/tab-separated-values": [".tsv"],
"text/x-c": [".c", ".cc", ".cxx", ".cpp", ".h", ".hh", ".dic"],
"text/x-java-source": [".java"],
"text/x-lua": [".lua"],
"text/x-python": [".py"],
"text/x-setext": [".etx"],
"text/x-sgml": [".sgm", ".sgml"],
"text/x-vcard": [".vcf"],
"text/xml": [".xml"],
"text/xul": [".xul"],
"text/yaml": [".yaml", ".yml"],
"video/3gpp": [".3gp", ".3gpp"],
"video/mp2t": [".ts"],
"video/mp4": [".mp4", ".mp4v", ".mpg4"],
"video/mpeg": [".mpeg", ".m1v", ".mpa", ".mpe", ".mpg"],
"video/quicktime": [".mov", ".qt"],
"video/webm": [".webm"],
"video/x-flv": [".flv"],
"video/x-m4v": [".m4v"],
"video/x-ms-asf": [".asf", ".asx"],
"video/x-ms-vob": [".vob"],
"video/x-ms-wmv": [".wmv"],
"video/x-msvideo": [".avi"],
"video/x-sgi-movie": [".*"]
},
"icons": [
{
"src": "/images/android-chrome-192x192.png",
"sizes": "192x192"
}
]
}
],
"launch_handler": { "launch_handler": {
"client_mode": "focus-existing" "client_mode": "focus-existing"
} }

View file

@ -19,42 +19,42 @@ class BrowserTabsConnector {
} }
static peerIsSameBrowser(peerId) { static peerIsSameBrowser(peerId) {
let peerIdsBrowser = JSON.parse(localStorage.getItem("peer_ids_browser")); let peerIdsBrowser = JSON.parse(localStorage.getItem('peer_ids_browser'));
return peerIdsBrowser return peerIdsBrowser
? peerIdsBrowser.indexOf(peerId) !== -1 ? peerIdsBrowser.indexOf(peerId) !== -1
: false; : false;
} }
static async addPeerIdToLocalStorage() { static async addPeerIdToLocalStorage() {
const peerId = sessionStorage.getItem("peer_id"); const peerId = sessionStorage.getItem('peer_id');
if (!peerId) return false; if (!peerId) return false;
let peerIdsBrowser = []; let peerIdsBrowser = [];
let peerIdsBrowserOld = JSON.parse(localStorage.getItem("peer_ids_browser")); let peerIdsBrowserOld = JSON.parse(localStorage.getItem('peer_ids_browser'));
if (peerIdsBrowserOld) peerIdsBrowser.push(...peerIdsBrowserOld); if (peerIdsBrowserOld) peerIdsBrowser.push(...peerIdsBrowserOld);
peerIdsBrowser.push(peerId); peerIdsBrowser.push(peerId);
peerIdsBrowser = peerIdsBrowser.filter(onlyUnique); peerIdsBrowser = peerIdsBrowser.filter(onlyUnique);
localStorage.setItem("peer_ids_browser", JSON.stringify(peerIdsBrowser)); localStorage.setItem('peer_ids_browser', JSON.stringify(peerIdsBrowser));
return peerIdsBrowser; return peerIdsBrowser;
} }
static async removePeerIdFromLocalStorage(peerId) { static async removePeerIdFromLocalStorage(peerId) {
let peerIdsBrowser = JSON.parse(localStorage.getItem("peer_ids_browser")); let peerIdsBrowser = JSON.parse(localStorage.getItem('peer_ids_browser'));
const index = peerIdsBrowser.indexOf(peerId); const index = peerIdsBrowser.indexOf(peerId);
peerIdsBrowser.splice(index, 1); peerIdsBrowser.splice(index, 1);
localStorage.setItem("peer_ids_browser", JSON.stringify(peerIdsBrowser)); localStorage.setItem('peer_ids_browser', JSON.stringify(peerIdsBrowser));
return peerId; return peerId;
} }
static async removeOtherPeerIdsFromLocalStorage() { static async removeOtherPeerIdsFromLocalStorage() {
const peerId = sessionStorage.getItem("peer_id"); const peerId = sessionStorage.getItem('peer_id');
if (!peerId) return false; if (!peerId) return false;
let peerIdsBrowser = [peerId]; let peerIdsBrowser = [peerId];
localStorage.setItem("peer_ids_browser", JSON.stringify(peerIdsBrowser)); localStorage.setItem('peer_ids_browser', JSON.stringify(peerIdsBrowser));
return peerIdsBrowser; return peerIdsBrowser;
} }
} }

1
public/scripts/heic2any.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -2,33 +2,30 @@ class Localization {
constructor() { constructor() {
Localization.defaultLocale = "en"; Localization.defaultLocale = "en";
Localization.supportedLocales = ["ar", "de", "en", "es", "fr", "id", "it", "ja", "nb", "nl", "ro", "ru", "tr", "zh-CN","pt-BR"]; Localization.supportedLocales = ["ar", "de", "en", "es", "fr", "id", "it", "ja", "nb", "nl", "ro", "ru", "tr", "zh-CN","pt-BR"];
Localization.supportedLocalesRTL = ["ar"]; Localization.supportedLocalesRtl = ["ar"];
Localization.translations = {}; Localization.translations = {};
Localization.defaultTranslations = {}; Localization.defaultTranslations = {};
Localization.systemLocale = Localization.getSupportedOrDefault(navigator.languages); Localization.systemLocale = Localization.getSupportedOrDefault(navigator.languages);
let storedLanguageCode = localStorage.getItem("language-code"); let storedLanguageCode = localStorage.getItem('language_code');
Localization.initialLocale = storedLanguageCode && Localization.isSupported(storedLanguageCode) Localization.initialLocale = storedLanguageCode && Localization.isSupported(storedLanguageCode)
? storedLanguageCode ? storedLanguageCode
: Localization.systemLocale; : Localization.systemLocale;
Localization
.setTranslation(Localization.initialLocale)
.then(_ => {
console.log("Initial translation successful.");
Events.fire("initial-translation-loaded");
});
} }
static isSupported(locale) { static isSupported(locale) {
return Localization.supportedLocales.indexOf(locale) > -1; return Localization.supportedLocales.indexOf(locale) > -1;
} }
static isRTLLanguage(locale) { static isRtlLanguage(locale) {
return Localization.supportedLocalesRTL.indexOf(locale) > -1; return Localization.supportedLocalesRtl.indexOf(locale) > -1;
}
static isCurrentLocaleRtl() {
return Localization.isRtlLanguage(Localization.locale);
} }
static getSupportedOrDefault(locales) { static getSupportedOrDefault(locales) {
@ -41,6 +38,10 @@ class Localization {
|| Localization.defaultLocale; || Localization.defaultLocale;
} }
async setInitialTranslation() {
await Localization.setTranslation(Localization.initialLocale)
}
static async setTranslation(locale) { static async setTranslation(locale) {
if (!locale) locale = Localization.systemLocale; if (!locale) locale = Localization.systemLocale;
@ -49,7 +50,7 @@ class Localization {
const htmlRootNode = document.querySelector('html'); const htmlRootNode = document.querySelector('html');
if (Localization.isRTLLanguage(locale)) { if (Localization.isRtlLanguage(locale)) {
htmlRootNode.setAttribute('dir', 'rtl'); htmlRootNode.setAttribute('dir', 'rtl');
} }
else { else {
@ -85,7 +86,7 @@ class Localization {
} }
static isSystemLocale() { static isSystemLocale() {
return !localStorage.getItem('language-code'); return !localStorage.getItem('language_code');
} }
static async fetchTranslationsFor(newLocale) { static async fetchTranslationsFor(newLocale) {
@ -121,7 +122,7 @@ class Localization {
} }
} }
static getTranslation(key, attr=null, data={}, useDefault=false) { static getTranslation(key, attr = null, data = {}, useDefault = false) {
const keys = key.split("."); const keys = key.split(".");
let translationCandidates = useDefault let translationCandidates = useDefault
@ -142,27 +143,45 @@ class Localization {
translation = translationCandidates[lastKey]; translation = translationCandidates[lastKey];
for (let j in data) { for (let j in data) {
translation = translation.replace(`{{${j}}}`, data[j]); if (translation.includes(`{{${j}}}`)) {
translation = translation.replace(`{{${j}}}`, data[j]);
} else {
console.warn(`Translation for your language ${Localization.locale.toUpperCase()} misses at least one data placeholder:`, key, attr, data);
Localization.logHelpCallKey(key);
Localization.logHelpCall();
translation = "";
break;
}
} }
} catch (e) { } catch (e) {
console.error(e);
translation = ""; translation = "";
} }
if (!translation) { if (!translation) {
if (!useDefault) { if (!useDefault) {
translation = this.getTranslation(key, attr, data, true);
console.warn(`Missing translation entry for your language ${Localization.locale.toUpperCase()}. Using ${Localization.defaultLocale.toUpperCase()} instead.`, key, attr); console.warn(`Missing translation entry for your language ${Localization.locale.toUpperCase()}. Using ${Localization.defaultLocale.toUpperCase()} instead.`, key, attr);
console.warn(`Translate this string here: https://hosted.weblate.org/browse/pairdrop/pairdrop-spa/${Localization.locale.toLowerCase()}/?q=${key}`) Localization.logHelpCallKey(key);
console.log("Help translating PairDrop: https://hosted.weblate.org/engage/pairdrop/"); Localization.logHelpCall();
translation = this.getTranslation(key, attr, data, true);
} }
else { else {
console.warn("Missing translation in default language:", key, attr); console.warn("Missing translation in default language:", key, attr);
Localization.logHelpCall();
} }
} }
return Localization.escapeHTML(translation); return Localization.escapeHTML(translation);
} }
static logHelpCall() {
console.log("Help translating PairDrop: https://hosted.weblate.org/engage/pairdrop/");
}
static logHelpCallKey(key) {
console.warn(`Translate this string here: https://hosted.weblate.org/browse/pairdrop/pairdrop-spa/${Localization.locale.toLowerCase()}/?q=${key}`);
}
static escapeHTML(unsafeText) { static escapeHTML(unsafeText) {
let div = document.createElement('div'); let div = document.createElement('div');
div.innerText = unsafeText; div.innerText = unsafeText;

View file

@ -1,39 +1,69 @@
class PairDrop { class PairDrop {
constructor() { constructor() {
this.$header = $$('header.opacity-0'); this.$headerNotificationBtn = $('notification');
this.$center = $$('#center'); this.$headerEditPairedDevicesBtn = $('edit-paired-devices');
this.$footer = $$('footer'); this.$footerPairedDevicesBadge = $$('.discovery-wrapper .badge-room-secret');
this.$xNoPeers = $$('x-no-peers'); this.$headerInstallBtn = $('install');
this.$headerNotificationButton = $('notification');
this.$editPairedDevicesHeaderBtn = $('edit-paired-devices'); this.deferredStyles = [
this.$footerInstructionsPairedDevices = $$('.discovery-wrapper .badge-room-secret'); "styles/deferred-styles.css"
this.$head = $$('head'); ];
this.$installBtn = $('install'); this.deferredScripts = [
"scripts/browser-tabs-connector.js",
"scripts/util.js",
"scripts/network.js",
"scripts/ui.js",
"scripts/qr-code.min.js",
"scripts/zip.min.js",
"scripts/no-sleep.min.js",
"scripts/heic2any.min.js"
];
this.registerServiceWorker(); this.registerServiceWorker();
Events.on('beforeinstallprompt', e => this.onPwaInstallable(e)); Events.on('beforeinstallprompt', e => this.onPwaInstallable(e));
const persistentStorage = new PersistentStorage(); this.persistentStorage = new PersistentStorage();
const themeUI = new ThemeUI(); this.localization = new Localization();
const backgroundCanvas = new BackgroundCanvas(); this.themeUI = new ThemeUI();
this.backgroundCanvas = new BackgroundCanvas();
this.headerUI = new HeaderUI();
this.centerUI = new CenterUI();
this.footerUI = new FooterUI();
Events.on('initial-translation-loaded', _ => { this.initialize()
// FooterUI needs translations .then(_ => {
const footerUI = new FooterUI(); console.log("Initialization completed.");
});
}
Events.on('fade-in-ui', _ => this.fadeInUI()) async initialize() {
Events.on('fade-in-header', _ => this.fadeInHeader()) // Translate page before fading in
await this.localization.setInitialTranslation()
console.log("Initial translation successful.");
// Evaluate UI elements and fade in UI // Show "Loading..." until connected to WsServer
this.evaluateUI(); await this.footerUI.showLoading();
// Load deferred assets // Evaluate css shifting UI elements and fade in UI elements
this.loadDeferredAssets(); await this.evaluatePermissionsAndRoomSecrets();
}); await this.headerUI.evaluateOverflowing();
await this.headerUI.fadeIn();
await this.footerUI._evaluateFooterBadges();
await this.footerUI.fadeIn();
await this.centerUI.fadeIn();
await this.backgroundCanvas.fadeIn();
// Translate page -> fires 'initial-translation-loaded' on finish // Load deferred assets
const localization = new Localization(); await this.loadDeferredAssets();
console.log("Loading of deferred assets completed.");
await this.hydrate();
console.log("UI hydrated.");
// Evaluate url params as soon as ws is connected
Events.on('ws-connected', _ => this.evaluateUrlParams(), {once: true});
} }
registerServiceWorker() { registerServiceWorker() {
@ -50,130 +80,148 @@ class PairDrop {
onPwaInstallable(e) { onPwaInstallable(e) {
if (!window.matchMedia('(display-mode: minimal-ui)').matches) { if (!window.matchMedia('(display-mode: minimal-ui)').matches) {
// only display install btn when not installed // only display install btn when not installed
this.$installBtn.removeAttribute('hidden'); this.$headerInstallBtn.removeAttribute('hidden');
this.$installBtn.addEventListener('click', () => { this.$headerInstallBtn.addEventListener('click', () => {
this.$installBtn.setAttribute('hidden', true); this.$headerInstallBtn.setAttribute('hidden', true);
e.prompt(); e.prompt();
}); });
} }
return e.preventDefault(); return e.preventDefault();
} }
evaluateUI() { async evaluatePermissionsAndRoomSecrets() {
// Check whether notification permissions have already been granted // Check whether notification permissions have already been granted
if ('Notification' in window && Notification.permission !== 'granted') { if ('Notification' in window && Notification.permission !== 'granted') {
this.$headerNotificationButton.removeAttribute('hidden'); this.$headerNotificationBtn.removeAttribute('hidden');
} }
PersistentStorage let roomSecrets = await PersistentStorage.getAllRoomSecrets();
.getAllRoomSecrets() if (roomSecrets.length > 0) {
.then(roomSecrets => { this.$headerEditPairedDevicesBtn.removeAttribute('hidden');
if (roomSecrets.length > 0) { this.$footerPairedDevicesBadge.removeAttribute('hidden');
this.$editPairedDevicesHeaderBtn.removeAttribute('hidden'); }
this.$footerInstructionsPairedDevices.removeAttribute('hidden');
}
})
.finally(() => {
Events.fire('evaluate-footer-badges');
Events.fire('fade-in-header');
});
} }
fadeInUI() { async loadDeferredAssets() {
this.$center.classList.remove('opacity-0');
this.$footer.classList.remove('opacity-0');
// Prevent flickering on load
setTimeout(() => {
this.$xNoPeers.classList.remove('no-animation-on-load');
}, 600);
}
fadeInHeader() {
this.$header.classList.remove('opacity-0');
}
loadDeferredAssets() {
console.log("Load deferred assets"); console.log("Load deferred assets");
this.deferredStyles = [ for (const url of this.deferredStyles) {
"styles/deferred-styles.css" await this.loadAndApplyStylesheet(url);
];
this.deferredScripts = [
"scripts/browser-tabs-connector.js",
"scripts/util.js",
"scripts/network.js",
"scripts/ui.js",
"scripts/qr-code.min.js",
"scripts/zip.min.js",
"scripts/no-sleep.min.js"
];
this.deferredStyles.forEach(url => this.loadStyleSheet(url, _ => this.onStyleLoaded(url)))
this.deferredScripts.forEach(url => this.loadScript(url, _ => this.onScriptLoaded(url)))
}
loadStyleSheet(url, callback) {
let stylesheet = document.createElement('link');
stylesheet.rel = 'stylesheet';
stylesheet.href = url;
stylesheet.type = 'text/css';
stylesheet.onload = callback;
this.$head.appendChild(stylesheet);
}
loadScript(url, callback) {
let script = document.createElement("script");
script.src = url;
script.onload = callback;
document.body.appendChild(script);
}
onStyleLoaded(url) {
// remove entry from array
const index = this.deferredStyles.indexOf(url);
if (index !== -1) {
this.deferredStyles.splice(index, 1);
} }
this.onAssetLoaded(); for (const url of this.deferredScripts) {
} await this.loadAndApplyScript(url);
onScriptLoaded(url) {
// remove entry from array
const index = this.deferredScripts.indexOf(url);
if (index !== -1) {
this.deferredScripts.splice(index, 1);
} }
this.onAssetLoaded();
} }
onAssetLoaded() { loadStyleSheet(url) {
if (this.deferredScripts.length || this.deferredStyles.length) return; return new Promise((resolve, reject) => {
let stylesheet = document.createElement('link');
stylesheet.rel = 'stylesheet';
stylesheet.href = url;
stylesheet.type = 'text/css';
stylesheet.onload = resolve;
stylesheet.onerror = reject;
console.log("Loading of deferred assets completed. Start UI hydration."); document.head.appendChild(stylesheet);
});
this.hydrate();
} }
hydrate() { async loadAndApplyStylesheet(url) {
const peersUI = new PeersUI(); try {
const languageSelectDialog = new LanguageSelectDialog(); await this.loadStyleSheet(url);
const receiveFileDialog = new ReceiveFileDialog(); console.log(`Stylesheet loaded successfully: ${url}`);
const receiveRequestDialog = new ReceiveRequestDialog(); } catch (error) {
const sendTextDialog = new SendTextDialog(); console.error('Error loading stylesheet:', error);
const receiveTextDialog = new ReceiveTextDialog(); }
const pairDeviceDialog = new PairDeviceDialog(); }
const clearDevicesDialog = new EditPairedDevicesDialog();
const publicRoomDialog = new PublicRoomDialog(); loadScript(url) {
const base64ZipDialog = new Base64ZipDialog(); return new Promise((resolve, reject) => {
const toast = new Toast(); let script = document.createElement("script");
const notifications = new Notifications(); script.src = url;
const networkStatusUI = new NetworkStatusUI(); script.onload = resolve;
const webShareTargetUI = new WebShareTargetUI(); script.onerror = reject;
const webFileHandlersUI = new WebFileHandlersUI();
const noSleepUI = new NoSleepUI(); document.body.appendChild(script);
const broadCast = new BrowserTabsConnector(); });
const server = new ServerConnection(); }
const peers = new PeersManager(server);
console.log("UI hydrated.") async loadAndApplyScript(url) {
try {
await this.loadScript(url);
console.log(`Script loaded successfully: ${url}`);
} catch (error) {
console.error('Error loading script:', error);
}
}
async hydrate() {
this.peersUI = new PeersUI();
this.languageSelectDialog = new LanguageSelectDialog();
this.receiveFileDialog = new ReceiveFileDialog();
this.receiveRequestDialog = new ReceiveRequestDialog();
this.sendTextDialog = new SendTextDialog();
this.receiveTextDialog = new ReceiveTextDialog();
this.pairDeviceDialog = new PairDeviceDialog();
this.clearDevicesDialog = new EditPairedDevicesDialog();
this.publicRoomDialog = new PublicRoomDialog();
this.base64Dialog = new Base64Dialog();
this.shareTextDialog = new ShareTextDialog();
this.toast = new Toast();
this.notifications = new Notifications();
this.networkStatusUI = new NetworkStatusUI();
this.webShareTargetUI = new WebShareTargetUI();
this.webFileHandlersUI = new WebFileHandlersUI();
this.noSleepUI = new NoSleepUI();
this.broadCast = new BrowserTabsConnector();
this.server = new ServerConnection();
this.peers = new PeersManager(this.server);
}
async evaluateUrlParams() {
// get url params
const urlParams = new URLSearchParams(window.location.search);
const hash = window.location.hash.substring(1);
// evaluate url params
if (urlParams.has('pair_key')) {
const pairKey = urlParams.get('pair_key');
this.pairDeviceDialog._pairDeviceJoin(pairKey);
}
else if (urlParams.has('room_id')) {
const roomId = urlParams.get('room_id');
this.publicRoomDialog._joinPublicRoom(roomId);
}
else if (urlParams.has('base64text')) {
const base64Text = urlParams.get('base64text');
await this.base64Dialog.evaluateBase64Text(base64Text, hash);
}
else if (urlParams.has('base64zip')) {
const base64Zip = urlParams.get('base64zip');
await this.base64Dialog.evaluateBase64Zip(base64Zip, hash);
}
else if (urlParams.has("share_target")) {
const shareTargetType = urlParams.get("share_target");
const title = urlParams.get('title') || '';
const text = urlParams.get('text') || '';
const url = urlParams.get('url') || '';
await this.webShareTargetUI.evaluateShareTarget(shareTargetType, title, text, url);
}
else if (urlParams.has("file_handler")) {
await this.webFileHandlersUI.evaluateLaunchQueue();
}
else if (urlParams.has("init")) {
const init = urlParams.get("init");
if (init === "pair") {
this.pairDeviceDialog._pairDeviceInitiate();
}
else if (init === "public_room") {
this.publicRoomDialog._createPublicRoom();
}
}
// remove url params from url
const urlWithoutParams = getUrlWithoutArguments();
window.history.replaceState({}, "Rewrite URL", urlWithoutParams);
} }
} }

View file

@ -433,47 +433,6 @@ class Peer {
: false; : false;
} }
getResizedImageDataUrl(file, width = undefined, height = undefined, quality = 0.7) {
return new Promise((resolve, reject) => {
let image = new Image();
image.src = URL.createObjectURL(file);
image.onload = _ => {
let imageWidth = image.width;
let imageHeight = image.height;
let canvas = document.createElement('canvas');
// resize the canvas and draw the image data into it
if (width && height) {
canvas.width = width;
canvas.height = height;
}
else if (width) {
canvas.width = width;
canvas.height = Math.floor(imageHeight * width / imageWidth)
}
else if (height) {
canvas.width = Math.floor(imageWidth * height / imageHeight);
canvas.height = height;
}
else {
canvas.width = imageWidth;
canvas.height = imageHeight
}
var ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
let dataUrl = canvas.toDataURL("image/jpeg", quality);
resolve(dataUrl);
}
image.onerror = _ => reject(`Could not create an image thumbnail from type ${file.type}`);
})
.then(dataUrl => {
return dataUrl;
})
.catch(e => console.error(e));
}
async requestFileTransfer(files) { async requestFileTransfer(files) {
let header = []; let header = [];
let totalSize = 0; let totalSize = 0;
@ -493,7 +452,11 @@ class Peer {
let dataUrl = ''; let dataUrl = '';
if (files[0].type.split('/')[0] === 'image') { if (files[0].type.split('/')[0] === 'image') {
dataUrl = await this.getResizedImageDataUrl(files[0], 400, null, 0.9); try {
dataUrl = await getThumbnailAsDataUrl(files[0], 400, null, 0.9);
} catch (e) {
console.error(e);
}
} }
Events.fire('set-progress', {peerId: this._peerId, progress: 1, status: 'prepare'}) Events.fire('set-progress', {peerId: this._peerId, progress: 1, status: 'prepare'})
@ -1118,20 +1081,9 @@ class PeersManager {
this.peers[detail.to]._respondToFileTransferRequest(detail.accepted); this.peers[detail.to]._respondToFileTransferRequest(detail.accepted);
} }
_onFilesSelected(message) { async _onFilesSelected(message) {
let inputFiles = Array.from(message.files); let files = await mime.addMissingMimeTypesToFiles(message.files);
delete message.files; await this.peers[message.to].requestFileTransfer(files);
let files = [];
const l = inputFiles.length;
for (let i=0; i<l; i++) {
// when filetype is empty guess via suffix
const inputFile = inputFiles.shift();
const file = inputFile.type
? inputFile
: new File([inputFile], inputFile.name, {type: mime.getMimeByFilename(inputFile.name)});
files.push(file)
}
this.peers[message.to].requestFileTransfer(files);
} }
_onSendText(message) { _onSendText(message) {

View file

@ -4,7 +4,7 @@ class PersistentStorage {
PersistentStorage.logBrowserNotCapable(); PersistentStorage.logBrowserNotCapable();
return; return;
} }
const DBOpenRequest = window.indexedDB.open('pairdrop_store', 4); const DBOpenRequest = window.indexedDB.open('pairdrop_store', 5);
DBOpenRequest.onerror = e => { DBOpenRequest.onerror = e => {
PersistentStorage.logBrowserNotCapable(); PersistentStorage.logBrowserNotCapable();
console.log('Error initializing database: '); console.log('Error initializing database: ');
@ -13,7 +13,7 @@ class PersistentStorage {
DBOpenRequest.onsuccess = _ => { DBOpenRequest.onsuccess = _ => {
console.log('Database initialised.'); console.log('Database initialised.');
}; };
DBOpenRequest.onupgradeneeded = e => { DBOpenRequest.onupgradeneeded = async e => {
const db = e.target.result; const db = e.target.result;
const txn = e.target.transaction; const txn = e.target.transaction;
@ -42,6 +42,14 @@ class PersistentStorage {
roomSecretsObjectStore4.createIndex('display_name', 'display_name'); roomSecretsObjectStore4.createIndex('display_name', 'display_name');
roomSecretsObjectStore4.createIndex('auto_accept', 'auto_accept'); roomSecretsObjectStore4.createIndex('auto_accept', 'auto_accept');
} }
if (e.oldVersion <= 4) {
// migrate to v5
const editedDisplayNameOld = await PersistentStorage.get('editedDisplayName');
if (editedDisplayNameOld) {
await PersistentStorage.set('edited_display_name', editedDisplayNameOld);
await PersistentStorage.delete('editedDisplayName');
}
}
} }
} }
@ -141,7 +149,7 @@ class PersistentStorage {
return(secrets); return(secrets);
} catch (e) { } catch (e) {
this.logBrowserNotCapable(); this.logBrowserNotCapable();
return 0; return [];
} }
} }

View file

@ -111,15 +111,91 @@ class ThemeUI {
} }
} }
class HeaderUI {
constructor() {
this.$header = $$('header');
this.$expandBtn = $('expand');
Events.on("resize", _ => this.evaluateOverflowing());
this.$expandBtn.addEventListener('click', _ => this.onExpandBtnClick());
}
async fadeIn() {
this.$header.classList.remove('opacity-0');
}
async evaluateOverflowing() {
// remove and reset bracket icon before evaluating
this.$expandBtn.setAttribute('hidden', true);
this.$expandBtn.classList.add('flipped');
const rtlLocale = Localization.isCurrentLocaleRtl();
let icon;
const $headerIconsShown = document.querySelectorAll('body > header:first-of-type > *:not([hidden])');
for (let i= 1; i < $headerIconsShown.length; i++) {
let isFurtherLeftThanLastIcon = $headerIconsShown[i].offsetLeft >= $headerIconsShown[i-1].offsetLeft;
let isFurtherRightThanLastIcon = $headerIconsShown[i].offsetLeft <= $headerIconsShown[i-1].offsetLeft;
if ((!rtlLocale && isFurtherLeftThanLastIcon) || (rtlLocale && isFurtherRightThanLastIcon)) {
// we have found the first icon on second row. Use previous icon.
icon = $headerIconsShown[i-1];
break;
}
}
if (icon) {
// overflowing
// add overflowing-hidden class
this.$header.classList.add('overflow-hidden');
// add expand btn 2 before icon
this.$expandBtn.removeAttribute('hidden');
icon.before(this.$expandBtn);
}
else {
// no overflowing
// add overflowing-hidden class
this.$header.classList.remove('overflow-hidden');
}
}
onExpandBtnClick() {
// toggle overflowing-hidden class and flip expand btn icon
if (this.$header.classList.contains('overflow-hidden')) {
this.$header.classList.remove('overflow-hidden');
this.$header.classList.add('overflow-expanded');
this.$expandBtn.classList.remove('flipped');
}
else {
this.$header.classList.add('overflow-hidden');
this.$header.classList.remove('overflow-expanded');
this.$expandBtn.classList.add('flipped');
}
}
}
class CenterUI {
constructor() {
this.$center = $$('#center');
this.$xNoPeers = $$('x-no-peers');
}
async fadeIn() {
this.$center.classList.remove('opacity-0');
// Prevent flickering on load
setTimeout(() => {
this.$xNoPeers.classList.remove('no-animation-on-load');
}, 600);
}
}
class FooterUI { class FooterUI {
constructor() { constructor() {
this.$footer = $$('footer');
this.$displayName = $('display-name'); this.$displayName = $('display-name');
this.$discoveryWrapper = $$('footer .discovery-wrapper'); this.$discoveryWrapper = $$('footer .discovery-wrapper');
// Show "Loading…"
this.$displayName.setAttribute('placeholder', this.$displayName.dataset.placeholder);
this.$displayName.addEventListener('keydown', e => this._onKeyDownDisplayName(e)); this.$displayName.addEventListener('keydown', e => this._onKeyDownDisplayName(e));
this.$displayName.addEventListener('keyup', e => this._onKeyUpDisplayName(e)); this.$displayName.addEventListener('keyup', e => this._onKeyUpDisplayName(e));
this.$displayName.addEventListener('blur', e => this._saveDisplayName(e.target.innerText)); this.$displayName.addEventListener('blur', e => this._saveDisplayName(e.target.innerText));
@ -133,7 +209,15 @@ class FooterUI {
Events.on('evaluate-footer-badges', _ => this._evaluateFooterBadges()); Events.on('evaluate-footer-badges', _ => this._evaluateFooterBadges());
} }
_evaluateFooterBadges() { async showLoading() {
this.$displayName.setAttribute('placeholder', this.$displayName.dataset.placeholder);
}
async fadeIn() {
this.$footer.classList.remove('opacity-0');
}
async _evaluateFooterBadges() {
if (this.$discoveryWrapper.querySelectorAll('div:last-of-type > span[hidden]').length < 2) { if (this.$discoveryWrapper.querySelectorAll('div:last-of-type > span[hidden]').length < 2) {
this.$discoveryWrapper.classList.remove('row'); this.$discoveryWrapper.classList.remove('row');
this.$discoveryWrapper.classList.add('column'); this.$discoveryWrapper.classList.add('column');
@ -143,17 +227,15 @@ class FooterUI {
this.$discoveryWrapper.classList.add('row'); this.$discoveryWrapper.classList.add('row');
} }
Events.fire('redraw-canvas'); Events.fire('redraw-canvas');
Events.fire('fade-in-ui');
} }
_loadSavedDisplayName() { async _loadSavedDisplayName() {
this._getSavedDisplayName() const displayName = await this._getSavedDisplayName()
.then(displayName => {
console.log("Retrieved edited display name:", displayName) if (!displayName) return;
if (displayName) {
Events.fire('self-display-name-changed', displayName); console.log("Retrieved edited display name:", displayName)
} Events.fire('self-display-name-changed', displayName);
});
} }
_onDisplayName(displayName){ _onDisplayName(displayName){
@ -184,13 +266,13 @@ class FooterUI {
if (newDisplayName === savedDisplayName) return; if (newDisplayName === savedDisplayName) return;
if (newDisplayName) { if (newDisplayName) {
PersistentStorage.set('editedDisplayName', newDisplayName) PersistentStorage.set('edited_display_name', newDisplayName)
.then(_ => { .then(_ => {
Events.fire('notify-user', Localization.getTranslation("notifications.display-name-changed-permanently")); Events.fire('notify-user', Localization.getTranslation("notifications.display-name-changed-permanently"));
}) })
.catch(_ => { .catch(_ => {
console.log("This browser does not support IndexedDB. Use localStorage instead."); console.log("This browser does not support IndexedDB. Use localStorage instead.");
localStorage.setItem('editedDisplayName', newDisplayName); localStorage.setItem('edited_display_name', newDisplayName);
Events.fire('notify-user', Localization.getTranslation("notifications.display-name-changed-temporarily")); Events.fire('notify-user', Localization.getTranslation("notifications.display-name-changed-temporarily"));
}) })
.finally(() => { .finally(() => {
@ -199,10 +281,10 @@ class FooterUI {
}); });
} }
else { else {
PersistentStorage.delete('editedDisplayName') PersistentStorage.delete('edited_display_name')
.catch(_ => { .catch(_ => {
console.log("This browser does not support IndexedDB. Use localStorage instead.") console.log("This browser does not support IndexedDB. Use localStorage instead.")
localStorage.removeItem('editedDisplayName'); localStorage.removeItem('edited_display_name');
}) })
.finally(() => { .finally(() => {
Events.fire('notify-user', Localization.getTranslation("notifications.display-name-random-again")); Events.fire('notify-user', Localization.getTranslation("notifications.display-name-random-again"));
@ -214,13 +296,13 @@ class FooterUI {
_getSavedDisplayName() { _getSavedDisplayName() {
return new Promise((resolve) => { return new Promise((resolve) => {
PersistentStorage.get('editedDisplayName') PersistentStorage.get('edited_display_name')
.then(displayName => { .then(displayName => {
if (!displayName) displayName = ""; if (!displayName) displayName = "";
resolve(displayName); resolve(displayName);
}) })
.catch(_ => { .catch(_ => {
let displayName = localStorage.getItem('editedDisplayName'); let displayName = localStorage.getItem('edited_display_name');
if (!displayName) displayName = ""; if (!displayName) displayName = "";
resolve(displayName); resolve(displayName);
}) })
@ -234,16 +316,16 @@ class BackgroundCanvas {
this.cCtx = this.c.getContext('2d'); this.cCtx = this.c.getContext('2d');
this.$footer = $$('footer'); this.$footer = $$('footer');
// fade-in on load
Events.on('fade-in-ui', _ => this._fadeIn());
// redraw canvas // redraw canvas
Events.on('resize', _ => this.init()); Events.on('resize', _ => this.init());
Events.on('redraw-canvas', _ => this.init()); Events.on('redraw-canvas', _ => this.init());
Events.on('translation-loaded', _ => this.init()); Events.on('translation-loaded', _ => this.init());
// ShareMode
Events.on('share-mode-changed', e => this.onShareModeChanged(e.detail.active));
} }
_fadeIn() { async fadeIn() {
this.c.classList.remove('opacity-0'); this.c.classList.remove('opacity-0');
} }
@ -263,16 +345,24 @@ class BackgroundCanvas {
this.x0 = this.w / 2; this.x0 = this.w / 2;
this.y0 = this.h - this.offset; this.y0 = this.h - this.offset;
this.dw = Math.round(Math.max(this.w, this.h, 1000) / 13); this.dw = Math.round(Math.max(this.w, this.h, 1000) / 13);
this.baseColor = '165, 165, 165';
this.baseOpacity = 0.3;
this.drawCircles(this.cCtx); this.drawCircles(this.cCtx);
} }
onShareModeChanged(active) {
this.baseColor = active ? '165, 165, 255' : '165, 165, 165';
this.baseOpacity = active ? 0.5 : 0.3;
this.drawCircles(this.cCtx);
}
drawCircle(ctx, radius) { drawCircle(ctx, radius) {
ctx.beginPath(); ctx.beginPath();
ctx.lineWidth = 2; ctx.lineWidth = 2;
let opacity = Math.max(0, 0.3 * (1 - 1.2 * radius / Math.max(this.w, this.h))); let opacity = Math.max(0, this.baseOpacity * (1 - 1.2 * radius / Math.max(this.w, this.h)));
ctx.strokeStyle = `rgba(165, 165, 165, ${opacity})`; ctx.strokeStyle = `rgba(${this.baseColor}, ${opacity})`;
ctx.arc(this.x0, this.y0, radius, 0, 2 * Math.PI); ctx.arc(this.x0, this.y0, radius, 0, 2 * Math.PI);
ctx.stroke(); ctx.stroke();
} }

File diff suppressed because it is too large Load diff

View file

@ -104,305 +104,315 @@ const zipper = (() => {
const mime = (() => { const mime = (() => {
const suffixToMimeMap = {
"cpl": "application/cpl+xml",
"gpx": "application/gpx+xml",
"gz": "application/gzip",
"jar": "application/java-archive",
"war": "application/java-archive",
"ear": "application/java-archive",
"class": "application/java-vm",
"js": "application/javascript",
"mjs": "application/javascript",
"json": "application/json",
"map": "application/json",
"webmanifest": "application/manifest+json",
"doc": "application/msword",
"dot": "application/msword",
"wiz": "application/msword",
"bin": "application/octet-stream",
"dms": "application/octet-stream",
"lrf": "application/octet-stream",
"mar": "application/octet-stream",
"so": "application/octet-stream",
"dist": "application/octet-stream",
"distz": "application/octet-stream",
"pkg": "application/octet-stream",
"bpk": "application/octet-stream",
"dump": "application/octet-stream",
"elc": "application/octet-stream",
"deploy": "application/octet-stream",
"img": "application/octet-stream",
"msp": "application/octet-stream",
"msm": "application/octet-stream",
"buffer": "application/octet-stream",
"oda": "application/oda",
"oxps": "application/oxps",
"pdf": "application/pdf",
"asc": "application/pgp-signature",
"sig": "application/pgp-signature",
"prf": "application/pics-rules",
"p7c": "application/pkcs7-mime",
"cer": "application/pkix-cert",
"ai": "application/postscript",
"eps": "application/postscript",
"ps": "application/postscript",
"apk": "application/vnd.android.package-archive",
"m3u8": "application/vnd.apple.mpegurl",
"pkpass": "application/vnd.apple.pkpass",
"kml": "application/vnd.google-earth.kml+xml",
"kmz": "application/vnd.google-earth.kmz",
"cab": "application/vnd.ms-cab-compressed",
"xls": "application/vnd.ms-excel",
"xlm": "application/vnd.ms-excel",
"xla": "application/vnd.ms-excel",
"xlc": "application/vnd.ms-excel",
"xlt": "application/vnd.ms-excel",
"xlw": "application/vnd.ms-excel",
"msg": "application/vnd.ms-outlook",
"ppt": "application/vnd.ms-powerpoint",
"pot": "application/vnd.ms-powerpoint",
"ppa": "application/vnd.ms-powerpoint",
"pps": "application/vnd.ms-powerpoint",
"pwz": "application/vnd.ms-powerpoint",
"mpp": "application/vnd.ms-project",
"mpt": "application/vnd.ms-project",
"xps": "application/vnd.ms-xpsdocument",
"odb": "application/vnd.oasis.opendocument.database",
"ods": "application/vnd.oasis.opendocument.spreadsheet",
"odt": "application/vnd.oasis.opendocument.text",
"osm": "application/vnd.openstreetmap.data+xml",
"pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
"xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"pcap": "application/vnd.tcpdump.pcap",
"cap": "application/vnd.tcpdump.pcap",
"dmp": "application/vnd.tcpdump.pcap",
"wpd": "application/vnd.wordperfect",
"wasm": "application/wasm",
"7z": "application/x-7z-compressed",
"dmg": "application/x-apple-diskimage",
"bcpio": "application/x-bcpio",
"torrent": "application/x-bittorrent",
"cbr": "application/x-cbr",
"cba": "application/x-cbr",
"cbt": "application/x-cbr",
"cbz": "application/x-cbr",
"cb7": "application/x-cbr",
"vcd": "application/x-cdlink",
"crx": "application/x-chrome-extension",
"cpio": "application/x-cpio",
"csh": "application/x-csh",
"deb": "application/x-debian-package",
"udeb": "application/x-debian-package",
"dvi": "application/x-dvi",
"arc": "application/x-freearc",
"gtar": "application/x-gtar",
"hdf": "application/x-hdf",
"h5": "application/x-hdf5",
"php": "application/x-httpd-php",
"iso": "application/x-iso9660-image",
"key": "application/x-iwork-keynote-sffkey",
"numbers": "application/x-iwork-numbers-sffnumbers",
"pages": "application/x-iwork-pages-sffpages",
"latex": "application/x-latex",
"run": "application/x-makeself",
"mif": "application/x-mif",
"lnk": "application/x-ms-shortcut",
"mdb": "application/x-msaccess",
"exe": "application/x-msdownload",
"dll": "application/x-msdownload",
"com": "application/x-msdownload",
"bat": "application/x-msdownload",
"msi": "application/x-msdownload",
"pub": "application/x-mspublisher",
"cdf": "application/x-netcdf",
"nc": "application/x-netcdf",
"pl": "application/x-perl",
"pm": "application/x-perl",
"prc": "application/x-pilot",
"pdb": "application/x-pilot",
"p12": "application/x-pkcs12",
"pfx": "application/x-pkcs12",
"ram": "application/x-pn-realaudio",
"pyc": "application/x-python-code",
"pyo": "application/x-python-code",
"rar": "application/x-rar-compressed",
"rpm": "application/x-redhat-package-manager",
"sh": "application/x-sh",
"shar": "application/x-shar",
"swf": "application/x-shockwave-flash",
"sql": "application/x-sql",
"srt": "application/x-subrip",
"sv4cpio": "application/x-sv4cpio",
"sv4crc": "application/x-sv4crc",
"gam": "application/x-tads",
"tar": "application/x-tar",
"tcl": "application/x-tcl",
"tex": "application/x-tex",
"roff": "application/x-troff",
"t": "application/x-troff",
"tr": "application/x-troff",
"man": "application/x-troff-man",
"me": "application/x-troff-me",
"ms": "application/x-troff-ms",
"ustar": "application/x-ustar",
"src": "application/x-wais-source",
"xpi": "application/x-xpinstall",
"xhtml": "application/xhtml+xml",
"xht": "application/xhtml+xml",
"xsl": "application/xml",
"rdf": "application/xml",
"wsdl": "application/xml",
"xpdl": "application/xml",
"zip": "application/zip",
"3gp": "audio/3gp",
"3gpp": "audio/3gpp",
"3g2": "audio/3gpp2",
"3gpp2": "audio/3gpp2",
"aac": "audio/aac",
"adts": "audio/aac",
"loas": "audio/aac",
"ass": "audio/aac",
"au": "audio/basic",
"snd": "audio/basic",
"mid": "audio/midi",
"midi": "audio/midi",
"kar": "audio/midi",
"rmi": "audio/midi",
"mpga": "audio/mpeg",
"mp2": "audio/mpeg",
"mp2a": "audio/mpeg",
"mp3": "audio/mpeg",
"m2a": "audio/mpeg",
"m3a": "audio/mpeg",
"oga": "audio/ogg",
"ogg": "audio/ogg",
"spx": "audio/ogg",
"opus": "audio/opus",
"aif": "audio/x-aiff",
"aifc": "audio/x-aiff",
"aiff": "audio/x-aiff",
"flac": "audio/x-flac",
"m4a": "audio/x-m4a",
"m3u": "audio/x-mpegurl",
"wma": "audio/x-ms-wma",
"ra": "audio/x-pn-realaudio",
"wav": "audio/x-wav",
"otf": "font/otf",
"ttf": "font/ttf",
"woff": "font/woff",
"woff2": "font/woff2",
"emf": "image/emf",
"gif": "image/gif",
"heic": "image/heic",
"heif": "image/heif",
"ief": "image/ief",
"jpeg": "image/jpeg",
"jpg": "image/jpeg",
"pict": "image/pict",
"pct": "image/pict",
"pic": "image/pict",
"png": "image/png",
"svg": "image/svg+xml",
"svgz": "image/svg+xml",
"tif": "image/tiff",
"tiff": "image/tiff",
"psd": "image/vnd.adobe.photoshop",
"djvu": "image/vnd.djvu",
"djv": "image/vnd.djvu",
"dwg": "image/vnd.dwg",
"dxf": "image/vnd.dxf",
"dds": "image/vnd.ms-dds",
"webp": "image/webp",
"3ds": "image/x-3ds",
"ras": "image/x-cmu-raster",
"ico": "image/x-icon",
"bmp": "image/x-ms-bmp",
"pnm": "image/x-portable-anymap",
"pbm": "image/x-portable-bitmap",
"pgm": "image/x-portable-graymap",
"ppm": "image/x-portable-pixmap",
"rgb": "image/x-rgb",
"tga": "image/x-tga",
"xbm": "image/x-xbitmap",
"xpm": "image/x-xpixmap",
"xwd": "image/x-xwindowdump",
"eml": "message/rfc822",
"mht": "message/rfc822",
"mhtml": "message/rfc822",
"nws": "message/rfc822",
"obj": "model/obj",
"stl": "model/stl",
"dae": "model/vnd.collada+xml",
"ics": "text/calendar",
"ifb": "text/calendar",
"css": "text/css",
"csv": "text/csv",
"html": "text/html",
"htm": "text/html",
"shtml": "text/html",
"markdown": "text/markdown",
"md": "text/markdown",
"txt": "text/plain",
"text": "text/plain",
"conf": "text/plain",
"def": "text/plain",
"list": "text/plain",
"log": "text/plain",
"in": "text/plain",
"ini": "text/plain",
"rtx": "text/richtext",
"rtf": "text/rtf",
"tsv": "text/tab-separated-values",
"c": "text/x-c",
"cc": "text/x-c",
"cxx": "text/x-c",
"cpp": "text/x-c",
"h": "text/x-c",
"hh": "text/x-c",
"dic": "text/x-c",
"java": "text/x-java-source",
"lua": "text/x-lua",
"py": "text/x-python",
"etx": "text/x-setext",
"sgm": "text/x-sgml",
"sgml": "text/x-sgml",
"vcf": "text/x-vcard",
"xml": "text/xml",
"xul": "text/xul",
"yaml": "text/yaml",
"yml": "text/yaml",
"ts": "video/mp2t",
"mp4": "video/mp4",
"mp4v": "video/mp4",
"mpg4": "video/mp4",
"mpeg": "video/mpeg",
"m1v": "video/mpeg",
"mpa": "video/mpeg",
"mpe": "video/mpeg",
"mpg": "video/mpeg",
"mov": "video/quicktime",
"qt": "video/quicktime",
"webm": "video/webm",
"flv": "video/x-flv",
"m4v": "video/x-m4v",
"asf": "video/x-ms-asf",
"asx": "video/x-ms-asf",
"vob": "video/x-ms-vob",
"wmv": "video/x-ms-wmv",
"avi": "video/x-msvideo",
"*": "video/x-sgi-movie"
}
return { return {
getMimeByFilename(filename) { async guessMimeByFilename(filename) {
try { const split = filename.split('.');
const arr = filename.split('.'); if (split.length === 1) {
const suffix = arr[arr.length - 1].toLowerCase(); // Filename does not include suffix
return { return "";
"cpl": "application/cpl+xml",
"gpx": "application/gpx+xml",
"gz": "application/gzip",
"jar": "application/java-archive",
"war": "application/java-archive",
"ear": "application/java-archive",
"class": "application/java-vm",
"js": "application/javascript",
"mjs": "application/javascript",
"json": "application/json",
"map": "application/json",
"webmanifest": "application/manifest+json",
"doc": "application/msword",
"dot": "application/msword",
"wiz": "application/msword",
"bin": "application/octet-stream",
"dms": "application/octet-stream",
"lrf": "application/octet-stream",
"mar": "application/octet-stream",
"so": "application/octet-stream",
"dist": "application/octet-stream",
"distz": "application/octet-stream",
"pkg": "application/octet-stream",
"bpk": "application/octet-stream",
"dump": "application/octet-stream",
"elc": "application/octet-stream",
"deploy": "application/octet-stream",
"img": "application/octet-stream",
"msp": "application/octet-stream",
"msm": "application/octet-stream",
"buffer": "application/octet-stream",
"oda": "application/oda",
"oxps": "application/oxps",
"pdf": "application/pdf",
"asc": "application/pgp-signature",
"sig": "application/pgp-signature",
"prf": "application/pics-rules",
"p7c": "application/pkcs7-mime",
"cer": "application/pkix-cert",
"ai": "application/postscript",
"eps": "application/postscript",
"ps": "application/postscript",
"apk": "application/vnd.android.package-archive",
"m3u8": "application/vnd.apple.mpegurl",
"pkpass": "application/vnd.apple.pkpass",
"kml": "application/vnd.google-earth.kml+xml",
"kmz": "application/vnd.google-earth.kmz",
"cab": "application/vnd.ms-cab-compressed",
"xls": "application/vnd.ms-excel",
"xlm": "application/vnd.ms-excel",
"xla": "application/vnd.ms-excel",
"xlc": "application/vnd.ms-excel",
"xlt": "application/vnd.ms-excel",
"xlw": "application/vnd.ms-excel",
"msg": "application/vnd.ms-outlook",
"ppt": "application/vnd.ms-powerpoint",
"pot": "application/vnd.ms-powerpoint",
"ppa": "application/vnd.ms-powerpoint",
"pps": "application/vnd.ms-powerpoint",
"pwz": "application/vnd.ms-powerpoint",
"mpp": "application/vnd.ms-project",
"mpt": "application/vnd.ms-project",
"xps": "application/vnd.ms-xpsdocument",
"odb": "application/vnd.oasis.opendocument.database",
"ods": "application/vnd.oasis.opendocument.spreadsheet",
"odt": "application/vnd.oasis.opendocument.text",
"osm": "application/vnd.openstreetmap.data+xml",
"pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
"xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
"docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"pcap": "application/vnd.tcpdump.pcap",
"cap": "application/vnd.tcpdump.pcap",
"dmp": "application/vnd.tcpdump.pcap",
"wpd": "application/vnd.wordperfect",
"wasm": "application/wasm",
"7z": "application/x-7z-compressed",
"dmg": "application/x-apple-diskimage",
"bcpio": "application/x-bcpio",
"torrent": "application/x-bittorrent",
"cbr": "application/x-cbr",
"cba": "application/x-cbr",
"cbt": "application/x-cbr",
"cbz": "application/x-cbr",
"cb7": "application/x-cbr",
"vcd": "application/x-cdlink",
"crx": "application/x-chrome-extension",
"cpio": "application/x-cpio",
"csh": "application/x-csh",
"deb": "application/x-debian-package",
"udeb": "application/x-debian-package",
"dvi": "application/x-dvi",
"arc": "application/x-freearc",
"gtar": "application/x-gtar",
"hdf": "application/x-hdf",
"h5": "application/x-hdf5",
"php": "application/x-httpd-php",
"iso": "application/x-iso9660-image",
"key": "application/x-iwork-keynote-sffkey",
"numbers": "application/x-iwork-numbers-sffnumbers",
"pages": "application/x-iwork-pages-sffpages",
"latex": "application/x-latex",
"run": "application/x-makeself",
"mif": "application/x-mif",
"lnk": "application/x-ms-shortcut",
"mdb": "application/x-msaccess",
"exe": "application/x-msdownload",
"dll": "application/x-msdownload",
"com": "application/x-msdownload",
"bat": "application/x-msdownload",
"msi": "application/x-msdownload",
"pub": "application/x-mspublisher",
"cdf": "application/x-netcdf",
"nc": "application/x-netcdf",
"pl": "application/x-perl",
"pm": "application/x-perl",
"prc": "application/x-pilot",
"pdb": "application/x-pilot",
"p12": "application/x-pkcs12",
"pfx": "application/x-pkcs12",
"ram": "application/x-pn-realaudio",
"pyc": "application/x-python-code",
"pyo": "application/x-python-code",
"rar": "application/x-rar-compressed",
"rpm": "application/x-redhat-package-manager",
"sh": "application/x-sh",
"shar": "application/x-shar",
"swf": "application/x-shockwave-flash",
"sql": "application/x-sql",
"srt": "application/x-subrip",
"sv4cpio": "application/x-sv4cpio",
"sv4crc": "application/x-sv4crc",
"gam": "application/x-tads",
"tar": "application/x-tar",
"tcl": "application/x-tcl",
"tex": "application/x-tex",
"roff": "application/x-troff",
"t": "application/x-troff",
"tr": "application/x-troff",
"man": "application/x-troff-man",
"me": "application/x-troff-me",
"ms": "application/x-troff-ms",
"ustar": "application/x-ustar",
"src": "application/x-wais-source",
"xpi": "application/x-xpinstall",
"xhtml": "application/xhtml+xml",
"xht": "application/xhtml+xml",
"xsl": "application/xml",
"rdf": "application/xml",
"wsdl": "application/xml",
"xpdl": "application/xml",
"zip": "application/zip",
"3gp": "audio/3gp",
"3gpp": "audio/3gpp",
"3g2": "audio/3gpp2",
"3gpp2": "audio/3gpp2",
"aac": "audio/aac",
"adts": "audio/aac",
"loas": "audio/aac",
"ass": "audio/aac",
"au": "audio/basic",
"snd": "audio/basic",
"mid": "audio/midi",
"midi": "audio/midi",
"kar": "audio/midi",
"rmi": "audio/midi",
"mpga": "audio/mpeg",
"mp2": "audio/mpeg",
"mp2a": "audio/mpeg",
"mp3": "audio/mpeg",
"m2a": "audio/mpeg",
"m3a": "audio/mpeg",
"oga": "audio/ogg",
"ogg": "audio/ogg",
"spx": "audio/ogg",
"opus": "audio/opus",
"aif": "audio/x-aiff",
"aifc": "audio/x-aiff",
"aiff": "audio/x-aiff",
"flac": "audio/x-flac",
"m4a": "audio/x-m4a",
"m3u": "audio/x-mpegurl",
"wma": "audio/x-ms-wma",
"ra": "audio/x-pn-realaudio",
"wav": "audio/x-wav",
"otf": "font/otf",
"ttf": "font/ttf",
"woff": "font/woff",
"woff2": "font/woff2",
"emf": "image/emf",
"gif": "image/gif",
"heic": "image/heic",
"heif": "image/heif",
"ief": "image/ief",
"jpeg": "image/jpeg",
"jpg": "image/jpeg",
"pict": "image/pict",
"pct": "image/pict",
"pic": "image/pict",
"png": "image/png",
"svg": "image/svg+xml",
"svgz": "image/svg+xml",
"tif": "image/tiff",
"tiff": "image/tiff",
"psd": "image/vnd.adobe.photoshop",
"djvu": "image/vnd.djvu",
"djv": "image/vnd.djvu",
"dwg": "image/vnd.dwg",
"dxf": "image/vnd.dxf",
"dds": "image/vnd.ms-dds",
"webp": "image/webp",
"3ds": "image/x-3ds",
"ras": "image/x-cmu-raster",
"ico": "image/x-icon",
"bmp": "image/x-ms-bmp",
"pnm": "image/x-portable-anymap",
"pbm": "image/x-portable-bitmap",
"pgm": "image/x-portable-graymap",
"ppm": "image/x-portable-pixmap",
"rgb": "image/x-rgb",
"tga": "image/x-tga",
"xbm": "image/x-xbitmap",
"xpm": "image/x-xpixmap",
"xwd": "image/x-xwindowdump",
"eml": "message/rfc822",
"mht": "message/rfc822",
"mhtml": "message/rfc822",
"nws": "message/rfc822",
"obj": "model/obj",
"stl": "model/stl",
"dae": "model/vnd.collada+xml",
"ics": "text/calendar",
"ifb": "text/calendar",
"css": "text/css",
"csv": "text/csv",
"html": "text/html",
"htm": "text/html",
"shtml": "text/html",
"markdown": "text/markdown",
"md": "text/markdown",
"txt": "text/plain",
"text": "text/plain",
"conf": "text/plain",
"def": "text/plain",
"list": "text/plain",
"log": "text/plain",
"in": "text/plain",
"ini": "text/plain",
"rtx": "text/richtext",
"rtf": "text/rtf",
"tsv": "text/tab-separated-values",
"c": "text/x-c",
"cc": "text/x-c",
"cxx": "text/x-c",
"cpp": "text/x-c",
"h": "text/x-c",
"hh": "text/x-c",
"dic": "text/x-c",
"java": "text/x-java-source",
"lua": "text/x-lua",
"py": "text/x-python",
"etx": "text/x-setext",
"sgm": "text/x-sgml",
"sgml": "text/x-sgml",
"vcf": "text/x-vcard",
"xml": "text/xml",
"xul": "text/xul",
"yaml": "text/yaml",
"yml": "text/yaml",
"ts": "video/mp2t",
"mp4": "video/mp4",
"mp4v": "video/mp4",
"mpg4": "video/mp4",
"mpeg": "video/mpeg",
"m1v": "video/mpeg",
"mpa": "video/mpeg",
"mpe": "video/mpeg",
"mpg": "video/mpeg",
"mov": "video/quicktime",
"qt": "video/quicktime",
"webm": "video/webm",
"flv": "video/x-flv",
"m4v": "video/x-m4v",
"asf": "video/x-ms-asf",
"asx": "video/x-ms-asf",
"vob": "video/x-ms-vob",
"wmv": "video/x-ms-wmv",
"avi": "video/x-msvideo",
"*": "video/x-sgi-movie",
}[suffix] || '';
} catch (e) {
console.error(e);
return '';
} }
const suffix = split[split.length - 1].toLowerCase();
return suffixToMimeMap[suffix] || "";
},
async addMissingMimeTypesToFiles(files) {
// if filetype is empty guess via suffix otherwise leave unchanged
for (let i = 0; i < files.length; i++) {
if (!files[i].type) {
files[i] = new File([files[i]], files[i].name, {type: await mime.guessMimeByFilename(files[i].name) || ""});
}
}
return files;
} }
}; };
@ -458,3 +468,119 @@ function base64ToArrayBuffer(base64) {
} }
return bytes.buffer; return bytes.buffer;
} }
async function fileToBlob (file) {
return new Blob([new Uint8Array(await file.arrayBuffer())], {type: file.type});
}
function getThumbnailAsDataUrl(file, width = undefined, height = undefined, quality = 0.7) {
return new Promise(async (resolve, reject) => {
try {
if (file.type === "image/heif" || file.type === "image/heic") {
// browsers can't show heic files --> convert to jpeg before creating thumbnail
let blob = await fileToBlob(file);
file = await heic2any({
blob,
toType: "image/jpeg",
quality: quality
});
}
let imageUrl = URL.createObjectURL(file);
let image = new Image();
image.src = imageUrl;
await waitUntilImageIsLoaded(imageUrl);
let imageWidth = image.width;
let imageHeight = image.height;
let canvas = document.createElement('canvas');
// resize the canvas and draw the image data into it
if (width && height) {
canvas.width = width;
canvas.height = height;
}
else if (width) {
canvas.width = width;
canvas.height = Math.floor(imageHeight * width / imageWidth)
}
else if (height) {
canvas.width = Math.floor(imageWidth * height / imageHeight);
canvas.height = height;
}
else {
canvas.width = imageWidth;
canvas.height = imageHeight
}
let ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
let dataUrl = canvas.toDataURL("image/jpeg", quality);
resolve(dataUrl);
} catch (e) {
console.error(e);
reject(new Error(`Could not create an image thumbnail from type ${file.type}`));
}
})
}
// Resolves returned promise when image is loaded and throws error if image cannot be shown
function waitUntilImageIsLoaded(imageUrl, timeout = 10000) {
return new Promise((resolve, reject) => {
let image = new Image();
image.src = imageUrl;
const onLoad = () => {
cleanup();
resolve();
};
const onError = () => {
cleanup();
reject(new Error('Image failed to load.'));
};
const cleanup = () => {
clearTimeout(timeoutId);
image.onload = null;
image.onerror = null;
URL.revokeObjectURL(imageUrl);
};
const timeoutId = setTimeout(() => {
cleanup();
reject(new Error('Image loading timed out.'));
}, timeout);
image.onload = onLoad;
image.onerror = onError;
});
}
async function decodeBase64Files(base64) {
if (!base64) throw new Error('Base64 is empty');
let bstr = atob(base64), n = bstr.length, u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
const zipBlob = new File([u8arr], 'archive.zip');
let files = [];
const zipEntries = await zipper.getEntries(zipBlob);
for (let i = 0; i < zipEntries.length; i++) {
let fileBlob = await zipper.getData(zipEntries[i]);
files.push(new File([fileBlob], zipEntries[i].filename));
}
return files
}
async function decodeBase64Text(base64) {
if (!base64) throw new Error('Base64 is empty');
return decodeURIComponent(escape(window.atob(base64)))
}

View file

@ -1,20 +1,5 @@
/* All styles in this sheet are not needed on page load and deferred */ /* All styles in this sheet are not needed on page load and deferred */
/* Paste mode */
#cancel-paste-mode {
z-index: 21;
margin: 0;
padding: 0;
position: absolute;
top: 0;
right: 0;
left: 0;
width: 100vw;
height: 56px;
background-color: var(--primary-color);
color: rgb(238, 238, 238);
}
/* Text Input */ /* Text Input */
.textarea { .textarea {
box-sizing: border-box; box-sizing: border-box;
@ -22,17 +7,21 @@
outline: none; outline: none;
padding: 16px 24px; padding: 16px 24px;
border-radius: 12px; border-radius: 12px;
font-size: inherit; font-size: 16px;
font-family: inherit; font-family: inherit;
display: block; display: block;
overflow: auto; overflow: auto;
resize: none; resize: none;
line-height: 16px; line-height: 16px;
max-height: 300px; max-height: 350px;
word-break: break-word; word-break: break-word;
word-wrap: anywhere; word-wrap: anywhere;
} }
.textarea:before {
opacity: 0.5;
}
/* Peers */ /* Peers */
x-peers:has(> x-peer) { x-peers:has(> x-peer) {
@ -279,6 +268,56 @@ x-peer[drop] x-icon {
transform: scale(1.1); transform: scale(1.1);
} }
/* Checkboxes as slider */
.switch {
display: inline-block;
height: 20px;
position: relative;
width: 30px;
}
.switch input {
display:none;
}
.slider {
background-color: rgba(128, 128, 128, 0.5);
cursor: pointer;
top: 0;
bottom: 0;
right: 0;
left: 0;
position: absolute;
transition: .4s;
}
.slider:before {
background-color: #fff;
content: "";
position: absolute;
top: 2px;
left: 2px;
width: 16px;
height: 16px;
transition: .4s;
}
input:checked + .slider {
background-color: var(--primary-color);
}
input:checked + .slider:before {
transform: translateX(10px);
}
.slider.round {
border-radius: 20px;
}
.slider.round:before {
border-radius: 50%;
}
/* Dialog */ /* Dialog */
@ -287,38 +326,30 @@ x-dialog x-background {
z-index: 30; z-index: 30;
transition: opacity 300ms; transition: opacity 300ms;
will-change: opacity; will-change: opacity;
overflow: overlay; padding: 10px 5px 20px;
overflow: scroll
} }
x-dialog x-paper { x-dialog x-paper {
position: relative;
display: flex; display: flex;
margin: auto;
flex-direction: column; flex-direction: column;
width: calc(100vw - 10px); width: 400px;
z-index: 3; z-index: 3;
border-radius: 30px; border-radius: 30px;
max-width: 400px;
overflow: hidden; overflow: hidden;
box-sizing: border-box; box-sizing: border-box;
transition: transform 300ms; transition: transform 300ms;
will-change: transform; will-change: transform;
} }
#pair-device-dialog x-paper,
#edit-paired-devices-dialog x-paper,
#public-room-dialog x-paper,
#language-select-dialog x-paper {
position: absolute;
top: max(50%, 350px);
margin-top: -328.5px;
}
x-paper > .row:first-of-type { x-paper > .row:first-of-type {
background-color: var(--accent-color); background-color: var(--accent-color);
padding: 10px;
margin-bottom: 5px; margin-bottom: 5px;
} }
x-paper > .row:first-of-type h2 { x-paper .dialog-title {
color: white; color: white;
} }
@ -357,6 +388,10 @@ x-dialog a {
/* Pair Devices Dialog & Public Room Dialog */ /* Pair Devices Dialog & Public Room Dialog */
#public-room-dialog x-paper {
width: 450px;
}
.input-key-container { .input-key-container {
width: 100%; width: 100%;
display: flex; display: flex;
@ -394,15 +429,15 @@ x-dialog a {
user-select: text; user-select: text;
display: inline-block; display: inline-block;
font-size: 45px; font-size: 45px;
letter-spacing: min(calc((100vw - 80px - 99px) / 100 * 7), 20px); letter-spacing: 18px;
text-indent: calc(0.5 * (11px + min(calc((100vw - 80px - 99px) / 100 * 6), 28px))); text-indent: 15px;
margin: 10px 0; margin: 10px 0;
} }
.key-qr-code { .key-qr-code {
width: fit-content; width: fit-content;
align-self: center; align-self: center;
margin-top: 15px; margin-top: 5px;
margin-bottom: 10px; margin-bottom: 10px;
} }
@ -424,8 +459,8 @@ x-dialog hr {
} }
.hr-note { .hr-note {
margin-top: 23px; margin-top: 13px;
margin-bottom: 31px; margin-bottom: 21px;
} }
.hr-note hr { .hr-note hr {
@ -447,10 +482,6 @@ x-dialog hr {
text-transform: uppercase; text-transform: uppercase;
} }
#pair-device-dialog x-background {
padding: 16px!important;
}
/* Edit Paired Devices Dialog */ /* Edit Paired Devices Dialog */
.paired-devices-wrapper:empty:before { .paired-devices-wrapper:empty:before {
content: attr(data-empty); content: attr(data-empty);
@ -474,6 +505,10 @@ x-dialog hr {
align-items: center; align-items: center;
} }
.paired-device:empty {
padding: 47px;
}
.paired-device:not(:last-child) { .paired-device:not(:last-child) {
border-bottom: solid 4px var(--paired-device-color); border-bottom: solid 4px var(--paired-device-color);
} }
@ -489,30 +524,11 @@ x-dialog hr {
border-bottom: solid 2px rgba(128, 128, 128, 0.5); border-bottom: solid 2px rgba(128, 128, 128, 0.5);
opacity: 1; opacity: 1;
} }
.paired-device span {
width: 100%;
}
.paired-device > .button-wrapper { .paired-device > .button-wrapper > * {
display: flex; min-height: 38px;
height: 36px; padding-left: 5px;
justify-content: space-between; padding-right: 5px;
flex-direction: row;
align-items: center;
width: 100%;
}
.paired-device > .button-wrapper > label,
.paired-device > .button-wrapper > button {
display: flex;
align-items: center;
text-align: center;
white-space: nowrap;
justify-content: center;
width: 50%;
padding-left: 6px;
padding-right: 6px;
height: 36px;
} }
.paired-device > .button-wrapper > :not(:last-child) { .paired-device > .button-wrapper > :not(:last-child) {
@ -529,32 +545,17 @@ x-dialog hr {
} }
/* button row*/ /* button row*/
x-paper > .button-row { .btn-row .btn {
margin: 3px;
flex-grow: 1;
height: 50px; height: 50px;
width: 120px; /* fixed width needed to ensure same width for all buttons */
}
x-paper > .btn-row {
margin: 5px 10px 10px; margin: 5px 10px 10px;
} }
x-paper > .button-row > .btn {
height: 100%;
width: 100%;
}
html:not([dir="rtl"]) x-paper > .button-row > .btn:not(:first-child) {
margin-right: 5px;
}
html:not([dir="rtl"]) x-paper > .button-row > .btn:not(:last-child) {
margin-left: 5px;
}
html[dir="rtl"] x-paper > .button-row > .btn:not(:first-child) {
margin-right: 5px;
}
html[dir="rtl"] x-paper > .button-row > .btn:not(:last-child) {
margin-left: 5px;
}
.language-buttons > button > span { .language-buttons > button > span {
margin: 0 0.3em; margin: 0 0.3em;
} }
@ -597,7 +598,10 @@ x-dialog .dialog-subheader {
#send-text-dialog, #send-text-dialog,
#receive-text-dialog { #receive-text-dialog {
font-size: 16px; /* prevents auto-zoom on edit */ font-size: 16px; /* prevents auto-zoom on edit */
}
.textarea.overflowing {
--shadow-color-rgb: var(--shadow-color-secondary-rgb); --shadow-color-rgb: var(--shadow-color-secondary-rgb);
--shadow-color-cover-rgb: var(--shadow-color-secondary-cover-rgb); --shadow-color-cover-rgb: var(--shadow-color-secondary-cover-rgb);
} }
@ -607,10 +611,6 @@ x-dialog .dialog-subheader {
--shadow-color-cover-rgb: var(--shadow-color-dialog-cover-rgb); --shadow-color-cover-rgb: var(--shadow-color-dialog-cover-rgb);
} }
#text-input:before {
opacity: 0.5;
}
/* Receive Text Dialog */ /* Receive Text Dialog */
#receive-text-dialog #text { #receive-text-dialog #text {
@ -634,11 +634,82 @@ x-dialog .dialog-subheader {
pointer-events: none; pointer-events: none;
} }
/* Do not call it 'share-panel', 'share-pannel' or 'sharepanel' as iOS Safari does not show any element with these classnames... */
.shr-panel {
min-width: 250px;
max-width: calc(100vw - 20px);
overflow: hidden;
color: white;
background-color: var(--primary-color);
background-image: linear-gradient(225deg, var(--accent-color) 0%, color-mix(in srgb, var(--accent-color) 60%, black) 100%);
}
.shr-panel > div {
margin: 4px 2px;
}
.shr-panel > div:not(:first-child) {
margin-top: 2px;
}
.shr-panel .thumb > div {
width: 36px;
height: 36px;
background: white;
border-radius: 6px;
margin-right: 6px;
}
.shr-panel .text-thumb svg {
width: 18px;
height: 36px;
}
.shr-panel .file-thumb svg {
width: 36px;
height: 36px;
}
.shr-panel .thumb .image-thumb {
background-size: cover;
border-radius: 6px;
background-position: center;
}
.shr-panel .btn {
height: 36px;
}
.share-descriptor {
justify-content: center;
}
.share-descriptor > span {
display: inline;
margin-bottom: 0;
margin-top: 0;
height: 20px;
max-width: 15rem;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.share-descriptor > span:first-child {
font-weight: bold;
}
.share-descriptor > span:not(:first-child) {
font-size: small;
}
#base64-paste-btn, #base64-paste-btn,
#base64-paste-dialog .textarea { #base64-paste-dialog .textarea {
width: 100%; width: 100%;
height: 40vh; height: 40vh;
border: solid 12px #438cff; border: solid 12px #438cff;
margin: 6px;
} }
#base64-paste-dialog .textarea { #base64-paste-dialog .textarea {

View file

@ -31,12 +31,65 @@ html {
} }
.p1 { .p1 {
padding: 5px;
}
.p2 {
padding: 10px; padding: 10px;
} }
.row-reverse { .pb0 {
padding-bottom: 0;
}
.mx1 {
margin-left: 5px;
margin-right: 5px;
}
.m1 {
margin: 5px;
}
.cursive {
font-style: italic;
}
.wrap {
display: flex; display: flex;
flex-direction: row-reverse; flex-wrap: wrap;
}
.wrap-reverse {
display: flex;
flex-wrap: wrap-reverse;
}
.grow {
display: flex;
flex-grow: 1;
}
.grow-2 {
display: flex;
flex-grow: 2;
}
.shrink {
display: flex;
flex-shrink: 1;
}
.flex {
display: flex;
}
.align-center {
align-items: center;
}
.space-evenly {
justify-content: space-evenly;
} }
.space-between { .space-between {
@ -48,6 +101,11 @@ html {
flex-direction: row; flex-direction: row;
} }
.row-reverse {
display: flex;
flex-direction: row-reverse;
}
.column { .column {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -59,10 +117,6 @@ html {
justify-content: center; justify-content: center;
} }
.grow {
flex-grow: 1;
}
.full { .full {
position: absolute; position: absolute;
top: 0; top: 0;
@ -76,7 +130,7 @@ html {
} }
header { header {
position: absolute; position: relative;
align-items: baseline; align-items: baseline;
padding: 8px 12px; padding: 8px 12px;
box-sizing: border-box; box-sizing: border-box;
@ -86,67 +140,95 @@ header {
right: 0; right: 0;
} }
header.overflow-hidden {
overflow: hidden;
}
header:not(.overflow-expanded) {
height: 56px;
}
header > * { header > * {
margin-left: 4px; margin-left: 4px;
margin-right: 4px; margin-right: 4px;
} }
header > div { header > * {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-self: flex-start; align-self: flex-start;
touch-action: manipulation; touch-action: manipulation;
} }
header > div .icon-button { header > .icon-button {
height: 40px; height: 40px;
}
header * {
transition: all 300ms; transition: all 300ms;
} }
header > div > div { #theme-wrapper > div {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
header > div:not(:hover) .icon-button:not(.selected) { /* expand theme buttons */
#theme-wrapper:not(:hover) .icon-button:not(.selected) {
height: 0; height: 0;
opacity: 0; opacity: 0;
} }
#theme-wrapper:hover::before { #theme-wrapper:hover::before {
border-radius: 20px; border-radius: 20px;
background: currentColor; background: var(--primary-color);
opacity: 0.1; opacity: 0.2;
transition: opacity 300ms; transition: opacity 300ms;
content: ''; content: '';
position: absolute; position: absolute;
width: 40px; width: 40px;
height: 120px;
top: 0; top: 0;
bottom: 0;
margin-top: 8px; margin-top: 8px;
margin-bottom: 8px; margin-bottom: 8px;
} }
header > div:hover .icon-button.selected::before { #theme-wrapper:hover .icon-button:not(.selected):hover:before {
opacity: 0.1; opacity: 0.3;
}
#theme-wrapper:hover .icon-button.selected::before {
opacity: 0.3;
} }
@media (pointer: coarse) { @media (pointer: coarse) {
header > div:hover .icon-button.selected:hover::before { #theme-wrapper:hover .icon-button.selected:hover::before {
opacity: 0.2; opacity: 0.3;
} }
header > div .icon-button:not(.selected) { #theme-wrapper .icon-button:not(.selected) {
height: 0; height: 0;
opacity: 0; opacity: 0;
pointer-events: none; pointer-events: none;
} }
header > div > div { #theme-wrapper > div {
flex-direction: column-reverse; flex-direction: column-reverse;
} }
} }
#expand > .icon {
transition: transform 150ms ease-out
}
html:not([dir="rtl"]) #expand.flipped > .icon {
transform: rotate(-90deg);
}
html[dir="rtl"] #expand.flipped > .icon {
transform: rotate(90deg);
}
[hidden] { [hidden] {
display: none !important; display: none !important;
} }
@ -177,7 +259,7 @@ h1 {
} }
h2 { h2 {
font-size: 24px; font-size: 22px;
font-weight: 400; font-weight: 400;
letter-spacing: -.012em; letter-spacing: -.012em;
line-height: 32px; line-height: 32px;
@ -201,6 +283,10 @@ h3 {
text-align: center; text-align: center;
} }
.text-white {
color: white !important;
}
.font-body1, .font-body1,
body { body {
font-size: 14px; font-size: 14px;
@ -276,19 +362,16 @@ x-noscript {
/* Animations */ /* Animations */
/* Opacity for elements at keyframe 100% is set on element (default 1) */
@keyframes fade-in { @keyframes fade-in {
0% { 0% {
opacity: 0; opacity: 0;
} }
100% {
opacity: 1;
}
} }
#center { #center {
position: relative; position: relative;
display: flex; display: flex;
margin-top: 56px;
flex-direction: column-reverse; flex-direction: column-reverse;
flex-grow: 1; flex-grow: 1;
justify-content: space-around; justify-content: space-around;
@ -301,18 +384,10 @@ x-noscript {
/* Peers */ /* Peers */
#x-peers-filler {
display: flex;
flex-grow: 1;
}
x-peers { x-peers {
position: relative; position: relative;
display: flex; display: flex;
flex-flow: row wrap; flex-flow: row wrap;
flex-grow: 1;
align-items: start !important;
justify-content: center;
z-index: 2; z-index: 2;
transition: background-color 0.5s ease; transition: background-color 0.5s ease;
@ -324,8 +399,6 @@ x-peers {
--peers-per-row: 6; /* default if browser does not support :has selector */ --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)); --x-peers-width: min(100vw, calc(var(--peers-per-row) * (var(--peer-width) + 25px) - 16px));
width: var(--x-peers-width); width: var(--x-peers-width);
margin-right: 20px;
margin-left: 20px;
} }
/* Empty Peers List */ /* Empty Peers List */
@ -382,30 +455,35 @@ footer .logo {
margin-top: -10px; margin-top: -10px;
} }
.discovery-wrapper { .border {
font-size: 14px;
margin: 15px auto auto;
border: 2px solid var(--border-color); border: 2px solid var(--border-color);
}
.panel {
font-size: 14px;
padding: 2px; padding: 2px;
background-color: rgb(var(--bg-color)); background-color: rgb(var(--bg-color));
transition: background-color 0.5s ease; transition: background-color 0.5s ease;
min-height: 24px; min-height: 24px;
} }
.discovery-wrapper.column { .panel.column {
border-radius: 16px; border-radius: 16px;
} }
.discovery-wrapper.row { .panel.row {
border-radius: 12px; border-radius: 12px;
} }
/*You can be discovered wrapper*/ .panel > div:first-of-type {
.discovery-wrapper > div:first-of-type {
padding-left: 4px; padding-left: 4px;
padding-right: 4px; padding-right: 4px;
} }
/* You can be discovered wrapper */
.discovery-wrapper {
margin: 15px auto auto;
}
.discovery-wrapper .badge { .discovery-wrapper .badge {
word-break: keep-all; word-break: keep-all;
@ -421,10 +499,6 @@ footer .logo {
white-space: nowrap; white-space: nowrap;
} }
.badge-gradient {
background-image: linear-gradient(180deg, color-mix(in srgb, var(--badge-color) 80%, white) 0%, var(--badge-color) 50%);
}
.badge-room-ip { .badge-room-ip {
--badge-color: var(--primary-color); --badge-color: var(--primary-color);
} }
@ -447,8 +521,11 @@ footer .logo {
text-align: left; text-align: left;
border: none; border: none;
outline: none; outline: none;
height: 20px;
max-width: 15em; max-width: 15em;
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
cursor: text; cursor: text;
margin-bottom: -6px; margin-bottom: -6px;
padding-bottom: 0.1rem; padding-bottom: 0.1rem;
@ -456,11 +533,10 @@ footer .logo {
border-right: solid 1rem transparent; border-right: solid 1rem transparent;
border-left: solid 1rem transparent; border-left: solid 1rem transparent;
background-clip: padding-box; background-clip: padding-box;
overflow: hidden;
z-index: 1; z-index: 1;
} }
#edit-pen { .edit-pen {
width: 1rem; width: 1rem;
height: 1rem; height: 1rem;
margin-bottom: -2px; margin-bottom: -2px;
@ -468,16 +544,16 @@ footer .logo {
} }
html:not([dir="rtl"]) #display-name, html:not([dir="rtl"]) #display-name,
html:not([dir="rtl"]) #edit-pen { html:not([dir="rtl"]) .edit-pen {
margin-left: -1rem; margin-left: -1rem;
} }
html[dir="rtl"] #display-name, html[dir="rtl"] #display-name,
html[dir="rtl"] #edit-pen { html[dir="rtl"] .edit-pen {
margin-right: -1rem; margin-right: -1rem;
} }
html[dir="rtl"] #edit-pen { html[dir="rtl"] .edit-pen {
transform: rotateY(180deg); transform: rotateY(180deg);
} }
@ -505,6 +581,11 @@ x-dialog:not([show]) x-background {
overflow: hidden; overflow: hidden;
} }
.btn-small {
font-size: 12px;
line-height: 22px;
}
.btn[disabled] { .btn[disabled] {
color: var(--btn-disabled-color); color: var(--btn-disabled-color);
cursor: not-allowed; cursor: not-allowed;
@ -531,6 +612,7 @@ x-dialog:not([show]) x-background {
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
z-index: -1;
opacity: 0; opacity: 0;
background-color: var(--accent-color); background-color: var(--accent-color);
transition: opacity 300ms; transition: opacity 300ms;
@ -538,27 +620,44 @@ x-dialog:not([show]) x-background {
.btn:not([disabled]):hover:before, .btn:not([disabled]):hover:before,
.icon-button:hover:before { .icon-button:hover:before {
opacity: 0.1; opacity: 0.3;
} }
.btn[selected], .btn[selected],
.icon-button[selected] { .icon-button[selected] {
opacity: 0.1; opacity: 0.3;
} }
.btn:focus:before, .btn:focus:before,
.icon-button:focus:before { .icon-button:focus:before {
opacity: 0.2; opacity: 0.4;
}
.btn-round {
border-radius: 50%;
} }
.btn-rounded { .btn-rounded {
border-radius: 12px; border-radius: 12px;
} }
.btn-small.btn-rounded {
border-radius: 6px;
}
.btn-grey { .btn-grey {
background-color: var(--bg-color-secondary); background-color: var(--bg-color-secondary);
} }
.btn-dark {
background-color: #262628;
}
.btn-primary {
background: var(--primary-color);
color: rgb(var(--bg-color));
}
button::-moz-focus-inner { button::-moz-focus-inner {
border: 0; border: 0;
} }
@ -686,16 +785,17 @@ canvas.circles {
} }
x-toast { x-toast {
display: flex;
justify-content: space-between;
position: absolute; position: absolute;
min-height: 48px; min-height: 48px;
top: 50px; top: 50px;
width: 100%; max-width: 400px;
max-width: 344px;
background-color: rgb(var(--text-color)); background-color: rgb(var(--text-color));
color: var(--dialog-bg-color); color: var(--dialog-bg-color);
align-items: center; align-items: center;
box-sizing: border-box; box-sizing: border-box;
padding: 8px 24px; padding: 8px;
z-index: 40; z-index: 40;
transition: opacity 200ms, transform 300ms ease-out; transition: opacity 200ms, transform 300ms ease-out;
cursor: default; cursor: default;
@ -704,22 +804,34 @@ x-toast {
pointer-events: all; pointer-events: all;
} }
x-toast:not([show]):not(:hover) { x-toast.top-row {
top: 3px;
}
x-toast:not([show]) {
opacity: 0; opacity: 0;
transform: translateY(-100px); transform: translateY(calc(-100% + -55px));
}
x-toast span {
flex-grow: 1;
margin: auto 4px auto 10px
}
x-dialog[show] ~ div x-toast {
background-color: var(--lt-dialog-bg-color);
color: rgb(var(--lt-text-color));
} }
/* Instructions */ /* Instructions */
x-instructions { x-instructions {
display: flex;
position: relative; position: relative;
opacity: 0.5; opacity: 0.5;
text-align: center; text-align: center;
margin-left: 10px; margin: 50px 10px 0px;
margin-right: 10px;
display: flex;
flex-direction: column; flex-direction: column;
flex-grow: 1;
justify-content: center; justify-content: center;
} }
@ -735,12 +847,10 @@ x-instructions[drop-bg]:not([drop-peer]):before {
content: attr(data-drop-bg); content: attr(data-drop-bg);
} }
x-instructions p {
display: none;
}
x-peers:empty,
x-peers:empty~x-instructions { x-peers:empty~x-instructions {
opacity: 0 !important; display: none;
} }
@media (hover: none) and (pointer: coarse) { @media (hover: none) and (pointer: coarse) {
@ -796,43 +906,70 @@ body {
/* Constant colors */ /* Constant colors */
--primary-color: #4285f4; --primary-color: #4285f4;
--paired-device-color: #00a69c; --paired-device-color: #00a69c;
--public-room-color: #db8500; --public-room-color: #ed9d01;
--accent-color: var(--primary-color); --accent-color: var(--primary-color);
--ws-peer-color: #ff6b6b; --ws-peer-color: #ff6b6b;
--btn-disabled-color: #5B5B66; --btn-disabled-color: #5B5B66;
/* shadows */ /* shadows */
--shadow-color-rgb: var(--text-color); --shadow-color-rgb: var(--text-color);
--shadow-color-cover-rgb: var(--bg-color); --shadow-color-cover-rgb: var(--bg-color);
/* Light theme colors */
--lt-text-color: 51,51,51;
--lt-dialog-bg-color: #fff;
--lt-bg-color: 255,255,255;
--lt-bg-color-secondary: #f2f2f2;
--lt-border-color: #a9a9a9;
--lt-badge-color: #a5a5a5;
--lt-shadow-color-secondary-rgb: 0,0,0;
--lt-shadow-color-secondary-cover-rgb: 242,242,242;
--lt-shadow-color-dialog-rgb: 0,0,0;
--lt-shadow-color-dialog-cover-rgb: 242,242,242;
/* Dark theme colors */
--dt-text-color: 238,238,238;
--dt-dialog-bg-color: #141414;
--dt-bg-color: 0,0,0;
--dt-bg-color-secondary: #262628;
--dt-border-color: #919191;
--dt-badge-color: #717171;
--dt-shadow-color-secondary-rgb: 255,255,255;
--dt-shadow-color-secondary-cover-rgb: 38,38,38;
--dt-shadow-color-dialog-rgb: 255,255,255;
--dt-shadow-color-dialog-cover-rgb: 38,38,38;
} }
/* Light theme colors */ /* Light theme colors */
body { body {
--text-color: 51,51,51; --text-color: var(--lt-text-color);
--dialog-bg-color: #fff; --dialog-bg-color: var(--lt-dialog-bg-color);
--bg-color: 255,255,255; --bg-color: var(--lt-bg-color);
--bg-color-secondary: #f2f2f2; --bg-color-secondary: var(--lt-bg-color-secondary);
--border-color: rgb(169, 169, 169); --border-color: var(--lt-border-color);
--badge-color: #a5a5a5; --badge-color: var(--lt-badge-color);
--shadow-color-secondary-rgb: 0,0,0; --shadow-color-secondary-rgb: var(--lt-shadow-color-secondary-rgb);
--shadow-color-secondary-cover-rgb: 242,242,242; --shadow-color-secondary-cover-rgb: var(--lt-shadow-color-secondary-cover-rgb);
--shadow-color-dialog-rgb: 0,0,0; --shadow-color-dialog-rgb: var(--lt-shadow-color-dialog-rgb);
--shadow-color-dialog-cover-rgb: 242,242,242; --shadow-color-dialog-cover-rgb: var(--lt-shadow-color-dialog-cover-rgb);
} }
/* Dark theme colors */ /* Dark theme colors */
body.dark-theme { body.dark-theme {
--text-color: 238,238,238; --text-color: var(--dt-text-color);
--dialog-bg-color: #121212; --dialog-bg-color: var(--dt-dialog-bg-color);
--bg-color: 0,0,0; --bg-color: var(--dt-bg-color);
--bg-color-secondary: #262628; --bg-color-secondary: var(--dt-bg-color-secondary);
--border-color: rgb(91, 91, 91); --border-color: var(--dt-border-color);
--badge-color: #717171; --badge-color: var(--dt-badge-color);
--shadow-color-secondary-rgb: 255,255,255; --shadow-color-secondary-rgb: var(--dt-shadow-color-secondary-rgb);
--shadow-color-secondary-cover-rgb: 38,38,38; --shadow-color-secondary-cover-rgb: var(--dt-shadow-color-secondary-cover-rgb);
--shadow-color-dialog-rgb: 255,255,255; --shadow-color-dialog-rgb: var(--dt-shadow-color-dialog-rgb);
--shadow-color-dialog-cover-rgb: 38,38,38; --shadow-color-dialog-cover-rgb: var(--dt-shadow-color-dialog-cover-rgb);
} }
/* Styles for users who prefer dark mode at the OS level */ /* Styles for users who prefer dark mode at the OS level */
@ -840,33 +977,33 @@ body.dark-theme {
/* defaults to dark theme */ /* defaults to dark theme */
body { body {
--text-color: 238,238,238; --text-color: var(--dt-text-color);
--dialog-bg-color: #121212; --dialog-bg-color: var(--dt-dialog-bg-color);
--bg-color-secondary: #262628; --bg-color: var(--dt-bg-color);
--bg-color: 0,0,0; --bg-color-secondary: var(--dt-bg-color-secondary);
--border-color: rgb(91, 91, 91); --border-color: var(--dt-border-color);
--badge-color: #717171; --badge-color: var(--dt-badge-color);
--shadow-color-secondary-rgb: 255,255,255; --shadow-color-secondary-rgb: var(--dt-shadow-color-secondary-rgb);
--shadow-color-secondary-cover-rgb: 38,38,38; --shadow-color-secondary-cover-rgb: var(--dt-shadow-color-secondary-cover-rgb);
--shadow-color-dialog-rgb: 255,255,255; --shadow-color-dialog-rgb: var(--dt-shadow-color-dialog-rgb);
--shadow-color-dialog-cover-rgb: 38,38,38; --shadow-color-dialog-cover-rgb: var(--dt-shadow-color-dialog-cover-rgb);
} }
/* Override dark mode with light mode styles if the user decides to swap */ /* Override dark mode with light mode styles if the user decides to swap */
body.light-theme { body.light-theme {
--text-color: 51,51,51; --text-color: var(--lt-text-color);
--dialog-bg-color: #fff; --dialog-bg-color: var(--lt-dialog-bg-color);
--bg-color: 255,255,255; --bg-color: var(--lt-bg-color);
--bg-color-secondary: #f2f2f2; --bg-color-secondary: var(--lt-bg-color-secondary);
--border-color: rgb(169, 169, 169); --border-color: var(--lt-border-color);
--badge-color: #a5a5a5; --badge-color: var(--lt-badge-color);
--shadow-color-secondary-rgb: 0,0,0; --shadow-color-secondary-rgb: var(--lt-shadow-color-secondary-rgb);
--shadow-color-secondary-cover-rgb: 242,242,242; --shadow-color-secondary-cover-rgb: var(--lt-shadow-color-secondary-cover-rgb);
--shadow-color-dialog-rgb: 0,0,0; --shadow-color-dialog-rgb: var(--lt-shadow-color-dialog-rgb);
--shadow-color-dialog-cover-rgb: 242,242,242; --shadow-color-dialog-cover-rgb: var(--lt-shadow-color-dialog-cover-rgb);
} }
} }
@ -877,28 +1014,6 @@ body {
transition: background-color 0.5s ease; transition: background-color 0.5s ease;
} }
x-dialog x-paper {
background-color: var(--dialog-bg-color);
}
.textarea {
color: rgb(var(--text-color)) !important;
background-color: var(--bg-color-secondary) !important;
}
.textarea * {
margin: 0 !important;
padding: 0 !important;
color: unset !important;
background: unset !important;
border: unset !important;
opacity: unset !important;
font-family: inherit !important;
font-size: inherit !important;
font-style: unset !important;
font-weight: unset !important;
}
/* Gradient for wifi-tether icon */ /* Gradient for wifi-tether icon */
#primaryGradient .start-color { #primaryGradient .start-color {
stop-color: var(--primary-color); stop-color: var(--primary-color);
@ -946,8 +1061,8 @@ html {
/* webkit scrollbar style*/ /* webkit scrollbar style*/
::-webkit-scrollbar{ ::-webkit-scrollbar{
width: 4px; width: 0;
height: 4px; height: 0;
} }
::-webkit-scrollbar-thumb{ ::-webkit-scrollbar-thumb{

View file

@ -37,7 +37,7 @@ export default class Peer {
return true; return true;
} }
this.requestRate += 1; this.requestRate += 1;
setTimeout(_ => this.requestRate -= 1, 10000); setTimeout(() => this.requestRate -= 1, 10000);
return false; return false;
} }
@ -124,8 +124,8 @@ export default class Peer {
_setPeerId(request) { _setPeerId(request) {
const searchParams = new URL(request.url, "http://server").searchParams; const searchParams = new URL(request.url, "http://server").searchParams;
let peerId = searchParams.get("peer_id"); let peerId = searchParams.get('peer_id');
let peerIdHash = searchParams.get("peer_id_hash"); let peerIdHash = searchParams.get('peer_id_hash');
if (peerId && Peer.isValidUuid(peerId) && this.isPeerIdHashValid(peerId, peerIdHash)) { if (peerId && Peer.isValidUuid(peerId) && this.isPeerIdHashValid(peerId, peerIdHash)) {
this.id = peerId; this.id = peerId;
} else { } else {
@ -135,7 +135,7 @@ export default class Peer {
_setRtcSupported(request) { _setRtcSupported(request) {
const searchParams = new URL(request.url, "http://server").searchParams; const searchParams = new URL(request.url, "http://server").searchParams;
this.rtcSupported = searchParams.get("webrtc_supported") === "true"; this.rtcSupported = searchParams.get('webrtc_supported') === "true";
} }
_setName(req) { _setName(req) {