Merge branch 'next' into translate
|
@ -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
|
||||
|
||||
on:
|
||||
|
@ -13,6 +24,6 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
- name: Build the Docker image
|
||||
run: docker build --pull . -f Dockerfile -t pairdrop
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
# 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 Docker image and push it to ghcr.io whenever a new version tag is pushed
|
||||
|
||||
name: GHCR Image CI
|
||||
|
||||
on:
|
||||
|
@ -27,16 +29,16 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
|
||||
- name: Setup qemu
|
||||
uses: docker/setup-qemu-action@v2.1.0
|
||||
uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
|
||||
|
||||
- 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
|
||||
uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
|
||||
uses: docker/login-action@343f7c4344506bcbf9b4de18042ae17996df046d # v3.0.0
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
|
@ -44,12 +46,12 @@ jobs:
|
|||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
|
||||
uses: docker/metadata-action@31cebacef4805868f9ce9a0cb03ee36c32df2ac4 # v5.3.0
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
|
||||
uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5.1.0
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
|
|
@ -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 }}
|
15
README.md
|
@ -67,10 +67,11 @@ Developed based on [Snapdrop](https://github.com/RobinLinus/snapdrop)
|
|||
* 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 directly from context menu on Windows](/docs/how-to.md#send-files-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 directly from share menu on Android](/docs/how-to.md#send-directly-from-share-menu-on-android)
|
||||
* [Send directly via command-line interface](/docs/how-to.md#send-directly-via-command-line-interface)
|
||||
* [Send files directly from context menu on Windows](/docs/how-to.md#send-multiple-files-and-directories-directly-from-context-menu-on-windows)
|
||||
* [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 files directly from share menu on iOS](/docs/how-to.md#send-directly-from-share-menu-on-ios)
|
||||
* [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
|
||||
* 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
|
||||
* [Progressive Web App](https://wikipedia.org/wiki/Progressive_Web_App)
|
||||
* [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
|
||||
* [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).
|
||||
|
||||
|
|
132
docs/how-to.md
|
@ -1,84 +1,120 @@
|
|||
# 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
|
||||
I created an iOS shortcut to send images, files, folder, URLs \
|
||||
or text directly from the share-menu
|
||||
https://routinehub.co/shortcut/13990/
|
||||
|
||||
[//]: # (Todo: add doku with screenshots)
|
||||
[//]: # (Todo: Add screenshots)
|
||||
|
||||
<br>
|
||||
|
||||
## 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.
|
||||
|
||||
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 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.
|
||||
|
||||
### Usage
|
||||
```bash
|
||||
$ pairdrop -h
|
||||
Current domain: https://pairdrop.net/
|
||||
pairdrop -h
|
||||
```
|
||||
```bash
|
||||
Send files or text with PairDrop via command-line interface.
|
||||
Current domain: https://pairdrop-dev.onrender.com/
|
||||
|
||||
Usage:
|
||||
Open PairDrop: pairdrop
|
||||
Send files: pairdrop file/directory
|
||||
Send text: pairdrop -t "text"
|
||||
Specify domain: pairdrop -d "https://pairdrop.net/"
|
||||
Show this help text: pairdrop (-h|--help)
|
||||
Open PairDrop: pairdrop
|
||||
Send files: pairdrop file1/directory1 (file2/directory2 file3/directory3 ...)
|
||||
Send text: pairdrop -t "text"
|
||||
Specify domain: pairdrop -d "https://pairdrop.net/"
|
||||
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
|
||||
Download the bash file: [pairdrop-cli/pairdrop](/pairdrop-cli/pairdrop).
|
||||
|
||||
#### Linux
|
||||
1. Put the file in a preferred folder e.g. `/usr/local/bin`
|
||||
2. Make sure the bash file is executable. Otherwise, use `chmod +x pairdrop`
|
||||
3. Add absolute path of the folder to PATH variable to make `pairdrop` available globally by executing
|
||||
`export PATH=$PATH:/opt/pairdrop-cli`
|
||||
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. Make sure the bash file `/usr/local/bin/pairdrop-cli/pairdrop` is executable. Otherwise, use `chmod +x pairdrop`
|
||||
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
|
||||
1. add bash file to `/usr/local/bin`
|
||||
|
||||
<br>
|
||||
|
||||
#### Windows
|
||||
1. Put file in a preferred folder e.g. `C:\Users\Public\pairdrop-cli`
|
||||
2. Search for and open `Edit environment variables for your account`
|
||||
3. Click `Environment Variables…`
|
||||
4. Under *System Variables* select `Path` and click *Edit...*
|
||||
5. Click *New*, insert the preferred folder (`C:\Users\Public\pairdrop-cli`), click *OK* until all windows are closed
|
||||
6. Reopen Command prompt window
|
||||
1. Download the latest _pairdrop-cli.zip_ from the [releases page](https://github.com/schlagmichdoch/PairDrop/releases)
|
||||
2. Put file in a preferred folder e.g. `C:\Program Files\pairdrop-cli`
|
||||
3. Search for and open `Edit environment variables for your account`
|
||||
4. Click `Environment Variables…`
|
||||
5. Under *System Variables* select `Path` and click *Edit...*
|
||||
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)
|
||||
|
|
Before Width: | Height: | Size: 326 KiB After Width: | Height: | Size: 1.5 MiB |
|
@ -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.
|
|
@ -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.
|
|
@ -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.
|
|
@ -0,0 +1 @@
|
|||
.pairdrop-cli-config
|
|
@ -12,10 +12,12 @@ help()
|
|||
echo
|
||||
echo "Usage:"
|
||||
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 "Specify domain:\t\t$(basename "$0") -d \"https://pairdrop.net/\""
|
||||
echo -e "Show this help text:\t$(basename "$0") (-h|--help)"
|
||||
echo
|
||||
echo "This pairdrop-cli version was released alongside v1.10.0"
|
||||
}
|
||||
|
||||
openPairDrop()
|
||||
|
@ -36,7 +38,7 @@ openPairDrop()
|
|||
elif [[ $OS == "WSL" || $OS == "WSL2" ]];then
|
||||
powershell.exe /c "Start-Process ${url}"
|
||||
else
|
||||
xdg-open "$url"
|
||||
xdg-open "$url" > /dev/null 2>&1
|
||||
fi
|
||||
|
||||
|
||||
|
@ -62,7 +64,7 @@ setOs()
|
|||
specifyDomain()
|
||||
{
|
||||
[[ ! $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"
|
||||
}
|
||||
|
||||
|
@ -87,75 +89,228 @@ sendText()
|
|||
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()
|
||||
{
|
||||
params="base64zip=hash"
|
||||
if [[ $1 == */ ]]; then
|
||||
path="${1::-1}"
|
||||
else
|
||||
path=$1
|
||||
fi
|
||||
zipPath="${path}_pairdrop.zip"
|
||||
zipPath=${zipPath// /_}
|
||||
workingDir="$(pwd)"
|
||||
tmpDir="/tmp/pairdrop-cli-temp/"
|
||||
tmpDirPS="\$env:TEMP/pairdrop-cli-temp/"
|
||||
|
||||
index=0
|
||||
directoryBaseNamesUnix=()
|
||||
directoryPathsUnix=()
|
||||
filePathsUnix=()
|
||||
directoryCount=0
|
||||
fileCount=0
|
||||
pathsPS=""
|
||||
|
||||
[[ -a "$zipPath" ]] && echo "Cannot overwrite $zipPath. Please remove first." && exit
|
||||
|
||||
if [[ -d $path ]]; then
|
||||
zipPathTemp="${path}_pairdrop_temp.zip"
|
||||
[[ -a "$zipPathTemp" ]] && echo "Cannot overwrite $zipPathTemp. Please remove first." && exit
|
||||
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
|
||||
#create tmp folder if it does not exist already
|
||||
if [[ ! -d "$tmpDir" ]]; then
|
||||
mkdir "$tmpDir"
|
||||
fi
|
||||
|
||||
# remove temporary temp file
|
||||
rm "$zipPath"
|
||||
for arg in "$@"; do
|
||||
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"
|
||||
|
||||
# Copy $hash to clipboard
|
||||
if [[ $OS == "Windows" || $OS == "WSL" || $OS == "WSL2" ]];then
|
||||
echo -n "$hash" | clip.exe
|
||||
elif [[ $OS == "Mac" ]];then
|
||||
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
|
||||
(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
|
||||
hash=
|
||||
fi
|
||||
|
||||
|
||||
openPairDrop
|
||||
exit
|
||||
}
|
||||
|
@ -165,31 +320,32 @@ sendFiles()
|
|||
# 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';
|
||||
SCRIPTPATH="${BASH_SOURCE[0]:-$0}";
|
||||
script_path="${BASH_SOURCE[0]:-$0}";
|
||||
|
||||
while [ -h "$SCRIPTPATH" ];
|
||||
while [ -h "$script_path" ];
|
||||
do
|
||||
cd "$( dirname -- "$SCRIPTPATH"; )";
|
||||
SCRIPTPATH="$( readlink -f -- "$SCRIPTPATH"; )";
|
||||
cd "$( dirname -- "$script_path"; )";
|
||||
script_path="$( readlink -f -- "$script_path"; )";
|
||||
done
|
||||
|
||||
cd "$( dirname -- "$SCRIPTPATH"; )" > '/dev/null';
|
||||
SCRIPTPATH="$( pwd; )";
|
||||
cd "$( dirname -- "$script_path"; )" > '/dev/null';
|
||||
script_path="$( pwd; )";
|
||||
popd > '/dev/null';
|
||||
|
||||
CONFIGPATH="${SCRIPTPATH}/.pairdrop-cli-config"
|
||||
config_path="${script_path}/.pairdrop-cli-config"
|
||||
|
||||
[ ! -f "$CONFIGPATH" ] &&
|
||||
specifyDomain "https://pairdrop.net/" &&
|
||||
[ ! -f "$CONFIGPATH" ] &&
|
||||
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 "$config_path" ] &&
|
||||
specifyDomain "https://pairdrop.net/" &&
|
||||
[ ! -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"
|
||||
|
||||
[ ! -f "$CONFIGPATH" ] || export "$(grep -v '^#' "$CONFIGPATH" | xargs)"
|
||||
[ ! -f "$config_path" ] || export "$(grep -v '^#' "$config_path" | xargs)"
|
||||
|
||||
setOs
|
||||
|
||||
############################################################
|
||||
# Process the input options. Add options as needed. #
|
||||
############################################################
|
||||
|
@ -198,24 +354,23 @@ setOs
|
|||
[[ $# -eq 0 ]] && openPairDrop && exit
|
||||
|
||||
# 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
|
||||
case $option in
|
||||
d) # specify domain
|
||||
specifyDomain "$2"
|
||||
exit;;
|
||||
t) # Send text
|
||||
sendText
|
||||
exit;;
|
||||
h | ?) # display help and exit
|
||||
help
|
||||
exit;;
|
||||
esac
|
||||
case $option in
|
||||
d) # specify domain - show help and exit if too many arguments
|
||||
[[ $# -gt 2 ]] && help && exit
|
||||
specifyDomain "$2"
|
||||
exit;;
|
||||
t) # Send text - show help and exit if too many arguments
|
||||
[[ $# -gt 2 ]] && help && exit
|
||||
sendText
|
||||
exit;;
|
||||
h | ?) # display help and exit
|
||||
help
|
||||
exit;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Send file(s)
|
||||
# display help and exit if 2 arguments are given or if file does not exist
|
||||
[[ $# -eq 2 ]] || [[ ! -a $1 ]] && help && exit
|
||||
|
||||
sendFiles "$1"
|
||||
sendFiles "$@"
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/bash
|
||||
parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" || exit ; pwd -P )
|
||||
|
||||
cd "$parent_path" || exit
|
||||
|
||||
./pairdrop "$@"
|
|
@ -0,0 +1,3 @@
|
|||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
|
||||
& "$scriptDir\pairdrop.sh" $args
|
|
@ -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[@]}"
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 62 KiB |
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.5 KiB |
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 60 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 27 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 8.9 KiB |
Before Width: | Height: | Size: 232 KiB After Width: | Height: | Size: 269 KiB |
Before Width: | Height: | Size: 232 KiB After Width: | Height: | Size: 271 KiB |
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.4 MiB |
Before Width: | Height: | Size: 232 KiB After Width: | Height: | Size: 271 KiB |
Before Width: | Height: | Size: 1.7 MiB After Width: | Height: | Size: 2.5 MiB |
Before Width: | Height: | Size: 180 KiB After Width: | Height: | Size: 190 KiB |
Before Width: | Height: | Size: 201 KiB After Width: | Height: | Size: 231 KiB |
After Width: | Height: | Size: 221 KiB |
|
@ -40,7 +40,7 @@
|
|||
</head>
|
||||
|
||||
<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">
|
||||
<svg class="icon">
|
||||
<use xlink:href="#info-outline"></use>
|
||||
|
@ -95,20 +95,49 @@
|
|||
<use xlink:href="#public-room-icon"></use>
|
||||
</svg>
|
||||
</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>
|
||||
<!-- Center -->
|
||||
<div id="center" class="opacity-0">
|
||||
<!-- Peers -->
|
||||
<div class="x-peers-filler"></div>
|
||||
<x-peers class="center"></x-peers>
|
||||
<x-no-peers class="no-animation-on-load" data-i18n-key="instructions.no-peers" data-i18n-attrs="data-drop-bg">
|
||||
<x-peers class="center grow"></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">
|
||||
<h2 data-i18n-key="instructions.no-peers-title" data-i18n-attrs="text"></h2>
|
||||
<div data-i18n-key="instructions.no-peers-subtitle" data-i18n-attrs="text"></div>
|
||||
</x-no-peers>
|
||||
<x-instructions data-i18n-key="instructions.x-instructions" data-i18n-attrs="desktop mobile data-drop-peer data-drop-bg">
|
||||
<p id="paste-filename"></p>
|
||||
</x-instructions>
|
||||
<x-instructions class="fade-in" data-i18n-key="instructions.x-instructions" data-i18n-attrs="desktop mobile data-drop-peer data-drop-bg"></x-instructions>
|
||||
<div class="shr-panel panel column" hidden>
|
||||
<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>
|
||||
<span data-i18n-key="footer.traffic" 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">
|
||||
<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>
|
||||
<svg id="edit-pen" class="icon">
|
||||
<svg class="icon edit-pen">
|
||||
<use xlink:href="#edit-pen-icon"></use>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="discovery-wrapper row">
|
||||
<div class="discovery-wrapper panel border row">
|
||||
<div class="row center">
|
||||
<span data-i18n-key="footer.discovery" data-i18n-attrs="text"></span>
|
||||
</div>
|
||||
<div class="row center">
|
||||
<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-gradient 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>
|
||||
<div class="row center wrap">
|
||||
<span class="badge badge-room-ip" data-i18n-key="footer.on-this-network" data-i18n-attrs="text title"></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-room-public-id pointer" data-i18n-key="footer.public-room-devices" data-i18n-attrs="title" hidden>in room IAIAI</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -150,8 +179,8 @@
|
|||
<x-dialog id="language-select-dialog">
|
||||
<x-background class="full center">
|
||||
<x-paper shadow="2">
|
||||
<div class="row center">
|
||||
<h2 class="center" data-i18n-key="dialogs.language-selector-title" data-i18n-attrs="text"></h2>
|
||||
<div class="row center p2">
|
||||
<h2 class="dialog-title" data-i18n-key="dialogs.language-selector-title" data-i18n-attrs="text"></h2>
|
||||
</div>
|
||||
<div class="language-buttons">
|
||||
<button class="btn fw" data-i18n-key="dialogs.system-language" data-i18n-attrs="text"></button>
|
||||
|
@ -229,7 +258,7 @@
|
|||
<span>(Japanese)</span>
|
||||
</button>
|
||||
</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>
|
||||
</div>
|
||||
</x-paper>
|
||||
|
@ -240,12 +269,12 @@
|
|||
<form action="#">
|
||||
<x-background class="full center text-center">
|
||||
<x-paper shadow="2">
|
||||
<div class="row center">
|
||||
<h2 class="center" data-i18n-key="dialogs.pair-devices-title" data-i18n-attrs="text"></h2>
|
||||
<div class="row center p2">
|
||||
<h2 class="dialog-title" data-i18n-key="dialogs.pair-devices-title" data-i18n-attrs="text"></h2>
|
||||
</div>
|
||||
<div class="row center">
|
||||
<div class="row center p2">
|
||||
<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>
|
||||
<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>
|
||||
|
@ -259,7 +288,7 @@
|
|||
<span data-i18n-key="dialogs.hr-or" data-i18n-attrs="text"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row center">
|
||||
<div class="row center p2">
|
||||
<div class="column fw">
|
||||
<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>
|
||||
|
@ -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>
|
||||
</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="button" data-i18n-key="dialogs.cancel" data-i18n-attrs="text" close></button>
|
||||
</div>
|
||||
|
@ -285,8 +314,8 @@
|
|||
<form action="#">
|
||||
<x-background class="full center text-center">
|
||||
<x-paper shadow="2">
|
||||
<div class="row center">
|
||||
<h2 class="center" data-i18n-key="dialogs.edit-paired-devices-title" data-i18n-attrs="text"></h2>
|
||||
<div class="row center p2">
|
||||
<h2 class="dialog-title" data-i18n-key="dialogs.edit-paired-devices-title" data-i18n-attrs="text"></h2>
|
||||
</div>
|
||||
<div class="paired-devices-wrapper" data-i18n-key="dialogs.paired-devices-wrapper" data-i18n-attrs="data-empty"></div>
|
||||
<div class="font-subheading center">
|
||||
|
@ -296,7 +325,7 @@
|
|||
<span data-i18n-key="dialogs.auto-accept-instructions-2" data-i18n-attrs="text"></span>
|
||||
</p>
|
||||
</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>
|
||||
</div>
|
||||
</x-paper>
|
||||
|
@ -308,14 +337,12 @@
|
|||
<form action="#">
|
||||
<x-background class="full center text-center">
|
||||
<x-paper shadow="2">
|
||||
<div class="row center">
|
||||
<div class="column">
|
||||
<h2 class="center" data-i18n-key="dialogs.temporary-public-room-title" data-i18n-attrs="text"></h2>
|
||||
</div>
|
||||
<div class="row center p2">
|
||||
<h2 class="dialog-title" data-i18n-key="dialogs.temporary-public-room-title" data-i18n-attrs="text"></h2>
|
||||
</div>
|
||||
<div class="row center">
|
||||
<div class="row center p2">
|
||||
<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>
|
||||
<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>
|
||||
|
@ -329,7 +356,7 @@
|
|||
<span data-i18n-key="dialogs.hr-or" data-i18n-attrs="text"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row center">
|
||||
<div class="row center p2">
|
||||
<div class="column fw">
|
||||
<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>
|
||||
|
@ -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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="center row-reverse button-row">
|
||||
<button class="btn btn-rounded btn-grey" type="submit" data-i18n-key="dialogs.join" data-i18n-attrs="text" disabled></button>
|
||||
<div class="center row-reverse btn-row wrap">
|
||||
<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 leave-room" type="button" data-i18n-key="dialogs.leave" data-i18n-attrs="text"></button>
|
||||
</div>
|
||||
</x-paper>
|
||||
</x-background>
|
||||
|
@ -354,15 +383,13 @@
|
|||
<x-dialog id="receive-request-dialog">
|
||||
<x-background class="full center">
|
||||
<x-paper shadow="2">
|
||||
<div class="row center">
|
||||
<div class="column">
|
||||
<h2 class="center"></h2>
|
||||
</div>
|
||||
<div class="row center p2">
|
||||
<h2 class="dialog-title"></h2>
|
||||
</div>
|
||||
<div class="row center p1">
|
||||
<div class="row center p2">
|
||||
<div class="column center file-description">
|
||||
<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>
|
||||
</div>
|
||||
<div class="row file-name">
|
||||
|
@ -375,8 +402,8 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="center file-preview"></div>
|
||||
<div class="row-reverse center button-row">
|
||||
<button id="accept-request" class="btn btn-rounded btn-grey" title="ENTER" data-i18n-key="dialogs.accept" data-i18n-attrs="text" autofocus></button>
|
||||
<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 disabled></button>
|
||||
<button id="decline-request" class="btn btn-rounded btn-grey" title="ESCAPE" data-i18n-key="dialogs.decline" data-i18n-attrs="text"></button>
|
||||
</div>
|
||||
</x-paper>
|
||||
|
@ -386,15 +413,13 @@
|
|||
<x-dialog id="receive-file-dialog">
|
||||
<x-background class="full center">
|
||||
<x-paper shadow="2">
|
||||
<div class="row center">
|
||||
<div class="column">
|
||||
<h2 class="center"></h2>
|
||||
</div>
|
||||
<div class="row center p2">
|
||||
<h2 class="dialog-title"></h2>
|
||||
</div>
|
||||
<div class="row center p1">
|
||||
<div class="row center p2">
|
||||
<div class="column center file-description">
|
||||
<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>
|
||||
</div>
|
||||
<div class="row file-name">
|
||||
|
@ -407,9 +432,9 @@
|
|||
</div>
|
||||
</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="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>
|
||||
</div>
|
||||
</x-paper>
|
||||
|
@ -420,25 +445,23 @@
|
|||
<form action="#">
|
||||
<x-background class="full center">
|
||||
<x-paper shadow="2">
|
||||
<div class="row center">
|
||||
<div class="column">
|
||||
<h2 class="center" data-i18n-key="dialogs.send-message-title" data-i18n-attrs="text"></h2>
|
||||
</div>
|
||||
<div class="row center p2">
|
||||
<h2 class="dialog-title" data-i18n-key="dialogs.send-message-title" data-i18n-attrs="text"></h2>
|
||||
</div>
|
||||
<div class="row center p1 display-name-wrapper">
|
||||
<div class="row center p2 display-name-wrapper">
|
||||
<div class="column">
|
||||
<div class="text-center">
|
||||
<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 class="row p1">
|
||||
<div class="row p2">
|
||||
<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 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="button" title="ESCAPE" data-i18n-key="dialogs.cancel" data-i18n-attrs="text" close></button>
|
||||
</div>
|
||||
|
@ -450,34 +473,71 @@
|
|||
<x-dialog id="receive-text-dialog">
|
||||
<x-background class="full center">
|
||||
<x-paper shadow="2">
|
||||
<div class="row center">
|
||||
<h2 class="text-center" data-i18n-key="dialogs.receive-text-title" data-i18n-attrs="text"></h2>
|
||||
<div class="row center p2">
|
||||
<h2 class="dialog-title" class="text-center" data-i18n-key="dialogs.receive-text-title" data-i18n-attrs="text"></h2>
|
||||
</div>
|
||||
<div class="row center p1 display-name-wrapper">
|
||||
<div class="row center p2 display-name-wrapper">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row center p1">
|
||||
<div class="row center p2">
|
||||
<div class="column fw">
|
||||
<div id="text" class="textarea"></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="close" class="btn btn-rounded btn-grey" title="ESCAPE" data-i18n-key="dialogs.close" data-i18n-attrs="text"></button>
|
||||
</div>
|
||||
</x-paper>
|
||||
</x-background>
|
||||
</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 -->
|
||||
<x-dialog id="base64-paste-dialog">
|
||||
<x-background class="full center">
|
||||
<x-paper shadow="2">
|
||||
<button class="btn btn-rounded btn-grey center" id="base64-paste-btn" title="Paste"></button>
|
||||
<div class="textarea" placeholder="Paste here to send files" title="CMD/⌘ + V" contenteditable hidden></div>
|
||||
<div class="row-reverse center button-row">
|
||||
<div class="row center p2">
|
||||
<h2 class="dialog-title"></h2>
|
||||
</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>
|
||||
</div>
|
||||
</x-paper>
|
||||
|
@ -485,7 +545,14 @@
|
|||
</x-dialog>
|
||||
<!-- Toast -->
|
||||
<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>
|
||||
<!-- About Page -->
|
||||
<x-about id="about" class="full center column">
|
||||
|
@ -560,9 +627,9 @@
|
|||
<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>
|
||||
</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>
|
||||
</g>
|
||||
</symbol>
|
||||
<symbol id="homescreen">
|
||||
<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>
|
||||
|
@ -572,11 +639,13 @@
|
|||
<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>
|
||||
</symbol>
|
||||
<symbol id="icon-theme-auto" viewBox="0 0 24 24">
|
||||
<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>
|
||||
<symbol id="icon-theme-auto" 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>
|
||||
</symbol>
|
||||
<symbol id="icon-theme-light" viewBox="0 0 24 24">
|
||||
<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>
|
||||
<symbol id="icon-theme-light" 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>
|
||||
</symbol>
|
||||
<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>
|
||||
|
@ -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. -->
|
||||
<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 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>
|
||||
<!-- Scripts -->
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
"click-to-show": "اضغط للعرض"
|
||||
},
|
||||
"header": {
|
||||
"cancel-paste-mode": "تمّ",
|
||||
"cancel-share-mode": "تمّ",
|
||||
"theme-auto_title": "تكيٌف المظهر مع النظام",
|
||||
"install_title": "تثبيت PairDrop",
|
||||
"theme-dark_title": "إستخدام دائما المظهر المظلم",
|
||||
|
@ -70,12 +70,12 @@
|
|||
},
|
||||
"instructions": {
|
||||
"x-instructions_mobile": "انقر لإرسال الملفات أو انقر لفترة طويلة لإرسال رسالة",
|
||||
"click-to-send": "انقر للإرسال",
|
||||
"activate-paste-mode-and-other-files": "و{{count}} ملفات أخرى",
|
||||
"tap-to-send": "انقر للإرسال",
|
||||
"activate-paste-mode-base": "افتح PairDrop على الأجهزة الأخرى للإرسال",
|
||||
"x-instructions-share-mode_desktop": "انقر للإرسال",
|
||||
"activate-share-mode-and-other-files-plural": "و{{count}} ملفات أخرى",
|
||||
"x-instructions-share-mode_mobile": "انقر للإرسال",
|
||||
"activate-share-mode-base": "افتح PairDrop على الأجهزة الأخرى للإرسال",
|
||||
"no-peers-subtitle": "قم بإقران الأجهزة أو ادخل إلى غرفة عامة لتتمكن من إكتشافها على الشبكات الأخرى",
|
||||
"activate-paste-mode-shared-text": "النص المشترك",
|
||||
"activate-share-mode-shared-text": "النص المشترك",
|
||||
"x-instructions_desktop": "انقر لإرسال الملفات أو انقر بزر الماوس الأيمن لإرسال رسالة",
|
||||
"no-peers-title": "افتح PairDrop على الأجهزة الأخرى لإرسال الملفات",
|
||||
"x-instructions_data-drop-bg": "حرر لتحديد المستلم",
|
||||
|
@ -84,7 +84,7 @@
|
|||
},
|
||||
"peer-ui": {
|
||||
"processing": "مُعالجة …",
|
||||
"click-to-send-paste-mode": "انقر للإرسال {{descriptor}}",
|
||||
"click-to-send-share-mode": "انقر للإرسال {{descriptor}}",
|
||||
"click-to-send": "انقر لإرسال الملفات أو انقر بزر الماوس الأيمن لإرسال رسالة",
|
||||
"waiting": "يُرجى الإنتظار…",
|
||||
"connection-hash": "للتحقق من أمان التشفير الشامل، قم بمقارنة رقم الأمان هذا على كلا الجهازين",
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"theme-auto_title": "Systemstil verwenden",
|
||||
"theme-dark_title": "Immer dunklen Stil verwenden",
|
||||
"theme-light_title": "Immer hellen Stil verwenden",
|
||||
"cancel-paste-mode": "Fertig",
|
||||
"cancel-share-mode": "Fertig",
|
||||
"language-selector_title": "Sprache Wählen",
|
||||
"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_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",
|
||||
"click-to-send": "Klicke zum Senden von",
|
||||
"tap-to-send": "Tippe zum Senden von",
|
||||
"x-instructions-share-mode_desktop": "Klicke 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-bg": "Loslassen um Empfänger auszuwählen",
|
||||
"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-paste-mode-and-other-files": "und {{count}} anderen Dateien",
|
||||
"activate-paste-mode-shared-text": "freigegebenem Text"
|
||||
"activate-share-mode-base": "Öffne PairDrop auf anderen Geräten zum Senden von",
|
||||
"activate-share-mode-and-other-files-plural": "und {{count}} anderen Dateien",
|
||||
"activate-share-mode-shared-text": "freigegebenem Text"
|
||||
},
|
||||
"document-titles": {
|
||||
"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",
|
||||
"connection-hash": "Um die Ende-zu-Ende Verschlüsselung zu verifizieren, vergleiche die Sicherheitsnummer auf beiden Geräten",
|
||||
"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…",
|
||||
"processing": "Bearbeitung läuft…",
|
||||
"preparing": "Vorbereitung läuft…"
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
"pair-device_title": "Pair your devices permanently",
|
||||
"edit-paired-devices_title": "Edit paired devices",
|
||||
"join-public-room_title": "Join public room temporarily",
|
||||
"cancel-paste-mode": "Done"
|
||||
"cancel-share-mode": "Cancel",
|
||||
"edit-share-mode": "Edit"
|
||||
},
|
||||
"instructions": {
|
||||
"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_data-drop-peer": "Release to send to peer",
|
||||
"x-instructions_data-drop-bg": "Release to select recipient",
|
||||
"click-to-send": "Click to send",
|
||||
"tap-to-send": "Tap to send",
|
||||
"activate-paste-mode-base": "Open PairDrop on other devices to send",
|
||||
"activate-paste-mode-and-other-files": "and {{count}} other files",
|
||||
"activate-paste-mode-shared-text": "shared text",
|
||||
"x-instructions-share-mode_desktop": "Click to send {{descriptor}}",
|
||||
"x-instructions-share-mode_mobile": "Tap to send {{descriptor}}",
|
||||
"activate-share-mode-base": "Open PairDrop on other devices to send",
|
||||
"activate-share-mode-and-other-file": "and 1 other file",
|
||||
"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!"
|
||||
},
|
||||
"footer": {
|
||||
|
@ -56,6 +60,7 @@
|
|||
"cancel": "Cancel",
|
||||
"edit-paired-devices-title": "Edit Paired Devices",
|
||||
"unpair": "Unpair",
|
||||
"paired-device-removed": "Paired device removed.",
|
||||
"paired-devices-wrapper_data-empty": "No paired devices.",
|
||||
"auto-accept-instructions-1": "Activate",
|
||||
"auto-accept": "auto-accept",
|
||||
|
@ -76,9 +81,11 @@
|
|||
"send": "Send",
|
||||
"receive-text-title": "Message Received",
|
||||
"copy": "Copy",
|
||||
"base64-title-files": "Share Files",
|
||||
"base64-title-text": "Share Text",
|
||||
"base64-processing": "Processing…",
|
||||
"base64-tap-to-paste": "Tap here to paste {{type}}",
|
||||
"base64-paste-to-send": "Paste here to send {{type}}",
|
||||
"base64-tap-to-paste": "Tap here to share {{type}}",
|
||||
"base64-paste-to-send": "Paste clipboard here to share {{type}}",
|
||||
"base64-text": "text",
|
||||
"base64-files": "files",
|
||||
"file-other-description-image": "and 1 other image",
|
||||
|
@ -94,7 +101,12 @@
|
|||
"language-selector-title": "Set Language",
|
||||
"system-language": "System Language",
|
||||
"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": {
|
||||
"close-about_aria-label": "Close About PairDrop",
|
||||
|
@ -156,7 +168,7 @@
|
|||
"message-received-plural": "{{count}} Messages Received"
|
||||
},
|
||||
"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",
|
||||
"connection-hash": "To verify the security of the end-to-end encryption, compare this security number on both devices",
|
||||
"preparing": "Preparing…",
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"language-selector_title": "Configurar Idioma",
|
||||
"about_title": "Sobre PairDrop",
|
||||
"about_aria-label": "Abrir Sobre PairDrop",
|
||||
"cancel-paste-mode": "Listo",
|
||||
"cancel-share-mode": "Listo",
|
||||
"install_title": "Instalar PairDrop",
|
||||
"theme-dark_title": "Siempre usar tema oscuro",
|
||||
"pair-device_title": "Empareja tus dispositivos permanentemente",
|
||||
|
@ -73,12 +73,12 @@
|
|||
},
|
||||
"instructions": {
|
||||
"x-instructions_mobile": "Toque para enviar archivos o toque prologádamente para enviar un mensaje",
|
||||
"click-to-send": "Haga clic para enviar",
|
||||
"activate-paste-mode-and-other-files": "y {{count}} archivos diferentes",
|
||||
"tap-to-send": "Toca para enviar",
|
||||
"activate-paste-mode-base": "Abra PairDrop en otros dispositivos para enviar",
|
||||
"x-instructions-share-mode_desktop": "Haga clic para enviar",
|
||||
"activate-share-mode-and-other-files-plural": "y {{count}} archivos diferentes",
|
||||
"x-instructions-share-mode_mobile": "Toca 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",
|
||||
"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",
|
||||
"no-peers-title": "Abra PairDrop en otros dispositivos para enviar archivos",
|
||||
"x-instructions_data-drop-peer": "Liberar para enviar a un par",
|
||||
|
@ -87,7 +87,7 @@
|
|||
},
|
||||
"peer-ui": {
|
||||
"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",
|
||||
"waiting": "Esperando…",
|
||||
"connection-hash": "Para verificar la seguridad del cifrado de extremo a extremo, compare este número de seguridad en ambos dispositivos",
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
"pair-device_title": "Associez vos appareils de manière permanente",
|
||||
"edit-paired-devices_title": "Gérer les appareils couplés",
|
||||
"join-public-room_title": "Rejoindre temporairement la salle publique",
|
||||
"cancel-paste-mode": "Terminé"
|
||||
"cancel-share-mode": "Terminé"
|
||||
},
|
||||
"instructions": {
|
||||
"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_data-drop-peer": "Déposer pour envoyer au destinataire",
|
||||
"x-instructions_data-drop-bg": "Lâcher pour choisir le destinataire",
|
||||
"click-to-send": "Cliquez pour envoyer",
|
||||
"tap-to-send": "Appuyez pour envoyer",
|
||||
"activate-paste-mode-base": "Ouvrez PairDrop sur d'autres appareils pour envoyer",
|
||||
"activate-paste-mode-and-other-files": "et {{count}} autres fichiers",
|
||||
"activate-paste-mode-shared-text": "texte partagé"
|
||||
"x-instructions-share-mode_desktop": "Cliquez pour envoyer",
|
||||
"x-instructions-share-mode_mobile": "Appuyez pour envoyer",
|
||||
"activate-share-mode-base": "Ouvrez PairDrop sur d'autres appareils pour envoyer",
|
||||
"activate-share-mode-and-other-files-plural": "et {{count}} autres fichiers",
|
||||
"activate-share-mode-shared-text": "texte partagé"
|
||||
},
|
||||
"footer": {
|
||||
"known-as": "Vous êtes connu comme :",
|
||||
|
@ -149,7 +149,7 @@
|
|||
"message-received-plural": "{{count}} Messages reçus"
|
||||
},
|
||||
"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",
|
||||
"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…",
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
"room-url-copied-to-clipboard": "Tautan ke ruang publik disalin ke papan klip"
|
||||
},
|
||||
"header": {
|
||||
"cancel-paste-mode": "Selesai",
|
||||
"cancel-share-mode": "Selesai",
|
||||
"theme-auto_title": "Sesuaikan tema dengan sistem",
|
||||
"install_title": "Instal PairDrop",
|
||||
"theme-dark_title": "Selalu gunakan tema gelap",
|
||||
|
@ -73,12 +73,12 @@
|
|||
},
|
||||
"instructions": {
|
||||
"x-instructions_mobile": "Ketuk untuk mengirim file atau ketuk lama untuk mengirim pesan",
|
||||
"click-to-send": "Klik untuk mengirim",
|
||||
"activate-paste-mode-and-other-files": "dan {{count}} file lainnya",
|
||||
"tap-to-send": "Ketuk untuk mengirim",
|
||||
"activate-paste-mode-base": "Buka PairDrop di perangkat lain untuk berkirim",
|
||||
"x-instructions-share-mode_desktop": "Klik untuk mengirim",
|
||||
"activate-share-mode-and-other-files-plural": "dan {{count}} file lainnya",
|
||||
"x-instructions-share-mode_mobile": "Ketuk untuk mengirim",
|
||||
"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",
|
||||
"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",
|
||||
"no-peers-title": "Buka PairDrop di perangkat lain untuk berkirim file",
|
||||
"x-instructions_data-drop-peer": "Lepaskan untuk mengirim ke rekan",
|
||||
|
@ -87,7 +87,7 @@
|
|||
},
|
||||
"peer-ui": {
|
||||
"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",
|
||||
"waiting": "Menunggu…",
|
||||
"connection-hash": "Untuk memverifikasi keamanan enkripsi end-to-end, bandingkan nomor keamanan ini pada kedua perangkat",
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
"known-as": "Sei visibile come:"
|
||||
},
|
||||
"header": {
|
||||
"cancel-paste-mode": "Fatto",
|
||||
"cancel-share-mode": "Fatto",
|
||||
"theme-auto_title": "Adatta il tema al sistema automaticamente",
|
||||
"install_title": "Installa PairDrop",
|
||||
"theme-dark_title": "Usa sempre il tema scuro",
|
||||
|
@ -30,12 +30,12 @@
|
|||
},
|
||||
"instructions": {
|
||||
"x-instructions_mobile": "Tocca per inviare file o tocco prolungato per inviare un messaggio",
|
||||
"click-to-send": "Clicca per inviare",
|
||||
"activate-paste-mode-and-other-files": "e altri {{count}} files",
|
||||
"tap-to-send": "Tocca per inviare",
|
||||
"activate-paste-mode-base": "Apri PairDrop su altri dispositivi per inviare",
|
||||
"x-instructions-share-mode_desktop": "Clicca per inviare",
|
||||
"activate-share-mode-and-other-files-plural": "e altri {{count}} files",
|
||||
"x-instructions-share-mode_mobile": "Tocca 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",
|
||||
"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",
|
||||
"no-peers-title": "Apri PairDrop su altri dispositivi per inviare files",
|
||||
"x-instructions_data-drop-peer": "Rilascia per inviare al peer",
|
||||
|
@ -139,7 +139,7 @@
|
|||
},
|
||||
"peer-ui": {
|
||||
"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",
|
||||
"waiting": "In attesa…",
|
||||
"connection-hash": "Per verificare la sicurezza della crittografia end-to-end, confronta questo numero di sicurezza su entrambi i dispositivi",
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
"room-url-copied-to-clipboard": "パブリックルームへのリンクをクリップボードにコピーしました"
|
||||
},
|
||||
"header": {
|
||||
"cancel-paste-mode": "完了",
|
||||
"cancel-share-mode": "完了",
|
||||
"theme-auto_title": "テーマをシステムの設定に自動的に合わせる",
|
||||
"install_title": "PairDropをインストール",
|
||||
"theme-dark_title": "常にダークテーマを使用する",
|
||||
|
@ -73,12 +73,12 @@
|
|||
},
|
||||
"instructions": {
|
||||
"x-instructions_mobile": "タップしてファイルを送信または長押ししてメッセージを送信します",
|
||||
"click-to-send": "クリックして送信",
|
||||
"activate-paste-mode-and-other-files": "とその他{{count}}個のファイル",
|
||||
"tap-to-send": "タップして送信",
|
||||
"activate-paste-mode-base": "他のデバイスでPairDropを開いて送信します",
|
||||
"x-instructions-share-mode_desktop": "クリックして送信",
|
||||
"activate-share-mode-and-other-files-plural": "とその他{{count}}個のファイル",
|
||||
"x-instructions-share-mode_mobile": "タップして送信",
|
||||
"activate-share-mode-base": "他のデバイスでPairDropを開いて送信します",
|
||||
"no-peers-subtitle": "デバイスをペア設定するかパブリックルームに参加すると、他のネットワーク上からも見つけられるようになります",
|
||||
"activate-paste-mode-shared-text": "共有されたテキスト",
|
||||
"activate-share-mode-shared-text": "共有されたテキスト",
|
||||
"x-instructions_desktop": "左クリックしてファイルを送信または右クリックしてメッセージを送信します",
|
||||
"no-peers-title": "他のデバイスでPairDropを開いてファイルを送信します",
|
||||
"x-instructions_data-drop-peer": "離すとこの相手に送信します",
|
||||
|
@ -87,7 +87,7 @@
|
|||
},
|
||||
"peer-ui": {
|
||||
"processing": "処理中…",
|
||||
"click-to-send-paste-mode": "クリックして{{descriptor}}を送信",
|
||||
"click-to-send-share-mode": "クリックして{{descriptor}}を送信",
|
||||
"click-to-send": "クリックしてファイルを送信または右クリックしてメッセージを送信します",
|
||||
"waiting": "待機中…",
|
||||
"connection-hash": "エンドツーエンド暗号化のセキュリティを確認するには、両方のデバイスのセキュリティナンバーを確認します",
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
"theme-light_title": "Alltid bruk lys drakt",
|
||||
"theme-dark_title": "Alltid bruk mørk drakt",
|
||||
"notification_title": "Skru på merknader",
|
||||
"cancel-paste-mode": "Ferdig",
|
||||
"cancel-share-mode": "Ferdig",
|
||||
"install_title": "Installer PairDrop",
|
||||
"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_mobile": "Trykk for å sende filer, eller lang-trykk for å sende en melding",
|
||||
"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-title": "Åpne PairDrop på andre enheter for å sende filer",
|
||||
"no-peers-subtitle": "Sammenkoble enheter for å kunne oppdages på andre nettverk",
|
||||
"x-instructions_data-drop-peer": "Slipp for å sende til likemann",
|
||||
"tap-to-send": "Trykk for å sende",
|
||||
"activate-paste-mode-base": "Åpne PairDrop på andre enheter for å sende",
|
||||
"activate-paste-mode-and-other-files": "og {{count}} andre filer",
|
||||
"activate-paste-mode-shared-text": "delt tekst"
|
||||
"x-instructions-share-mode_mobile": "Trykk for å sende",
|
||||
"activate-share-mode-base": "Åpne PairDrop på andre enheter for å sende",
|
||||
"activate-share-mode-and-other-files-plural": "og {{count}} andre filer",
|
||||
"activate-share-mode-shared-text": "delt tekst"
|
||||
},
|
||||
"dialogs": {
|
||||
"input-key-on-this-device": "Skriv inn denne nøkkelen på en annen enhet",
|
||||
|
@ -132,7 +132,7 @@
|
|||
"processing": "Behandler …",
|
||||
"transferring": "Overfører …",
|
||||
"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."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,7 +55,7 @@
|
|||
"click-to-show": "Klik om te tonen"
|
||||
},
|
||||
"header": {
|
||||
"cancel-paste-mode": "Klaar",
|
||||
"cancel-share-mode": "Klaar",
|
||||
"theme-auto_title": "Gebruik systeemstijl",
|
||||
"install_title": "PairDrop installeren",
|
||||
"theme-dark_title": "Altijd donkere modus gebruiken",
|
||||
|
@ -70,12 +70,12 @@
|
|||
},
|
||||
"instructions": {
|
||||
"x-instructions_mobile": "Tik om bestanden te versturen of houdt vast om een bericht te sturen",
|
||||
"click-to-send": "Klik om te verzenden",
|
||||
"activate-paste-mode-and-other-files": "en {{count}} andere bestanden",
|
||||
"tap-to-send": "Tik om te verzenden",
|
||||
"activate-paste-mode-base": "Open PairDrop op andere apparaten om te verzenden",
|
||||
"x-instructions-share-mode_desktop": "Klik om te verzenden",
|
||||
"activate-share-mode-and-other-files-plural": "en {{count}} andere bestanden",
|
||||
"x-instructions-share-mode_mobile": "Tik 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",
|
||||
"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",
|
||||
"no-peers-title": "Open PairDrop op andere apparaten om bestanden te versturen",
|
||||
"x-instructions_data-drop-peer": "Laat los om naar peer te versturen",
|
||||
|
@ -84,7 +84,7 @@
|
|||
},
|
||||
"peer-ui": {
|
||||
"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",
|
||||
"waiting": "Wachten…",
|
||||
"connection-hash": "Vergelijk dit veiligheidsnummer op beide apparaten, om de beveiliging van de eind-tot-eind versleuteling te verifiëren",
|
||||
|
|
|
@ -58,7 +58,7 @@
|
|||
"room-url-copied-to-clipboard": "Link către sala publică copiat în clipboard"
|
||||
},
|
||||
"header": {
|
||||
"cancel-paste-mode": "Gata",
|
||||
"cancel-share-mode": "Gata",
|
||||
"theme-auto_title": "Adaptează tema la sistem",
|
||||
"install_title": "Instalează PairDrop",
|
||||
"theme-dark_title": "Utilizați mereu tema întunecoasă",
|
||||
|
@ -73,12 +73,12 @@
|
|||
},
|
||||
"instructions": {
|
||||
"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",
|
||||
"activate-paste-mode-and-other-files": "și {{count}} alte fișiere",
|
||||
"tap-to-send": "Atinge pentru a trimite",
|
||||
"activate-paste-mode-base": "Deschideți PairDrop pe alte dispozitive pentru a trimite",
|
||||
"x-instructions-share-mode_desktop": "Clic pentru a trimite",
|
||||
"activate-share-mode-and-other-files-plural": "și {{count}} alte fișiere",
|
||||
"x-instructions-share-mode_mobile": "Atinge 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",
|
||||
"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",
|
||||
"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",
|
||||
|
@ -87,7 +87,7 @@
|
|||
},
|
||||
"peer-ui": {
|
||||
"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",
|
||||
"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",
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"about_aria-label": "Открыть страницу \"О сервисе\"",
|
||||
"pair-device_title": "Связать ваши устройства навсегда",
|
||||
"install_title": "Установить PairDrop",
|
||||
"cancel-paste-mode": "Выполнено",
|
||||
"cancel-share-mode": "Выполнено",
|
||||
"edit-paired-devices_title": "Редактировать связанные устройства",
|
||||
"notification_title": "Включить уведомления",
|
||||
"about_title": "О сервисе",
|
||||
|
@ -16,16 +16,16 @@
|
|||
"instructions": {
|
||||
"x-instructions_desktop": "Нажмите, чтобы отправить файлы, или щелкните правой кнопкой мыши, чтобы отправить сообщение",
|
||||
"no-peers_data-drop-bg": "Отпустите, чтобы выбрать получателя",
|
||||
"click-to-send": "Нажмите, чтобы отправить",
|
||||
"x-instructions-share-mode_desktop": "Нажмите, чтобы отправить",
|
||||
"x-instructions_data-drop-bg": "Отпустите, чтобы выбрать получателя",
|
||||
"tap-to-send": "Прикоснитесь, чтобы отправить",
|
||||
"x-instructions-share-mode_mobile": "Прикоснитесь, чтобы отправить",
|
||||
"x-instructions_data-drop-peer": "Отпустите, чтобы послать узлу",
|
||||
"x-instructions_mobile": "Прикоснитесь коротко, чтобы отправить файлы, или долго, чтобы отправить сообщение",
|
||||
"no-peers-title": "Откройте PairDrop на других устройствах, чтобы отправить файлы",
|
||||
"no-peers-subtitle": "Свяжите устройства или войдите в публичную комнату, чтобы вас могли обнаружить из других сетей",
|
||||
"activate-paste-mode-and-other-files": "и {{count}} других файлов",
|
||||
"activate-paste-mode-base": "Откройте PairDrop на других устройствах, чтобы отправить",
|
||||
"activate-paste-mode-shared-text": "общий текст"
|
||||
"activate-share-mode-and-other-files-plural": "и {{count}} других файлов",
|
||||
"activate-share-mode-base": "Откройте PairDrop на других устройствах, чтобы отправить",
|
||||
"activate-share-mode-shared-text": "общий текст"
|
||||
},
|
||||
"footer": {
|
||||
"display-name_data-placeholder": "Загрузка…",
|
||||
|
@ -148,7 +148,7 @@
|
|||
"room-url-copied-to-clipboard": "Ссылка на публичную комнату была скопирована в буфер обмена"
|
||||
},
|
||||
"peer-ui": {
|
||||
"click-to-send-paste-mode": "Нажмите, чтобы отправить {{descriptor}}",
|
||||
"click-to-send-share-mode": "Нажмите, чтобы отправить {{descriptor}}",
|
||||
"preparing": "Подготовка…",
|
||||
"transferring": "Передача…",
|
||||
"processing": "Обработка…",
|
||||
|
|
|
@ -9,19 +9,19 @@
|
|||
"install_title": "PairDrop'u Yükle",
|
||||
"pair-device_title": "Cihazı kalıcı olarak eşle",
|
||||
"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",
|
||||
"language-selector_title": "Dili Seç"
|
||||
},
|
||||
"instructions": {
|
||||
"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",
|
||||
"click-to-send": "Göndermek için tıkla",
|
||||
"activate-paste-mode-and-other-files": "ve {{count}} diğer dosya",
|
||||
"tap-to-send": "Göndermek için dokun",
|
||||
"activate-paste-mode-base": "Göndermek için diğer cihazlarda PairDrop'u açın",
|
||||
"x-instructions-share-mode_desktop": "Göndermek için tıkla",
|
||||
"activate-share-mode-and-other-files-plural": "ve {{count}} diğer dosya",
|
||||
"x-instructions-share-mode_mobile": "Göndermek için dokun",
|
||||
"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",
|
||||
"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",
|
||||
"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",
|
||||
|
@ -139,7 +139,7 @@
|
|||
},
|
||||
"peer-ui": {
|
||||
"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",
|
||||
"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",
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
"theme-dark_title": "总是使用暗黑主题",
|
||||
"notification_title": "开启通知",
|
||||
"edit-paired-devices_title": "管理已配对设备",
|
||||
"cancel-paste-mode": "完成",
|
||||
"cancel-share-mode": "完成",
|
||||
"join-public-room_title": "暂时加入公共房间",
|
||||
"language-selector_title": "设置语言"
|
||||
},
|
||||
|
@ -21,11 +21,11 @@
|
|||
"x-instructions_desktop": "点击以发送文件 或 右键来发送信息",
|
||||
"x-instructions_mobile": "轻触以发送文件 或 长按来发送信息",
|
||||
"x-instructions_data-drop-bg": "释放来选择接收者",
|
||||
"click-to-send": "点击发送",
|
||||
"tap-to-send": "轻触发送",
|
||||
"activate-paste-mode-base": "在其他设备上打开 PairDrop 来发送",
|
||||
"activate-paste-mode-and-other-files": "和 {{count}} 个其他的文件",
|
||||
"activate-paste-mode-shared-text": "分享文本"
|
||||
"x-instructions-share-mode_desktop": "点击发送",
|
||||
"x-instructions-share-mode_mobile": "轻触发送",
|
||||
"activate-share-mode-base": "在其他设备上打开 PairDrop 来发送",
|
||||
"activate-share-mode-and-other-files-plural": "和 {{count}} 个其他的文件",
|
||||
"activate-share-mode-shared-text": "分享文本"
|
||||
},
|
||||
"footer": {
|
||||
"routed": "途径服务器",
|
||||
|
@ -155,7 +155,7 @@
|
|||
"image-transfer-requested": "图片传输请求"
|
||||
},
|
||||
"peer-ui": {
|
||||
"click-to-send-paste-mode": "点击发送 {{descriptor}}",
|
||||
"click-to-send-share-mode": "点击发送 {{descriptor}}",
|
||||
"click-to-send": "点击以发送文件 或 右键来发送信息",
|
||||
"connection-hash": "若要验证端到端加密的安全性,请在两个设备上比较此安全编号",
|
||||
"preparing": "准备中…",
|
||||
|
|
|
@ -65,6 +65,11 @@
|
|||
"src": "images/pairdrop_screenshot_mobile_7.png",
|
||||
"sizes": "1170x2532",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "images/pairdrop_screenshot_mobile_8.png",
|
||||
"sizes": "1170x2532",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"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": {
|
||||
"client_mode": "focus-existing"
|
||||
}
|
||||
|
|
|
@ -19,42 +19,42 @@ class BrowserTabsConnector {
|
|||
}
|
||||
|
||||
static peerIsSameBrowser(peerId) {
|
||||
let peerIdsBrowser = JSON.parse(localStorage.getItem("peer_ids_browser"));
|
||||
let peerIdsBrowser = JSON.parse(localStorage.getItem('peer_ids_browser'));
|
||||
return peerIdsBrowser
|
||||
? peerIdsBrowser.indexOf(peerId) !== -1
|
||||
: false;
|
||||
}
|
||||
|
||||
static async addPeerIdToLocalStorage() {
|
||||
const peerId = sessionStorage.getItem("peer_id");
|
||||
const peerId = sessionStorage.getItem('peer_id');
|
||||
if (!peerId) return false;
|
||||
|
||||
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);
|
||||
peerIdsBrowser.push(peerId);
|
||||
peerIdsBrowser = peerIdsBrowser.filter(onlyUnique);
|
||||
localStorage.setItem("peer_ids_browser", JSON.stringify(peerIdsBrowser));
|
||||
localStorage.setItem('peer_ids_browser', JSON.stringify(peerIdsBrowser));
|
||||
|
||||
return peerIdsBrowser;
|
||||
}
|
||||
|
||||
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);
|
||||
peerIdsBrowser.splice(index, 1);
|
||||
localStorage.setItem("peer_ids_browser", JSON.stringify(peerIdsBrowser));
|
||||
localStorage.setItem('peer_ids_browser', JSON.stringify(peerIdsBrowser));
|
||||
return peerId;
|
||||
}
|
||||
|
||||
|
||||
static async removeOtherPeerIdsFromLocalStorage() {
|
||||
const peerId = sessionStorage.getItem("peer_id");
|
||||
const peerId = sessionStorage.getItem('peer_id');
|
||||
if (!peerId) return false;
|
||||
|
||||
let peerIdsBrowser = [peerId];
|
||||
localStorage.setItem("peer_ids_browser", JSON.stringify(peerIdsBrowser));
|
||||
localStorage.setItem('peer_ids_browser', JSON.stringify(peerIdsBrowser));
|
||||
return peerIdsBrowser;
|
||||
}
|
||||
}
|
|
@ -2,33 +2,30 @@ class Localization {
|
|||
constructor() {
|
||||
Localization.defaultLocale = "en";
|
||||
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.defaultTranslations = {};
|
||||
|
||||
Localization.systemLocale = Localization.getSupportedOrDefault(navigator.languages);
|
||||
|
||||
let storedLanguageCode = localStorage.getItem("language-code");
|
||||
let storedLanguageCode = localStorage.getItem('language_code');
|
||||
|
||||
Localization.initialLocale = storedLanguageCode && Localization.isSupported(storedLanguageCode)
|
||||
? storedLanguageCode
|
||||
: Localization.systemLocale;
|
||||
|
||||
Localization
|
||||
.setTranslation(Localization.initialLocale)
|
||||
.then(_ => {
|
||||
console.log("Initial translation successful.");
|
||||
Events.fire("initial-translation-loaded");
|
||||
});
|
||||
}
|
||||
|
||||
static isSupported(locale) {
|
||||
return Localization.supportedLocales.indexOf(locale) > -1;
|
||||
}
|
||||
|
||||
static isRTLLanguage(locale) {
|
||||
return Localization.supportedLocalesRTL.indexOf(locale) > -1;
|
||||
static isRtlLanguage(locale) {
|
||||
return Localization.supportedLocalesRtl.indexOf(locale) > -1;
|
||||
}
|
||||
|
||||
static isCurrentLocaleRtl() {
|
||||
return Localization.isRtlLanguage(Localization.locale);
|
||||
}
|
||||
|
||||
static getSupportedOrDefault(locales) {
|
||||
|
@ -41,6 +38,10 @@ class Localization {
|
|||
|| Localization.defaultLocale;
|
||||
}
|
||||
|
||||
async setInitialTranslation() {
|
||||
await Localization.setTranslation(Localization.initialLocale)
|
||||
}
|
||||
|
||||
static async setTranslation(locale) {
|
||||
if (!locale) locale = Localization.systemLocale;
|
||||
|
||||
|
@ -49,7 +50,7 @@ class Localization {
|
|||
|
||||
const htmlRootNode = document.querySelector('html');
|
||||
|
||||
if (Localization.isRTLLanguage(locale)) {
|
||||
if (Localization.isRtlLanguage(locale)) {
|
||||
htmlRootNode.setAttribute('dir', 'rtl');
|
||||
}
|
||||
else {
|
||||
|
@ -85,7 +86,7 @@ class Localization {
|
|||
}
|
||||
|
||||
static isSystemLocale() {
|
||||
return !localStorage.getItem('language-code');
|
||||
return !localStorage.getItem('language_code');
|
||||
}
|
||||
|
||||
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(".");
|
||||
|
||||
let translationCandidates = useDefault
|
||||
|
@ -142,27 +143,45 @@ class Localization {
|
|||
translation = translationCandidates[lastKey];
|
||||
|
||||
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) {
|
||||
console.error(e);
|
||||
translation = "";
|
||||
}
|
||||
|
||||
if (!translation) {
|
||||
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(`Translate this string here: https://hosted.weblate.org/browse/pairdrop/pairdrop-spa/${Localization.locale.toLowerCase()}/?q=${key}`)
|
||||
console.log("Help translating PairDrop: https://hosted.weblate.org/engage/pairdrop/");
|
||||
Localization.logHelpCallKey(key);
|
||||
Localization.logHelpCall();
|
||||
translation = this.getTranslation(key, attr, data, true);
|
||||
}
|
||||
else {
|
||||
console.warn("Missing translation in default language:", key, attr);
|
||||
Localization.logHelpCall();
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
let div = document.createElement('div');
|
||||
div.innerText = unsafeText;
|
||||
|
|
|
@ -1,39 +1,69 @@
|
|||
class PairDrop {
|
||||
|
||||
constructor() {
|
||||
this.$header = $$('header.opacity-0');
|
||||
this.$center = $$('#center');
|
||||
this.$footer = $$('footer');
|
||||
this.$xNoPeers = $$('x-no-peers');
|
||||
this.$headerNotificationButton = $('notification');
|
||||
this.$editPairedDevicesHeaderBtn = $('edit-paired-devices');
|
||||
this.$footerInstructionsPairedDevices = $$('.discovery-wrapper .badge-room-secret');
|
||||
this.$head = $$('head');
|
||||
this.$installBtn = $('install');
|
||||
this.$headerNotificationBtn = $('notification');
|
||||
this.$headerEditPairedDevicesBtn = $('edit-paired-devices');
|
||||
this.$footerPairedDevicesBadge = $$('.discovery-wrapper .badge-room-secret');
|
||||
this.$headerInstallBtn = $('install');
|
||||
|
||||
this.deferredStyles = [
|
||||
"styles/deferred-styles.css"
|
||||
];
|
||||
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();
|
||||
|
||||
Events.on('beforeinstallprompt', e => this.onPwaInstallable(e));
|
||||
|
||||
const persistentStorage = new PersistentStorage();
|
||||
const themeUI = new ThemeUI();
|
||||
const backgroundCanvas = new BackgroundCanvas();
|
||||
this.persistentStorage = new PersistentStorage();
|
||||
this.localization = new Localization();
|
||||
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', _ => {
|
||||
// FooterUI needs translations
|
||||
const footerUI = new FooterUI();
|
||||
this.initialize()
|
||||
.then(_ => {
|
||||
console.log("Initialization completed.");
|
||||
});
|
||||
}
|
||||
|
||||
Events.on('fade-in-ui', _ => this.fadeInUI())
|
||||
Events.on('fade-in-header', _ => this.fadeInHeader())
|
||||
async initialize() {
|
||||
// Translate page before fading in
|
||||
await this.localization.setInitialTranslation()
|
||||
console.log("Initial translation successful.");
|
||||
|
||||
// Evaluate UI elements and fade in UI
|
||||
this.evaluateUI();
|
||||
// Show "Loading..." until connected to WsServer
|
||||
await this.footerUI.showLoading();
|
||||
|
||||
// Load deferred assets
|
||||
this.loadDeferredAssets();
|
||||
});
|
||||
// Evaluate css shifting UI elements and fade in UI elements
|
||||
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
|
||||
const localization = new Localization();
|
||||
// Load deferred assets
|
||||
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() {
|
||||
|
@ -50,130 +80,148 @@ class PairDrop {
|
|||
onPwaInstallable(e) {
|
||||
if (!window.matchMedia('(display-mode: minimal-ui)').matches) {
|
||||
// only display install btn when not installed
|
||||
this.$installBtn.removeAttribute('hidden');
|
||||
this.$installBtn.addEventListener('click', () => {
|
||||
this.$installBtn.setAttribute('hidden', true);
|
||||
this.$headerInstallBtn.removeAttribute('hidden');
|
||||
this.$headerInstallBtn.addEventListener('click', () => {
|
||||
this.$headerInstallBtn.setAttribute('hidden', true);
|
||||
e.prompt();
|
||||
});
|
||||
}
|
||||
return e.preventDefault();
|
||||
}
|
||||
|
||||
evaluateUI() {
|
||||
async evaluatePermissionsAndRoomSecrets() {
|
||||
// Check whether notification permissions have already been granted
|
||||
if ('Notification' in window && Notification.permission !== 'granted') {
|
||||
this.$headerNotificationButton.removeAttribute('hidden');
|
||||
this.$headerNotificationBtn.removeAttribute('hidden');
|
||||
}
|
||||
|
||||
PersistentStorage
|
||||
.getAllRoomSecrets()
|
||||
.then(roomSecrets => {
|
||||
if (roomSecrets.length > 0) {
|
||||
this.$editPairedDevicesHeaderBtn.removeAttribute('hidden');
|
||||
this.$footerInstructionsPairedDevices.removeAttribute('hidden');
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
Events.fire('evaluate-footer-badges');
|
||||
Events.fire('fade-in-header');
|
||||
});
|
||||
let roomSecrets = await PersistentStorage.getAllRoomSecrets();
|
||||
if (roomSecrets.length > 0) {
|
||||
this.$headerEditPairedDevicesBtn.removeAttribute('hidden');
|
||||
this.$footerPairedDevicesBadge.removeAttribute('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
fadeInUI() {
|
||||
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() {
|
||||
async loadDeferredAssets() {
|
||||
console.log("Load deferred assets");
|
||||
this.deferredStyles = [
|
||||
"styles/deferred-styles.css"
|
||||
];
|
||||
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);
|
||||
for (const url of this.deferredStyles) {
|
||||
await this.loadAndApplyStylesheet(url);
|
||||
}
|
||||
this.onAssetLoaded();
|
||||
}
|
||||
|
||||
onScriptLoaded(url) {
|
||||
// remove entry from array
|
||||
const index = this.deferredScripts.indexOf(url);
|
||||
if (index !== -1) {
|
||||
this.deferredScripts.splice(index, 1);
|
||||
for (const url of this.deferredScripts) {
|
||||
await this.loadAndApplyScript(url);
|
||||
}
|
||||
this.onAssetLoaded();
|
||||
}
|
||||
|
||||
onAssetLoaded() {
|
||||
if (this.deferredScripts.length || this.deferredStyles.length) return;
|
||||
loadStyleSheet(url) {
|
||||
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.");
|
||||
|
||||
this.hydrate();
|
||||
document.head.appendChild(stylesheet);
|
||||
});
|
||||
}
|
||||
|
||||
hydrate() {
|
||||
const peersUI = new PeersUI();
|
||||
const languageSelectDialog = new LanguageSelectDialog();
|
||||
const receiveFileDialog = new ReceiveFileDialog();
|
||||
const receiveRequestDialog = new ReceiveRequestDialog();
|
||||
const sendTextDialog = new SendTextDialog();
|
||||
const receiveTextDialog = new ReceiveTextDialog();
|
||||
const pairDeviceDialog = new PairDeviceDialog();
|
||||
const clearDevicesDialog = new EditPairedDevicesDialog();
|
||||
const publicRoomDialog = new PublicRoomDialog();
|
||||
const base64ZipDialog = new Base64ZipDialog();
|
||||
const toast = new Toast();
|
||||
const notifications = new Notifications();
|
||||
const networkStatusUI = new NetworkStatusUI();
|
||||
const webShareTargetUI = new WebShareTargetUI();
|
||||
const webFileHandlersUI = new WebFileHandlersUI();
|
||||
const noSleepUI = new NoSleepUI();
|
||||
const broadCast = new BrowserTabsConnector();
|
||||
const server = new ServerConnection();
|
||||
const peers = new PeersManager(server);
|
||||
console.log("UI hydrated.")
|
||||
async loadAndApplyStylesheet(url) {
|
||||
try {
|
||||
await this.loadStyleSheet(url);
|
||||
console.log(`Stylesheet loaded successfully: ${url}`);
|
||||
} catch (error) {
|
||||
console.error('Error loading stylesheet:', error);
|
||||
}
|
||||
}
|
||||
|
||||
loadScript(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let script = document.createElement("script");
|
||||
script.src = url;
|
||||
script.onload = resolve;
|
||||
script.onerror = reject;
|
||||
|
||||
document.body.appendChild(script);
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -433,47 +433,6 @@ class Peer {
|
|||
: 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) {
|
||||
let header = [];
|
||||
let totalSize = 0;
|
||||
|
@ -493,7 +452,11 @@ class Peer {
|
|||
|
||||
let dataUrl = '';
|
||||
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'})
|
||||
|
@ -1118,20 +1081,9 @@ class PeersManager {
|
|||
this.peers[detail.to]._respondToFileTransferRequest(detail.accepted);
|
||||
}
|
||||
|
||||
_onFilesSelected(message) {
|
||||
let inputFiles = Array.from(message.files);
|
||||
delete message.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);
|
||||
async _onFilesSelected(message) {
|
||||
let files = await mime.addMissingMimeTypesToFiles(message.files);
|
||||
await this.peers[message.to].requestFileTransfer(files);
|
||||
}
|
||||
|
||||
_onSendText(message) {
|
||||
|
|
|
@ -4,7 +4,7 @@ class PersistentStorage {
|
|||
PersistentStorage.logBrowserNotCapable();
|
||||
return;
|
||||
}
|
||||
const DBOpenRequest = window.indexedDB.open('pairdrop_store', 4);
|
||||
const DBOpenRequest = window.indexedDB.open('pairdrop_store', 5);
|
||||
DBOpenRequest.onerror = e => {
|
||||
PersistentStorage.logBrowserNotCapable();
|
||||
console.log('Error initializing database: ');
|
||||
|
@ -13,7 +13,7 @@ class PersistentStorage {
|
|||
DBOpenRequest.onsuccess = _ => {
|
||||
console.log('Database initialised.');
|
||||
};
|
||||
DBOpenRequest.onupgradeneeded = e => {
|
||||
DBOpenRequest.onupgradeneeded = async e => {
|
||||
const db = e.target.result;
|
||||
const txn = e.target.transaction;
|
||||
|
||||
|
@ -42,6 +42,14 @@ class PersistentStorage {
|
|||
roomSecretsObjectStore4.createIndex('display_name', 'display_name');
|
||||
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);
|
||||
} catch (e) {
|
||||
this.logBrowserNotCapable();
|
||||
return 0;
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
||||
constructor() {
|
||||
this.$footer = $$('footer');
|
||||
this.$displayName = $('display-name');
|
||||
this.$discoveryWrapper = $$('footer .discovery-wrapper');
|
||||
|
||||
// Show "Loading…"
|
||||
this.$displayName.setAttribute('placeholder', this.$displayName.dataset.placeholder);
|
||||
|
||||
this.$displayName.addEventListener('keydown', e => this._onKeyDownDisplayName(e));
|
||||
this.$displayName.addEventListener('keyup', e => this._onKeyUpDisplayName(e));
|
||||
this.$displayName.addEventListener('blur', e => this._saveDisplayName(e.target.innerText));
|
||||
|
@ -133,7 +209,15 @@ class FooterUI {
|
|||
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) {
|
||||
this.$discoveryWrapper.classList.remove('row');
|
||||
this.$discoveryWrapper.classList.add('column');
|
||||
|
@ -143,17 +227,15 @@ class FooterUI {
|
|||
this.$discoveryWrapper.classList.add('row');
|
||||
}
|
||||
Events.fire('redraw-canvas');
|
||||
Events.fire('fade-in-ui');
|
||||
}
|
||||
|
||||
_loadSavedDisplayName() {
|
||||
this._getSavedDisplayName()
|
||||
.then(displayName => {
|
||||
console.log("Retrieved edited display name:", displayName)
|
||||
if (displayName) {
|
||||
Events.fire('self-display-name-changed', displayName);
|
||||
}
|
||||
});
|
||||
async _loadSavedDisplayName() {
|
||||
const displayName = await this._getSavedDisplayName()
|
||||
|
||||
if (!displayName) return;
|
||||
|
||||
console.log("Retrieved edited display name:", displayName)
|
||||
Events.fire('self-display-name-changed', displayName);
|
||||
}
|
||||
|
||||
_onDisplayName(displayName){
|
||||
|
@ -184,13 +266,13 @@ class FooterUI {
|
|||
if (newDisplayName === savedDisplayName) return;
|
||||
|
||||
if (newDisplayName) {
|
||||
PersistentStorage.set('editedDisplayName', newDisplayName)
|
||||
PersistentStorage.set('edited_display_name', newDisplayName)
|
||||
.then(_ => {
|
||||
Events.fire('notify-user', Localization.getTranslation("notifications.display-name-changed-permanently"));
|
||||
})
|
||||
.catch(_ => {
|
||||
console.log("This browser does not support IndexedDB. Use localStorage instead.");
|
||||
localStorage.setItem('editedDisplayName', newDisplayName);
|
||||
localStorage.setItem('edited_display_name', newDisplayName);
|
||||
Events.fire('notify-user', Localization.getTranslation("notifications.display-name-changed-temporarily"));
|
||||
})
|
||||
.finally(() => {
|
||||
|
@ -199,10 +281,10 @@ class FooterUI {
|
|||
});
|
||||
}
|
||||
else {
|
||||
PersistentStorage.delete('editedDisplayName')
|
||||
PersistentStorage.delete('edited_display_name')
|
||||
.catch(_ => {
|
||||
console.log("This browser does not support IndexedDB. Use localStorage instead.")
|
||||
localStorage.removeItem('editedDisplayName');
|
||||
localStorage.removeItem('edited_display_name');
|
||||
})
|
||||
.finally(() => {
|
||||
Events.fire('notify-user', Localization.getTranslation("notifications.display-name-random-again"));
|
||||
|
@ -214,13 +296,13 @@ class FooterUI {
|
|||
|
||||
_getSavedDisplayName() {
|
||||
return new Promise((resolve) => {
|
||||
PersistentStorage.get('editedDisplayName')
|
||||
PersistentStorage.get('edited_display_name')
|
||||
.then(displayName => {
|
||||
if (!displayName) displayName = "";
|
||||
resolve(displayName);
|
||||
})
|
||||
.catch(_ => {
|
||||
let displayName = localStorage.getItem('editedDisplayName');
|
||||
let displayName = localStorage.getItem('edited_display_name');
|
||||
if (!displayName) displayName = "";
|
||||
resolve(displayName);
|
||||
})
|
||||
|
@ -234,16 +316,16 @@ class BackgroundCanvas {
|
|||
this.cCtx = this.c.getContext('2d');
|
||||
this.$footer = $$('footer');
|
||||
|
||||
// fade-in on load
|
||||
Events.on('fade-in-ui', _ => this._fadeIn());
|
||||
|
||||
// redraw canvas
|
||||
Events.on('resize', _ => this.init());
|
||||
Events.on('redraw-canvas', _ => this.init());
|
||||
Events.on('translation-loaded', _ => this.init());
|
||||
|
||||
// ShareMode
|
||||
Events.on('share-mode-changed', e => this.onShareModeChanged(e.detail.active));
|
||||
}
|
||||
|
||||
_fadeIn() {
|
||||
async fadeIn() {
|
||||
this.c.classList.remove('opacity-0');
|
||||
}
|
||||
|
||||
|
@ -263,16 +345,24 @@ class BackgroundCanvas {
|
|||
this.x0 = this.w / 2;
|
||||
this.y0 = this.h - this.offset;
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
ctx.beginPath();
|
||||
ctx.lineWidth = 2;
|
||||
let opacity = Math.max(0, 0.3 * (1 - 1.2 * radius / Math.max(this.w, this.h)));
|
||||
ctx.strokeStyle = `rgba(165, 165, 165, ${opacity})`;
|
||||
let opacity = Math.max(0, this.baseOpacity * (1 - 1.2 * radius / Math.max(this.w, this.h)));
|
||||
ctx.strokeStyle = `rgba(${this.baseColor}, ${opacity})`;
|
||||
ctx.arc(this.x0, this.y0, radius, 0, 2 * Math.PI);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
|
1025
public/scripts/ui.js
|
@ -104,305 +104,315 @@ const zipper = (() => {
|
|||
|
||||
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 {
|
||||
getMimeByFilename(filename) {
|
||||
try {
|
||||
const arr = filename.split('.');
|
||||
const suffix = arr[arr.length - 1].toLowerCase();
|
||||
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 '';
|
||||
async guessMimeByFilename(filename) {
|
||||
const split = filename.split('.');
|
||||
if (split.length === 1) {
|
||||
// Filename does not include suffix
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -457,4 +467,120 @@ function base64ToArrayBuffer(base64) {
|
|||
bytes[i] = binary_string.charCodeAt(i);
|
||||
}
|
||||
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)))
|
||||
}
|
|
@ -1,20 +1,5 @@
|
|||
/* 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 */
|
||||
.textarea {
|
||||
box-sizing: border-box;
|
||||
|
@ -22,17 +7,21 @@
|
|||
outline: none;
|
||||
padding: 16px 24px;
|
||||
border-radius: 12px;
|
||||
font-size: inherit;
|
||||
font-size: 16px;
|
||||
font-family: inherit;
|
||||
display: block;
|
||||
overflow: auto;
|
||||
resize: none;
|
||||
line-height: 16px;
|
||||
max-height: 300px;
|
||||
max-height: 350px;
|
||||
word-break: break-word;
|
||||
word-wrap: anywhere;
|
||||
}
|
||||
|
||||
.textarea:before {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* Peers */
|
||||
|
||||
x-peers:has(> x-peer) {
|
||||
|
@ -279,6 +268,56 @@ x-peer[drop] x-icon {
|
|||
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 */
|
||||
|
||||
|
@ -287,38 +326,30 @@ x-dialog x-background {
|
|||
z-index: 30;
|
||||
transition: opacity 300ms;
|
||||
will-change: opacity;
|
||||
overflow: overlay;
|
||||
padding: 10px 5px 20px;
|
||||
overflow: scroll
|
||||
}
|
||||
|
||||
x-dialog x-paper {
|
||||
position: relative;
|
||||
display: flex;
|
||||
margin: auto;
|
||||
flex-direction: column;
|
||||
width: calc(100vw - 10px);
|
||||
width: 400px;
|
||||
z-index: 3;
|
||||
border-radius: 30px;
|
||||
max-width: 400px;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
transition: transform 300ms;
|
||||
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 {
|
||||
background-color: var(--accent-color);
|
||||
padding: 10px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
x-paper > .row:first-of-type h2 {
|
||||
x-paper .dialog-title {
|
||||
color: white;
|
||||
}
|
||||
|
||||
|
@ -357,6 +388,10 @@ x-dialog a {
|
|||
|
||||
/* Pair Devices Dialog & Public Room Dialog */
|
||||
|
||||
#public-room-dialog x-paper {
|
||||
width: 450px;
|
||||
}
|
||||
|
||||
.input-key-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
@ -394,15 +429,15 @@ x-dialog a {
|
|||
user-select: text;
|
||||
display: inline-block;
|
||||
font-size: 45px;
|
||||
letter-spacing: min(calc((100vw - 80px - 99px) / 100 * 7), 20px);
|
||||
text-indent: calc(0.5 * (11px + min(calc((100vw - 80px - 99px) / 100 * 6), 28px)));
|
||||
letter-spacing: 18px;
|
||||
text-indent: 15px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.key-qr-code {
|
||||
width: fit-content;
|
||||
align-self: center;
|
||||
margin-top: 15px;
|
||||
margin-top: 5px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
|
@ -424,8 +459,8 @@ x-dialog hr {
|
|||
}
|
||||
|
||||
.hr-note {
|
||||
margin-top: 23px;
|
||||
margin-bottom: 31px;
|
||||
margin-top: 13px;
|
||||
margin-bottom: 21px;
|
||||
}
|
||||
|
||||
.hr-note hr {
|
||||
|
@ -447,10 +482,6 @@ x-dialog hr {
|
|||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
#pair-device-dialog x-background {
|
||||
padding: 16px!important;
|
||||
}
|
||||
|
||||
/* Edit Paired Devices Dialog */
|
||||
.paired-devices-wrapper:empty:before {
|
||||
content: attr(data-empty);
|
||||
|
@ -474,6 +505,10 @@ x-dialog hr {
|
|||
align-items: center;
|
||||
}
|
||||
|
||||
.paired-device:empty {
|
||||
padding: 47px;
|
||||
}
|
||||
|
||||
.paired-device:not(:last-child) {
|
||||
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);
|
||||
opacity: 1;
|
||||
}
|
||||
.paired-device span {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.paired-device > .button-wrapper {
|
||||
display: flex;
|
||||
height: 36px;
|
||||
justify-content: space-between;
|
||||
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 > * {
|
||||
min-height: 38px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.paired-device > .button-wrapper > :not(:last-child) {
|
||||
|
@ -529,32 +545,17 @@ x-dialog hr {
|
|||
}
|
||||
|
||||
/* button row*/
|
||||
x-paper > .button-row {
|
||||
.btn-row .btn {
|
||||
margin: 3px;
|
||||
flex-grow: 1;
|
||||
height: 50px;
|
||||
width: 120px; /* fixed width needed to ensure same width for all buttons */
|
||||
}
|
||||
|
||||
x-paper > .btn-row {
|
||||
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 {
|
||||
margin: 0 0.3em;
|
||||
}
|
||||
|
@ -597,7 +598,10 @@ x-dialog .dialog-subheader {
|
|||
|
||||
#send-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-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);
|
||||
}
|
||||
|
||||
#text-input:before {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* Receive Text Dialog */
|
||||
|
||||
#receive-text-dialog #text {
|
||||
|
@ -634,11 +634,82 @@ x-dialog .dialog-subheader {
|
|||
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-dialog .textarea {
|
||||
width: 100%;
|
||||
height: 40vh;
|
||||
border: solid 12px #438cff;
|
||||
margin: 6px;
|
||||
}
|
||||
|
||||
#base64-paste-dialog .textarea {
|
||||
|
|
|
@ -31,12 +31,65 @@ html {
|
|||
}
|
||||
|
||||
.p1 {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.p2 {
|
||||
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;
|
||||
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 {
|
||||
|
@ -48,6 +101,11 @@ html {
|
|||
flex-direction: row;
|
||||
}
|
||||
|
||||
.row-reverse {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
@ -59,10 +117,6 @@ html {
|
|||
justify-content: center;
|
||||
}
|
||||
|
||||
.grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.full {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
@ -76,7 +130,7 @@ html {
|
|||
}
|
||||
|
||||
header {
|
||||
position: absolute;
|
||||
position: relative;
|
||||
align-items: baseline;
|
||||
padding: 8px 12px;
|
||||
box-sizing: border-box;
|
||||
|
@ -86,67 +140,95 @@ header {
|
|||
right: 0;
|
||||
}
|
||||
|
||||
header.overflow-hidden {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
header:not(.overflow-expanded) {
|
||||
height: 56px;
|
||||
}
|
||||
|
||||
header > * {
|
||||
margin-left: 4px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
header > div {
|
||||
header > * {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-self: flex-start;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
header > div .icon-button {
|
||||
header > .icon-button {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
header * {
|
||||
transition: all 300ms;
|
||||
}
|
||||
|
||||
header > div > div {
|
||||
#theme-wrapper > div {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
header > div:not(:hover) .icon-button:not(.selected) {
|
||||
/* expand theme buttons */
|
||||
#theme-wrapper:not(:hover) .icon-button:not(.selected) {
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#theme-wrapper:hover::before {
|
||||
border-radius: 20px;
|
||||
background: currentColor;
|
||||
opacity: 0.1;
|
||||
background: var(--primary-color);
|
||||
opacity: 0.2;
|
||||
transition: opacity 300ms;
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 40px;
|
||||
height: 120px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
margin-top: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
header > div:hover .icon-button.selected::before {
|
||||
opacity: 0.1;
|
||||
#theme-wrapper:hover .icon-button:not(.selected):hover:before {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
#theme-wrapper:hover .icon-button.selected::before {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
@media (pointer: coarse) {
|
||||
header > div:hover .icon-button.selected:hover::before {
|
||||
opacity: 0.2;
|
||||
#theme-wrapper:hover .icon-button.selected:hover::before {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
header > div .icon-button:not(.selected) {
|
||||
#theme-wrapper .icon-button:not(.selected) {
|
||||
height: 0;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
header > div > div {
|
||||
#theme-wrapper > div {
|
||||
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] {
|
||||
display: none !important;
|
||||
}
|
||||
|
@ -177,7 +259,7 @@ h1 {
|
|||
}
|
||||
|
||||
h2 {
|
||||
font-size: 24px;
|
||||
font-size: 22px;
|
||||
font-weight: 400;
|
||||
letter-spacing: -.012em;
|
||||
line-height: 32px;
|
||||
|
@ -201,6 +283,10 @@ h3 {
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.text-white {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
.font-body1,
|
||||
body {
|
||||
font-size: 14px;
|
||||
|
@ -276,19 +362,16 @@ x-noscript {
|
|||
|
||||
/* Animations */
|
||||
|
||||
/* Opacity for elements at keyframe 100% is set on element (default 1) */
|
||||
@keyframes fade-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
#center {
|
||||
position: relative;
|
||||
display: flex;
|
||||
margin-top: 56px;
|
||||
flex-direction: column-reverse;
|
||||
flex-grow: 1;
|
||||
justify-content: space-around;
|
||||
|
@ -301,18 +384,10 @@ x-noscript {
|
|||
|
||||
/* Peers */
|
||||
|
||||
#x-peers-filler {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
x-peers {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
flex-grow: 1;
|
||||
align-items: start !important;
|
||||
justify-content: center;
|
||||
|
||||
z-index: 2;
|
||||
transition: background-color 0.5s ease;
|
||||
|
@ -324,8 +399,6 @@ x-peers {
|
|||
--peers-per-row: 6; /* default if browser does not support :has selector */
|
||||
--x-peers-width: min(100vw, calc(var(--peers-per-row) * (var(--peer-width) + 25px) - 16px));
|
||||
width: var(--x-peers-width);
|
||||
margin-right: 20px;
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
/* Empty Peers List */
|
||||
|
@ -382,30 +455,35 @@ footer .logo {
|
|||
margin-top: -10px;
|
||||
}
|
||||
|
||||
.discovery-wrapper {
|
||||
font-size: 14px;
|
||||
margin: 15px auto auto;
|
||||
.border {
|
||||
border: 2px solid var(--border-color);
|
||||
}
|
||||
|
||||
.panel {
|
||||
font-size: 14px;
|
||||
padding: 2px;
|
||||
background-color: rgb(var(--bg-color));
|
||||
transition: background-color 0.5s ease;
|
||||
min-height: 24px;
|
||||
}
|
||||
|
||||
.discovery-wrapper.column {
|
||||
.panel.column {
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.discovery-wrapper.row {
|
||||
.panel.row {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
/*You can be discovered wrapper*/
|
||||
.discovery-wrapper > div:first-of-type {
|
||||
.panel > div:first-of-type {
|
||||
padding-left: 4px;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
/* You can be discovered wrapper */
|
||||
.discovery-wrapper {
|
||||
margin: 15px auto auto;
|
||||
}
|
||||
|
||||
.discovery-wrapper .badge {
|
||||
word-break: keep-all;
|
||||
|
@ -421,10 +499,6 @@ footer .logo {
|
|||
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-color: var(--primary-color);
|
||||
}
|
||||
|
@ -447,8 +521,11 @@ footer .logo {
|
|||
text-align: left;
|
||||
border: none;
|
||||
outline: none;
|
||||
height: 20px;
|
||||
max-width: 15em;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
cursor: text;
|
||||
margin-bottom: -6px;
|
||||
padding-bottom: 0.1rem;
|
||||
|
@ -456,11 +533,10 @@ footer .logo {
|
|||
border-right: solid 1rem transparent;
|
||||
border-left: solid 1rem transparent;
|
||||
background-clip: padding-box;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
#edit-pen {
|
||||
.edit-pen {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
margin-bottom: -2px;
|
||||
|
@ -468,16 +544,16 @@ footer .logo {
|
|||
}
|
||||
|
||||
html:not([dir="rtl"]) #display-name,
|
||||
html:not([dir="rtl"]) #edit-pen {
|
||||
html:not([dir="rtl"]) .edit-pen {
|
||||
margin-left: -1rem;
|
||||
}
|
||||
|
||||
html[dir="rtl"] #display-name,
|
||||
html[dir="rtl"] #edit-pen {
|
||||
html[dir="rtl"] .edit-pen {
|
||||
margin-right: -1rem;
|
||||
}
|
||||
|
||||
html[dir="rtl"] #edit-pen {
|
||||
html[dir="rtl"] .edit-pen {
|
||||
transform: rotateY(180deg);
|
||||
}
|
||||
|
||||
|
@ -505,6 +581,11 @@ x-dialog:not([show]) x-background {
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
.btn-small {
|
||||
font-size: 12px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.btn[disabled] {
|
||||
color: var(--btn-disabled-color);
|
||||
cursor: not-allowed;
|
||||
|
@ -531,6 +612,7 @@ x-dialog:not([show]) x-background {
|
|||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: -1;
|
||||
opacity: 0;
|
||||
background-color: var(--accent-color);
|
||||
transition: opacity 300ms;
|
||||
|
@ -538,27 +620,44 @@ x-dialog:not([show]) x-background {
|
|||
|
||||
.btn:not([disabled]):hover:before,
|
||||
.icon-button:hover:before {
|
||||
opacity: 0.1;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.btn[selected],
|
||||
.icon-button[selected] {
|
||||
opacity: 0.1;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.btn:focus:before,
|
||||
.icon-button:focus:before {
|
||||
opacity: 0.2;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.btn-round {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.btn-rounded {
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.btn-small.btn-rounded {
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.btn-grey {
|
||||
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 {
|
||||
border: 0;
|
||||
}
|
||||
|
@ -686,16 +785,17 @@ canvas.circles {
|
|||
}
|
||||
|
||||
x-toast {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
position: absolute;
|
||||
min-height: 48px;
|
||||
top: 50px;
|
||||
width: 100%;
|
||||
max-width: 344px;
|
||||
max-width: 400px;
|
||||
background-color: rgb(var(--text-color));
|
||||
color: var(--dialog-bg-color);
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
padding: 8px 24px;
|
||||
padding: 8px;
|
||||
z-index: 40;
|
||||
transition: opacity 200ms, transform 300ms ease-out;
|
||||
cursor: default;
|
||||
|
@ -704,22 +804,34 @@ x-toast {
|
|||
pointer-events: all;
|
||||
}
|
||||
|
||||
x-toast:not([show]):not(:hover) {
|
||||
x-toast.top-row {
|
||||
top: 3px;
|
||||
}
|
||||
|
||||
x-toast:not([show]) {
|
||||
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 */
|
||||
|
||||
x-instructions {
|
||||
display: flex;
|
||||
position: relative;
|
||||
opacity: 0.5;
|
||||
text-align: center;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
display: flex;
|
||||
margin: 50px 10px 0px;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
|
@ -735,12 +847,10 @@ x-instructions[drop-bg]:not([drop-peer]):before {
|
|||
content: attr(data-drop-bg);
|
||||
}
|
||||
|
||||
x-instructions p {
|
||||
display: none;
|
||||
}
|
||||
|
||||
x-peers:empty,
|
||||
x-peers:empty~x-instructions {
|
||||
opacity: 0 !important;
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (hover: none) and (pointer: coarse) {
|
||||
|
@ -796,43 +906,70 @@ body {
|
|||
/* Constant colors */
|
||||
--primary-color: #4285f4;
|
||||
--paired-device-color: #00a69c;
|
||||
--public-room-color: #db8500;
|
||||
--public-room-color: #ed9d01;
|
||||
--accent-color: var(--primary-color);
|
||||
--ws-peer-color: #ff6b6b;
|
||||
--btn-disabled-color: #5B5B66;
|
||||
|
||||
/* shadows */
|
||||
--shadow-color-rgb: var(--text-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 */
|
||||
body {
|
||||
--text-color: 51,51,51;
|
||||
--dialog-bg-color: #fff;
|
||||
--bg-color: 255,255,255;
|
||||
--bg-color-secondary: #f2f2f2;
|
||||
--border-color: rgb(169, 169, 169);
|
||||
--badge-color: #a5a5a5;
|
||||
--text-color: var(--lt-text-color);
|
||||
--dialog-bg-color: var(--lt-dialog-bg-color);
|
||||
--bg-color: var(--lt-bg-color);
|
||||
--bg-color-secondary: var(--lt-bg-color-secondary);
|
||||
--border-color: var(--lt-border-color);
|
||||
--badge-color: var(--lt-badge-color);
|
||||
|
||||
--shadow-color-secondary-rgb: 0,0,0;
|
||||
--shadow-color-secondary-cover-rgb: 242,242,242;
|
||||
--shadow-color-dialog-rgb: 0,0,0;
|
||||
--shadow-color-dialog-cover-rgb: 242,242,242;
|
||||
--shadow-color-secondary-rgb: var(--lt-shadow-color-secondary-rgb);
|
||||
--shadow-color-secondary-cover-rgb: var(--lt-shadow-color-secondary-cover-rgb);
|
||||
--shadow-color-dialog-rgb: var(--lt-shadow-color-dialog-rgb);
|
||||
--shadow-color-dialog-cover-rgb: var(--lt-shadow-color-dialog-cover-rgb);
|
||||
}
|
||||
|
||||
/* Dark theme colors */
|
||||
body.dark-theme {
|
||||
--text-color: 238,238,238;
|
||||
--dialog-bg-color: #121212;
|
||||
--bg-color: 0,0,0;
|
||||
--bg-color-secondary: #262628;
|
||||
--border-color: rgb(91, 91, 91);
|
||||
--badge-color: #717171;
|
||||
--text-color: var(--dt-text-color);
|
||||
--dialog-bg-color: var(--dt-dialog-bg-color);
|
||||
--bg-color: var(--dt-bg-color);
|
||||
--bg-color-secondary: var(--dt-bg-color-secondary);
|
||||
--border-color: var(--dt-border-color);
|
||||
--badge-color: var(--dt-badge-color);
|
||||
|
||||
--shadow-color-secondary-rgb: 255,255,255;
|
||||
--shadow-color-secondary-cover-rgb: 38,38,38;
|
||||
--shadow-color-dialog-rgb: 255,255,255;
|
||||
--shadow-color-dialog-cover-rgb: 38,38,38;
|
||||
--shadow-color-secondary-rgb: var(--dt-shadow-color-secondary-rgb);
|
||||
--shadow-color-secondary-cover-rgb: var(--dt-shadow-color-secondary-cover-rgb);
|
||||
--shadow-color-dialog-rgb: var(--dt-shadow-color-dialog-rgb);
|
||||
--shadow-color-dialog-cover-rgb: var(--dt-shadow-color-dialog-cover-rgb);
|
||||
}
|
||||
|
||||
/* Styles for users who prefer dark mode at the OS level */
|
||||
|
@ -840,33 +977,33 @@ body.dark-theme {
|
|||
|
||||
/* defaults to dark theme */
|
||||
body {
|
||||
--text-color: 238,238,238;
|
||||
--dialog-bg-color: #121212;
|
||||
--bg-color-secondary: #262628;
|
||||
--bg-color: 0,0,0;
|
||||
--border-color: rgb(91, 91, 91);
|
||||
--badge-color: #717171;
|
||||
--text-color: var(--dt-text-color);
|
||||
--dialog-bg-color: var(--dt-dialog-bg-color);
|
||||
--bg-color: var(--dt-bg-color);
|
||||
--bg-color-secondary: var(--dt-bg-color-secondary);
|
||||
--border-color: var(--dt-border-color);
|
||||
--badge-color: var(--dt-badge-color);
|
||||
|
||||
--shadow-color-secondary-rgb: 255,255,255;
|
||||
--shadow-color-secondary-cover-rgb: 38,38,38;
|
||||
--shadow-color-dialog-rgb: 255,255,255;
|
||||
--shadow-color-dialog-cover-rgb: 38,38,38;
|
||||
--shadow-color-secondary-rgb: var(--dt-shadow-color-secondary-rgb);
|
||||
--shadow-color-secondary-cover-rgb: var(--dt-shadow-color-secondary-cover-rgb);
|
||||
--shadow-color-dialog-rgb: var(--dt-shadow-color-dialog-rgb);
|
||||
--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 */
|
||||
body.light-theme {
|
||||
--text-color: 51,51,51;
|
||||
--dialog-bg-color: #fff;
|
||||
--bg-color: 255,255,255;
|
||||
--bg-color-secondary: #f2f2f2;
|
||||
--border-color: rgb(169, 169, 169);
|
||||
--badge-color: #a5a5a5;
|
||||
--text-color: var(--lt-text-color);
|
||||
--dialog-bg-color: var(--lt-dialog-bg-color);
|
||||
--bg-color: var(--lt-bg-color);
|
||||
--bg-color-secondary: var(--lt-bg-color-secondary);
|
||||
--border-color: var(--lt-border-color);
|
||||
--badge-color: var(--lt-badge-color);
|
||||
|
||||
--shadow-color-secondary-rgb: 0,0,0;
|
||||
--shadow-color-secondary-cover-rgb: 242,242,242;
|
||||
--shadow-color-dialog-rgb: 0,0,0;
|
||||
--shadow-color-dialog-cover-rgb: 242,242,242;
|
||||
--shadow-color-secondary-rgb: var(--lt-shadow-color-secondary-rgb);
|
||||
--shadow-color-secondary-cover-rgb: var(--lt-shadow-color-secondary-cover-rgb);
|
||||
--shadow-color-dialog-rgb: var(--lt-shadow-color-dialog-rgb);
|
||||
--shadow-color-dialog-cover-rgb: var(--lt-shadow-color-dialog-cover-rgb);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -877,28 +1014,6 @@ body {
|
|||
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 */
|
||||
#primaryGradient .start-color {
|
||||
stop-color: var(--primary-color);
|
||||
|
@ -946,8 +1061,8 @@ html {
|
|||
/* webkit scrollbar style*/
|
||||
|
||||
::-webkit-scrollbar{
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb{
|
||||
|
|
|
@ -37,7 +37,7 @@ export default class Peer {
|
|||
return true;
|
||||
}
|
||||
this.requestRate += 1;
|
||||
setTimeout(_ => this.requestRate -= 1, 10000);
|
||||
setTimeout(() => this.requestRate -= 1, 10000);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -124,8 +124,8 @@ export default class Peer {
|
|||
|
||||
_setPeerId(request) {
|
||||
const searchParams = new URL(request.url, "http://server").searchParams;
|
||||
let peerId = searchParams.get("peer_id");
|
||||
let peerIdHash = searchParams.get("peer_id_hash");
|
||||
let peerId = searchParams.get('peer_id');
|
||||
let peerIdHash = searchParams.get('peer_id_hash');
|
||||
if (peerId && Peer.isValidUuid(peerId) && this.isPeerIdHashValid(peerId, peerIdHash)) {
|
||||
this.id = peerId;
|
||||
} else {
|
||||
|
@ -135,7 +135,7 @@ export default class Peer {
|
|||
|
||||
_setRtcSupported(request) {
|
||||
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) {
|
||||
|
|