Merge branch 'flutter-upgrade' into isolated-ffmpeg
34
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -1,24 +1,21 @@
|
||||
name: Report a bug
|
||||
description: Let us know if something's not working the way you expected.
|
||||
description: For regressions only (things that were working earlier)
|
||||
labels: []
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Before opening a new bug report, please ensure
|
||||
1. you are on the latest version (it might've already been fixed),
|
||||
2. you've searched for existing issues (please add your observations as a comment there instead of creating a duplicate).
|
||||
|
||||
If you are self hosting, please create a community [Q&A](https://github.com/ente-io/ente/discussions/categories/q-a) instead.
|
||||
Before opening a new issue, **please** ensure
|
||||
1. You are on the latest version,
|
||||
2. You've searched for existing issues,
|
||||
3. It was working earlier (otherwise use [this](https://github.com/ente-io/ente/discussions/categories/enhancements))
|
||||
4. It is not about self hosting (otherwise use [this](https://github.com/ente-io/ente/discussions/categories/q-a))
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Description
|
||||
description: >
|
||||
Please describe the bug. If possible, also include the steps to
|
||||
reproduce the behaviour, and the expected behaviour (sometimes
|
||||
bugs are just expectation mismatches, in which case this would be
|
||||
a good fit for [feature
|
||||
requests](https://github.com/ente-io/ente/discussions/categories/feature-requests)).
|
||||
Describe the bug and steps to reproduce the behaviour, and how it
|
||||
differs from the previously working behaviour.
|
||||
validations:
|
||||
required: true
|
||||
- type: input
|
||||
@@ -30,15 +27,12 @@ body:
|
||||
attributes:
|
||||
label: Last working version
|
||||
description: >
|
||||
The version where the feature was last known to be working. It is
|
||||
fine if you don't remember the exact version (mention roughly
|
||||
then), but if there just isn't a last known working version, then
|
||||
it is likely that what is being reported is not an issue but a
|
||||
feature request. The difference between the two categories is not
|
||||
just semantic - feature requests use GitHub discussions and so can
|
||||
be [upvoted by the
|
||||
community](https://github.com/ente-io/ente/discussions/categories/feature-requests)
|
||||
(issues can't be).
|
||||
The version where things were last known to be working. It is fine
|
||||
if you don't remember the exact version (mention roughly then),
|
||||
but **if there just isn't a last working version, then please file
|
||||
it as an
|
||||
[enhancement](https://github.com/ente-io/ente/discussions/categories/enhancements))**
|
||||
(where the community upvotes can be used to help prioritize).
|
||||
placeholder: e.g. v1.2.3
|
||||
- type: dropdown
|
||||
attributes:
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +1,5 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Feature requests and questions
|
||||
- name: Enhacements, feature requests, feedback and questions
|
||||
url: https://github.com/ente-io/ente/discussions
|
||||
about: Please use Discussions for everything apart from the above.
|
||||
|
||||
33
.github/workflows/auth-release.yml
vendored
@@ -36,7 +36,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
build-linux-latest:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-22.04
|
||||
|
||||
defaults:
|
||||
run:
|
||||
@@ -83,7 +83,7 @@ jobs:
|
||||
# disable this step if release tag contains nightly or beta
|
||||
if: startsWith(github.ref, 'refs/tags/auth-v') && !contains(github.ref, 'nightly') && !contains(github.ref, 'beta')
|
||||
run: |
|
||||
flutter build appbundle --release --flavor playstore --dart-define=app.flavor=playstore
|
||||
flutter build appbundle --release --flavor playstore --dart-define=app.flavor=playstore --dart-define=cronetHttpNoPlay=true
|
||||
env:
|
||||
SIGNING_KEY_PATH: "/home/runner/work/_temp/keystore/ente_auth_key.jks"
|
||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||
@@ -93,7 +93,7 @@ jobs:
|
||||
- name: Install dependencies for desktop build
|
||||
run: |
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y libsecret-1-dev libsodium-dev libfuse2 ninja-build libgtk-3-dev dpkg-dev pkg-config rpm patchelf libsqlite3-dev locate libayatana-appindicator3-dev libffi-dev libtiff6 xz-utils libarchive-tools libcurl4-openssl-dev
|
||||
sudo apt-get install -y libsecret-1-dev libsodium-dev libfuse2 ninja-build libgtk-3-dev dpkg-dev pkg-config rpm patchelf libsqlite3-dev locate libayatana-appindicator3-dev libffi-dev libtiff5 xz-utils libarchive-tools libcurl4-openssl-dev
|
||||
sudo updatedb --localpaths='/usr/lib/x86_64-linux-gnu'
|
||||
|
||||
- name: Install appimagetool
|
||||
@@ -117,7 +117,9 @@ jobs:
|
||||
mv dist/**/*-*-linux.deb artifacts/ente-${{ github.ref_name }}-x86_64.deb
|
||||
|
||||
- name: Generate checksums
|
||||
run: sha256sum artifacts/ente-* >> artifacts/sha256sum-rpm-appimage
|
||||
run: |
|
||||
sha256sum artifacts/ente-auth-*.apk >> artifacts/sha256sum-apk
|
||||
sha256sum artifacts/ente-auth-*.deb artifacts/ente-auth-*.rpm artifacts/ente-auth-*.AppImage >> artifacts/sha256sum-linux
|
||||
|
||||
- name: Create a draft GitHub release
|
||||
uses: ncipollo/release-action@v1
|
||||
@@ -139,6 +141,7 @@ jobs:
|
||||
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
environment: "auth-win-build"
|
||||
|
||||
defaults:
|
||||
run:
|
||||
@@ -172,14 +175,22 @@ jobs:
|
||||
- name: Retain Windows EXE and DLLs
|
||||
run: cp -r build/windows/x64/runner/Release ente-${{ github.ref_name }}-windows
|
||||
|
||||
- name: Code sign Windows installer and EXE
|
||||
uses: dlemstra/code-sign-action@v1
|
||||
- name: Sign files with Trusted Signing
|
||||
uses: azure/trusted-signing-action@v0
|
||||
with:
|
||||
certificate: "${{ secrets.WINDOWS_CERTIFICATE }}"
|
||||
password: "${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}"
|
||||
files: |
|
||||
auth/artifacts/ente-${{ github.ref_name }}-installer.exe
|
||||
auth/ente-${{ github.ref_name }}-windows/auth.exe
|
||||
azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
azure-client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
|
||||
endpoint: ${{ secrets.AZURE_ENDPOINT }}
|
||||
trusted-signing-account-name: ${{ secrets.AZURE_CODE_SIGNING_NAME }}
|
||||
certificate-profile-name: ${{ secrets.AZURE_CERT_PROFILE_NAME }}
|
||||
files: |
|
||||
${{ github.workspace }}/auth/artifacts/ente-${{ github.ref_name }}-installer.exe
|
||||
${{ github.workspace }}/auth/ente-${{ github.ref_name }}-windows/auth.exe
|
||||
file-digest: SHA256
|
||||
timestamp-rfc3161: http://timestamp.acs.microsoft.com
|
||||
timestamp-digest: SHA256
|
||||
|
||||
|
||||
- name: Zip Windows EXE and DLLs
|
||||
run: tar.exe -a -c -f artifacts/ente-${{ github.ref_name }}-windows.zip ente-${{ github.ref_name }}-windows
|
||||
|
||||
70
.github/workflows/auth-win-sign.yml
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
name: "Windows build & Sign (auth)"
|
||||
|
||||
|
||||
on:
|
||||
workflow_dispatch: # Allow manually running the action
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.24.3"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
environment: "auth-win-build"
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: auth
|
||||
|
||||
steps:
|
||||
- name: Checkout code and submodules
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install Flutter ${{ env.FLUTTER_VERSION }}
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
cache: true
|
||||
|
||||
- name: Create artifacts directory
|
||||
run: mkdir artifacts
|
||||
|
||||
- name: Build Windows installer
|
||||
run: |
|
||||
flutter config --enable-windows-desktop
|
||||
# dart pub global activate flutter_distributor
|
||||
dart pub global activate --source git https://github.com/ente-io/flutter_distributor_fork --git-ref develop --git-path packages/flutter_distributor
|
||||
make innoinstall
|
||||
flutter_distributor package --platform=windows --targets=exe --skip-clean
|
||||
mv dist/**/*-windows-setup.exe artifacts/ente-${{ github.ref_name }}-installer.exe
|
||||
|
||||
- name: Retain Windows EXE and DLLs
|
||||
run: cp -r build/windows/x64/runner/Release ente-${{ github.ref_name }}-windows
|
||||
|
||||
- name: Sign files with Trusted Signing
|
||||
uses: azure/trusted-signing-action@v0
|
||||
with:
|
||||
azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }}
|
||||
azure-client-id: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
azure-client-secret: ${{ secrets.AZURE_CLIENT_SECRET }}
|
||||
endpoint: ${{ secrets.AZURE_ENDPOINT }}
|
||||
trusted-signing-account-name: ${{ secrets.AZURE_CODE_SIGNING_NAME }}
|
||||
certificate-profile-name: ${{ secrets.AZURE_CERT_PROFILE_NAME }}
|
||||
files: |
|
||||
${{ github.workspace }}/auth/artifacts/ente-${{ github.ref_name }}-installer.exe
|
||||
${{ github.workspace }}/auth/ente-${{ github.ref_name }}-windows/auth.exe
|
||||
file-digest: SHA256
|
||||
timestamp-rfc3161: http://timestamp.acs.microsoft.com
|
||||
timestamp-digest: SHA256
|
||||
|
||||
- name: Zip Windows EXE and DLLs
|
||||
run: tar.exe -a -c -f artifacts/ente-${{ github.ref_name }}-windows.zip ente-${{ github.ref_name }}-windows
|
||||
|
||||
- name: Generate checksums
|
||||
run: sha256sum artifacts/ente-* > artifacts/sha256sum-windows
|
||||
32
.github/workflows/docs-lint.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: "Lint (docs)"
|
||||
|
||||
on:
|
||||
# Run on every pull request (open or push to it) that changes docs/
|
||||
pull_request:
|
||||
paths:
|
||||
- "docs/**"
|
||||
- ".github/workflows/docs-lint.yml"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
run:
|
||||
working-directory: docs
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup node and enable yarn caching
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: "yarn"
|
||||
cache-dependency-path: "web/yarn.lock"
|
||||
|
||||
- run: yarn install
|
||||
|
||||
- run: yarn pretty:check
|
||||
77
.github/workflows/mobile-internal-release-rust.yml
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
name: "Internal release (photos)"
|
||||
|
||||
on:
|
||||
workflow_dispatch: # Allow manually running the action
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.24.3"
|
||||
RUST_VERSION: "1.85.1"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: mobile
|
||||
|
||||
steps:
|
||||
- name: Checkout code and submodules
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup JDK 17
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 17
|
||||
|
||||
- name: Install Flutter ${{ env.FLUTTER_VERSION }}
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
cache: true
|
||||
|
||||
- name: Install Rust ${{ env.RUST_VERSION }}
|
||||
uses: dtolnay/rust-toolchain@master
|
||||
with:
|
||||
toolchain: ${{ env.RUST_VERSION }}
|
||||
|
||||
- name: Install Flutter Rust Bridge
|
||||
run: cargo install flutter_rust_bridge_codegen
|
||||
|
||||
- name: Setup keys
|
||||
uses: timheuer/base64-to-file@v1
|
||||
with:
|
||||
fileName: "keystore/ente_photos_key.jks"
|
||||
encodedString: ${{ secrets.SIGNING_KEY_PHOTOS }}
|
||||
|
||||
- name: Build PlayStore AAB
|
||||
run: |
|
||||
flutter build appbundle --dart-define=cronetHttpNoPlay=true --release --flavor playstore
|
||||
env:
|
||||
SIGNING_KEY_PATH: "/home/runner/work/_temp/keystore/ente_photos_key.jks"
|
||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS_PHOTOS }}
|
||||
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD_PHOTOS }}
|
||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_STORE_PASSWORD_PHOTOS }}
|
||||
|
||||
- name: Upload AAB to PlayStore
|
||||
uses: r0adkll/upload-google-play@v1
|
||||
with:
|
||||
serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}
|
||||
packageName: io.ente.photos
|
||||
releaseFiles: mobile/build/app/outputs/bundle/playstoreRelease/app-playstore-release.aab
|
||||
track: internal
|
||||
|
||||
- name: Notify Discord
|
||||
uses: sarisia/actions-status-discord@v1
|
||||
with:
|
||||
webhook: ${{ secrets.DISCORD_INTERNAL_RELEASE_WEBHOOK }}
|
||||
nodetail: true
|
||||
title: "🏆 Internal release available for Photos"
|
||||
description: "[Download](https://play.google.com/store/apps/details?id=io.ente.photos)"
|
||||
color: 0x00ff00
|
||||
@@ -63,6 +63,6 @@ jobs:
|
||||
with:
|
||||
webhook: ${{ secrets.DISCORD_INTERNAL_RELEASE_WEBHOOK }}
|
||||
nodetail: true
|
||||
title: "🏆 Internal release available for Photos"
|
||||
title: "🏆 Internal release Photos (Branch: ${{ github.ref_name }})"
|
||||
description: "[Download](https://play.google.com/store/apps/details?id=io.ente.photos)"
|
||||
color: 0x00ff00
|
||||
|
||||
@@ -23,7 +23,7 @@ Just hang around, enjoy the vibe. Answer someone's query on our
|
||||
[Discord](https://discord.gg/z2YVKkycX3), or pile on in the sporadic #off-topic
|
||||
rants there. Chuckle (or wince!) at our [Twitter](https://twitter.com/enteio)
|
||||
memes. Suggest a new feature in our [Github
|
||||
Discussions](https://github.com/ente-io/ente/discussions/new?category=feature-requests),
|
||||
Discussions](https://github.com/ente-io/ente/discussions/new?category=enhancements),
|
||||
or upvote the existing ones that you feel we should focus on first. Provide your
|
||||
opinion on existing threads.
|
||||
|
||||
@@ -68,8 +68,8 @@ best to start small. Consider some well-scoped changes, say like adding more
|
||||
Each of the individual product/platform specific directories in this repository
|
||||
have instructions on setting up a dev environment.
|
||||
|
||||
For anything beyond trivial bug fixes, please use [features requests and
|
||||
discussions](https://github.com/ente-io/ente/discussions) instead of performing
|
||||
For anything beyond trivial bug fixes, please use
|
||||
[discussions](https://github.com/ente-io/ente/discussions) instead of performing
|
||||
code changes directly.
|
||||
|
||||
> [!TIP]
|
||||
|
||||
@@ -39,11 +39,24 @@
|
||||
"title": "Ankama",
|
||||
"slug": "ankama"
|
||||
},
|
||||
{
|
||||
"title": "Ankara University",
|
||||
"slug": "ankara_university",
|
||||
"altNames": [
|
||||
"Ankara Üniversitesi"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Anycoin Direct",
|
||||
"slug": "anycoindirect"
|
||||
},
|
||||
{
|
||||
{
|
||||
"title": "AR24",
|
||||
"altNames": [
|
||||
"Docaposte AR24"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Aruba",
|
||||
"slug": "aruba",
|
||||
"hex": "ef8a33"
|
||||
@@ -132,6 +145,10 @@
|
||||
"Binance US"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Bitazza",
|
||||
"slug": "bitazza"
|
||||
},
|
||||
{
|
||||
"title": "Bitkub",
|
||||
"slug": "bitkub"
|
||||
@@ -188,7 +205,11 @@
|
||||
"slug": "blue_sky"
|
||||
},
|
||||
{
|
||||
"title": "bonify"
|
||||
"title": "bonify",
|
||||
"slug": "bonify",
|
||||
"altNames": [
|
||||
"bonify.de"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Booking",
|
||||
@@ -292,6 +313,15 @@
|
||||
{
|
||||
"title": "CSAM"
|
||||
},
|
||||
{
|
||||
"title": "CSSBuy",
|
||||
"slug": "cssbuy",
|
||||
"altNames": [
|
||||
"CSS Buy",
|
||||
"CSS-Buy",
|
||||
"cssbuy.com"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "CSFloat"
|
||||
},
|
||||
@@ -299,6 +329,10 @@
|
||||
"title": "CSGORoll",
|
||||
"slug": "csgoroll"
|
||||
},
|
||||
{
|
||||
"title": "Cryptee",
|
||||
"slug": "cryptee"
|
||||
},
|
||||
{
|
||||
"title": "Cwallet",
|
||||
"altNames": [
|
||||
@@ -431,6 +465,9 @@
|
||||
"title": "Finanzfluss",
|
||||
"slug": "finanzfluss"
|
||||
},
|
||||
{
|
||||
"title": "Finary"
|
||||
},
|
||||
{
|
||||
"title": "Firefox",
|
||||
"slug": "mozilla"
|
||||
@@ -445,6 +482,16 @@
|
||||
"title": "FreeTaxUSA",
|
||||
"slug": "freetaxusa"
|
||||
},
|
||||
{
|
||||
"title": "FZJ",
|
||||
"slug": "fzj",
|
||||
"hex": "023d6b",
|
||||
"altNames": [
|
||||
"Forschungszentrum Jülich",
|
||||
"FZJ IdP",
|
||||
"iffLogin"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "G2A"
|
||||
},
|
||||
@@ -452,6 +499,9 @@
|
||||
"title": "Gate.io",
|
||||
"slug": "gateio.svg"
|
||||
},
|
||||
{
|
||||
"title": "GERID"
|
||||
},
|
||||
{
|
||||
"title": "GitHub"
|
||||
},
|
||||
@@ -706,8 +756,8 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Mercado Livre",
|
||||
"slug": "mercado_livre",
|
||||
"title": "Mercado Libre",
|
||||
"slug": "mercado_libre",
|
||||
"altNames": [
|
||||
"Mercado Libre",
|
||||
"MercadoLibre",
|
||||
@@ -733,6 +783,7 @@
|
||||
{
|
||||
"title": "Mistral",
|
||||
"altNames": [
|
||||
"Le Chat",
|
||||
"Mistral AI",
|
||||
"MistralAI"
|
||||
]
|
||||
@@ -877,11 +928,15 @@
|
||||
"欧易"
|
||||
]
|
||||
},
|
||||
{
|
||||
{
|
||||
"title": "OnShape",
|
||||
"slug": "onshape",
|
||||
"hex": "7abb5e"
|
||||
},
|
||||
{
|
||||
"title": "Oracle Cloud",
|
||||
"slug": "oracle_cloud"
|
||||
},
|
||||
{
|
||||
"title": "Parqet",
|
||||
"slug": "parqet"
|
||||
@@ -982,6 +1037,14 @@
|
||||
"qiniu"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "R10.net",
|
||||
"slug": "r10",
|
||||
"altNames": [
|
||||
"R10",
|
||||
"r10.net"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Raindrop.io",
|
||||
"slug": "raindrop_io",
|
||||
@@ -1001,7 +1064,7 @@
|
||||
"title": "RealMe",
|
||||
"slug": "realme"
|
||||
},
|
||||
{
|
||||
{
|
||||
"title": "RealVNC",
|
||||
"slug": "realvnc",
|
||||
"hex": "488aec"
|
||||
@@ -1268,6 +1331,14 @@
|
||||
"title": "US Mobile",
|
||||
"slug": "us_mobile"
|
||||
},
|
||||
{
|
||||
"title": "uollet",
|
||||
"slug": "uollet",
|
||||
"altNames": [
|
||||
"UOLLET",
|
||||
"uollet.com.br"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Vikunja"
|
||||
},
|
||||
@@ -1353,6 +1424,34 @@
|
||||
},
|
||||
{
|
||||
"title": "CoinSpot"
|
||||
},
|
||||
{
|
||||
"title": "Aternos",
|
||||
"slug": "aternos"
|
||||
},
|
||||
{
|
||||
"title": "Toshl Finance",
|
||||
"slug": "toshl_finance",
|
||||
"altNames": [
|
||||
"Toshl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "Tebex",
|
||||
"slug": "tebex",
|
||||
"altNames": [
|
||||
"tebex",
|
||||
"tebex.io",
|
||||
"buycraft"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "xAI",
|
||||
"slug": "xai"
|
||||
},
|
||||
{
|
||||
"title": "Cronometer",
|
||||
"slug": "cronometer"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
1
auth/assets/custom-icons/icons/ankara_university.svg
Normal file
|
After Width: | Height: | Size: 20 KiB |
6
auth/assets/custom-icons/icons/ar24.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<svg width="500" height="500" viewBox="0 0 500 500" fill="#0000FF" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M139.63 306.55H125.64C123.41 306.55 121.53 306.5 119.99 306.39C118.45 306.28 117.13 305.99 116.01 305.51C114.9 305.04 113.92 304.32 113.08 303.36C112.22 302.41 111.38 301.09 110.53 299.39L103.85 286.35H35.47L25.29 306.55H0L58.37 194.11H81.26L139.63 306.55ZM93.2 265.36L69.66 218.6L46.12 265.36H93.2Z"/>
|
||||
<path d="M265.23 306.55H245.67C241.96 306.55 238.93 306.07 236.6 305.12C234.27 304.16 232.31 302.68 230.72 300.67L206.17 270.76H177.48V306.55H153.95V195.23C156.92 195.12 160.16 195.04 163.66 194.99C167.17 194.93 170.77 194.86 174.5 194.75C178.21 194.64 181.92 194.57 185.64 194.51C189.36 194.46 192.86 194.43 196.15 194.43C209.74 194.43 220.97 195.41 229.84 197.37C238.71 199.34 246 202.91 251.74 208.1C258.11 214.04 261.3 222.1 261.3 232.28C261.3 237.37 260.61 241.85 259.23 245.72C257.84 249.59 255.88 252.96 253.33 255.81C250.78 258.68 247.7 261.09 244.09 263.05C240.49 265.02 236.45 266.63 231.99 267.9L265.23 306.55ZM196.25 250.25C199.75 250.25 203.27 250.22 206.82 250.17C210.38 250.12 213.74 249.88 216.93 249.45C220.1 249.03 223.02 248.36 225.67 247.46C228.32 246.55 230.5 245.24 232.19 243.54C233.67 241.94 234.82 240.29 235.61 238.59C236.4 236.89 236.81 234.7 236.81 232.03C236.81 230.23 236.45 228.47 235.77 226.77C235.08 225.06 234.15 223.62 232.99 222.45C231.4 220.85 229.44 219.6 227.11 218.7C224.77 217.79 222.1 217.12 219.07 216.7C216.05 216.27 212.63 216.03 208.81 215.98C205 215.93 200.81 215.9 196.25 215.9H187.18C183.58 215.9 180.34 215.95 177.48 216.06V250.1C180.34 250.2 183.58 250.25 187.18 250.25H196.25Z"/>
|
||||
<path d="M324.59 213.35C318.34 213.35 312.61 215.03 307.41 218.37C302.22 221.7 297.93 225.65 294.54 230.22L276.09 217.97C282.13 210.65 289.36 204.66 297.79 200C306.22 195.33 315.47 193 325.55 193C332.33 193 338.53 193.87 344.15 195.62C349.77 197.37 354.59 199.84 358.63 203.02C362.65 206.2 365.78 210.07 368.01 214.63C370.23 219.19 371.35 224.27 371.35 229.89C371.35 235.2 370.36 239.86 368.4 243.89C366.44 247.92 363.85 251.6 360.61 254.94C357.38 258.28 353.69 261.38 349.56 264.25C345.42 267.11 341.18 269.97 336.84 272.84L317.27 285.72H370.87V306.55H279.42V286.04L318.39 260.27C322.2 257.73 325.86 255.32 329.36 253.03C332.86 250.76 335.93 248.42 338.58 246.04C341.24 243.65 343.35 241.13 344.95 238.48C346.54 235.83 347.33 232.91 347.33 229.74C347.33 227.41 346.72 225.23 345.5 223.22C344.28 221.2 342.64 219.45 340.57 217.97C338.51 216.49 336.09 215.35 333.34 214.55C330.58 213.75 327.66 213.35 324.59 213.35Z"/>
|
||||
<path d="M457.22 306.55V283.49H388.84V259.95L455.47 195.23H480.12V263.77H500V283.49H480.12V306.55L457.22 306.55ZM413.17 263.77H457.22V220.35L413.17 263.77Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
13
auth/assets/custom-icons/icons/aternos.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" shape-rendering="geometricPrecision" text-rendering="geometricPrecision" image-rendering="optimizeQuality" fill-rule="evenodd" xml:space="preserve"
|
||||
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="300mm" height="300mm" viewBox="0 0 16000 16000" preserveAspectRatio="xMidYMid meet">
|
||||
<g>
|
||||
<path fill="#2b87d3" d="M0 8000 l0 -8000 8000 0 8000 0 0 8000 0 8000 -8000 0 -8000 0 0 -8000z m13990 0 l0 -5990 -5990 0 -5990 0 0 5990 0 5990 5990 0 5990 0 0 -5990z"/>
|
||||
<path fill="#2b87d3" d="M2995 12998 c-3 -7 -4 -911 -3 -2008 l3 -1995 1005 0 1005 0 3 997 2 998 998 2 997 3 0 1005 0 1005 -2003 3 c-1597 2 -2004 0 -2007 -10z"/>
|
||||
<path fill="#2b87d3" d="M8995 12998 c-3 -7 -4 -461 -3 -1008 l3 -995 997 -3 998 -2 2 -998 3 -997 1005 0 1005 0 0 2005 0 2005 -2003 3 c-1597 2 -2004 0 -2007 -10z"/>
|
||||
<path fill="#2b87d3" d="M5995 9998 c-3 -7 -4 -911 -3 -2008 l3 -1995 2005 0 2005 0 0 2005 0 2005 -2003 3 c-1597 2 -2004 0 -2007 -10z"/>
|
||||
<path fill="#2b87d3" d="M2995 6998 c-3 -7 -4 -911 -3 -2008 l3 -1995 2005 0 2005 0 0 1005 0 1005 -997 3 -998 2 -2 998 -3 997 -1003 3 c-797 2 -1004 0 -1007 -10z"/>
|
||||
<path fill="#2b87d3" d="M10997 7003 c-4 -3 -7 -453 -7 -1000 l0 -993 -997 -2 -998 -3 0 -1005 0 -1005 2005 0 2005 0 0 2005 0 2005 -1001 3 c-550 1 -1004 -1 -1007 -5z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
28
auth/assets/custom-icons/icons/bitazza.svg
Normal file
@@ -0,0 +1,28 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="836.84424" height="914.80933">
|
||||
<defs>
|
||||
<clipPath id="a">
|
||||
<path stroke-linecap="round" d="M-186.41-125.66h372.816v351.267H-186.41Z"/>
|
||||
</clipPath>
|
||||
<clipPath id="b">
|
||||
<path stroke-linecap="round" d="M-186.41-95.22h372.816v351.267H-186.41Z"/>
|
||||
</clipPath>
|
||||
<clipPath id="c">
|
||||
<path stroke-linecap="round" d="M-160.08-140.83h372.816v351.267H-160.08Z"/>
|
||||
</clipPath>
|
||||
<clipPath id="d">
|
||||
<path stroke-linecap="round" d="M-212.74-140.83h372.816v351.267H-212.74Z"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<g clip-path="url(#a)" transform="translate(418.40805 457.4024)scale(3.64)">
|
||||
<path d="m298.246 129.84-6.486-4.585 6.486-4.586c12.26215-8.68661 19.24883-23.025924 18.53269-38.036075-.71613-15.010151-9.03624-28.619172-22.06969-36.098925l-.568-.349c-12.19994-7.033047-27.02434-7.819595-39.9-2.117l-7.053 3.144-.83-7.687c-1.51376-14.021838-9.62342-26.481265-21.832-33.542l-.2-.087C217.64616 2.0246361 210.0606-.00958995 202.34 0c-8.04957.00100343-15.94637 2.1978633-22.84 6.354l-1.114.677c-11.58345 7.090871-19.28369 19.094034-20.9 32.579l-.917 7.6-6.967-3.1c-12.8836-5.747507-27.74063-4.976398-39.96 2.074l-.7.394c-13.604075 7.890873-21.986349 22.420072-22.008 38.147v.787c.02516 14.283035 6.954274 27.67261 18.6 35.942L112 126.04l-6.463 4.586c-12.260313 8.67436-19.255033 22.99852-18.556462 38.00091.698572 15.00239 8.994121 28.61458 22.007462 36.11209l.633.393c12.22088 7.0137 27.04998 7.79342 39.939 2.1l7.054-3.123.807 7.687c1.51242 13.97659 9.57745 26.40246 21.727 33.475l.284.153c13.92748 8.0385 31.12909 7.84683 44.874-.5l1.092-.655c11.59433-7.07976 19.29833-19.08878 20.9-32.579l.9-7.6 6.987 3.123c12.9143 5.73028 27.78955 4.94317 40.027-2.118l.611-.372c13.60439-7.88085 21.98922-22.40383 22.012-38.126v-.808c-.0274-14.27871-6.94846-27.66532-18.583-35.943M292 166.591c-.0145 6.86067-3.67326 13.19693-9.608 16.639l-.654.394c-5.96032 3.4063-13.27768 3.4063-19.238 0l-17.884-10.351-22.841 13.211v20.133c-.0194 6.70609-3.51599 12.92214-9.237 16.421l-1.114.677c-5.99641 3.62641-13.48927 3.70989-19.565.218l-.24-.153c-5.94051-3.43919-9.60731-9.7748-9.63-16.639v-20.657l-22.841-13.211-17.884 10.351c-5.9447 3.40734-13.2503 3.40734-19.195 0l-.677-.415c-5.92257-3.40301-9.59053-9.69703-9.63171-16.52753-.0412-6.83049 3.5506-13.16829 9.43171-16.64247l18.648-11.027-.153-26.313-18.3-10.569c-5.94649-3.42296-9.61685-9.756715-9.63-16.618v-.787c.0184-6.865358 3.68634-13.20295 9.63-16.639l.655-.415c5.9528-3.406398 13.2642-3.406398 19.217 0l17.884 10.351 22.841-13.189V44.7c.0433-6.705114 3.54343-12.913272 9.258-16.421l1.114-.677c5.98556-3.637575 13.47843-3.721536 19.544-.219l.24.131c5.95616 3.434267 9.62726 9.785677 9.63 16.661v20.658l22.841 13.189L262.5 67.671c5.96032-3.406299 13.27768-3.406299 19.238 0l.654.415c5.91101 3.411809 9.56893 9.702792 9.61008 16.527659.0412 6.824866-3.54064 13.159502-9.41008 16.642341l-18.67 11.027.153 26.313 18.321 10.569c5.93573 3.43032 9.59519 9.76235 9.604 16.618z" style="opacity:1;fill:#10f48b;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0" transform="translate(-201.88 -125.66)" vector-effect="non-scaling-stroke"/>
|
||||
</g>
|
||||
<g clip-path="url(#b)" transform="translate(418.40805 346.6008)scale(3.64)">
|
||||
<path d="m255.89 111.218-37.166 21.432-37.165-21.432 37.165-21.463Z" style="opacity:1;fill:#10f48b;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0" transform="translate(-218.72 -111.2)" vector-effect="non-scaling-stroke"/>
|
||||
</g>
|
||||
<g clip-path="url(#c)" transform="translate(322.56685 512.6212)scale(3.64)">
|
||||
<path d="M209.3 153.613v42.927l-37.166-21.463v-42.9z" style="opacity:1;fill:#10f48b;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0" transform="translate(-190.72 -164.36)" vector-effect="non-scaling-stroke"/>
|
||||
</g>
|
||||
<g clip-path="url(#d)" transform="translate(514.24925 512.6212)scale(3.64)">
|
||||
<path d="M273.357 132.181v42.9l-37.166 21.459v-42.927z" style="opacity:1;fill:#10f48b;fill-rule:nonzero;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0" transform="translate(-254.77 -164.36)" vector-effect="non-scaling-stroke"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.5 KiB |
@@ -1,29 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="logosandtypes_com" data-name="logosandtypes com" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 150 150">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #101010;
|
||||
}
|
||||
|
||||
.cls-2 {
|
||||
fill: none;
|
||||
}
|
||||
|
||||
.cls-3 {
|
||||
fill: url(#linear-gradient);
|
||||
}
|
||||
</style>
|
||||
<linearGradient id="linear-gradient" x1="186.97" y1="96.04" x2="45.7" y2="96.04" gradientTransform="translate(0 150.11) scale(1 -1)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#165cc3"/>
|
||||
<stop offset="1" stop-color="#3ddabb"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<g id="Layer_3" data-name="Layer 3">
|
||||
<g id="Layer_2" data-name="Layer 2">
|
||||
<path id="Layer_3-2" data-name="Layer 3-2" class="cls-2" d="M0,0H150V150H0V0Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<path class="cls-1" d="M111.63,75.01c.06,.86,.08,1.72,.08,2.59,0,20.52-16.62,37.16-37.14,37.16-20.52,0-37.16-16.62-37.16-37.14,0-20.52,16.62-37.16,37.14-37.16,0,0,.02,0,.02,0,1.61,0,3.22,.1,4.82,.32l12.7-17.11C62.3,14,30.31,30.3,20.63,60.09c-9.68,29.79,6.62,61.78,36.41,71.47,29.79,9.68,61.78-6.62,71.47-36.41,4.29-13.2,3.59-27.52-1.97-40.24l-14.9,20.11Z"/>
|
||||
<polygon class="cls-3" points="120.26 4.82 74.49 66.53 62.93 53.99 45.67 69.89 76.4 103.32 149.5 4.82 120.26 4.82"/>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 150 150">
|
||||
<defs>
|
||||
<linearGradient id="a" x1="186.97" x2="45.7" y1="96.04" y2="96.04"
|
||||
gradientTransform="matrix(1 0 0 -1 0 150.11)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#165cc3" />
|
||||
<stop offset="1" stop-color="#3ddabb" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path
|
||||
d="M111.63 75.01c.06.86.08 1.72.08 2.59 0 20.52-16.62 37.16-37.14 37.16S37.41 98.14 37.41 77.62s16.62-37.16 37.14-37.16h.02c1.61 0 3.22.1 4.82.32l12.7-17.11C62.3 14 30.31 30.3 20.63 60.09s6.62 61.78 36.41 71.47c29.79 9.68 61.78-6.62 71.47-36.41 4.29-13.2 3.59-27.52-1.97-40.24l-14.9 20.11Z"
|
||||
style="fill:#EFEFEF; mix-blend-mode: difference" />
|
||||
<path d="M120.26 4.82 74.49 66.53 62.93 53.99l-17.26 15.9 30.73 33.43 73.1-98.5z"
|
||||
style="fill:url(#a)" />
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 876 B |
25
auth/assets/custom-icons/icons/cronometer.svg
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
width="107.51429"
|
||||
height="102.75398"
|
||||
viewBox="0 0 107.51429 102.75398"
|
||||
fill="none"
|
||||
version="1.1"
|
||||
id="svg12"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs12" />
|
||||
<g
|
||||
id="g12"
|
||||
transform="translate(34.950067,-215.99315)">
|
||||
<path
|
||||
d="m 57.85843,271.77769 c -0.774,-0.1257 -1.5665,0.0595 -2.2048,0.5152 -0.6382,0.4557 -1.0704,1.145 -1.2028,1.9179 -0.1447,0.8786 -0.3109,1.7775 -0.496,2.6697 -1.7799,8.5172 -5.1174,16.6325 -9.8446,23.9376 -5.0162,7.5169 -10.8489,12.0047 -15.6015,12.0047 -2.2725,0.0492 -4.5367,-0.2938 -6.6926,-1.0138 -5.6912,-1.775 -11.7884,-1.7717 -17.4777,0.009 -2.1479,0.7139 -4.4023,1.0536 -6.6652,1.0044 -4.7486,0 -10.58,-4.4851 -15.5963,-11.9979 -4.727,-7.3046 -8.0652,-15.4188 -9.84715,-23.935 -1.97756,-9.485 -1.60717,-17.8346 1.06924,-24.1485 1.09829,-2.7795 2.74951,-5.307 4.85381,-7.4292 2.1042,-2.1223 4.6176,-3.7952 7.3876,-4.9171 2.77,-1.122 5.7387,-1.6698 8.7268,-1.6101 2.988,0.0597 5.9328,0.7255 8.6558,1.9572 l 0.1663,0.0635 c 3.1372,1.3741 6.5201,2.0997 9.9449,2.1331 h 0.127 c 3.4272,-0.0346 6.8117,-0.763 9.9498,-2.1412 l 0.1502,-0.0581 c 2.981,-1.3116 6.2053,-1.979 9.462,-1.9586 1.8593,0.0219 3.7051,0.317 5.4785,0.8759 1.02,0.3144 2.0172,0.6982 2.9846,1.1489 0.7121,0.333 1.5269,0.3705 2.2665,0.1042 0.7397,-0.2662 1.3439,-0.8144 1.6805,-1.5248 0.1645,-0.3511 0.2583,-0.7312 0.2755,-1.1185 0.0172,-0.3873 -0.0421,-0.7742 -0.1749,-1.1385 -0.1328,-0.3643 -0.3362,-0.6987 -0.5986,-0.9841 -0.2625,-0.2854 -0.5788,-0.5161 -0.9307,-0.6789 -1.2159,-0.5667 -2.4693,-1.0495 -3.7512,-1.445 -2.3402,-0.7366 -4.7769,-1.1202 -7.2302,-1.1381 -3.9898,-0.0245 -7.9413,0.7777 -11.6058,2.356 l -0.1514,0.0581 c -1.5551,0.6563 -3.1723,1.1544 -4.8271,1.4869 -0.2018,-1.6639 -0.5606,-3.305 -1.0719,-4.9013 11.4383,-2.0749 11.0342,-15.88816 11.0342,-15.88816 -9.3133,1.40309 -13.3294,4.93919 -14.9623,8.33877 -1.5519,-2.01717 -3.4478,-3.74459 -5.6002,-5.10275 -0.3906,-0.24958 -0.8265,-0.41979 -1.2828,-0.50093 -0.4563,-0.0811 -0.9238,-0.0716 -1.3765,0.0281 -0.4526,0.0997 -0.8812,0.28749 -1.2613,0.55278 -0.38,0.26529 -0.7041,0.60283 -0.9537,0.99335 -0.2496,0.39052 -0.4198,0.82638 -0.5009,1.28269 -0.0812,0.45631 -0.0717,0.92412 0.028,1.37675 0.0997,0.45262 0.2875,0.88118 0.5528,1.2612 0.2653,0.38003 0.6028,0.70409 0.9933,0.95367 1.9269,1.29176 3.5632,2.97116 4.8046,4.93076 1.2415,1.9597 2.0607,4.1567 2.4055,6.4507 -1.2634,-0.3369 -2.5038,-0.7536 -3.7143,-1.2476 l -0.1759,-0.0676 c -3.6617,-1.5742 -7.6092,-2.3745 -11.5949,-2.3506 -9.0309,0 -20.73562,6.0327 -25.63155,17.5723 -3.97541,9.3756 -2.962867,20.2042 -1.41244,27.6387 1.94038,9.2532 5.57254,18.0686 10.71379,26.003 6.3017,9.4378 13.5982,14.6369 20.5461,14.6369 2.8438,0.049 5.6759,-0.375 8.3806,-1.255 4.5889,-1.454 9.515,-1.454 14.1039,0 2.6964,0.878 5.5198,1.3 8.3549,1.251 6.8478,0 14.3376,-5.3384 20.5461,-14.645 5.1404,-7.9344 8.7713,-16.75 10.7098,-26.0031 0.2041,-0.9746 0.3851,-1.9559 0.5406,-2.917 0.062,-0.3835 0.0481,-0.7755 -0.0416,-1.1535 -0.0897,-0.3779 -0.2534,-0.7345 -0.4812,-1.0492 -0.2278,-0.3147 -0.5154,-0.5813 -0.8464,-0.7846 -0.3311,-0.2033 -0.6991,-0.3393 -1.0828,-0.4001"
|
||||
fill="#ff6733"
|
||||
id="path11" />
|
||||
<path
|
||||
d="m 71.33493,256.39499 v 0.0121 l -8.2649,-5.8475 -0.4271,-0.2298 0.0512,-0.5177 0.2393,-10.0798 h 0.0095 c 0.0046,-0.5208 -0.1263,-1.0339 -0.3802,-1.4887 -0.2538,-0.4548 -0.6219,-0.8357 -1.0675,-1.1053 -0.4471,-0.2723 -0.9569,-0.4242 -1.4801,-0.441 -0.5233,-0.0169 -1.0422,0.102 -1.5059,0.3451 l -8.7538,4.3593 c -1.2619,0.6789 -2.3372,1.6578 -3.1318,2.8502 -0.7946,1.1925 -1.2839,2.5621 -1.4247,3.9881 l -0.7408,7.7913 -6.3059,3.3658 c -2.4638,-3.7027 -5.7171,-6.8136 -9.5264,-9.1092 -3.8093,-2.2956 -8.0799,-3.7189 -12.5047,-4.1675 -4.4249,-0.4486 -8.894,0.0886 -13.0866,1.573 -4.1925,1.4843 -8.0041,3.879 -11.1609,7.0119 -2.7859,2.7623 -4.9974,6.0489 -6.5065,9.6702 -1.509,3.6213 -2.286,7.5057 -2.286,11.4288 0,3.9232 0.777,7.8075 2.286,11.4288 1.5091,3.6214 3.7206,6.908 6.5065,9.6702 4.197,4.1711 9.5317,7.0104 15.3356,8.1623 5.8039,1.1518 11.8188,0.5648 17.2905,-1.6873 5.4718,-2.2522 10.157,-6.0694 13.4684,-10.9731 3.3114,-4.9037 5.1017,-10.6758 5.1465,-16.5928 0.0013,-3.8327 -0.7435,-7.6291 -2.1926,-11.1774 l 6.2181,-3.3117 -0.0109,0.0311 6.7206,4.0741 c 1.2315,0.7427 2.6257,1.1736 4.0615,1.2554 1.4357,0.0817 2.8695,-0.1882 4.1772,-0.7863 l 8.7534,-4.3593 c 0.472,-0.2216 0.877,-0.5622 1.177,-0.9882 0.3,-0.426 0.483,-0.9228 0.532,-1.4413 0.049,-0.5185 -0.038,-1.0409 -0.252,-1.5156 -0.214,-0.4747 -0.548,-0.8853 -0.97,-1.1915 m -34.1485,19.4107 c -0.0347,4.7475 -1.47,9.3791 -4.1262,13.3141 -2.6561,3.9351 -6.4148,6.9984 -10.8048,8.806 -4.39,1.8076 -9.2158,2.2789 -13.8727,1.355 -4.6568,-0.924 -8.9373,-3.2021 -12.3047,-6.5488 -2.2347,-2.2161 -4.0086,-4.8527 -5.2191,-7.7579 -1.2105,-2.9051 -1.8335,-6.0211 -1.8335,-9.1684 0,-3.1472 0.623,-6.2632 1.8335,-9.1684 1.2105,-2.9051 2.9844,-5.5417 5.2191,-7.7578 2.5007,-2.4815 5.514,-4.3861 8.8284,-5.5801 3.3145,-1.194 6.85,-1.6486 10.3587,-1.3319 3.5087,0.3167 6.9057,1.397 9.9528,3.1652 3.0471,1.7682 5.6703,4.1815 7.6862,7.0707 l -8.1614,4.3552 c -1.3138,-1.6306 -2.9557,-2.9671 -4.8188,-3.9229 -1.8632,-0.9559 -3.9062,-1.5098 -5.997,-1.6259 -2.0908,-0.1161 -4.1826,0.2082 -6.1401,0.952 -1.9576,0.7437 -3.7372,1.8903 -5.2234,3.3654 -1.3826,1.3719 -2.4797,3.0038 -3.2285,4.8018 -0.7489,1.798 -1.1343,3.7264 -1.1343,5.674 0,1.9477 0.3854,3.8761 1.1343,5.6741 0.7488,1.7979 1.8459,3.4299 3.2285,4.8018 2.317,2.3457 5.3482,3.8548 8.6165,4.2898 3.2684,0.4351 6.5886,-0.2286 9.4386,-1.8865 2.85,-1.6579 5.0682,-4.2161 6.3055,-7.2723 1.2374,-3.0562 1.4238,-6.437 0.53,-9.6107 l 8.2064,-4.3687 c 1.0104,2.6766 1.5273,5.5142 1.526,8.3752 z m -26.0801,1.2734 c 0.3553,0.6991 0.9725,1.2295 1.7171,1.4759 0.7445,0.2463 1.5564,0.1885 2.2586,-0.1607 l 6.9193,-3.6848 c 0.2696,2.1001 -0.213,4.2278 -1.3623,6.006 -1.1493,1.7782 -2.891,3.0921 -4.9165,3.7087 -2.0255,0.6167 -4.2042,0.4963 -6.1494,-0.3399 -1.9452,-0.8361 -3.5316,-2.334 -4.4779,-4.228 -0.9463,-1.8941 -1.1912,-4.0621 -0.6917,-6.1196 0.4995,-2.0576 1.7111,-3.8718 3.4205,-5.1212 1.7094,-1.2494 3.806,-1.8532 5.9181,-1.7044 2.1121,0.1487 4.1034,1.0405 5.6207,2.5172 l -6.9345,3.6997 c -0.3472,0.1714 -0.6571,0.4099 -0.9118,0.7018 -0.2546,0.2918 -0.449,0.6311 -0.5719,0.9984 -0.1229,0.3673 -0.172,0.7553 -0.1442,1.1416 0.0278,0.3863 0.1317,0.7633 0.3059,1.1093 z m 39.7718,-27.0764 c 0.219,-1.157 0.3879,-2.1708 1.4557,-2.7467 l 4.5109,-2.2438 -0.6085,5.5191 -5.7029,2.8386 z m 8.0467,10.3339 c -0.5151,0.173 -1.0642,0.2192 -1.6009,0.1347 -0.5367,-0.0845 -1.0453,-0.2971 -1.4824,-0.6199 l -2.9222,-1.7424 5.7042,-2.8386 4.8109,2.8224 z"
|
||||
fill="#ff6733"
|
||||
id="path12" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.6 KiB |
1
auth/assets/custom-icons/icons/cryptee.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg width="1400" height="1400" xmlns="http://www.w3.org/2000/svg"><path d="M699.914 0C1004.659 0 1263.915 194.786 1360 466.662l-399.246-.003C896.674 395.059 803.556 350 699.914 350c-193.276 0-349.957 156.7-349.957 350s156.681 350 349.957 350c103.641 0 196.76-45.059 260.84-116.658L1360 933.34C1263.915 1205.214 1004.659 1400 699.914 1400 313.362 1400 0 1086.6 0 700S313.362 0 699.914 0zm347.087 747.002L1398 747a696.274 696.274 0 0 1-12.453 93H1021a345.75 345.75 0 0 0 26.001-92.998zM1385.547 560A696.301 696.301 0 0 1 1398 653l-351-.002A345.762 345.762 0 0 0 1021 560h364.547z" fill-rule="evenodd"/></svg>
|
||||
|
After Width: | Height: | Size: 608 B |
9
auth/assets/custom-icons/icons/cssbuy.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg width="145" height="39" viewBox="0 0 145 39" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<rect width="145" height="39" fill="url(#pattern0_2030_2)"/>
|
||||
<defs>
|
||||
<pattern id="pattern0_2030_2" patternContentUnits="objectBoundingBox" width="1" height="1">
|
||||
<use xlink:href="#image0_2030_2" transform="scale(0.00689655 0.025641)"/>
|
||||
</pattern>
|
||||
<image id="image0_2030_2" width="145" height="39" preserveAspectRatio="none" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAJEAAAAnCAMAAAA4lVp5AAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAERUExURQAAAGy8Kmy8Kmy9Kmy8Km28KWy9Km2/Lmy8Kmu8Kmu8Kmy8KWy7KGy9Kmu9KWu8Km27K3C/KGy8Kmy8KW29K2y8Kmy8K3C/MGy9Kmy7LGy8KWu8LGy7Kmy9KnDCKWy8KWy7Km28Kmy7Kmy8Kmu5Km29Km26KGq6Kmu8Kmy9Kmu7Kmy8Kmy8Kmy8Kmy8Kmy8Km69KWy9K2u8KoC/IG28Km27Kmy7Kmy5K2y8Km2+LGi3KGq6Kmu8KWy9Kmq8K2y6Kmy7Kmy8Kn7ERazZh9ruyv///+Py177ioYjJUpHNX+335LXdlHXAN9HqvKPVecjmr5rRbfb78qPVerbeldvuyuTz2O335cjmsK3ZiNHqvb/iok0+o7UAAABBdFJOUwD359fHm1UcxmbMv0B/cN+PIKOgj9CQEMBA8EyA1BlQkrDb4DdgPzCkyE/v7c+2r12QnwjDMeJC9VIgYFiXSGh4PBaO/QAAAAFiS0dERY6zqFcAAAAHdElNRQfpBR8TLQ+T8HIEAAAAAW9yTlQBz6J3mgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyNS0wNS0zMVQxOTo0NToxNSswMDowMFaVqvwAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjUtMDUtMzFUMTk6NDU6MTUrMDA6MDAnyBJAAAAAKHRFWHRkYXRlOnRpbWVzdGFtcAAyMDI1LTA1LTMxVDE5OjQ1OjE1KzAwOjAwcN0znwAAAFplWElmTU0AKgAAAAgABQESAAMAAAABAAEAAAEaAAUAAAABAAAASgEbAAUAAAABAAAAUgEoAAMAAAABAAIAAAITAAMAAAABAAEAAAAAAAAAAABIAAAAAQAAAEgAAAABH1L3NAAABF9JREFUWMPNV3lf2kAQpbWXUlFKOUJpFSnQ1tqD3tYeGxIgEJRww/f/IN3ZczYE/QlqnT8kzmY3L++9md3EyEpx5+7GvfsPHsauI1ZDJOLR5m1DRMjW1WNaExEh8ce3DREh2xHLJuTgTnw3eeOIyJPFZVMXQb5eROTpwrJpYzyzFiK77jhuw2m2UM5rOm3H6dgq4de7NNM9XcZCluZyFo1EJg/jlxEujOfMleFIAL4jU02R6QUi0Za4n5mrWjSVEOCArsLKiPouioA/zm/o1Fn4LnEPef7CWLVAU5a43gO+VkU0cI0IfEgyhoLhkKX6oCG7Gg3HjCUxdd9YtUgzhoIrIuLvPun5xO4zXSY02YKLKQg6ohdDejEDPMDNFIZ6fO6BsWqJkDRGdBlrYw8BisAT5oHnuzbnbcjHIUN/4YffNaFXAzH7JVo0iUGU6T9l7q5KRVq8WqlUeKaip0EyixHB+q4qsZYQaaafOpsNemJAOHw4mUr/43LbUyAoRTuE5Dkgmt0T2RxXsojrEMZLGJHvojeG54/mAG8OZYe19TVHOF6FjF0VgOIKRwI9XtRfWdckkxrKwXQR97IRzCxdG2WYuKfh+14jRJQBUoF4s3uo6SpKsqg+AibIW0SvkTF8NBNWDoXPe8+ZZoVXZLtuon+7rIBLluZAPr0s6UqrokxyzfDkoSjucMjuo3jipl/gCTkUp4+kUFmiO2VK0pUhsnHl5JUphkeiIAVmNyT+zGiYC4jK5vyytrukKy3pqgqp2BxengdqorsMEfEHHFOg3OTxfunOIxHBi2f5Awt5haOgssrYNOj4ERBI/Zbno+8MRD0SHf507Jo281ivcO0oRJSBEi7puJQljZKW0g8cBT+iM+wbPkLv3AG+Wp1uY6DNQ7eMetMJkL+mUYiItjD3h2QjJXIJTRdvXRaa8kCtODeqvwfVxMpqrhLQGOGvuGlk9q8YpkVv9tK74BjZeoqaxCzDUlKa8YzQAXu1FbDNo6f3UoFoqIlpLEGUQBbWNVRG2ZKmC8YPC1ozGu/VkiNd452A7yi+RglHgJlg0lNv0ItAlML/gGVJyNiYLnki1zLHPqglW+IU5ogDEVDB/Ntudjptsc3ZvDd16uxoN46qNbzxM3hFiaiqRNMkJtlkpRnER7WmcV7j/vHHKMNKbYrvaUUgAh9ITbI1Ip9eIOoIYNYBbGdIMxqfPqtF1WmVKiP0kA0aAPkh3IHRLLCxa2xT266BZAIepA+rUiZNIrN+MWbEl6+67ww4gNFAb1p9npqp7mlPGHHjgbmxyeUKxIwU5mLniCf1oZL1xvCXwbfvaAHf87zwCaDlhZo5vckO3aMQ5YxsSelRzUuIOYyoiG2u4scOWT/kYnmVSedS2B9J9lmZ3othRMu+C45rV4fovLCskD6RmvH4uXUTiBYiWjMRm2tiWgXQRd9yJ782bhbROZqpOP598ufv9kqxAqLMeZr9j7DI5b52rz3gS05udbc5/gHZ/BLSJh/eDgAAAABJRU5ErkJggg=="/>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.0 KiB |
3
auth/assets/custom-icons/icons/finary.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="500" height="500" viewBox="0 0 500 500" fill="#F1C086" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M166.67 62C74.62 62 0 136.62 0 228.67H333.33C425.38 228.67 500 154.05 500 62H166.67ZM166.67 270.33C74.62 270.33 0 344.95 0 437H154.76C246.81 437 321.43 362.38 321.43 270.33H166.67Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 302 B |
7
auth/assets/custom-icons/icons/fzj.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
<svg width="21.087mm" height="21.735mm" version="1.1" viewBox="0 0 21.087 21.735" xmlns="http://www.w3.org/2000/svg">
|
||||
<g transform="translate(34.396 -25.136)">
|
||||
<path d="m-26.575 34.404 3.5438-9.2136c-0.38165-0.0363-0.76329-0.0545-1.1631-0.0545-1.9082 0-3.7983 0.49067-5.452 1.4538-1.6538 0.96316-3.035 2.3261-3.9982 3.998-0.09087 0.14538-0.16356 0.30893-0.25443 0.47249v0.0545l-0.14538 0.39981c-1.0904 2.7804 0.56337 4.834 2.1626 5.4337 0.45433 0.16355 0.92685 0.25441 1.3994 0.25441 0.72694 0 1.4175-0.18172 2.0536-0.54517 0.6179-0.36347 1.1449-0.8723 1.5084-1.4902 0.14539-0.23624 0.25443-0.49067 0.34529-0.76326m6.4334 2.3625c-2.1081 5.5246-7.2149 7.5962-11.885 6.7966 0.70877 0.72692 1.5084 1.3448 2.3807 1.8536 1.6538 0.96316 3.5257 1.4538 5.452 1.4538 1.9082 0 3.7619-0.49067 5.4339-1.4538 1.6538-0.96316 3.035-2.3261 3.9982-3.9798 0.94502-1.6537 1.4539-3.5437 1.4539-5.4337 0-1.9082-0.50886-3.8163-1.4539-5.4518-0.5452-0.94499-1.1994-1.7628-1.9809-2.4897l-3.3984 8.7048" fill="#023d6b" fill-rule="evenodd" stroke-width=".26458"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
8
auth/assets/custom-icons/icons/gerid.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generator: https://ezgif.com/png-to-svg -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="195" height="195">
|
||||
<path d="M0,0 L44,0 L70,2 L94,6 L98,8 L98,39 L95,75 L91,98 L85,114 L76,131 L65,145 L58,153 L55,154 L55,144 L51,131 L42,121 L38,118 L38,98 L34,98 L33,29 L28,27 L26,26 L26,89 L22,89 L22,25 L20,25 L20,89 L13,89 L12,29 L11,30 L11,98 L6,98 L6,118 L-3,125 L-9,135 L-11,143 L-11,154 L-15,152 L-25,141 L-35,126 L-43,110 L-48,95 L-52,69 L-54,44 L-55,8 L-50,6 L-26,2 Z " fill="#046097" transform="translate(75,8)"/>
|
||||
<path d="M0,0 L44,0 L70,2 L94,6 L98,8 L98,39 L95,75 L91,98 L85,114 L76,131 L65,145 L58,153 L55,154 L55,144 L51,131 L42,121 L38,118 L38,98 L34,98 L33,29 L28,27 L26,26 L26,89 L22,89 L22,25 L20,24 L22,24 L22,1 L0,1 Z " fill="#0495C0" transform="translate(75,8)"/>
|
||||
<path d="M0,0 L8,0 L13,3 L15,7 L15,15 L6,21 L0,20 L-7,16 L-8,10 L-5,3 Z " fill="#0A669B" transform="translate(93,165)"/>
|
||||
<path d="M0,0 L6,1 L10,5 L11,7 L11,15 L2,21 L-2,20 L0,20 Z " fill="#0D99C1" transform="translate(97,165)"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 8.0 KiB |
@@ -1,15 +1,7 @@
|
||||
<svg width="500" height="500" viewBox="0 0 500 500" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g style="mix-blend-mode:difference">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M363.636 23H409.091V477.545H363.636V23ZM0 23H45.4545V477.545H0V23ZM227.273 295.727H181.818V386.636H227.273V295.727ZM272.727 113.909H318.182V204.818H272.727V113.909Z" fill="white"/>
|
||||
</g>
|
||||
<path d="M136.364 386.636H45.4545V477.545H136.364V386.636Z" fill="#EA3326"/>
|
||||
<path d="M500 386.636H409.091V477.545H500V386.636Z" fill="#EA3326"/>
|
||||
<path d="M136.364 295.727H45.4545V386.636H136.364V295.727Z" fill="#EB5829"/>
|
||||
<path d="M318.182 295.727H227.273V386.636H318.182V295.727Z" fill="#EB5829"/>
|
||||
<path d="M500 295.727H409.091V386.636H500V295.727Z" fill="#EB5829"/>
|
||||
<path d="M136.364 23H45.4545V113.909H136.364V23Z" fill="#F7D046"/>
|
||||
<path d="M500 23H409.091V113.909H500V23Z" fill="#F7D046"/>
|
||||
<path d="M227.273 113.909H45.4545V204.818H227.273V113.909Z" fill="#F2A73B"/>
|
||||
<path d="M500 113.909H318.182V204.818H500V113.909Z" fill="#F2A73B"/>
|
||||
<path d="M500 204.818H45.4545V295.727H500V204.818Z" fill="#EE792F"/>
|
||||
</svg>
|
||||
<svg width="500" height="500" viewBox="0 0 500 500" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M71.41 73H142.83V143.75H71.41V73ZM357.12 73H428.54V143.75H357.12V73Z" fill="#FFD800"/>
|
||||
<path d="M71.41 143.75H214.25V214.5H71.41V143.75ZM285.7 143.75H428.54V214.5H285.7V143.75Z" fill="#FFAF00"/>
|
||||
<path d="M71.41 214.5H428.54V285.25H71.41V214.5Z" fill="#FF8205"/>
|
||||
<path d="M71.41 285.27H142.83V356.02H71.41V285.27ZM214.27 285.27H285.69V356.02H214.27V285.27ZM357.12 285.27H428.54V356.02H357.12V285.27Z" fill="#FA500F"/>
|
||||
<path d="M0 356.06H214.3V426.82H0V356.06ZM285.7 356.06H500V426.82H285.7V356.06Z" fill="#E10500"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 628 B |
1
auth/assets/custom-icons/icons/oracle_cloud.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg height="2100" viewBox="0 0 32 21" width="3200" xmlns="http://www.w3.org/2000/svg"><path d="m9.9 20.1c-5.5 0-9.9-4.4-9.9-9.9s4.4-9.9 9.9-9.9h11.6c5.5 0 9.9 4.4 9.9 9.9s-4.4 9.9-9.9 9.9zm11.3-3.5c3.6 0 6.4-2.9 6.4-6.4 0-3.6-2.9-6.4-6.4-6.4h-11c-3.6 0-6.4 2.9-6.4 6.4s2.9 6.4 6.4 6.4z" fill="#c74634"/></svg>
|
||||
|
After Width: | Height: | Size: 310 B |
96
auth/assets/custom-icons/icons/r10.svg
Normal file
@@ -0,0 +1,96 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 25.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 280.6 100" style="enable-background:new 0 0 280.6 100;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{enable-background:new ;}
|
||||
.st1{fill:#FFFFFF;}
|
||||
.st2{fill:#3F4257;}
|
||||
.st3{fill-rule:evenodd;clip-rule:evenodd;fill:#3F4257;}
|
||||
.st4{fill-rule:evenodd;clip-rule:evenodd;fill:#EA4335;}
|
||||
.st5{fill:#EA4335;}
|
||||
.st6{fill:#FBBC05;}
|
||||
.st7{fill:#4285F4;}
|
||||
.st8{fill:#34A853;}
|
||||
</style>
|
||||
<g>
|
||||
<g class="st0">
|
||||
<path class="st1" d="M114.5,57.9h-6.3L97.9,46.2h-7.2v11.7h-5.4V24.1c5.3,0,10.5,0,15.8,0c7.8,0,12,5.4,12,11
|
||||
c0,4.8-2.5,9.5-9.2,10.5L114,57L114.5,57.9z M90.7,29.1v12.2H101c4.5,0,6.5-2.8,6.5-6c0-3-2-6.2-6.4-6.2H90.7z"/>
|
||||
<polygon class="st1" points="124.5,57.9 129.9,57.9 129.9,24.2 118.7,27.7 118.7,32.4 124.5,30.8 "/>
|
||||
<path class="st1" d="M139.7,38.1c0-19.6,28.4-19.5,28.4,0V44c0,19.5-28.4,19.6-28.4,0V38.1z M145.2,44c0,12.8,17.5,12.8,17.5,0
|
||||
v-5.9c0-12.6-17.5-12.8-17.5,0V44z"/>
|
||||
</g>
|
||||
<g class="st0">
|
||||
<path class="st1" d="M188.8,24.2l22.8,27.9V24.2h2.6v33.8h-1L190.3,30v27.9h-2.6V24.2H188.8z"/>
|
||||
<path class="st1" d="M226.3,39.7h19.8v2.4h-19.8v13.3h21.4v2.5h-24V24.2h23.4v2.5h-20.8V39.7z"/>
|
||||
<path class="st1" d="M263.3,26.5h-12.2v-2.3h27.1v2.3h-12.2v31.4h-2.6V26.5z"/>
|
||||
</g>
|
||||
<g id="Group_174_3_" transform="translate(-5071.999 -1624)">
|
||||
<path class="st1" d="M5106.8,1722c-5.8,0-11.4-1.2-16.4-3.6c-4.9-2.3-9.1-5.7-12-9.8c-5-7.1-6.1-16-3-25l0-0.1
|
||||
c7.8-22.5,13-25.7,18.7-27.4l0.5-0.1l0,0.2l0.8-0.1c0.5,0,0.9-0.1,1.4-0.1c0.6,0,1.2,0,1.8,0.1l0.8,0.1l0.4,0.1
|
||||
c1.9,0.3,3.8,0.8,5.7,1.5l0,0l0,0c0.4,0.1,0.9,0.2,1.3,0.2c0,0,0,0,0,0c5.2,0,12.8-10.1,18.8-28.9c-0.3,1.7-1.1,6.5-1.4,8.1
|
||||
c-1.2,6.5-2.3,12.7-3.2,17.7l-0.1,0.5l0.5,0.2c2.8,1.2,5,3.8,6.5,7.6l0.1,0.3l0.3,0.1c5.2,1.9,9.3,4.6,12.1,8
|
||||
c2.5,3,4.2,6.5,4.8,10.5c0.9,5.3,0,11.6-2.5,17.6c-2.2,5.2-5.3,9.8-8.5,12.3C5126,1718.6,5116.6,1722,5106.8,1722L5106.8,1722z
|
||||
M5094.2,1635.7l0.5,2.5l-0.2,16.8L5094.2,1635.7z M5099.2,1654.4c-0.2-8.7,5.1-17.6,12.8-21.7c-0.7,0.9-1.4,1.8-2,2.7
|
||||
C5103.8,1639,5100.3,1645.3,5099.2,1654.4z"/>
|
||||
<g>
|
||||
<path class="st1" d="M5128.3,1663.1c-1.7-4.1-4-6.7-6.9-8c1.5-8.3,3.6-20,5.7-31.1c-0.4,1.7-1.1,3.4-1.7,5
|
||||
c-1.7,9.3-4.2,20.4-5.5,27.4c3.2,1.5,5.6,4.2,6.9,7.6c27.3,9.9,16.5,38.3,6.9,46.1c-28,22.7-67.7,4-57.3-26.9
|
||||
c6.8-20.3,12.9-24.2,18.7-26v0c2.6-0.5,6.9,0.4,9.2,0.9c1.4,0.4,2.2,0.5,2.5,0.5c8,0.5,15.6-18.5,18.5-29.8
|
||||
c-2.1,5.3-11.8,30.7-19.9,28.3c-1.9-0.7-3.8-1.2-5.8-1.6c0.8-8.8,4-15.8,10.7-19.6c1.2-1.8,2.7-3.6,4.2-5.1
|
||||
c-9.9,3.5-16.8,14.4-16.1,24.6c-1.1-0.1-2.2-0.1-3.3,0l0.2-17.2l-2.1-9.6l0.5,26.8c-6.3,1.9-11.6,5.9-19.1,27.8l0,0
|
||||
c-7.8,22.6,10.4,39.4,32.1,39.4c9.2,0,19-3.1,27.7-10C5144.6,1704.6,5157.1,1673.7,5128.3,1663.1z"/>
|
||||
<polygon class="st2" points="5114.7,1630.8 5114.7,1630.8 5114.7,1630.8 "/>
|
||||
</g>
|
||||
<g id="Group_1_3_" transform="translate(5072 1624)">
|
||||
<path id="Path_839_2_" class="st3" d="M33.1,41.9c1.8-4.6,5.7-7.7,10.2-7.7c6.2,0,11.3,6.1,11.3,13.6s-5.1,13.6-11.3,13.6
|
||||
c-4.5,0-8.4-3.2-10.2-7.8c0.1-0.2,0.2-0.3,0.3-0.5c1.7,4.2,5.2,7.2,9.4,7.2c5.7,0,10.4-5.6,10.4-12.4s-4.6-12.4-10.4-12.4
|
||||
c-4.1,0-7.7,2.9-9.4,7.1C33.3,42.2,33.2,42.1,33.1,41.9z M25.1,36.4c2.9,0,5.5,1.9,7.1,4.8c-1.2-2.3-3.6-3.7-6.2-3.8
|
||||
c-4.4,0-7.9,4.6-7.9,10.3S22.5,58,26,58s4.7-1.9,6.1-4.2c-1.6,2.8-3.2,5.1-7,5.1c-3.8,0-8.7-5-8.7-11.2
|
||||
C16.4,41.5,20.3,36.4,25.1,36.4L25.1,36.4z"/>
|
||||
<path id="Path_840_2_" class="st3" d="M27.8,45.5c0.9,0,1.6-0.7,1.6-1.5c0-0.9-0.7-1.6-1.5-1.6c-0.9,0-1.6,0.7-1.6,1.5
|
||||
c0,0,0,0,0,0C26.3,44.8,27,45.4,27.8,45.5z"/>
|
||||
<path id="Path_842_2_" class="st3" d="M46.9,46.4c0.9,0,1.6-0.6,1.6-1.5c0-0.9-0.6-1.6-1.5-1.6s-1.6,0.6-1.6,1.5c0,0,0,0,0,0.1
|
||||
C45.4,45.7,46.1,46.4,46.9,46.4z"/>
|
||||
<path id="Path_843_2_" class="st3" d="M26.1,62.8l0.6,0.1l-2.3,23.4l-0.6-0.1L26.1,62.8z"/>
|
||||
<path id="Path_844_2_" class="st3" d="M38.6,65.3l0.6,0l1.5,23.5l-0.6,0L38.6,65.3z"/>
|
||||
<path id="Path_845_2_" class="st3" d="M51.6,64.2l0.5-0.1l3.1,19.7L54.6,84L51.6,64.2z"/>
|
||||
<g>
|
||||
<polygon class="st2" points="8.1,57.5 8.1,57.5 8.1,57.5 8.1,57.5 "/>
|
||||
<path class="st2" d="M8.1,57.5c-4.3,39.6,64.1,47.3,56.7-0.1c0,0,0,0,0,0c0,0,0,0,0,0s0,0,0,0c-0.3,0.2-0.6,0.4-0.9,0.6
|
||||
c-0.3,0.2-0.6,0.5-1,0.7c-0.9,0.6-2.2,1.2-3.8,1.9C41.4,66.9,17.2,62.2,8.1,57.5 M9.3,58.9c7.6,7,37.3,10.1,53,2.1
|
||||
c0,0,0.3-0.1,0.5-0.3C67.3,101.9,5.8,94.4,9.3,58.9z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<circle class="st1" cx="177.1" cy="53.6" r="4"/>
|
||||
<path id="Forma_1_3_" class="st4" d="M153,74.7c-1,0-2.1,0.3-2.9,1c-0.7,0.6-1.3,1.3-1.7,2c-0.4-0.8-1-1.5-1.7-2
|
||||
c-0.8-0.7-1.8-1-2.9-1c-2.9,0-5.2,2.4-5.2,5.6c0,3.5,2.8,5.8,7,9.4c0.7,0.6,1.5,1.3,2.4,2c0.2,0.2,0.6,0.2,0.8,0
|
||||
c0.8-0.7,1.6-1.4,2.4-2c4.2-3.6,7-6,7-9.4C158.1,77.1,155.9,74.7,153,74.7z"/>
|
||||
<g>
|
||||
<path class="st1" d="M86.6,71.9l5.2,15.7l5.3-15.7h1.5l5.3,15.7l5.2-15.7h2.7l-6.6,19h-2.4l-4.9-14.2L93,90.9h-2.4l-6.6-19H86.6z"
|
||||
/>
|
||||
<path class="st1" d="M125.1,88.7c-1.4,1.6-3.7,2.4-5.8,2.4c-4.1,0-7-2.7-7-7c0-4,2.9-6.9,6.9-6.9c4.1,0,7.2,2.5,6.7,7.9h-11.4
|
||||
c0.2,2.5,2.3,4,4.9,4c1.5,0,3.4-0.6,4.3-1.7L125.1,88.7L125.1,88.7z M123.8,83.2c-0.1-2.6-1.9-4-4.5-4c-2.3,0-4.4,1.4-4.7,4
|
||||
L123.8,83.2L123.8,83.2z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st5" d="M214.6,84.2c0,4.8-3.7,8.3-8.3,8.3s-8.3-3.5-8.3-8.3c0-4.8,3.7-8.3,8.3-8.3S214.6,79.4,214.6,84.2z M211,84.2
|
||||
c0-3-2.2-5-4.7-5s-4.7,2-4.7,5c0,3,2.2,5,4.7,5S211,87.2,211,84.2z"/>
|
||||
<path class="st6" d="M232.6,84.2c0,4.8-3.7,8.3-8.3,8.3s-8.3-3.5-8.3-8.3c0-4.8,3.7-8.3,8.3-8.3S232.6,79.4,232.6,84.2z M229,84.2
|
||||
c0-3-2.2-5-4.7-5s-4.7,2-4.7,5c0,3,2.2,5,4.7,5S229,87.2,229,84.2z"/>
|
||||
<path class="st7" d="M249.9,76.4v14.9c0,6.1-3.6,8.7-7.9,8.7c-4,0-6.5-2.7-7.4-4.9l3.2-1.3c0.6,1.4,2,3,4.2,3
|
||||
c2.7,0,4.4-1.7,4.4-4.9v-1.2h-0.1c-0.8,1-2.4,1.9-4.4,1.9c-4.2,0-8-3.6-8-8.3c0-4.7,3.8-8.3,8-8.3c2,0,3.6,0.9,4.4,1.9h0.1v-1.4
|
||||
L249.9,76.4L249.9,76.4z M246.7,84.3c0-2.9-2-5.1-4.4-5.1c-2.5,0-4.6,2.1-4.6,5.1c0,2.9,2.1,5,4.6,5
|
||||
C244.7,89.3,246.7,87.2,246.7,84.3z"/>
|
||||
<path class="st8" d="M255.6,67.7V92H252V67.7H255.6z"/>
|
||||
<path class="st5" d="M269.5,87l2.8,1.9c-0.9,1.4-3.1,3.7-6.9,3.7c-4.7,0-8.3-3.7-8.3-8.3c0-4.9,3.6-8.3,7.8-8.3
|
||||
c4.3,0,6.4,3.4,7.1,5.3l0.4,0.9l-11.1,4.6c0.9,1.7,2.2,2.5,4,2.5C267.3,89.3,268.6,88.4,269.5,87L269.5,87z M260.8,84l7.4-3.1
|
||||
c-0.4-1-1.6-1.8-3.1-1.8C263.3,79.1,260.7,80.8,260.8,84z"/>
|
||||
<path class="st7" d="M184.5,82.1v-3.5h11.9c0.1,0.6,0.2,1.3,0.2,2.1c0,2.6-0.7,5.9-3.1,8.3c-2.3,2.4-5.2,3.6-9,3.6
|
||||
c-7.1,0-13.1-5.8-13.1-12.9c0-7.1,6-12.9,13.1-12.9c3.9,0,6.7,1.5,8.9,3.6l-2.5,2.5c-1.5-1.4-3.6-2.5-6.4-2.5
|
||||
c-5.2,0-9.3,4.2-9.3,9.4c0,5.2,4.1,9.4,9.3,9.4c3.4,0,5.3-1.4,6.5-2.6c1-1,1.7-2.4,1.9-4.4L184.5,82.1z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.8 KiB |
19
auth/assets/custom-icons/icons/tebex.svg
Normal file
@@ -0,0 +1,19 @@
|
||||
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256" width="256" height="256">
|
||||
<title>vikunja</title>
|
||||
<defs>
|
||||
<clipPath clipPathUnits="userSpaceOnUse" id="cp1">
|
||||
<path d="m211.62 166.19v47.1h-167.62v-47.1z"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
<style>
|
||||
.s0 { fill: #101010 }
|
||||
.s1 { fill: #ffffff }
|
||||
</style>
|
||||
<path class="s0" d="m226.9 46.8q-0.8-0.9-1.6-1.9-0.8-1-1.6-1.9-0.9-1-1.7-1.9-0.9-0.9-1.7-1.9c-24.1-25.2-56.9-39.2-92.3-39.2-8.3 0-16.7 0.8-24.9 2.4-8.3 1.7-16.3 4.1-24.1 7.3-7.7 3.2-15.1 7.2-22.1 11.9-7 4.6-13.4 10-19.4 15.9-5.9 6-11.3 12.4-15.9 19.4-4.7 7-8.7 14.4-11.9 22.1-3.2 7.8-5.6 15.8-7.3 24.1-1.6 8.2-2.4 16.6-2.4 24.9 0 8.4 0.8 16.8 2.5 25 1.6 8.3 4 16.3 7.2 24.1 3.2 7.7 7.2 15.1 11.9 22.1 4.6 7 10 13.4 15.9 19.4 6 5.9 12.4 11.3 19.4 15.9 7 4.7 14.4 8.7 22.1 11.9 7.8 3.2 15.8 5.6 24.1 7.2 8.2 1.7 16.6 2.5 24.9 2.4 8.4 0.1 16.8-0.7 25-2.4 8.3-1.6 16.3-4 24-7.2 7.8-3.2 15.2-7.2 22.2-11.9 6.9-4.6 13.4-10 19.3-15.9 6-6 11.3-12.4 16-19.4 4.7-7 8.7-14.4 11.9-22.1 3.2-7.8 5.6-15.8 7.2-24.1 1.7-8.2 2.5-16.6 2.4-25 0-29.2-10.5-58.2-29.1-81.2"/>
|
||||
<g id="Clip-Path" clip-path="url(#cp1)">
|
||||
<g>
|
||||
<path fill-rule="evenodd" class="s1" d="m117.5 183.8c2.5-3.5 6.5-5.5 11.3-5.5 9.1 0 15.6 7 15.6 16.7 0 9.8-6.6 16.8-15.8 16.8-4.8 0-8.7-1.8-11.2-5.3l-0.2 4.5h-6.9v-44.8h7.2zm20.2 11.4c0-5.7-4.1-10.4-9.3-10.4-5.2 0-9.4 4.7-9.4 10.4 0 5.8 4.2 10.5 9.4 10.5 5.2 0 9.3-4.7 9.3-10.5zm-70.4 9.4l0.3-0.2 2.8 5.7-0.1 0.1c-2.7 2.1-5.6 3.1-8.8 3.1-7 0-10.9-4-10.9-11.4v-16.7h-6.6l1.6-6.5h5v-8.3h7.3v8.3h11.6v6.5h-11.6v16.7c0 2.9 1.5 4.6 4.3 4.6 1.9 0 3.3-0.5 5.1-1.9zm36.1-9.8q0 0.5-0.1 0.9 0 0.4 0 0.8v0.3h-23.7c0.7 5.6 4.1 8.6 9.5 8.6 3.5 0 6.3-1.2 8.9-3.8l0.2-0.2 3.8 4.7-0.1 0.1c-3.4 3.8-7.6 5.5-13.2 5.5-9.9 0-16.3-6.6-16.3-16.8 0-9.7 6.7-16.8 16-16.8 9.1 0 15 6.6 15 16.7zm-7.3-2.7c-0.6-4.8-3.5-7.6-7.8-7.6-4.3 0-7.6 3-8.5 7.6zm82.8 2.7q0 0.5 0 0.9-0.1 0.4-0.1 0.8v0.3h-23.7c0.7 5.6 4.1 8.6 9.5 8.6 3.5 0 6.3-1.2 8.9-3.8l0.2-0.2 3.9 4.7-0.2 0.1c-3.4 3.8-7.6 5.5-13.2 5.5-9.9 0-16.2-6.6-16.2-16.8 0-9.7 6.6-16.8 15.9-16.8 9.1 0 15 6.6 15 16.7zm-23.6-2.7h16.3c-0.6-4.9-3.4-7.6-7.8-7.6-4.3 0-7.6 3-8.5 7.6zm55-13.4l-11.2 15.4 12.5 16.9h-8.6l-8.1-11.5-7.8 11.5h-8.2l12.1-16.5-11.5-15.8h8.5l7.1 10.5 7-10.5z"/>
|
||||
</g>
|
||||
</g>
|
||||
<path class="s1" d="m130.9 71.8c4.5-7.3 12.5-9.5 12.5-9.5 0 0-15.3-4-15.3-20.3 0 16.3-15.3 20.3-15.3 20.3 0 0 8 2.2 12.4 9.5h-20.9v28.2l4.8-8.7h9.5v48.6l19 19.4v-59.5c-4.9-2.3-11.8-8.1-14.3-12.5 4.2 1.3 10 3 14.4 4h14.2v-19.5z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
103
auth/assets/custom-icons/icons/toshl_finance.svg
Normal file
|
After Width: | Height: | Size: 16 KiB |
15
auth/assets/custom-icons/icons/uollet.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg width="500" height="404" viewBox="0 0 500 404" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M232.61 401.571C220.894 398.642 214.011 395.42 204.052 388.39C199.219 384.876 154.991 341.672 105.783 292.172C37.244 223.34 15.1299 200.347 11.1757 193.61C-3.03008 169.006 -3.61588 146.16 8.97892 121.263C14.6905 109.987 26.6995 96.806 75.0284 48.4771C115.303 8.20299 118.378 6.00622 139.321 1.46623C151.33 -1.02344 160.41 -0.437633 174.176 3.9559C188.235 8.20299 197.901 15.5256 224.262 41.7403C237.443 54.7745 249.013 65.4654 250.038 65.4654C250.916 65.4654 251.649 66.7835 251.649 68.3945C251.649 70.0054 251.063 71.3235 250.331 71.3235C249.745 71.3235 243.887 76.5957 237.443 83.0396C224.409 95.9273 216.647 109.108 215.329 120.385C214.596 127.561 217.525 144.549 219.576 144.549C220.308 144.549 220.894 145.867 220.894 147.478C220.894 151.725 240.811 171.203 248.72 174.865C252.381 176.475 258.532 178.672 262.34 179.697C268.93 181.455 269.955 181.308 279.914 177.501C285.772 175.157 291.044 172.521 291.63 171.35C292.216 170.325 293.241 169.446 293.973 169.446C294.559 169.446 304.664 159.926 316.527 148.21C336.59 128.586 338.494 126.975 343.62 126.975C348.746 126.975 350.943 128.879 386.091 164.027C406.301 184.237 422.997 201.812 422.997 202.837C422.997 203.715 423.582 204.594 424.315 204.594C424.9 204.594 427.39 208.695 429.733 213.821C433.102 221.29 433.98 225.39 434.42 237.253C435.006 249.555 434.713 251.459 432.662 251.459C431.344 251.459 430.319 252.337 430.319 253.362C430.319 254.388 400.15 285.289 363.391 321.902C303.492 381.507 295.145 389.123 286.065 393.809C267.465 403.328 250.184 405.818 232.61 401.571ZM2.24217 152.311C1.94926 149.968 1.65636 151.579 1.65636 155.533C1.65636 159.634 1.94926 161.391 2.24217 159.634C2.53507 157.73 2.53507 154.508 2.24217 152.311Z" fill="#461EC5"/>
|
||||
<path d="M430.319 233.152C430.319 231.981 429.733 230.955 429.001 230.955C428.269 230.955 427.976 229.637 428.415 228.026C429.733 222.901 433.248 224.512 433.248 230.223C433.248 233.006 432.662 235.349 431.784 235.349C431.052 235.349 430.319 234.324 430.319 233.152Z" fill="#36198A"/>
|
||||
<path d="M421.386 210.745C417.138 205.766 416.846 203.13 420.8 203.13C424.022 203.13 424.461 203.715 424.461 208.255C424.461 214.113 424.315 214.26 421.386 210.745Z" fill="#36198A"/>
|
||||
<path d="M252.527 182.48C247.695 180.869 241.104 177.647 237.736 175.304C228.949 169.299 212.4 150.407 215.768 150.407C216.354 150.407 215.622 149.382 214.45 148.064C213.132 146.892 212.4 145.135 212.839 144.403C213.425 143.671 212.986 143.085 212.107 143.085C211.082 143.085 210.789 141.913 211.375 140.156C211.814 138.545 211.814 137.227 211.082 137.227C210.496 137.227 209.91 131.662 209.91 124.778C209.91 117.895 210.496 112.33 211.228 112.33C211.814 112.33 212.107 111.305 211.521 110.133C211.082 108.962 211.375 107.936 212.253 107.936C213.132 107.936 213.571 106.911 212.986 105.74C212.546 104.568 212.839 103.543 213.718 103.543C214.597 103.543 214.889 102.957 214.45 102.225C214.011 101.493 214.597 100.467 215.915 100.028C217.233 99.5887 217.965 98.1242 217.526 97.099C217.086 95.9274 217.672 94.6094 218.844 94.17C220.015 93.7306 220.748 92.8519 220.601 92.2661C220.162 90.8016 244.18 66.9301 246.084 66.9301C246.816 66.9301 246.962 66.1978 246.377 65.3191C245.791 64.2939 246.377 64.001 248.134 64.5868C250.331 65.3191 256.921 59.4611 278.01 38.665C307.007 9.96053 311.694 6.7386 330.732 1.75926C347.721 -2.78073 370.714 1.90571 385.505 12.7431C392.242 17.7224 440.571 65.3191 440.571 67.0765C440.571 67.6623 441.45 68.541 442.475 68.8339C443.353 69.2733 438.374 69.5662 431.052 69.5662C420.946 69.4197 416.406 70.0055 411.72 72.2023C404.69 75.4242 348.16 129.904 351.821 129.904C353.432 129.904 354.165 131.222 354.165 134.298C354.165 138.252 354.604 138.691 358.558 138.691C362.659 138.691 362.952 139.131 362.952 144.11V149.529L353.579 140.302C344.938 131.808 343.767 131.076 341.423 132.98C339.959 134.151 330 143.817 319.163 154.508C308.325 165.052 298.953 173.839 298.367 173.839C297.781 173.839 296.609 174.718 296.023 175.743C295.438 176.915 290.458 179.551 284.893 181.601C273.177 186.288 264.683 186.581 252.527 182.48ZM288.847 141.913C297.342 134.737 299.538 127.414 295.731 119.067C292.069 110.865 287.529 107.204 278.596 105.007C271.859 103.543 270.395 103.689 265.562 106.033C261.315 108.229 259.557 110.573 256.775 117.309C253.406 125.511 253.406 125.95 255.749 132.394C258.386 139.423 263.951 144.549 271.42 147.039C276.253 148.65 283.429 146.6 288.847 141.913Z" fill="#36198A"/>
|
||||
<path d="M383.455 170.178L378.476 165.052H383.894C389.167 165.052 389.313 165.199 389.313 170.178C389.313 172.961 389.167 175.304 388.874 175.304C388.581 175.304 386.238 172.961 383.455 170.178Z" fill="#36198A"/>
|
||||
<path d="M430.026 239.157C429.44 228.612 428.561 225.097 424.021 216.017C419.335 206.498 414.649 201.372 382.136 168.714C361.926 148.65 345.084 130.929 344.498 129.465C343.327 125.95 398.392 71.9093 407.326 67.6622C412.012 65.6119 416.552 64.8796 426.218 64.8796H438.959L459.023 84.5041C480.698 105.886 488.899 116.87 492.854 129.758C494.318 134.298 495.929 138.398 496.661 138.838C497.394 139.423 497.54 141.181 497.101 142.792C496.515 144.403 496.808 146.16 497.686 146.746C499.59 147.918 499.59 161.684 497.54 163.002C496.661 163.441 496.222 165.199 496.661 166.663C497.101 168.128 496.075 171.789 494.611 174.865C493.146 177.794 491.828 181.601 491.828 183.066C491.828 186.434 485.97 197.272 484.213 197.272C483.627 197.272 483.041 198.15 483.041 199.175C483.041 201.372 434.419 251.459 432.223 251.459C431.344 251.459 430.465 246.333 430.026 239.157Z" fill="#2B146D"/>
|
||||
<path d="M266.586 151.432C260.435 149.089 251.355 141.913 252.38 140.009C252.966 139.277 252.527 138.691 251.648 138.691C250.77 138.691 250.477 138.105 250.916 137.227C251.355 136.494 251.209 135.762 250.33 135.762C249.451 135.762 249.159 134.737 249.598 133.565C250.184 132.394 249.891 131.368 249.159 131.368C248.426 131.368 247.84 128.732 247.987 125.51C247.987 122.289 248.426 119.652 248.866 119.652C250.33 119.652 253.259 112.183 252.38 110.719C251.795 109.987 252.234 109.401 252.966 109.401C253.845 109.401 254.431 108.669 254.138 107.643C253.991 106.765 256.481 104.421 259.703 102.664C267.026 98.417 275.959 96.806 278.742 99.1492C279.913 100.028 281.524 100.467 282.257 100.028C282.989 99.4421 284.014 99.735 284.6 100.614C285.186 101.492 286.064 101.785 286.797 101.346C288.847 100.028 297.341 108.229 300.124 114.527C303.638 122.289 303.638 124.046 299.684 124.046C297.195 124.046 295.73 122.581 293.973 118.334C291.044 111.451 282.257 104.568 276.691 104.714C271.566 104.861 260.582 110.133 259.41 113.062C258.971 114.234 257.36 115.259 255.895 115.259C253.552 115.259 253.113 116.43 253.113 121.849C253.113 125.657 254.138 130.49 255.31 132.687C257.946 137.812 265.415 144.256 270.101 145.428C272.591 146.014 273.616 147.332 273.616 149.821C273.616 153.776 273.323 153.776 266.586 151.432Z" fill="#2B146D"/>
|
||||
<path d="M275.081 149.821C275.081 147.039 276.106 146.014 279.621 145.281C285.186 144.11 293.973 135.03 295.145 129.611C295.731 126.536 296.756 125.51 299.392 125.51C303.346 125.51 303.493 126.682 301.442 134.444C299.538 141.034 293.827 147.039 286.065 150.554C277.864 154.361 275.081 154.068 275.081 149.821Z" fill="#2B146D"/>
|
||||
<path d="M265.561 150.114C257.507 146.014 254.578 143.231 251.502 136.494C247.841 128.44 247.987 122.289 252.381 113.794C260.143 98.2705 281.818 94.9022 293.241 107.204C299.978 114.38 301.003 115.845 300.27 117.895C299.978 118.774 300.417 119.652 301.296 119.652C302.174 119.652 302.907 121.996 302.907 124.778C302.907 127.561 302.174 129.904 301.296 129.904C300.417 129.904 299.978 131.222 300.417 132.833C300.856 134.444 300.563 135.762 299.685 135.762C298.952 135.762 298.659 136.348 299.099 137.08C299.538 137.812 298.952 138.838 297.634 139.277C296.316 139.863 295.584 140.595 296.17 141.181C296.609 141.62 295.584 142.792 293.68 143.817C291.923 144.696 290.605 146.16 290.751 147.039C290.898 147.918 288.408 148.943 285.332 149.528C282.11 149.968 279.474 151.139 279.474 151.872C279.474 154.215 271.859 153.19 265.561 150.114Z" fill="#1F0E4E"/>
|
||||
<rect x="1.21729" y="150.407" width="4.39354" height="10.2516" fill="#461EC5"/>
|
||||
<path d="M462.539 88.1654C506.161 130.726 511.237 166.488 480.113 203.13L462.539 88.1654Z" fill="#2B146D"/>
|
||||
<circle cx="275.081" cy="125.51" r="27.8257" fill="#1F0E4E"/>
|
||||
<path d="M222.359 89.6299C196.86 117.609 199.757 132.937 221.627 159.926L222.359 89.6299Z" fill="#36198A"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.3 KiB |
35
auth/assets/custom-icons/icons/xai.svg
Normal file
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 27.5.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
version="1.1"
|
||||
id="katman_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 841.89 595.28"
|
||||
style="enable-background:new 0 0 841.89 595.28;"
|
||||
xml:space="preserve"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs id="defs4" />
|
||||
<g
|
||||
id="g4"
|
||||
style="fill:currentColor">
|
||||
<polygon
|
||||
points="557.09,211.99 565.4,538.36 631.96,538.36 640.28,93.18"
|
||||
id="polygon1"
|
||||
style="fill:currentColor" />
|
||||
<polygon
|
||||
points="640.28,56.91 538.72,56.91 379.35,284.53 430.13,357.05"
|
||||
id="polygon2"
|
||||
style="fill:currentColor" />
|
||||
<polygon
|
||||
points="201.61,538.36 303.17,538.36 353.96,465.84 303.17,393.31"
|
||||
id="polygon3"
|
||||
style="fill:currentColor" />
|
||||
<polygon
|
||||
points="201.61,211.99 430.13,538.36 531.69,538.36 303.17,211.99"
|
||||
id="polygon4"
|
||||
style="fill:currentColor" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -14,7 +14,6 @@ import 'package:ente_auth/models/key_gen_result.dart';
|
||||
import 'package:ente_auth/models/private_key_attributes.dart';
|
||||
import 'package:ente_auth/store/authenticator_db.dart';
|
||||
import 'package:ente_auth/utils/directory_utils.dart';
|
||||
import 'package:ente_auth/utils/lock_screen_settings.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
@@ -33,7 +32,6 @@ class Configuration {
|
||||
static const emailKey = "email";
|
||||
static const keyAttributesKey = "key_attributes";
|
||||
|
||||
static const keyShouldShowLockScreen = "should_show_lock_screen";
|
||||
static const lastTempFolderClearTimeKey = "last_temp_folder_clear_time";
|
||||
static const keyKey = "key";
|
||||
static const secretKeyKey = "secret_key";
|
||||
@@ -133,7 +131,6 @@ class Configuration {
|
||||
key: key,
|
||||
);
|
||||
}
|
||||
await LockScreenSettings.instance.removePinAndPassword();
|
||||
await AuthenticatorDB.instance.clearTable();
|
||||
_key = null;
|
||||
_cachedToken = null;
|
||||
@@ -468,24 +465,6 @@ class Configuration {
|
||||
await _preferences.setBool(hasOptedForOfflineModeKey, true);
|
||||
}
|
||||
|
||||
Future<bool> shouldShowLockScreen() async {
|
||||
final bool isPin = await LockScreenSettings.instance.isPinSet();
|
||||
final bool isPass = await LockScreenSettings.instance.isPasswordSet();
|
||||
return isPin || isPass || shouldShowSystemLockScreen();
|
||||
}
|
||||
|
||||
bool shouldShowSystemLockScreen() {
|
||||
if (_preferences.containsKey(keyShouldShowLockScreen)) {
|
||||
return _preferences.getBool(keyShouldShowLockScreen)!;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setSystemLockScreen(bool value) {
|
||||
return _preferences.setBool(keyShouldShowLockScreen, value);
|
||||
}
|
||||
|
||||
void setVolatilePassword(String volatilePassword) {
|
||||
_volatilePassword = volatilePassword;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ const String roadmapURL = "https://roadmap.ente.io";
|
||||
const String kAccountsUrl = "https://accounts.ente.io";
|
||||
|
||||
const String githubFeatureRequestUrl =
|
||||
"https://github.com/ente-io/ente/discussions/categories/feature-requests?discussions_q=is%3Aopen+category%3A%22Feature+requests%22+label%3A%22-+auth%22+sort%3Atop";
|
||||
"https://github.com/ente-io/ente/discussions/categories/enhancements?discussions_q=is%3Aopen%+label%3A%22-+auth%22+sort%3Atop";
|
||||
const int microSecondsInDay = 86400000000;
|
||||
const int android11SDKINT = 30;
|
||||
const int galleryLoadStartTime = -8000000000000000; // Wednesday, March 6, 1748
|
||||
|
||||
@@ -73,7 +73,10 @@ class AuthenticatorGateway {
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<AuthEntity>> getDiff(int sinceTime, {int limit = 500}) async {
|
||||
Future<(List<AuthEntity>, int?)> getDiff(
|
||||
int sinceTime, {
|
||||
int limit = 500,
|
||||
}) async {
|
||||
try {
|
||||
final response = await _enteDio.get(
|
||||
"/authenticator/entity/diff",
|
||||
@@ -84,11 +87,12 @@ class AuthenticatorGateway {
|
||||
);
|
||||
final List<AuthEntity> authEntities = <AuthEntity>[];
|
||||
final diff = response.data["diff"] as List;
|
||||
final int? unixTimeInMicroSeconds = response.data["timestamp"] as int?;
|
||||
for (var entry in diff) {
|
||||
final AuthEntity entity = AuthEntity.fromMap(entry);
|
||||
authEntities.add(entity);
|
||||
}
|
||||
return authEntities;
|
||||
return (authEntities, unixTimeInMicroSeconds);
|
||||
} catch (e) {
|
||||
if (e is DioException && e.response?.statusCode == 401) {
|
||||
throw UnauthorizedError();
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
"saveAction": "حفظ",
|
||||
"nextTotpTitle": "التالي",
|
||||
"deleteCodeTitle": "حذف الرمز؟",
|
||||
"deleteCodeMessage": "هل أنت متأكد من أنك تريد حذف هذه الشيفرة؟ هذا الإجراء لا رجعة فيه.",
|
||||
"deleteCodeMessage": "هل أنت متيقِّن من أنك تريد حذف هذه الشيفرة؟ هذا الإجراء لا رجعة فيه.",
|
||||
"trashCode": "حذف الكود؟",
|
||||
"trashCodeMessage": "هل أنت متيقِّن أنك تريد حذف الكود الخاص بـ {account}؟",
|
||||
"trash": "سلة المهملات",
|
||||
@@ -173,6 +173,7 @@
|
||||
"invalidQRCode": "شيفرة استجابة سريعة غير صالحة",
|
||||
"noRecoveryKeyTitle": "لا يوجد مفتاح استرجاع؟",
|
||||
"enterEmailHint": "أدخل عنوان البريد الإلكتروني الخاص بك",
|
||||
"enterNewEmailHint": "أدخل عنوان بريدك الإلكتروني الجديد",
|
||||
"invalidEmailTitle": "عنوان البريد الإلكتروني غير صالح",
|
||||
"invalidEmailMessage": "الرجاء إدخال بريد إلكتروني صالح.",
|
||||
"deleteAccount": "إزالة الحساب",
|
||||
@@ -513,5 +514,10 @@
|
||||
"free5GB": "5GB مجانًا على <bold-green>ente</bold-green> صور",
|
||||
"loginWithAuthAccount": "سجّل الدخول باستخدام حساب المُصادقة",
|
||||
"freeStorageOffer": "خَصْم 10٪ على صور <bold-green>ente</bold-green>",
|
||||
"freeStorageOfferDescription": "استخدم الكود \"AUTH\" وأحصل على 10٪ خَصْم في السنة الأولى"
|
||||
"freeStorageOfferDescription": "استخدم الكود \"AUTH\" وأحصل على 10٪ خَصْم في السنة الأولى",
|
||||
"advanced": "متقدم",
|
||||
"algorithm": "الخوارزمية",
|
||||
"type": "النوع",
|
||||
"period": "المدّة",
|
||||
"digits": "الأرقام"
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
},
|
||||
"onBoardingBody": "Бяспечна зрабіць рэзервовую копію кодаў 2ФА",
|
||||
"onBoardingGetStarted": "Пачаць",
|
||||
"setupFirstAccount": "Наладзіць ваш першы ўліковы запіс",
|
||||
"setupFirstAccount": "Наладзіць свой першы ўліковы запіс",
|
||||
"importScanQrCode": "Сканіраваць код QR-код",
|
||||
"qrCode": "QR-код",
|
||||
"importEnterSetupKey": "Увесці ключ наладжвання",
|
||||
@@ -45,19 +45,38 @@
|
||||
"timeBasedKeyType": "Заснаваныя на часе (TOTP)",
|
||||
"counterBasedKeyType": "Заснаваныя на лічыльніку (HOTP)",
|
||||
"saveAction": "Захаваць",
|
||||
"nextTotpTitle": "наступны",
|
||||
"nextTotpTitle": "далей",
|
||||
"deleteCodeTitle": "Выдаліць код?",
|
||||
"deleteCodeMessage": "Вы сапраўды хочаце выдаліць гэты код? Гэта дзеянне з'яўляецца незваротным.",
|
||||
"trashCode": "Выдаліць код?",
|
||||
"trashCodeMessage": "Вы сапраўды хочаце выдаліць код для {account}?",
|
||||
"trash": "Сметніца",
|
||||
"viewLogsAction": "Паглядзець журнал",
|
||||
"preparingLogsTitle": "Падрыхтоўка журнала...",
|
||||
"viewLogsAction": "Паглядзець журналы",
|
||||
"preparingLogsTitle": "Падрыхтоўка журналаў...",
|
||||
"emailLogsTitle": "Адправіць журнал па электроннай пошце",
|
||||
"exportLogsAction": "Экспартаваць журнал",
|
||||
"reportABug": "Паведаміць пра памылку",
|
||||
"reportBug": "Паведаміць пра памылку",
|
||||
"emailLogsMessage": "Адпраўце журналы на {email}",
|
||||
"@emailLogsMessage": {
|
||||
"placeholders": {
|
||||
"email": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"copyEmailAction": "Скапіяваць электронную пошту",
|
||||
"exportLogsAction": "Экспартаваць журналы",
|
||||
"reportABug": "Паведаміць аб памылцы",
|
||||
"crashAndErrorReporting": "Справаздачы аб збоях і памылках",
|
||||
"reportBug": "Паведаміць аб памылцы",
|
||||
"emailUsMessage": "Адпраўце нам ліст на {email}",
|
||||
"@emailUsMessage": {
|
||||
"placeholders": {
|
||||
"email": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"contactSupport": "Звярнуцца ў службу падтрымкі",
|
||||
"rateUsOnStore": "Ацаніць нас у {storeName}",
|
||||
"blog": "Блог",
|
||||
"verifyPassword": "Праверыць пароль",
|
||||
"pleaseWait": "Пачакайце...",
|
||||
@@ -66,32 +85,128 @@
|
||||
"useRecoveryKey": "Выкарыстоўваць ключ аднаўлення",
|
||||
"incorrectPasswordTitle": "Няправільны пароль",
|
||||
"welcomeBack": "З вяртаннем!",
|
||||
"changeEmail": "Змяніць адрас электроннай пошты",
|
||||
"changePassword": "Змяніць пароль",
|
||||
"data": "Даныя",
|
||||
"importCodes": "Імпартаваць коды",
|
||||
"importTypePlainText": "Звычайны тэкст",
|
||||
"importTypeEnteEncrypted": "Шыфраванне экспартавання з Ente",
|
||||
"passwordForDecryptingExport": "Пароль для дэшыфроўкі экспартавання",
|
||||
"passwordEmptyError": "Пароль не можа быць пустым",
|
||||
"importFromApp": "Імпартаваць коды з {appName}",
|
||||
"exportCodes": "Экспартаваць коды",
|
||||
"importLabel": "Імпарт",
|
||||
"selectFile": "Выбраць файл",
|
||||
"ok": "OK",
|
||||
"cancel": "Скасаваць",
|
||||
"yes": "Так",
|
||||
"no": "Не",
|
||||
"email": "Электронная пошта",
|
||||
"support": "Падтрымка",
|
||||
"general": "Агульныя",
|
||||
"settings": "Налады",
|
||||
"copied": "Скапіявана",
|
||||
"pleaseTryAgain": "Калі ласка, паспрабуйце яшчэ раз",
|
||||
"delete": "Выдаліць",
|
||||
"enterYourPasswordHint": "Увядзіце ваш пароль",
|
||||
"forgotPassword": "Забылі пароль",
|
||||
"oops": "Вой",
|
||||
"faq": "Частыя пытанні",
|
||||
"scan": "Сканіраваць",
|
||||
"verify": "Праверыць",
|
||||
"verifyPasskey": "Праверыць ключ доступу",
|
||||
"loginWithTOTP": "Увайсці з TOTP",
|
||||
"recoverAccount": "Аднавіць уліковы запіс",
|
||||
"recover": "Аднавіць",
|
||||
"invalidQRCode": "Памылковы QR-код",
|
||||
"deleteAccount": "Выдаліць уліковы запіс",
|
||||
"noDeleteAccountAction": "Не, выдаліць уліковы запіс",
|
||||
"sendEmail": "Адправіць ліст",
|
||||
"createNewAccount": "Стварыць новы ўліковы запіс",
|
||||
"weakStrength": "Ненадзейны",
|
||||
"strongStrength": "Надзейны",
|
||||
"moderateStrength": "Умераная",
|
||||
"confirmPassword": "Пацвердзіць пароль",
|
||||
"close": "Закрыць",
|
||||
"oopsSomethingWentWrong": "Штосьці пайшло не так.",
|
||||
"selectLanguage": "Выберыце мову",
|
||||
"language": "Мова",
|
||||
"social": "Сацыяльныя сеткі",
|
||||
"security": "Бяспека",
|
||||
"searchHint": "Пошук...",
|
||||
"search": "Пошук"
|
||||
"search": "Пошук",
|
||||
"addCode": "Дадаць код",
|
||||
"scanAQrCode": "Сканіраваць QR-код",
|
||||
"edit": "Рэдагаваць",
|
||||
"share": "Абагуліць",
|
||||
"restore": "Аднавіць",
|
||||
"error": "Памылка",
|
||||
"saveKey": "Захаваць ключ",
|
||||
"save": "Захаваць",
|
||||
"send": "Адправіць",
|
||||
"back": "Назад",
|
||||
"createAccount": "Стварыць уліковы запіс",
|
||||
"password": "Пароль",
|
||||
"privacyPolicyTitle": "Палітыка прыватнасці",
|
||||
"termsOfServicesTitle": "Умовы",
|
||||
"changePasswordTitle": "Змяніць пароль",
|
||||
"resetPasswordTitle": "Скінуць пароль",
|
||||
"encryptionKeys": "Ключы шыфравання",
|
||||
"continueLabel": "Працягнуць",
|
||||
"insecureDevice": "Небяспечная прылада",
|
||||
"logInLabel": "Увайсці",
|
||||
"logout": "Выйсці",
|
||||
"exit": "Выхад",
|
||||
"theme": "Тема",
|
||||
"lightTheme": "Светлая",
|
||||
"darkTheme": "Цёмная",
|
||||
"systemTheme": "Сістэманая",
|
||||
"confirm": "Пацвердзіць",
|
||||
"emailYourLogs": "Адправіць журналы",
|
||||
"about": "Аб праграме",
|
||||
"privacy": "Прыватнасць",
|
||||
"terms": "Умовы",
|
||||
"downloadUpdate": "Спампаваць",
|
||||
"update": "Абнавіць",
|
||||
"warning": "Папярэджанне",
|
||||
"importSuccessTitle": "Ура!",
|
||||
"sorry": "Прабачце",
|
||||
"pendingSyncs": "Папярэджанне",
|
||||
"manualSort": "Карыстальніцкая",
|
||||
"incorrectCode": "Няправільны код",
|
||||
"enterPassword": "Увядзіце пароль",
|
||||
"export": "Экспартаваць",
|
||||
"singIn": "Увайсці",
|
||||
"androidBiometricSuccess": "Паспяхова",
|
||||
"@androidBiometricSuccess": {
|
||||
"description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidCancelButton": "Скасаваць",
|
||||
"@androidCancelButton": {
|
||||
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
|
||||
},
|
||||
"goToSettings": "Перайсці ў налады",
|
||||
"@goToSettings": {
|
||||
"description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters."
|
||||
},
|
||||
"iOSOkButton": "OK",
|
||||
"@iOSOkButton": {
|
||||
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters."
|
||||
},
|
||||
"passkey": "Ключ доступу",
|
||||
"pinText": "Замацаваць",
|
||||
"unpinText": "Адмацаваць",
|
||||
"pinned": "Замацавана",
|
||||
"tags": "Тэгі",
|
||||
"createNewTag": "Стварыць новы тэг",
|
||||
"tag": "Тэг",
|
||||
"create": "Стварыць",
|
||||
"viewRawCodes": "Паглядзець неапрацаваныя коды",
|
||||
"rawCodeData": "Неапрацаваныя даныя кода",
|
||||
"appLock": "Блакіроўка праграмы",
|
||||
"next": "Далей",
|
||||
"tapToUnlock": "Націсніце для разблакіроўкі",
|
||||
"type": "Тып",
|
||||
"period": "Перыяд",
|
||||
"digits": "Лічбы"
|
||||
}
|
||||
@@ -88,6 +88,8 @@
|
||||
"useRecoveryKey": "Použít obnovovací klíč",
|
||||
"incorrectPasswordTitle": "Nesprávné heslo",
|
||||
"welcomeBack": "Vítejte zpět!",
|
||||
"emailAlreadyRegistered": "E-mail je již registrován.",
|
||||
"emailNotRegistered": "E-mail není registrován.",
|
||||
"madeWithLoveAtPrefix": "vyrobeno s ❤️ v ",
|
||||
"supportDevs": "Předplaťte si <bold-green>ente</bold-green>, abyste nás podpořili",
|
||||
"supportDiscount": "Použijte kód \"AUTH\" pro získání 10% slevy na první rok",
|
||||
@@ -495,9 +497,18 @@
|
||||
"appLockOfflineModeWarning": "Zvolili jste si pokračování bez zálohování. Pokud zapomenete heslo do aplikace, přístup k datům bude uzamčen.",
|
||||
"duplicateCodes": "Duplikovat kódy",
|
||||
"noDuplicates": "✨ Žádné duplikáty",
|
||||
"youveNoDuplicateCodesThatCanBeCleared": "Nemáte žádné duplicitní kódy k odstranění",
|
||||
"deduplicateCodes": "Deduplikovat kódy",
|
||||
"deselectAll": "Zrušit výběr všech položek",
|
||||
"selectAll": "Vybrat vše",
|
||||
"deleteDuplicates": "Odstranit duplikáty",
|
||||
"plainHTML": "Prosté HTML"
|
||||
"plainHTML": "Prosté HTML",
|
||||
"tellUsWhatYouThink": "Sdělte nám svůj názor",
|
||||
"dropReviewiOS": "Zanechat recenzi na App Store",
|
||||
"dropReviewAndroid": "Zanechat recenzi na Play Store",
|
||||
"supportEnte": "Podpořte <bold-green>ente</bold-green>",
|
||||
"giveUsAStarOnGithub": "Dejte nám hvězdu na Githubu",
|
||||
"free5GB": "5GB zdarma na <bold-green>ente</bold-green> Fotky",
|
||||
"freeStorageOffer": "10% sleva na <bold-green>ente</bold-green> fotky",
|
||||
"freeStorageOfferDescription": "Použijte kód \"AUTH\" pro získání 10% slevy na první rok"
|
||||
}
|
||||
@@ -91,39 +91,39 @@
|
||||
"emailAlreadyRegistered": "E-Mail ist bereits registriert.",
|
||||
"emailNotRegistered": "E-Mail-Adresse nicht registriert.",
|
||||
"madeWithLoveAtPrefix": "gemacht mit ❤️ bei ",
|
||||
"supportDevs": "Bei <bold-green>ente</bold-green> registrieren, um das Projekt zu unterstützen",
|
||||
"supportDevs": "Abonnieren Sie <bold-green>ente</bold-green>, um das Projekt zu unterstützen",
|
||||
"supportDiscount": "Benutzen Sie den Rabattcode \"AUTH\" für 10 % Rabatt im ersten Jahr",
|
||||
"changeEmail": "E-Mail ändern",
|
||||
"changePassword": "Passwort ändern",
|
||||
"data": "Datei",
|
||||
"data": "Daten",
|
||||
"importCodes": "Codes importieren",
|
||||
"importTypePlainText": "Klartext",
|
||||
"importTypeEnteEncrypted": "Verschlüsselter Ente-Export",
|
||||
"passwordForDecryptingExport": "Passwort um den Export zu entschlüsseln",
|
||||
"passwordEmptyError": "Passwort kann nicht leer sein",
|
||||
"importFromApp": "Importiere Codes von {appName}",
|
||||
"importGoogleAuthGuide": "Exportiere deine Accounts von Google Authenticator zu einem QR-Code, durch die \"Konten übertragen\" Option. Scanne den QR-Code danach mit einem anderen Gerät.\n\nTipp: Du kannst die Kamera eines Laptops verwenden, um ein Foto den dem QR-Code zu erstellen.",
|
||||
"importSelectJsonFile": "Wähle eine JSON-Datei",
|
||||
"importFromApp": "Importieren Sie Codes von {appName}",
|
||||
"importGoogleAuthGuide": "Exportieren Sie Ihre Accounts von Google Authenticator zu einem QR-Code, mithilfe der \"Konten übertragen\" Option. Scanne den QR-Code danach mit einem anderen Gerät.\n\nTipp: Sie können die Kamera eines Laptops verwenden, um ein Foto vom QR-Code zu erstellen.",
|
||||
"importSelectJsonFile": "Wählen Sie eine JSON-Datei",
|
||||
"importSelectAppExport": "{appName} Exportdatei auswählen",
|
||||
"importEnteEncGuide": "Wähle die von Ente exportierte, verschlüsselte JSON-Datei",
|
||||
"importRaivoGuide": "Verwenden Sie die Option \"Export OTPs to Zip archive\" in den Raivo-Einstellungen.\n\nEntpacken Sie die Zip-Datei und importieren Sie die JSON-Datei.",
|
||||
"importEnteEncGuide": "Wählen Sie die von Ente exportierte, verschlüsselte JSON-Datei",
|
||||
"importRaivoGuide": "Verwenden Sie die Option \"Export OTPs to Zip archive\" in den Einstellungen von Raivo.\n\nEntpacken Sie die Zip-Datei und importieren Sie die JSON-Datei.",
|
||||
"importBitwardenGuide": "Verwenden Sie die Option \"Tresor exportieren\" innerhalb der Bitwarden Tools und importieren Sie die unverschlüsselte JSON-Datei.",
|
||||
"importAegisGuide": "Verwenden Sie die Option \"Tresor exportieren\" in den Aegis-Einstellungen.\n\nFalls Ihr Tresor verschlüsselt ist, müssen Sie das Passwort für den Tresor eingeben, um ihn zu entschlüsseln.",
|
||||
"importAegisGuide": "Verwenden Sie die Option \"Tresor exportieren\" in den Einstellungen von Aegis.\n\nFalls Ihr Tresor verschlüsselt ist, müssen Sie das Passwort für den Tresor eingeben, um ihn zu entschlüsseln.",
|
||||
"import2FasGuide": "Verwenden Sie unter \"Einstellungen → Backup\" die Option \"Exportieren\" in 2FAS.\n\nFalls Ihr Backup verschlüsselt ist, müssen Sie das Passwort eingeben, um das Backup zu entschlüsseln.",
|
||||
"importLastpassGuide": "Verwenden Sie die Option \"Konten übertragen → Konten in Datei exportieren\" in den Lastpass Authenticator Einstellungen. \nImportieren Sie anschließend die heruntergeladene JSON-Datei.",
|
||||
"exportCodes": "Codes exportieren",
|
||||
"importLabel": "Importieren",
|
||||
"importInstruction": "Bitte wählen sie eine Datei die Codes in folgendem Format beinhaltet",
|
||||
"importCodeDelimiterInfo": "Codes können in einer neuen Zeile stehen oder durch Kommata getrennt sein",
|
||||
"importInstruction": "Bitte wählen Sie eine Datei die Codes in folgendem Format beinhaltet",
|
||||
"importCodeDelimiterInfo": "Codes können in einer neuen Zeile stehen oder durch ein Komma getrennt sein",
|
||||
"selectFile": "Datei auswählen",
|
||||
"emailVerificationToggle": "E-Mail-Verifizierung",
|
||||
"emailVerificationEnableWarning": "Stellen Sie sicher, eine Kopie Ihrer Zwei-Faktor-Authentifizierung an anderer Stelle zu speichern, um zu vermeiden, dass Sie sich versehentlich aus Ihrem Account aussperren.",
|
||||
"authToChangeEmailVerificationSetting": "Bitte Authentifizieren um die E-Mail Bestätigung zu ändern",
|
||||
"emailVerificationEnableWarning": "Um zu vermeiden, versehentlich aus Ihrem Konto ausgesperrt zu werden, stellen Sie sicher, dass Sie den Zwei-Faktor-Authentifizierungscode für Ihr E-Mail-Konto außerhalb von Ente Auth speichern, bevor Sie die E-Mail-Verifizierung aktivieren.",
|
||||
"authToChangeEmailVerificationSetting": "Bitte authentifizieren, um die E-Mail Bestätigung zu ändern",
|
||||
"authenticateGeneric": "Bitte authentifizieren",
|
||||
"authToViewYourRecoveryKey": "Bitte authentifizieren um ihren Wiederherstellungscode anzuzeigen",
|
||||
"authToChangeYourEmail": "Bitte authentifizieren um ihre Emailadresse zu ändern",
|
||||
"authToChangeYourPassword": "Bitte authentifizieren um ihr Passwort zu ändern",
|
||||
"authToViewSecrets": "Bitte authentifizieren Sie sich, um ihren Wiederherstellungscode anzuzeigen",
|
||||
"authToViewYourRecoveryKey": "Bitte authentifizieren, um Ihren Wiederherstellungscode anzuzeigen",
|
||||
"authToChangeYourEmail": "Bitte authentifizieren, um Ihre E-Mail-Adresse zu ändern",
|
||||
"authToChangeYourPassword": "Bitte authentifizieren, um Ihr Passwort zu ändern",
|
||||
"authToViewSecrets": "Bitte authentifizieren, um Ihren Wiederherstellungscode anzuzeigen",
|
||||
"authToInitiateSignIn": "Bitte authentifizieren, um die Anmeldung zum Backup zu starten.",
|
||||
"ok": "Ok",
|
||||
"cancel": "Abbrechen",
|
||||
@@ -140,18 +140,18 @@
|
||||
"delete": "Löschen",
|
||||
"enterYourPasswordHint": "Geben Sie Ihr Passwort ein",
|
||||
"forgotPassword": "Passwort vergessen",
|
||||
"oops": "Hopla",
|
||||
"oops": "Hoppla",
|
||||
"suggestFeatures": "Features vorschlagen",
|
||||
"faq": "FAQ",
|
||||
"somethingWentWrongMessage": "Ein Fehler ist aufgetreten, bitte versuchen Sie es erneut",
|
||||
"leaveFamily": "Familie verlassen",
|
||||
"leaveFamilyMessage": "Sind Sie sicher, dass Sie den Familien-Plan verlassen wollen?",
|
||||
"inFamilyPlanMessage": "Sie haben einen Familien-Plan!",
|
||||
"hintForMobile": "Lange drücken, um den Code zu bearbeiten oder zu entfernen.",
|
||||
"hintForMobile": "Lange auf einen Code drücken, um ihn zu bearbeiten oder zu entfernen.",
|
||||
"hintForDesktop": "Klicken Sie mit der rechten Maustaste auf einen Code zum Bearbeiten oder Entfernen.",
|
||||
"scan": "Scannen",
|
||||
"scanACode": "Scan einen Code",
|
||||
"verify": "Überprüfen Sie",
|
||||
"verify": "Verifizieren",
|
||||
"verifyEmail": "E-Mail-Adresse verifizieren",
|
||||
"enterCodeHint": "Geben Sie den 6-stelligen Code \naus Ihrer Authentifikator-App ein.",
|
||||
"lostDeviceTitle": "Gerät verloren?",
|
||||
@@ -172,9 +172,10 @@
|
||||
},
|
||||
"invalidQRCode": "Ungültiger QR-Code",
|
||||
"noRecoveryKeyTitle": "Kein Wiederherstellungsschlüssel?",
|
||||
"enterEmailHint": "Geben Sie Ihre E-Mail Adresse ein",
|
||||
"invalidEmailTitle": "Ungültige E-Mail Adresse",
|
||||
"invalidEmailMessage": "Bitte geben Sie eine gültige E-Mail Adresse ein.",
|
||||
"enterEmailHint": "Geben Sie Ihre E-Mail-Adresse ein",
|
||||
"enterNewEmailHint": "Gib deine neue E-Mail-Adresse ein",
|
||||
"invalidEmailTitle": "Ungültige E-Mail-Adresse",
|
||||
"invalidEmailMessage": "Bitte geben Sie eine gültige E-Mail-Adresse ein.",
|
||||
"deleteAccount": "Konto löschen",
|
||||
"deleteAccountQuery": "Es tut uns leid, dass Sie gehen. Haben Sie ein Problem?",
|
||||
"yesSendFeedbackAction": "Ja, Feedback senden",
|
||||
@@ -187,7 +188,7 @@
|
||||
"moderateStrength": "Mittel",
|
||||
"confirmPassword": "Bestätigen Sie das Passwort",
|
||||
"close": "Schließen",
|
||||
"oopsSomethingWentWrong": "Ups, da ist etwas schief gelaufen.",
|
||||
"oopsSomethingWentWrong": "Hoppla, da ist etwas schiefgelaufen.",
|
||||
"selectLanguage": "Sprache auswählen",
|
||||
"language": "Sprache",
|
||||
"social": "Social",
|
||||
@@ -203,26 +204,26 @@
|
||||
"noResult": "Kein Ergebnis",
|
||||
"addCode": "Code hinzufügen",
|
||||
"scanAQrCode": "QR-Code scannen",
|
||||
"enterDetailsManually": "Details manuell hinzufügen",
|
||||
"enterDetailsManually": "Daten manuell hinzufügen",
|
||||
"edit": "Editieren",
|
||||
"share": "Teilen",
|
||||
"shareCodes": "Codes teilen",
|
||||
"shareCodesDuration": "Wählen Sie die Dauer aus, für die Sie die Codes teilen möchten.",
|
||||
"restore": "Wiederherstellen",
|
||||
"copiedToClipboard": "In die Zwischenablage kopieren",
|
||||
"copiedNextToClipboard": "Nächster Code wurde in die Zwischenablage kopiert",
|
||||
"copiedToClipboard": "In die Zwischenablage kopiert",
|
||||
"copiedNextToClipboard": "Nächster Code in die Zwischenablage kopiert",
|
||||
"error": "Fehler",
|
||||
"recoveryKeyCopiedToClipboard": "Wiederherstellungsschlüssel in die Zwischenablage kopiert",
|
||||
"recoveryKeyOnForgotPassword": "Sollten sie ihr Passwort vergessen, dann ist dieser Schlüssel die einzige Möglichkeit ihre Daten wiederherzustellen.",
|
||||
"recoveryKeySaveDescription": "Wir speichern diesen Schlüssel nicht. Sichern sie dieses diesen Schlüssel bestehend aus 24 Wörtern an einem sicheren Platz.",
|
||||
"recoveryKeySaveDescription": "Wir speichern diesen Schlüssel nicht. Sichern Sie diesen Schlüssel bestehend aus 24 Wörtern an einem sicheren Platz.",
|
||||
"doThisLater": "Auf später verschieben",
|
||||
"saveKey": "Schlüssel speichern",
|
||||
"save": "Speichern",
|
||||
"send": "Senden",
|
||||
"saveOrSendDescription": "Möchtest du dies in deinem Speicher (standardmäßig im Ordner Downloads) oder an andere Apps senden?",
|
||||
"saveOrSendDescription": "Möchtest du dies in deinem Speicher (standardmäßig im Ordner Downloads) speichern oder an andere Apps senden?",
|
||||
"saveOnlyDescription": "Möchtest du dies in deinem Speicher (standardmäßig im Ordner Downloads) speichern?",
|
||||
"back": "Zurück",
|
||||
"createAccount": "Account erstellen",
|
||||
"createAccount": "Konto erstellen",
|
||||
"passwordStrength": "Passwortstärke: {passwordStrengthValue}",
|
||||
"@passwordStrength": {
|
||||
"description": "Text to indicate the password strength",
|
||||
@@ -244,17 +245,17 @@
|
||||
"changePasswordTitle": "Passwort ändern",
|
||||
"resetPasswordTitle": "Passwort zurücksetzen",
|
||||
"encryptionKeys": "Verschlüsselungsschlüssel",
|
||||
"passwordWarning": "Wir speichern dieses Passwort nicht. Wenn du es vergisst, <underline>können wir deine Daten nicht entschlüsseln</underline>",
|
||||
"enterPasswordToEncrypt": "Gib ein Passwort ein, mit dem wir deine Daten verschlüsseln können",
|
||||
"enterNewPasswordToEncrypt": "Gib ein neues Passwort ein, mit dem wir deine Daten verschlüsseln können",
|
||||
"passwordWarning": "Wir speichern dieses Passwort nicht. Wenn Sie es vergessen, <underline>können wir Ihre Daten nicht entschlüsseln</underline>",
|
||||
"enterPasswordToEncrypt": "Geben Sie ein Passwort ein, mit dem wir Ihre Daten verschlüsseln können",
|
||||
"enterNewPasswordToEncrypt": "Geben Sie ein neues Passwort ein, mit dem wir Ihre Daten verschlüsseln können",
|
||||
"passwordChangedSuccessfully": "Passwort erfolgreich geändert",
|
||||
"generatingEncryptionKeys": "Generierung von Verschlüsselungsschlüsseln...",
|
||||
"continueLabel": "Weiter",
|
||||
"insecureDevice": "Unsicheres Gerät",
|
||||
"sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "Es tut uns leid, wir konnten keine sicheren Schlüssel auf diesem Gerät generieren.\n\nBitte registrieren Sie sich auf einem anderen Gerät.",
|
||||
"howItWorks": "So funktioniert's",
|
||||
"ackPasswordLostWarning": "Ich verstehe, dass der Verlust meines Passworts zum Verlust meiner Daten führen kann, denn diese ist <underline>Ende-zu-Ende verschlüsselt</underline>.",
|
||||
"loginTerms": "Durch das Klicken auf den Login-Button, stimme ich <u-terms>den Nutzungsbedingungen</u-terms> und den <u-policy>Datenschutzbestimmungen</u-policy> zu",
|
||||
"ackPasswordLostWarning": "Ich verstehe, dass der Verlust meines Passworts zum Verlust meiner Daten führen kann, denn diese sind <underline>Ende-zu-Ende verschlüsselt</underline>.",
|
||||
"loginTerms": "Durch das Klicken auf den Login-Button, stimme ich den <u-terms> Nutzungsbedingungen</u-terms> und den <u-policy>Datenschutzbestimmungen</u-policy> zu",
|
||||
"logInLabel": "Einloggen",
|
||||
"logout": "Ausloggen",
|
||||
"areYouSureYouWantToLogout": "Sind sie sicher, dass sie sich ausloggen möchten?",
|
||||
@@ -266,7 +267,7 @@
|
||||
"systemTheme": "System",
|
||||
"verifyingRecoveryKey": "Verifiziere Wiederherstellungsschlüssel...",
|
||||
"recoveryKeyVerified": "Wiederherstellungsschlüssel verifiziert",
|
||||
"recoveryKeySuccessBody": "Großartig! Ihr Wiederherstellungsschlüssel ist gültig. Vielen Dank für die Verifizierung.\n\nBitte denken sie daran, dass sie ihren Wiederherstellungsschlüssel sicher aufbewahren.",
|
||||
"recoveryKeySuccessBody": "Großartig! Ihr Wiederherstellungsschlüssel ist gültig. Vielen Dank für die Verifizierung.\n\nBitte denken Sie daran, den Wiederherstellungsschlüssel sicher aufzubewahren.",
|
||||
"invalidRecoveryKey": "Der eingegebene Wiederherstellungsschlüssel ist nicht gültig. Bitte stellen sie sicher, dass er aus 24 Wörtern besteht und prüfen sie die Schreibweise eines jeden.\n\nSollten sie einen Wiederherstellungsschlüssel im alten Format eingegeben haben vergewissern sie sich, dass er 64 Zeichen lang ist und prüfen sie jedes dieser Zeichen.",
|
||||
"recreatePasswordTitle": "Neues Passwort erstellen",
|
||||
"recreatePasswordBody": "Das benutzte Gerät ist nicht leistungsfähig genug das Passwort zu prüfen. Wir können es aber neu erstellen damit es auf jedem Gerät funktioniert. \n\nBitte loggen sie sich mit ihrem Wiederherstellungsschlüssel ein und erstellen sie ein neues Passwort (Sie können das selbe Passwort wieder verwenden, wenn sie möchten).",
|
||||
@@ -277,15 +278,15 @@
|
||||
"recoveryKeyVerifyReason": "Ihr Wiederherstellungsschlüssel ist der einzige Weg ihre Fotos wiederherzustellen sollten, sie ihr Passwort vergessen. Sie finden ihren Wiederherstellungsschlüssel unter Einstellungen > Account.\n\nBitte tragen sie ihren Wiederherstellungsschlüssel hier ein um zu prüfen ob sie in korrekt abgespeichert haben.",
|
||||
"confirmYourRecoveryKey": "Wiederherstellungsschlüssel bestätigen",
|
||||
"confirm": "Bestätigen",
|
||||
"emailYourLogs": "Email mit Logs senden",
|
||||
"emailYourLogs": "E-Mail mit Logs senden",
|
||||
"pleaseSendTheLogsTo": "Bitte Logs an {toEmail} senden",
|
||||
"copyEmailAddress": "Emailadresse kopieren",
|
||||
"copyEmailAddress": "E-Mail-Adresse kopieren",
|
||||
"exportLogs": "Logs exportieren",
|
||||
"enterYourRecoveryKey": "Wiederherstellungsschlüssel eingeben",
|
||||
"tempErrorContactSupportIfPersists": "Etwas ist schiefgelaufen. Bitte versuchen sie es später noch einmal. Sollte der Fehler weiter bestehen, kontaktieren sie unser Supportteam.",
|
||||
"tempErrorContactSupportIfPersists": "Etwas ist schiefgelaufen. Bitte versuchen Sie es später noch einmal. Sollte der Fehler weiter bestehen, kontaktieren sie unser Supportteam.",
|
||||
"networkHostLookUpErr": "Ente ist im Moment nicht erreichbar. Bitte überprüfen Sie Ihre Netzwerkeinstellungen. Sollte das Problem bestehen bleiben, wenden Sie sich bitte an den Support.",
|
||||
"networkConnectionRefusedErr": "Ente ist im Moment nicht erreichbar. Bitte versuchen Sie es später erneut. Sollte das Problem bestehen bleiben, wenden Sie sich bitte an den Support.",
|
||||
"itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "Etwas ist schiefgelaufen. Bitte versuchen sie es später noch einmal. Sollte der Fehler weiter bestehen, kontaktieren sie unser Supportteam.",
|
||||
"itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "Etwas ist schiefgelaufen. Bitte versuchen Sie es später noch einmal. Sollte der Fehler weiter bestehen, kontaktieren Sie unser Supportteam.",
|
||||
"about": "Über uns",
|
||||
"weAreOpenSource": "Wir sind Opensource!",
|
||||
"privacy": "Datenschutz",
|
||||
@@ -316,10 +317,10 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"sorry": "Entschuldigen sie",
|
||||
"importFailureDesc": "Ausgewählte Datei ließ sich nicht verarbeiten.\nBitte wenden sie sich an support@ente.io für Hilfe!",
|
||||
"sorry": "Entschuldigen Sie",
|
||||
"importFailureDesc": "Ausgewählte Datei ließ sich nicht verarbeiten.\nBitte wenden Sie sich an support@ente.io für Hilfe!",
|
||||
"pendingSyncs": "Warnung",
|
||||
"pendingSyncsWarningBody": "Einige Codes wurden nicht gesichert.\n\nBitte gehen sie sicher, dass sie einen Backupcode für diese Codes haben bevor sie sich ausloggen.",
|
||||
"pendingSyncsWarningBody": "Einige Codes wurden nicht gesichert.\n\nBitte gehen Sie sicher, dass Sie einen Backupcode für diese Codes haben bevor Sie sich ausloggen.",
|
||||
"checkInboxAndSpamFolder": "Bitte überprüfe deinen E-Mail-Posteingang (und Spam), um die Verifizierung abzuschließen",
|
||||
"tapToEnterCode": "Antippen, um den Code einzugeben",
|
||||
"resendEmail": "E-Mail erneut senden",
|
||||
@@ -339,10 +340,10 @@
|
||||
"mostFrequentlyUsed": "Häufig verwendet",
|
||||
"mostRecentlyUsed": "Zuletzt verwendet",
|
||||
"activeSessions": "Aktive Sitzungen",
|
||||
"somethingWentWrongPleaseTryAgain": "Ein Fehler ist aufgetreten, bitte versuche es erneut",
|
||||
"thisWillLogYouOutOfThisDevice": "Dadurch wirst du von diesem Gerät abgemeldet!",
|
||||
"thisWillLogYouOutOfTheFollowingDevice": "Dadurch wirst du von folgendem Gerät abgemeldet:",
|
||||
"terminateSession": "Sitzungen beenden?",
|
||||
"somethingWentWrongPleaseTryAgain": "Ein Fehler ist aufgetreten, bitte erneut versuchen",
|
||||
"thisWillLogYouOutOfThisDevice": "Dadurch werden Sie von diesem Gerät abgemeldet!",
|
||||
"thisWillLogYouOutOfTheFollowingDevice": "Dadurch werden Sie vom folgendem Gerät abgemeldet:",
|
||||
"terminateSession": "Sitzung beenden?",
|
||||
"terminate": "Beenden",
|
||||
"thisDevice": "Dieses Gerät",
|
||||
"toResetVerifyEmail": "Um Ihr Passwort zurückzusetzen, verifizieren Sie bitte zuerst Ihre E-Mail-Adresse.",
|
||||
@@ -352,7 +353,7 @@
|
||||
"incorrectCode": "Falscher Code",
|
||||
"sorryTheCodeYouveEnteredIsIncorrect": "Leider ist der eingegebene Code falsch",
|
||||
"emailChangedTo": "E-Mail-Adresse geändert zu {newEmail}",
|
||||
"authenticationFailedPleaseTryAgain": "Authentifizierung fehlgeschlagen, versuchen Sie es bitte erneut",
|
||||
"authenticationFailedPleaseTryAgain": "Authentifizierung fehlgeschlagen, bitte erneut versuchen",
|
||||
"authenticationSuccessful": "Authentifizierung erfolgreich!",
|
||||
"twofactorAuthenticationSuccessfullyReset": "Zwei-Faktor-Authentifizierung (2FA) erfolgreich zurückgesetzt",
|
||||
"incorrectRecoveryKey": "Falscher Wiederherstellungs-Schlüssel",
|
||||
@@ -365,22 +366,22 @@
|
||||
"passwordToEncryptExport": "Passwort zum Verschlüssen des Exports",
|
||||
"export": "Export",
|
||||
"useOffline": "Ohne Backup verwenden",
|
||||
"signInToBackup": "Melde dich an, um deine Codes zu sichern",
|
||||
"signInToBackup": "Melden Sie sich an, um Ihre Codes zu sichern",
|
||||
"singIn": "Anmelden",
|
||||
"sigInBackupReminder": "Bitte exportieren Sie Ihre Codes, um sicherzustellen, dass Sie ein Backup haben, aus dem Sie wiederherstellen können.",
|
||||
"sigInBackupReminder": "Bitte exportieren Sie Ihre Codes, um sicherzustellen, dass Sie ein Backup haben, das Sie wiederherstellen können.",
|
||||
"offlineModeWarning": "Sie haben sich dafür entschieden, ohne Sicherungen fortzufahren. Bitte führen Sie manuelle Sicherungen durch, um sicherzustellen, dass Ihre Codes sicher sind.",
|
||||
"showLargeIcons": "Große Symbole anzeigen",
|
||||
"compactMode": "Kompaktmodus",
|
||||
"shouldHideCode": "Codes ausblenden",
|
||||
"doubleTapToViewHiddenCode": "Sie können auf einen Eintrag doppelt tippen, um den Code anzuzeigen",
|
||||
"focusOnSearchBar": "Suche bei App-Start automatisch öffnen",
|
||||
"confirmUpdatingkey": "Sind Sie sich sicher, dass Sie den Secret Key bearbeiten wollen?",
|
||||
"focusOnSearchBar": "Suche beim App-Start fokussieren",
|
||||
"confirmUpdatingkey": "Sind Sie sich sicher, dass Sie den geheimen Schlüssel bearbeiten wollen?",
|
||||
"minimizeAppOnCopy": "Beim Kopieren App minimieren",
|
||||
"editCodeAuthMessage": "Authentifizieren, um Code zu bearbeiten",
|
||||
"deleteCodeAuthMessage": "Authentifizieren, um Code zu löschen",
|
||||
"showQRAuthMessage": "Authentifizieren, um QR-Code anzuzeigen",
|
||||
"confirmAccountDeleteTitle": "Kontolöschung bestätigen",
|
||||
"confirmAccountDeleteMessage": "Dieses Konto ist mit anderen Ente-Apps verknüpft, falls du welche verwendest.\n\nDeine hochgeladenen Daten werden in allen Ente-Apps zur Löschung vorgemerkt und dein Konto wird endgültig gelöscht.",
|
||||
"confirmAccountDeleteMessage": "Dieses Konto ist mit anderen Ente-Apps verknüpft, falls Sie welche verwenden.\n\nIhre hochgeladenen Daten werden in allen Ente-Apps zur Löschung vorgemerkt und Ihr Konto wird endgültig gelöscht.",
|
||||
"androidBiometricHint": "Identität bestätigen",
|
||||
"@androidBiometricHint": {
|
||||
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
|
||||
@@ -417,7 +418,7 @@
|
||||
"@goToSettings": {
|
||||
"description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters."
|
||||
},
|
||||
"androidGoToSettingsDescription": "Auf Ihrem Gerät ist keine biometrische Authentifizierung eingerichtet. Gehen Sie „Einstellungen“ > „Sicherheit“, um die biometrische Authentifizierung hinzuzufügen.",
|
||||
"androidGoToSettingsDescription": "Auf Ihrem Gerät ist keine biometrische Authentifizierung eingerichtet. Gehen Sie zu 'Einstellungen > Sicherheit', um die biometrische Authentifizierung hinzuzufügen.",
|
||||
"@androidGoToSettingsDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side."
|
||||
},
|
||||
@@ -434,9 +435,9 @@
|
||||
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters."
|
||||
},
|
||||
"noInternetConnection": "Keine Internetverbindung",
|
||||
"pleaseCheckYourInternetConnectionAndTryAgain": "Bitte überprüfe deine Internetverbindung und versuche es erneut.",
|
||||
"pleaseCheckYourInternetConnectionAndTryAgain": "Bitte überprüfen Sie Ihre Internetverbindung und versuchen Sie erneut.",
|
||||
"signOutFromOtherDevices": "Von anderen Geräten abmelden",
|
||||
"signOutOtherBody": "Falls du denkst, dass jemand dein Passwort kennen könnte, kannst du alle anderen Geräte von deinem Account abmelden.",
|
||||
"signOutOtherBody": "Falls Sie denken, dass jemand Ihr Passwort kennen könnte, können Sie alle anderen Geräte forcieren, sich von Ihrem Konto abzumelden.",
|
||||
"signOutOtherDevices": "Andere Geräte abmelden",
|
||||
"doNotSignOut": "Nicht abmelden",
|
||||
"hearUsWhereTitle": "Wie hast du von Ente erfahren? (optional)",
|
||||
@@ -458,7 +459,7 @@
|
||||
"pinText": "Anpinnen",
|
||||
"unpinText": "Lösen",
|
||||
"pinnedCodeMessage": "{code} wurde angepinnt",
|
||||
"unpinnedCodeMessage": "{code} wird nicht weiter angepinnt",
|
||||
"unpinnedCodeMessage": "{code} wurde losgelöst",
|
||||
"pinned": "Angeheftet",
|
||||
"tags": "Tags",
|
||||
"createNewTag": "Neuen Tag erstellen",
|
||||
@@ -474,7 +475,7 @@
|
||||
"rawCodeData": "Rohcode Daten",
|
||||
"appLock": "App-Sperre",
|
||||
"noSystemLockFound": "Keine Systemsperre gefunden",
|
||||
"toEnableAppLockPleaseSetupDevicePasscodeOrScreen": "Um die App-Sperre zu aktivieren, konfiguriere bitte den Gerätepasscode oder die Bildschirmsperre in den Systemeinstellungen.",
|
||||
"toEnableAppLockPleaseSetupDevicePasscodeOrScreen": "Um die App-Sperre zu aktivieren, konfigurieren Sie bitte den Gerätepasscode oder die Bildschirmsperre in den Systemeinstellungen.",
|
||||
"autoLock": "Automatisches Sperren",
|
||||
"immediately": "Sofort",
|
||||
"reEnterPassword": "Passwort erneut eingeben",
|
||||
@@ -494,23 +495,29 @@
|
||||
"setNewPin": "Neue PIN festlegen",
|
||||
"importFailureDescNew": "Die ausgewählte Datei konnte nicht verarbeitet werden.",
|
||||
"appLockNotEnabled": "App-Sperre nicht aktiviert",
|
||||
"appLockNotEnabledDescription": "Bitte aktivieren Sie die App-Sperre über Security > App-Sperre",
|
||||
"authToViewPasskey": "Bitte authentifizieren, um deinen Passkey zu sehen",
|
||||
"duplicateCodes": "Doppelte Codes",
|
||||
"appLockNotEnabledDescription": "Bitte aktivieren Sie die App-Sperre über Sicherheit > App-Sperre",
|
||||
"authToViewPasskey": "Bitte authentifizieren, um Ihren Passkey zu sehen",
|
||||
"appLockOfflineModeWarning": "Sie haben sich dazu entschieden, ohne Sicherungen fortzufahren. Wenn Sie Ihre App-Sperre vergessen, können Sie nicht mehr auf Ihre Daten zugreifen.",
|
||||
"duplicateCodes": "Codes duplizieren",
|
||||
"noDuplicates": "✨ Keine Duplikate",
|
||||
"youveNoDuplicateCodesThatCanBeCleared": "Du hast keine doppelten Codes, die bereinigt werden können",
|
||||
"deduplicateCodes": "Codes deduplizieren",
|
||||
"deselectAll": "Alle abwählen",
|
||||
"selectAll": "Alles auswählen",
|
||||
"selectAll": "Alle auswählen",
|
||||
"deleteDuplicates": "Duplikate löschen",
|
||||
"plainHTML": "Reines HTML",
|
||||
"tellUsWhatYouThink": "Sagen Sie uns, was Sie denken",
|
||||
"dropReviewiOS": "Hinterlasse eine Rezension im App Store",
|
||||
"dropReviewAndroid": "Hinterlasse eine Rezension im Google Play Store",
|
||||
"supportEnte": "Support <bold-green>ente</bold-green>",
|
||||
"dropReviewiOS": "Hinterlassen Sie eine Rezension im App Store",
|
||||
"dropReviewAndroid": "Hinterlassen Sie eine Rezension im Google Play Store",
|
||||
"supportEnte": "Unterstütze <bold-green>ente</bold-green>",
|
||||
"giveUsAStarOnGithub": "Gib uns einen Stern auf Github",
|
||||
"free5GB": "5GB kostenlos auf <bold-green>ente</bold-green> Photos",
|
||||
"loginWithAuthAccount": "Mit Ihrem Auth Account anmelden",
|
||||
"freeStorageOffer": "10% Rabatt für <bold-green>ente</bold-green> Photos",
|
||||
"freeStorageOfferDescription": "Verwende den Code \"AUTH\", um 10% im 1. Jahr zu sparen"
|
||||
"freeStorageOfferDescription": "Verwende den Code \"AUTH\", um 10% im ersten Jahr zu sparen",
|
||||
"advanced": "Erweitert",
|
||||
"algorithm": "Algorithmus",
|
||||
"type": "Typ",
|
||||
"period": "Periode",
|
||||
"digits": "Ziffern"
|
||||
}
|
||||
@@ -173,6 +173,7 @@
|
||||
"invalidQRCode": "Invalid QR code",
|
||||
"noRecoveryKeyTitle": "No recovery key?",
|
||||
"enterEmailHint": "Enter your email address",
|
||||
"enterNewEmailHint": "Enter your new email address",
|
||||
"invalidEmailTitle": "Invalid email address",
|
||||
"invalidEmailMessage": "Please enter a valid email address.",
|
||||
"deleteAccount": "Delete account",
|
||||
@@ -513,5 +514,10 @@
|
||||
"free5GB": "5GB free on <bold-green>ente</bold-green> Photos",
|
||||
"loginWithAuthAccount": "Login with your Auth account",
|
||||
"freeStorageOffer": "10% off on <bold-green>ente</bold-green> photos",
|
||||
"freeStorageOfferDescription": "Use code \"AUTH\" to get 10% off first year"
|
||||
"freeStorageOfferDescription": "Use code \"AUTH\" to get 10% off first year",
|
||||
"advanced": "Advanced",
|
||||
"algorithm": "Algorithm",
|
||||
"type": "Type",
|
||||
"period": "Period",
|
||||
"digits": "Digits"
|
||||
}
|
||||
@@ -173,6 +173,7 @@
|
||||
"invalidQRCode": "QR code non valide",
|
||||
"noRecoveryKeyTitle": "Pas de clé de récupération ?",
|
||||
"enterEmailHint": "Entrez votre adresse e-mail",
|
||||
"enterNewEmailHint": "Saisissez votre nouvelle adresse email",
|
||||
"invalidEmailTitle": "Adresse e-mail invalide",
|
||||
"invalidEmailMessage": "Veuillez saisir une adresse e-mail valide.",
|
||||
"deleteAccount": "Supprimer le compte",
|
||||
@@ -513,5 +514,10 @@
|
||||
"free5GB": "5 Go gratuits sur <bold-green>Ente</bold-green> Photos",
|
||||
"loginWithAuthAccount": "Connectez-vous avec votre compte Auth",
|
||||
"freeStorageOffer": "10% de réduction sur <bold-green>Ente</bold-green> Photos",
|
||||
"freeStorageOfferDescription": "Utilisez le code coupon \"AUTH\" pour obtenir 10% de réduction la première année"
|
||||
"freeStorageOfferDescription": "Utilisez le code coupon \"AUTH\" pour obtenir 10% de réduction la première année",
|
||||
"advanced": "Avancé",
|
||||
"algorithm": "Algorithme",
|
||||
"type": "Type",
|
||||
"period": "Période",
|
||||
"digits": "Chiffres"
|
||||
}
|
||||
@@ -173,6 +173,7 @@
|
||||
"invalidQRCode": "Érvénytelen QR-kód",
|
||||
"noRecoveryKeyTitle": "Nincs helyreállítási kulcs?",
|
||||
"enterEmailHint": "Adja meg az e-mail címét",
|
||||
"enterNewEmailHint": "Add meg az új e-mail címed",
|
||||
"invalidEmailTitle": "Érvénytelen e-mail cím",
|
||||
"invalidEmailMessage": "Kérjük, adjon meg egy érvényes e-mail címet.",
|
||||
"deleteAccount": "Fiók törlése",
|
||||
@@ -513,5 +514,6 @@
|
||||
"free5GB": "5GB ingyen <bold-green>ente <bold-green> Photos",
|
||||
"loginWithAuthAccount": "Jelentkezzen be Auth fiókjával",
|
||||
"freeStorageOffer": "10% kedvezmény on <bold-green>ente<bold-green> photos",
|
||||
"freeStorageOfferDescription": "Használja az \"AUTH\" kódot, hogy 10% kedvezményt kapjon az első évben"
|
||||
"freeStorageOfferDescription": "Használja az \"AUTH\" kódot, hogy 10% kedvezményt kapjon az első évben",
|
||||
"type": "Típus"
|
||||
}
|
||||
@@ -88,6 +88,8 @@
|
||||
"useRecoveryKey": "Gunakan kunci pemulihan",
|
||||
"incorrectPasswordTitle": "Kata sandi salah",
|
||||
"welcomeBack": "Selamat datang kembali!",
|
||||
"emailAlreadyRegistered": "Email sudah terdaftar.",
|
||||
"emailNotRegistered": "Email belum terdaftar.",
|
||||
"madeWithLoveAtPrefix": "dibuat dengan ❤️ di ",
|
||||
"supportDevs": "Berlangganan <bold-green>ente</bold-green> untuk mendukung kami",
|
||||
"supportDiscount": "Gunakan kode kupon \"AUTH\" untuk mendapatkan potongan 10% untuk tahun pertama",
|
||||
@@ -171,6 +173,7 @@
|
||||
"invalidQRCode": "Kode QR tidak valid",
|
||||
"noRecoveryKeyTitle": "Tidak punya kunci pemulihan?",
|
||||
"enterEmailHint": "Masukkan alamat email Anda",
|
||||
"enterNewEmailHint": "Masukkan alamat email baru anda",
|
||||
"invalidEmailTitle": "Alamat email tidak valid",
|
||||
"invalidEmailMessage": "Harap masukkan alamat email yang valid.",
|
||||
"deleteAccount": "Hapus akun",
|
||||
@@ -501,5 +504,12 @@
|
||||
"deselectAll": "Batalkan semua pilihan",
|
||||
"selectAll": "Pilih semua",
|
||||
"deleteDuplicates": "Hapus duplikat",
|
||||
"plainHTML": "HTML Sederhana"
|
||||
"plainHTML": "HTML Sederhana",
|
||||
"tellUsWhatYouThink": "Berikan pendapatmu",
|
||||
"dropReviewAndroid": "Berikan ulasan di Play Store",
|
||||
"advanced": "Lanjutan",
|
||||
"algorithm": "Algoritma",
|
||||
"type": "Tipe",
|
||||
"period": "Periode",
|
||||
"digits": "Digit"
|
||||
}
|
||||
@@ -173,6 +173,7 @@
|
||||
"invalidQRCode": "Codice QR non valido",
|
||||
"noRecoveryKeyTitle": "Nessuna chiave di recupero?",
|
||||
"enterEmailHint": "Inserisci il tuo indirizzo email",
|
||||
"enterNewEmailHint": "Inserisci il tuo nuovo indirizzo email",
|
||||
"invalidEmailTitle": "Indirizzo email non valido",
|
||||
"invalidEmailMessage": "Inserisci un indirizzo email valido.",
|
||||
"deleteAccount": "Elimina account",
|
||||
@@ -513,5 +514,10 @@
|
||||
"free5GB": "5GB gratis su <bold-green>ente</bold-green> Foto",
|
||||
"loginWithAuthAccount": "Accedi con il tuo account Auth",
|
||||
"freeStorageOffer": "10% di sconto su <bold-green>ente</bold-green> Foto",
|
||||
"freeStorageOfferDescription": "Utilizzare il codice \"AUTH\" per ottenere il 10% di sconto al primo anno"
|
||||
"freeStorageOfferDescription": "Utilizzare il codice \"AUTH\" per ottenere il 10% di sconto al primo anno",
|
||||
"advanced": "Avanzate",
|
||||
"algorithm": "Algoritmo",
|
||||
"type": "Tipo",
|
||||
"period": "Periodo",
|
||||
"digits": "Cifre"
|
||||
}
|
||||
@@ -508,9 +508,15 @@
|
||||
"tellUsWhatYouThink": "Pasakykite mums, ką manote",
|
||||
"dropReviewiOS": "Rašyti apžvalgą parduotuvėje „App Store“",
|
||||
"dropReviewAndroid": "Rašyti apžvalgą parduotuvėje „Play“ parduotuvė“",
|
||||
"supportEnte": "Paremti „<bold-green>ente</bold-green>“",
|
||||
"giveUsAStarOnGithub": "Suteikite mums žvaigždutę platformoje „Github“",
|
||||
"free5GB": "5 GB nemokami programai „<bold-green>ente</bold-green>“ nuotraukos",
|
||||
"loginWithAuthAccount": "Prisijungti su jūsų „Auth“ paskyra",
|
||||
"freeStorageOffer": "10 % nuolaida programai „<bold-green>ente</bold-green>“ nuotraukos",
|
||||
"freeStorageOfferDescription": "Naudokite kodą „AUTH“, kad gautumėte 10 % nuolaida pirmiesiems metams. "
|
||||
"freeStorageOfferDescription": "Naudokite kodą „AUTH“, kad gautumėte 10 % nuolaida pirmiesiems metams. ",
|
||||
"advanced": "Išplėstiniai",
|
||||
"algorithm": "Algoritmas",
|
||||
"type": "Tipas",
|
||||
"period": "Laikotarpis",
|
||||
"digits": "Skaitmenys"
|
||||
}
|
||||
@@ -173,6 +173,7 @@
|
||||
"invalidQRCode": "Ongeldige QR-code",
|
||||
"noRecoveryKeyTitle": "Geen herstelsleutel?",
|
||||
"enterEmailHint": "Voer je e-mailadres in",
|
||||
"enterNewEmailHint": "Voer uw nieuwe e-mailadres in",
|
||||
"invalidEmailTitle": "Ongeldig e-mailadres",
|
||||
"invalidEmailMessage": "Voer een geldig e-mailadres in.",
|
||||
"deleteAccount": "Account verwijderen",
|
||||
@@ -513,5 +514,10 @@
|
||||
"free5GB": "5GB gratis op <bold-green>ente</bold-green> Photos",
|
||||
"loginWithAuthAccount": "Log in met je Auth account",
|
||||
"freeStorageOffer": "10% korting op <bold-green>ente</bold-green> photos",
|
||||
"freeStorageOfferDescription": "Gebruik de code \"AUTH\" voor 10% korting op je eerste jaar"
|
||||
"freeStorageOfferDescription": "Gebruik de code \"AUTH\" voor 10% korting op je eerste jaar",
|
||||
"advanced": "Geavanceerd",
|
||||
"algorithm": "Algoritme",
|
||||
"type": "Type",
|
||||
"period": "Periode",
|
||||
"digits": "Cijfers"
|
||||
}
|
||||
@@ -173,6 +173,7 @@
|
||||
"invalidQRCode": "Nieprawidłowy kod QR",
|
||||
"noRecoveryKeyTitle": "Brak klucza odzyskiwania?",
|
||||
"enterEmailHint": "Wprowadź adres e-mail",
|
||||
"enterNewEmailHint": "Wprowadź nowy adres e-mail",
|
||||
"invalidEmailTitle": "Nieprawidłowy adres e-mail",
|
||||
"invalidEmailMessage": "Prosimy podać prawidłowy adres e-mail.",
|
||||
"deleteAccount": "Usuń konto",
|
||||
@@ -513,5 +514,10 @@
|
||||
"free5GB": "5 GB za darmo na zdjęcia <bold-green>ente</bold-green>",
|
||||
"loginWithAuthAccount": "Zaloguj się przy użyciu konta Auth",
|
||||
"freeStorageOffer": "10% zniżki na zdjęcia <bold-green>ente</bold-green>",
|
||||
"freeStorageOfferDescription": "Użyj kodu „AUTH”, aby uzyskać 10% zniżki na pierwszy rok"
|
||||
"freeStorageOfferDescription": "Użyj kodu „AUTH”, aby uzyskać 10% zniżki na pierwszy rok",
|
||||
"advanced": "Zaawansowane",
|
||||
"algorithm": "Algorytm",
|
||||
"type": "Rodzaj",
|
||||
"period": "Okres",
|
||||
"digits": "Cyfry"
|
||||
}
|
||||
@@ -173,6 +173,7 @@
|
||||
"invalidQRCode": "QR Code inválido",
|
||||
"noRecoveryKeyTitle": "Sem chave de recuperação?",
|
||||
"enterEmailHint": "Insira o endereço de e-mail",
|
||||
"enterNewEmailHint": "Insira seu novo e-mail",
|
||||
"invalidEmailTitle": "Endereço de e-mail inválido",
|
||||
"invalidEmailMessage": "Insira um endereço de e-mail válido.",
|
||||
"deleteAccount": "Excluir conta",
|
||||
@@ -513,5 +514,10 @@
|
||||
"free5GB": "5GB grátis no <bold-green>ente</bold-green> Photos",
|
||||
"loginWithAuthAccount": "Registrar-se com sua conta Auth",
|
||||
"freeStorageOffer": "10% de desconto no <bold-green>ente</bold-green> photos",
|
||||
"freeStorageOfferDescription": "Use o cupom \"AUTH\" para obter 10% de desconto no primeiro ano"
|
||||
"freeStorageOfferDescription": "Use o cupom \"AUTH\" para obter 10% de desconto no primeiro ano",
|
||||
"advanced": "Avançado",
|
||||
"algorithm": "Algoritmo",
|
||||
"type": "Tipo",
|
||||
"period": "Período",
|
||||
"digits": "Dígitos"
|
||||
}
|
||||
523
auth/lib/l10n/arb/app_sr.arb
Normal file
@@ -0,0 +1,523 @@
|
||||
{
|
||||
"account": "Налог",
|
||||
"unlock": "Откључај",
|
||||
"recoveryKey": "Резервни Кључ",
|
||||
"counterAppBarTitle": "Бројач",
|
||||
"@counterAppBarTitle": {
|
||||
"description": "Text shown in the AppBar of the Counter Page"
|
||||
},
|
||||
"onBoardingBody": "Сигурносно правити копију 2ФА кôдова",
|
||||
"onBoardingGetStarted": "Почети",
|
||||
"setupFirstAccount": "Подесити свој први налог",
|
||||
"importScanQrCode": "Скенирај QR кôд",
|
||||
"qrCode": "QR кôд",
|
||||
"importEnterSetupKey": "Унети кључ за подешавање",
|
||||
"importAccountPageTitle": "Унети детаље налога",
|
||||
"secretCanNotBeEmpty": "Тајна не може бити празна",
|
||||
"bothIssuerAndAccountCanNotBeEmpty": "И издавалац и рачун не могу бити празни",
|
||||
"incorrectDetails": "Погрешни детаљи",
|
||||
"pleaseVerifyDetails": "Проверите детаље и покушајте поново",
|
||||
"codeIssuerHint": "Издавач",
|
||||
"codeSecretKeyHint": "Тајни кључ",
|
||||
"secret": "Тајна",
|
||||
"all": "Све",
|
||||
"notes": "Белешке",
|
||||
"notesLengthLimit": "Белешке могу имати највише {count} знакова",
|
||||
"@notesLengthLimit": {
|
||||
"description": "Text to indicate the maximum number of characters allowed for notes",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"description": "The maximum number of characters allowed for notes",
|
||||
"type": "int",
|
||||
"example": "100"
|
||||
}
|
||||
}
|
||||
},
|
||||
"codeAccountHint": "Налог (you@domain.com)",
|
||||
"codeTagHint": "Ознака",
|
||||
"accountKeyType": "Тип кључа",
|
||||
"sessionExpired": "Сесија је истекла",
|
||||
"@sessionExpired": {
|
||||
"description": "Title of the dialog when the users current session is invalid/expired"
|
||||
},
|
||||
"pleaseLoginAgain": "Молимо да се поново пријавите",
|
||||
"loggingOut": "Одјављивање...",
|
||||
"timeBasedKeyType": "Временски (TOTP)",
|
||||
"counterBasedKeyType": "На основу бројања (HOTP)",
|
||||
"saveAction": "Сачувај",
|
||||
"nextTotpTitle": "следеће",
|
||||
"deleteCodeTitle": "Обрисати кôд?",
|
||||
"deleteCodeMessage": "Сигурно желите да избришете овај кôд? Ова акција је неповратна.",
|
||||
"trashCode": "Кôд у смеће?",
|
||||
"trashCodeMessage": "Сигурно желите да поставите кôд у смеће за {account}?",
|
||||
"trash": "Смеће",
|
||||
"viewLogsAction": "Прегледај извештаје",
|
||||
"sendLogsDescription": "Ово ће делите ваше записе како би нам помогли да вам исправимо проблем. Док преузмемо мере предострожности да осигурамо да осетљиве информације нису пријављене, охрабрујемо вас да прегледате ове записе пре него што их делите.",
|
||||
"preparingLogsTitle": "Спремање извештаја...",
|
||||
"emailLogsTitle": "Имејловати извештаје",
|
||||
"emailLogsMessage": "Пошаљите извештаје на {email}",
|
||||
"@emailLogsMessage": {
|
||||
"placeholders": {
|
||||
"email": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"copyEmailAction": "Копирати имејл",
|
||||
"exportLogsAction": "Извези изештаје",
|
||||
"reportABug": "Пријави грешку",
|
||||
"crashAndErrorReporting": "Пријављивање дања и грешке",
|
||||
"reportBug": "Пријaви грешку",
|
||||
"emailUsMessage": "Пошаљите нам имејл на {email}",
|
||||
"@emailUsMessage": {
|
||||
"placeholders": {
|
||||
"email": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"contactSupport": "Контактирати подршку",
|
||||
"rateUsOnStore": "Оцените нас на {storeName}",
|
||||
"blog": "Блог",
|
||||
"merchandise": "Роба",
|
||||
"verifyPassword": "Верификујте лозинку",
|
||||
"pleaseWait": "Молимо сачекајте...",
|
||||
"generatingEncryptionKeysTitle": "Генерисање кључева за шифровање...",
|
||||
"recreatePassword": "Поново креирати лозинку",
|
||||
"recreatePasswordMessage": "Тренутни уређај није довољно моћан да потврди вашу лозинку, тако да је морамо да регенеришемо једном на начин који ради са свим уређајима. \n\nПријавите се помоћу кључа за опоравак и обновите своју лозинку (можете поново користити исту ако желите).",
|
||||
"useRecoveryKey": "Користите кључ за опоравак",
|
||||
"incorrectPasswordTitle": "Неисправна лозинка",
|
||||
"welcomeBack": "Добродошли назад!",
|
||||
"emailAlreadyRegistered": "Имејл је већ регистрован.",
|
||||
"emailNotRegistered": "Имејл није регистрован.",
|
||||
"madeWithLoveAtPrefix": "урађено са ❤️ на ",
|
||||
"supportDevs": "Претплатити се на <bold-green>ente</bold-green> да би нас подржали",
|
||||
"supportDiscount": "Употребите купон \"AUTH\" да би добили попуст од 10% прве године",
|
||||
"changeEmail": "Промени имејл",
|
||||
"changePassword": "Промени лозинку",
|
||||
"data": "Подаци",
|
||||
"importCodes": "Увоз кôдова",
|
||||
"importTypePlainText": "Обичан текст",
|
||||
"importTypeEnteEncrypted": "Ente шифрован извоз",
|
||||
"passwordForDecryptingExport": "Лозинка за дешифровање извоза",
|
||||
"passwordEmptyError": "Лозинка не може да буде празна",
|
||||
"importFromApp": "Увоз кôдова од {appName}",
|
||||
"importGoogleAuthGuide": "Извезите своје рачуне од Google Authenticator на QR кôд помоћу опције \"Трансфер налоге\". Затим помоћу другог уређаја скенирајте QR кôд.\n\nСавет: можете користити веб камеру вашег лаптопа да бисте снимили слику QR кôда.",
|
||||
"importSelectJsonFile": "Одабрати JSON датотеку",
|
||||
"importSelectAppExport": "Одабрати извозну датотеку {appName}-а",
|
||||
"importEnteEncGuide": "Одабрати шифровану извозну JSON датотеку од Ente",
|
||||
"importRaivoGuide": "Употребите \"Export OTPs to Zip archive\" опцију из подешавања Raivo-а.\n\nИздвојите zip датотеку и увезите JSON датотеку.",
|
||||
"importBitwardenGuide": "Употребите \"Извоз Сефа\" из Bitwarden и увезите нешифровану JSON датотеку.",
|
||||
"importAegisGuide": "Употребити \"Export the vault\" из Aegis-а.\n\nАко је сеф шифрован, мораћете унети лозинку сефа да би га дешифровали.",
|
||||
"import2FasGuide": "Употребити \"Settings->Backup -Export\" из 2FAS-а.\n\nАко је ваша копија шифрирана, мораћете да унесете лозинку за дешифрирање копије",
|
||||
"importLastpassGuide": "Употребити \"Transfer accounts\" из Lastpass Authenticator и стисните \"Export accounts to file\". Унесите преузет JSON.",
|
||||
"exportCodes": "Извоз кôдова",
|
||||
"importLabel": "Увоз",
|
||||
"importInstruction": "Изаберите датотеку која садржи списак ваших кôдова у следећем формату",
|
||||
"importCodeDelimiterInfo": "Кôдови се могу одвојити зарезом или новом линијом",
|
||||
"selectFile": "Изаберите датотеку",
|
||||
"emailVerificationToggle": "Имејл провера",
|
||||
"emailVerificationEnableWarning": "Да бисте избегли да се закључате са свог рачуна, обавезно чувајте копију 2ФА имејла ван Ente Auth пре него што омогућите имејл верификацију.",
|
||||
"authToChangeEmailVerificationSetting": "Потврдите аутентичност да промените верификацији имејл",
|
||||
"authenticateGeneric": "Молимо потврдите аутентичност",
|
||||
"authToViewYourRecoveryKey": "Аутентификујте се да бисте погледали кључ за опоравак",
|
||||
"authToChangeYourEmail": "Аутентификујте се да бисте променили имејл",
|
||||
"authToChangeYourPassword": "Аутентификујте се да бисте променили лозинку",
|
||||
"authToViewSecrets": "Аутентификујте се да бисте прегледали Ваше тајне",
|
||||
"authToInitiateSignIn": "Аутентификујте се да бисте почели пријављивање за копију.",
|
||||
"ok": "У реду",
|
||||
"cancel": "Откажи",
|
||||
"yes": "Да",
|
||||
"no": "Не",
|
||||
"email": "Имејл",
|
||||
"support": "Подршка",
|
||||
"general": "Опште",
|
||||
"settings": "Подешавања",
|
||||
"copied": "Копирано",
|
||||
"pleaseTryAgain": "Пробајте поново",
|
||||
"existingUser": "Постојећи корисник",
|
||||
"newUser": "Нов у Ente",
|
||||
"delete": "Обриши",
|
||||
"enterYourPasswordHint": "Унесите лозинку",
|
||||
"forgotPassword": "Заборавио сам лозинку",
|
||||
"oops": "Упс",
|
||||
"suggestFeatures": "Предложи карактеристике",
|
||||
"faq": "Питања",
|
||||
"somethingWentWrongMessage": "Нешто је пошло наопако, покушајте поново",
|
||||
"leaveFamily": "Напусти family претплату",
|
||||
"leaveFamilyMessage": "Јесте ли сигурни да желите да напустите family чланство?",
|
||||
"inFamilyPlanMessage": "Имате family чланство!",
|
||||
"hintForMobile": "Дуго притисните кôд за уређивање или уклањање.",
|
||||
"hintForDesktop": "Десни клик на кôд за уређивање или уклањање.",
|
||||
"scan": "Скенирај",
|
||||
"scanACode": "Скенирај кôд",
|
||||
"verify": "Верификуј",
|
||||
"verifyEmail": "Потврди имејл",
|
||||
"enterCodeHint": "Унесите 6-цифрени кôд из\nапликације за аутентификацију",
|
||||
"lostDeviceTitle": "Узгубили сте уређај?",
|
||||
"twoFactorAuthTitle": "Дво-факторска аутентификација",
|
||||
"passkeyAuthTitle": "Верификација сигурносном кључем",
|
||||
"verifyPasskey": "Проверите сигурносни кључ",
|
||||
"loginWithTOTP": "Пријава са TOTP",
|
||||
"recoverAccount": "Опоравак налога",
|
||||
"enterRecoveryKeyHint": "Унети кључ за опоравак",
|
||||
"recover": "Опорави",
|
||||
"contactSupportViaEmailMessage": "Послати имејл на {email} са регистрованог имејла",
|
||||
"@contactSupportViaEmailMessage": {
|
||||
"placeholders": {
|
||||
"email": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
},
|
||||
"invalidQRCode": "Неважећи QR кôд",
|
||||
"noRecoveryKeyTitle": "Немате кључ за опоравак?",
|
||||
"enterEmailHint": "Унесите Ваш имејл",
|
||||
"enterNewEmailHint": "Унесите Ваш нови имејл",
|
||||
"invalidEmailTitle": "Погрешна имејл адреса",
|
||||
"invalidEmailMessage": "Унесите важећи имејл.",
|
||||
"deleteAccount": "Избриши налог",
|
||||
"deleteAccountQuery": "Жао нам је што одлазите. Да ли се суочавате са неком грешком?",
|
||||
"yesSendFeedbackAction": "Да, послати повратне информације",
|
||||
"noDeleteAccountAction": "Не, избрисати налог",
|
||||
"initiateAccountDeleteTitle": "Молимо вас да се аутентификујете за брисање рачуна",
|
||||
"sendEmail": "Шаљи имејл",
|
||||
"createNewAccount": "Креирај нови налог",
|
||||
"weakStrength": "Слабо",
|
||||
"strongStrength": "Јако",
|
||||
"moderateStrength": "Умерено",
|
||||
"confirmPassword": "Потврдите лозинку",
|
||||
"close": "Затвори",
|
||||
"oopsSomethingWentWrong": "Нешто није у реду.",
|
||||
"selectLanguage": "Изабери језик",
|
||||
"language": "Језик",
|
||||
"social": "Друштвене мреже",
|
||||
"security": "Безбедност",
|
||||
"lockscreen": "Закључавање екрана",
|
||||
"authToChangeLockscreenSetting": "Аутентификујте се да бисте променили закључавање екрана",
|
||||
"deviceLockEnablePreSteps": "Да бисте омогућили закључавање уређаја, молимо вас да подесите шифру уређаја или закључавање екрана у системским подешавањима.",
|
||||
"viewActiveSessions": "Видети активне сесије",
|
||||
"authToViewYourActiveSessions": "Аутентификујте се да бисте преглеадали активне сесије",
|
||||
"searchHint": "Претрага...",
|
||||
"search": "Претрага",
|
||||
"sorryUnableToGenCode": "Извините, не могу да генеришем кôд за {issuerName}",
|
||||
"noResult": "Нема резултата",
|
||||
"addCode": "Додај кôд",
|
||||
"scanAQrCode": "Скенирај QR кôд",
|
||||
"enterDetailsManually": "Ручно унети детеље",
|
||||
"edit": "Уреди",
|
||||
"share": "Подели",
|
||||
"shareCodes": "Дели кôдове",
|
||||
"shareCodesDuration": "Изаберите трајање за које желите да поделите кôдове.",
|
||||
"restore": "Врати",
|
||||
"copiedToClipboard": "Копирано у оставу",
|
||||
"copiedNextToClipboard": "Копирали следећи кôд у остави",
|
||||
"error": "Грешка",
|
||||
"recoveryKeyCopiedToClipboard": "Кључ за опоравак копирано у остави",
|
||||
"recoveryKeyOnForgotPassword": "Ако заборавите лозинку, једини начин на који можете повратити податке је са овим кључем.",
|
||||
"recoveryKeySaveDescription": "Не чувамо овај кључ, молимо да сачувате кључ од 24 речи на сигурном месту.",
|
||||
"doThisLater": "Уради то касније",
|
||||
"saveKey": "Сачувај кључ",
|
||||
"save": "Сачувај",
|
||||
"send": "Пошаљи",
|
||||
"saveOrSendDescription": "Да ли желите да ово сачувате у складиште (фасцикли за преузимање подразумевано) или да га пошаљете другим апликацијама?",
|
||||
"saveOnlyDescription": "Да ли желите да ово сачувате у складиште (фасцикли за преузимање подразумевано)?",
|
||||
"back": "Назад",
|
||||
"createAccount": "Направи налог",
|
||||
"passwordStrength": "Снага лозинке: {passwordStrengthValue}",
|
||||
"@passwordStrength": {
|
||||
"description": "Text to indicate the password strength",
|
||||
"placeholders": {
|
||||
"passwordStrengthValue": {
|
||||
"description": "The strength of the password as a string",
|
||||
"type": "String",
|
||||
"example": "Weak or Moderate or Strong"
|
||||
}
|
||||
},
|
||||
"message": "Password Strength: {passwordStrengthText}"
|
||||
},
|
||||
"password": "Лозинка",
|
||||
"signUpTerms": "Прихватам <u-terms>услове сервиса</u-terms> и <u-policy>политику приватности</u-policy>",
|
||||
"privacyPolicyTitle": "Политика приватности",
|
||||
"termsOfServicesTitle": "Услови",
|
||||
"encryption": "Шифровање",
|
||||
"setPasswordTitle": "Постави лозинку",
|
||||
"changePasswordTitle": "Промени лозинку",
|
||||
"resetPasswordTitle": "Ресетуј лозинку",
|
||||
"encryptionKeys": "Кључеве шифровања",
|
||||
"passwordWarning": "Не чувамо ову лозинку, па ако је заборавите, <underline>не можемо дешифрирати ваше податке</underline>",
|
||||
"enterPasswordToEncrypt": "Унесите лозинку за употребу за шифровање ваших података",
|
||||
"enterNewPasswordToEncrypt": "Унесите нову лозинку за употребу за шифровање ваших података",
|
||||
"passwordChangedSuccessfully": "Лозинка је успешно промењена",
|
||||
"generatingEncryptionKeys": "Генерисање кључева за шифровање...",
|
||||
"continueLabel": "Настави",
|
||||
"insecureDevice": "Уређај није сигуран",
|
||||
"sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "Извините, не можемо да генеришемо сигурне кључеве на овом уређају.\n\nМолимо пријавите се са другог уређаја.",
|
||||
"howItWorks": "Како то функционише",
|
||||
"ackPasswordLostWarning": "Разумем да ако изгубим лозинку, могу изгубити своје податке пошто су <underline>шифрирани од краја до краја</underline>.",
|
||||
"loginTerms": "Кликом на пријаву, прихватам <u-terms>услове сервиса</u-terms> и <u-policy>политику приватности</u-policy>",
|
||||
"logInLabel": "Пријави се",
|
||||
"logout": "Одјави ме",
|
||||
"areYouSureYouWantToLogout": "Да ли сте сигурни да се одјавите?",
|
||||
"yesLogout": "Да, одјави ме",
|
||||
"exit": "Излаз",
|
||||
"theme": "Тема",
|
||||
"lightTheme": "Светла",
|
||||
"darkTheme": "Tamna",
|
||||
"systemTheme": "Систем",
|
||||
"verifyingRecoveryKey": "Провера кључа за опоравак...",
|
||||
"recoveryKeyVerified": "Кључ за опоравак је проверен",
|
||||
"recoveryKeySuccessBody": "Сјајно! Ваш кључ за опоравак важи. Хвала за проверу.\n\nИмајте на уму да задржите кључ за опоравак на сигрном.",
|
||||
"invalidRecoveryKey": "Кључ за опоравак који сте унели није валидан. Молимо вас да будете сигурни да садржи 24 речи и проверите правопис сваког.\n\nАко сте унели старији кôд за опоравак, проверите да ли је дугачак 64 знака и проверите сваки од њих.",
|
||||
"recreatePasswordTitle": "Поново креирати лозинку",
|
||||
"recreatePasswordBody": "Тренутни уређај није довољно моћан да потврди вашу лозинку, али можемо регенерирати на начин који ради са свим уређајима.\n\nПријавите се помоћу кључа за опоравак и обновите своју лозинку (можете поново користити исту ако желите).",
|
||||
"invalidKey": "Неисправан кључ",
|
||||
"tryAgain": "Покушај поново",
|
||||
"viewRecoveryKey": "Видети кључ за опоравак",
|
||||
"confirmRecoveryKey": "Потврдити кључ за опоравак",
|
||||
"recoveryKeyVerifyReason": "Ваш кључ за опоравак је једини начин да се врате фотографије ако заборавите лозинку. Можете пронаћи свој кључ за опоравак у Подешавања> Рачун.\n\nОвдје унесите кључ за опоравак да бисте проверили да ли сте га исправно сачували.",
|
||||
"confirmYourRecoveryKey": "Потврдити кључ за опоравак",
|
||||
"confirm": "Потврди",
|
||||
"emailYourLogs": "Имејлирајте извештаје",
|
||||
"pleaseSendTheLogsTo": "Пошаљите извештаје на \n{toEmail}",
|
||||
"copyEmailAddress": "Копирати имејл адресу",
|
||||
"exportLogs": "Извези изештаје",
|
||||
"enterYourRecoveryKey": "Унети кључ за опоравак",
|
||||
"tempErrorContactSupportIfPersists": "Изгледа да је нешто погрешно. Покушајте поново након неког времена. Ако грешка настави, обратите се нашем тиму за подршку.",
|
||||
"networkHostLookUpErr": "Није могуће повезивање са Ente-ом, молимо вас да проверите мрежне поставке и контактирајте подршку ако грешка и даље постоји.",
|
||||
"networkConnectionRefusedErr": "Није могуће повезивање са Ente-ом, покушајте поново мало касније. Ако грешка настави, обратите се подршци.",
|
||||
"itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "Изгледа да је нешто погрешно. Покушајте поново након неког времена. Ако грешка настави, обратите се нашем тиму за подршку.",
|
||||
"about": "О програму",
|
||||
"weAreOpenSource": "Користимо отворени извор!",
|
||||
"privacy": "Приватност",
|
||||
"terms": "Услови",
|
||||
"checkForUpdates": "Провери ажурирања",
|
||||
"checkStatus": "Провери статус",
|
||||
"downloadUpdate": "Преузми",
|
||||
"criticalUpdateAvailable": "Критично ажурирање је доступно",
|
||||
"updateAvailable": "Доступно ажурирање",
|
||||
"update": "Ажурирај",
|
||||
"checking": "Провера...",
|
||||
"youAreOnTheLatestVersion": "Користите најновију верзију",
|
||||
"warning": "Упозорење",
|
||||
"exportWarningDesc": "Извозна датотека садржи осетљиве информације. Молимо вас да је чувате на сигурно.",
|
||||
"iUnderStand": "Разумем",
|
||||
"@iUnderStand": {
|
||||
"description": "Text for the button to confirm the user understands the warning"
|
||||
},
|
||||
"authToExportCodes": "Аутентификујте се да бисте извезли кôдове",
|
||||
"importSuccessTitle": "Jeeee!",
|
||||
"importSuccessDesc": "Увели сте {count} кôдова!",
|
||||
"@importSuccessDesc": {
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"description": "The number of codes imported",
|
||||
"type": "int",
|
||||
"example": "1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sorry": "Жао ми је",
|
||||
"importFailureDesc": "Нисам могао да анализирам изабрану датотеку.\nПишите на support@ente.io ако вам је потребна помоћ!",
|
||||
"pendingSyncs": "Упозорење",
|
||||
"pendingSyncsWarningBody": "Неки од ваших кôдова нису сачувани.\n\nМолимо вас осигурајте да имате резервну копију за ове кôдове пре него што се одјавите.",
|
||||
"checkInboxAndSpamFolder": "Молимо вас да проверите примљену пошту (и нежељену пошту) да бисте довршили верификацију",
|
||||
"tapToEnterCode": "Пипните да бисте унели кôд",
|
||||
"resendEmail": "Поново послати имејл",
|
||||
"weHaveSendEmailTo": "Послали смо имејл на <green>{email}</green>",
|
||||
"@weHaveSendEmailTo": {
|
||||
"description": "Text to indicate that we have sent a mail to the user",
|
||||
"placeholders": {
|
||||
"email": {
|
||||
"description": "The email address of the user",
|
||||
"type": "String",
|
||||
"example": "example@ente.io"
|
||||
}
|
||||
}
|
||||
},
|
||||
"manualSort": "Прилагођено",
|
||||
"editOrder": "Уреди поредак",
|
||||
"mostFrequentlyUsed": "Често коришћено",
|
||||
"mostRecentlyUsed": "Недавно коришћено",
|
||||
"activeSessions": "Активне сесије",
|
||||
"somethingWentWrongPleaseTryAgain": "Нешто је пошло наопако. Покушајте поново",
|
||||
"thisWillLogYouOutOfThisDevice": "Ово ће вас одјавити из овог уређаја!",
|
||||
"thisWillLogYouOutOfTheFollowingDevice": "Ово ће вас одјавити из овог уређаја:",
|
||||
"terminateSession": "Прекинути сесију?",
|
||||
"terminate": "Прекини",
|
||||
"thisDevice": "Овај уређај",
|
||||
"toResetVerifyEmail": "Да бисте ресетовали лозинку, прво потврдите свој имејл.",
|
||||
"thisEmailIsAlreadyInUse": "Овај имејл је већ у употреби",
|
||||
"verificationFailedPleaseTryAgain": "Неуспешна верификација, покушајте поново",
|
||||
"yourVerificationCodeHasExpired": "Ваш верификациони кôд је истекао",
|
||||
"incorrectCode": "Погрешан кôд",
|
||||
"sorryTheCodeYouveEnteredIsIncorrect": "Унет кôд није добар",
|
||||
"emailChangedTo": "Имејл промењен на {newEmail}",
|
||||
"authenticationFailedPleaseTryAgain": "Аутентификација није успела, покушајте поново",
|
||||
"authenticationSuccessful": "Успешна аутентификација!",
|
||||
"twofactorAuthenticationSuccessfullyReset": "Двофакторска аутентификација успешно рисетирана",
|
||||
"incorrectRecoveryKey": "Нетачан кључ за опоравак",
|
||||
"theRecoveryKeyYouEnteredIsIncorrect": "Унети кључ за опоравак је натачан",
|
||||
"enterPassword": "Унеси лозинку",
|
||||
"selectExportFormat": "Изабрати формат извоза",
|
||||
"exportDialogDesc": "Шифровани извоз ће бити заштићен лозинком по вашем избору.",
|
||||
"encrypted": "Шифровано",
|
||||
"plainText": "Обичан текст",
|
||||
"passwordToEncryptExport": "Лозинка за шифровање извоза",
|
||||
"export": "Извези",
|
||||
"useOffline": "Користите без резервних копија",
|
||||
"signInToBackup": "Пријавите се да бисте сачували кôдове",
|
||||
"singIn": "Пријавите се",
|
||||
"sigInBackupReminder": "Извезите кôдове да бисте имали резервну копију од које можете да их вратите.",
|
||||
"offlineModeWarning": "Одлучили сте да наставите без резервних копија. Молимо примите ручне резервне копије да бисте били сигурни да су ваше кодове на сигурном.",
|
||||
"showLargeIcons": "Прикажи велике иконе",
|
||||
"compactMode": "Компактни режим",
|
||||
"shouldHideCode": "Сакриј кодове",
|
||||
"doubleTapToViewHiddenCode": "Можете да двапут додирнете унос да бисте видели кôд",
|
||||
"focusOnSearchBar": "Фокус на претрагу на покретање",
|
||||
"confirmUpdatingkey": "Јесте ли сигурни да желите да ажурирате тајну кључ?",
|
||||
"minimizeAppOnCopy": "Умањи апликацију после копије",
|
||||
"editCodeAuthMessage": "Аутентификуј се за уред кôда",
|
||||
"deleteCodeAuthMessage": "Аутентификуј се за брсање кôда",
|
||||
"showQRAuthMessage": "Аутентификуј се за приказ QR кôда",
|
||||
"confirmAccountDeleteTitle": "Потврда брисања рачуна",
|
||||
"confirmAccountDeleteMessage": "Овај налог је повезан са другим Ente апликацијама, ако користите било коју.\n\nВаши преношени подаци, на свим Ente апликацијама биће заказани за брисање, и ваш рачун ће се трајно избрисати.",
|
||||
"androidBiometricHint": "Потврдите идентитет",
|
||||
"@androidBiometricHint": {
|
||||
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricNotRecognized": "Нисмо препознали. Покушати поново.",
|
||||
"@androidBiometricNotRecognized": {
|
||||
"description": "Message to let the user know that authentication was failed. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricSuccess": "Успех",
|
||||
"@androidBiometricSuccess": {
|
||||
"description": "Message to let the user know that authentication was successful. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidCancelButton": "Откажи",
|
||||
"@androidCancelButton": {
|
||||
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on Android side. Maximum 30 characters."
|
||||
},
|
||||
"androidSignInTitle": "Потребна аутентификација",
|
||||
"@androidSignInTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user that they need to scan biometric to continue. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidBiometricRequiredTitle": "Потребна је биометрија",
|
||||
"@androidBiometricRequiredTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user has not set up biometric authentication on their device. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidDeviceCredentialsRequiredTitle": "Потребни су акредитиви уређаја",
|
||||
"@androidDeviceCredentialsRequiredTitle": {
|
||||
"description": "Message showed as a title in a dialog which indicates the user has not set up credentials authentication on their device. It is used on Android side. Maximum 60 characters."
|
||||
},
|
||||
"androidDeviceCredentialsSetupDescription": "Потребни су акредитиви уређаја",
|
||||
"@androidDeviceCredentialsSetupDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure device credentials on their device. It shows in a dialog on Android side."
|
||||
},
|
||||
"goToSettings": "Иди на поставке",
|
||||
"@goToSettings": {
|
||||
"description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters."
|
||||
},
|
||||
"androidGoToSettingsDescription": "Биометријска аутентификација није постављена на вашем уређају. Идите на \"Подешавања> Сигурност\" да бисте додали биометријску аутентификацију.",
|
||||
"@androidGoToSettingsDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side."
|
||||
},
|
||||
"iOSLockOut": "Биометријска аутентификација је онемогућена. Закључајте и откључите екран да бисте је омогућили.",
|
||||
"@iOSLockOut": {
|
||||
"description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side."
|
||||
},
|
||||
"iOSGoToSettingsDescription": "Биометријска аутентификација није постављена на вашем уређају. Молимо или омогућите Touch ID или Face ID.",
|
||||
"@iOSGoToSettingsDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure Biometrics for their device. It shows in a dialog on iOS side."
|
||||
},
|
||||
"iOSOkButton": "У реду",
|
||||
"@iOSOkButton": {
|
||||
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters."
|
||||
},
|
||||
"noInternetConnection": "Нема интернет везе",
|
||||
"pleaseCheckYourInternetConnectionAndTryAgain": "Провери своју везу са интернетом и покушај поново.",
|
||||
"signOutFromOtherDevices": "Одјави се из других уређаја",
|
||||
"signOutOtherBody": "Ако мислиш да неко може знати твоју лозинку, можеш приморати одјављивање све остале уређаје које користе твој налог.",
|
||||
"signOutOtherDevices": "Одјави друге уређаје",
|
||||
"doNotSignOut": "Не одјави",
|
||||
"hearUsWhereTitle": "Како сте чули о Ente? (опционо)",
|
||||
"hearUsExplanation": "Не пратимо инсталацију апликације. Помогло би да нам кажеш како си нас нашао!",
|
||||
"recoveryKeySaved": "Кључ за опоравак сачуван у фасцикли за преузимање!",
|
||||
"waitingForBrowserRequest": "Чека се захтев за претраживач...",
|
||||
"waitingForVerification": "Чека се верификација...",
|
||||
"passkey": "Кључ за приступ",
|
||||
"passKeyPendingVerification": "Верификација је још у току",
|
||||
"loginSessionExpired": "Сесија је истекла",
|
||||
"loginSessionExpiredDetails": "Ваша сесија је истекла. Молимо пријавите се поново.",
|
||||
"developerSettingsWarning": "Сигурно желиш да промениш подешавања за програмере?",
|
||||
"developerSettings": "Подешавања за програмере",
|
||||
"serverEndpoint": "Крајња тачка сервера",
|
||||
"invalidEndpoint": "Погрешна крајња тачка",
|
||||
"invalidEndpointMessage": "Извини, крајња тачка коју си унео је неважећа. Унеси важећу крајњу тачку и покушај поново.",
|
||||
"endpointUpdatedMessage": "Крајна тачка успешно ажурирана",
|
||||
"customEndpoint": "Везано за {endpoint}",
|
||||
"pinText": "Закачи",
|
||||
"unpinText": "Откачи",
|
||||
"pinnedCodeMessage": "{code} је прикачен",
|
||||
"unpinnedCodeMessage": "{code} је одкачен",
|
||||
"pinned": "Прикачено",
|
||||
"tags": "Ознаке",
|
||||
"createNewTag": "Креирај нову ознаку",
|
||||
"tag": "Ознака",
|
||||
"create": "Направи",
|
||||
"editTag": "Уреди ознаку",
|
||||
"deleteTagTitle": "Обрисати ознаку?",
|
||||
"deleteTagMessage": "Сигурно желите да избришете ову ознаку? Ова акција је неповратна.",
|
||||
"somethingWentWrongParsingCode": "Нисмо били у стању да рашчланимо {x} кôдова.",
|
||||
"updateNotAvailable": "Ажурирање није доступно",
|
||||
"viewRawCodes": "Погледајте сирове кôдове",
|
||||
"rawCodes": "Сирове кôдове",
|
||||
"rawCodeData": "Податак сировог кôда",
|
||||
"appLock": "Закључавање апликације",
|
||||
"noSystemLockFound": "Није пронађено ниједно закључавање система",
|
||||
"toEnableAppLockPleaseSetupDevicePasscodeOrScreen": "Да бисте омогућили закључавање апликације, молимо вас да подесите шифру уређаја или закључавање екрана у системским подешавањима.",
|
||||
"autoLock": "Ауто-закључавање",
|
||||
"immediately": "Одмах",
|
||||
"reEnterPassword": "Поново унеси лозинку",
|
||||
"reEnterPin": "Поново унеси ПИН",
|
||||
"next": "Следеће",
|
||||
"tooManyIncorrectAttempts": "Превише погрешних покушаја",
|
||||
"tapToUnlock": "Додирните да бисте откључали",
|
||||
"setNewPassword": "Постави нову лозинку",
|
||||
"deviceLock": "Закључавање уређаја",
|
||||
"hideContent": "Сакриј садржај",
|
||||
"hideContentDescriptionAndroid": "Сакрива садржај апликације у пребацивање апликација и онемогућује снимке екрана",
|
||||
"hideContentDescriptioniOS": "Сакрива садржај апликације у пребацивање апликација",
|
||||
"autoLockFeatureDescription": "Време након којег се апликација блокира након што је постављенеа у позадину",
|
||||
"appLockDescription": "Изаберите између заданог закључавање екрана вашег уређаја и прилагођени екран за закључавање са ПИН-ом или лозинком.",
|
||||
"pinLock": "ПИН клокирање",
|
||||
"enterPin": "Унеси ПИН",
|
||||
"setNewPin": "Постави нови ПИН",
|
||||
"importFailureDescNew": "Није могао да анализира изабрану датотеку.",
|
||||
"appLockNotEnabled": "Блокирање апликације није упаљено",
|
||||
"appLockNotEnabledDescription": "Молимо упалие блокирање апликације на Безбедност > Блокирај апликацију",
|
||||
"authToViewPasskey": "Аутентификујте се да бисте прегледали кључ",
|
||||
"appLockOfflineModeWarning": "Одлучили сте да наставите без резервних копија. Ако заборавите лозинку, нећете моћи да приступите својим подацима.",
|
||||
"duplicateCodes": "Дупликатни кодови",
|
||||
"noDuplicates": "✨ Нема дупликата",
|
||||
"youveNoDuplicateCodesThatCanBeCleared": "Немате дупликатне кодове који се могу очистити",
|
||||
"deduplicateCodes": "Дедуплицирај кодове",
|
||||
"deselectAll": "Поништи избор свега",
|
||||
"selectAll": "Изабери све",
|
||||
"deleteDuplicates": "Обриши дупликате",
|
||||
"plainHTML": "HTML",
|
||||
"tellUsWhatYouThink": "Реци нам шта мислиш",
|
||||
"dropReviewiOS": "Напиши мишљење на App Store",
|
||||
"dropReviewAndroid": "Напиши мишљење на Play Store",
|
||||
"supportEnte": "Подржи <bold-green>ente</bold-green>",
|
||||
"giveUsAStarOnGithub": "Дај нам звездицу на Github",
|
||||
"free5GB": "5GB бесплатно на <bold-green>ente</bold-green> Photos",
|
||||
"loginWithAuthAccount": "Пријави се са твојим Auth налогом",
|
||||
"freeStorageOffer": "Попуст од 10% на <bold-green>ente</bold-green> photos",
|
||||
"freeStorageOfferDescription": "Употребите кôд \"AUTH\" да би добили попуст од 10% прве године",
|
||||
"advanced": "Напредно",
|
||||
"algorithm": "Алгоритам",
|
||||
"type": "Тип",
|
||||
"period": "Период",
|
||||
"digits": "Цифре"
|
||||
}
|
||||
@@ -173,6 +173,7 @@
|
||||
"invalidQRCode": "Ogiltig QR-kod",
|
||||
"noRecoveryKeyTitle": "Ingen återställningsnyckel?",
|
||||
"enterEmailHint": "Ange din e-postadress",
|
||||
"enterNewEmailHint": "Ange din nya e-postadress",
|
||||
"invalidEmailTitle": "Ogiltig e-postadress",
|
||||
"invalidEmailMessage": "Ange en giltig e-postadress.",
|
||||
"deleteAccount": "Radera konto",
|
||||
@@ -368,16 +369,19 @@
|
||||
"signInToBackup": "Logga in för att säkerhetskopiera dina koder",
|
||||
"singIn": "Logga in",
|
||||
"sigInBackupReminder": "Vänligen exportera dina koder för att säkerställa att du har en säkerhetskopia som du kan återställa från.",
|
||||
"offlineModeWarning": "Du har valt att fortsätta utan säkerhetskopior. Vänligen ta manuella säkerhetskopior för att se till att dina koder är säkra.",
|
||||
"showLargeIcons": "Visa stora ikoner",
|
||||
"compactMode": "Kompakt läge",
|
||||
"shouldHideCode": "Dölj koder",
|
||||
"doubleTapToViewHiddenCode": "Du kan dubbeltrycka på en post för att visa koden",
|
||||
"focusOnSearchBar": "Fokusera på sök vid appstart",
|
||||
"confirmUpdatingkey": "Är du säker på att du vill uppdatera den hemliga nyckeln?",
|
||||
"minimizeAppOnCopy": "Minimera appen vid kopiering",
|
||||
"editCodeAuthMessage": "Autentisera för att redigera kod",
|
||||
"deleteCodeAuthMessage": "Autentisera för att radera kod",
|
||||
"showQRAuthMessage": "Autentisera för att visa QR-kod",
|
||||
"confirmAccountDeleteTitle": "Bekräfta radering av kontot",
|
||||
"confirmAccountDeleteMessage": "Detta konto är kopplat till andra Ente apps, om du använder någon.\n\nDina uppladdade data, över alla Ente appar, kommer att schemaläggas för radering och ditt konto kommer att raderas permanent.",
|
||||
"androidBiometricHint": "Verifiera identitet",
|
||||
"@androidBiometricHint": {
|
||||
"description": "Hint message advising the user how to authenticate with biometrics. It is used on Android side. Maximum 60 characters."
|
||||
@@ -414,6 +418,18 @@
|
||||
"@goToSettings": {
|
||||
"description": "Message showed on a button that the user can click to go to settings pages from the current dialog. It is used on both Android and iOS side. Maximum 30 characters."
|
||||
},
|
||||
"androidGoToSettingsDescription": "Biometrisk autentisering är inte konfigurerad på din enhet. Gå till \"Inställningar > Säkerhet\" för att lägga till biometrisk autentisering.",
|
||||
"@androidGoToSettingsDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure biometric on their device. It shows in a dialog on Android side."
|
||||
},
|
||||
"iOSLockOut": "Biometrisk autentisering är inaktiverat. Lås och lås upp din skärm för att aktivera den.",
|
||||
"@iOSLockOut": {
|
||||
"description": "Message advising the user to re-enable biometrics on their device. It shows in a dialog on iOS side."
|
||||
},
|
||||
"iOSGoToSettingsDescription": "Biometrisk autentisering är inte konfigurerad på din enhet. Aktivera antingen Touch ID eller Face ID på din telefon.",
|
||||
"@iOSGoToSettingsDescription": {
|
||||
"description": "Message advising the user to go to the settings and configure Biometrics for their device. It shows in a dialog on iOS side."
|
||||
},
|
||||
"iOSOkButton": "OK",
|
||||
"@iOSOkButton": {
|
||||
"description": "Message showed on a button that the user can click to leave the current dialog. It is used on iOS side. Maximum 30 characters."
|
||||
@@ -421,6 +437,7 @@
|
||||
"noInternetConnection": "Ingen internetanslutning",
|
||||
"pleaseCheckYourInternetConnectionAndTryAgain": "Kontrollera din internetanslutning och försök igen.",
|
||||
"signOutFromOtherDevices": "Logga ut från andra enheter",
|
||||
"signOutOtherBody": "Om du tror att någon kanske känner till ditt lösenord kan du tvinga alla andra enheter med ditt konto att logga ut.",
|
||||
"signOutOtherDevices": "Logga ut andra enheter",
|
||||
"doNotSignOut": "Logga inte ut",
|
||||
"hearUsWhereTitle": "Hur hörde du talas om Ente? (valfritt)",
|
||||
@@ -450,6 +467,7 @@
|
||||
"create": "Skapa",
|
||||
"editTag": "Redigera tagg",
|
||||
"deleteTagTitle": "Radera tagg?",
|
||||
"deleteTagMessage": "Vill du ta bort den här koden? Det går inte att ångra den här åtgärden.",
|
||||
"somethingWentWrongParsingCode": "Vi kunde inte tolka {x} koder.",
|
||||
"updateNotAvailable": "Uppdateringen är inte tillgänglig",
|
||||
"viewRawCodes": "Visa råa koder",
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
"@counterAppBarTitle": {
|
||||
"description": "Text shown in the AppBar of the Counter Page"
|
||||
},
|
||||
"onBoardingBody": "2 Faktörlü Kimlik Doğrulama kodlarınızı koruyun",
|
||||
"onBoardingBody": "2FA kodlarınızı güvenli bir şekilde yedekleyin",
|
||||
"onBoardingGetStarted": "Başlayın",
|
||||
"setupFirstAccount": "İlk hesabınızı ekleyin",
|
||||
"importScanQrCode": "Karekod tara",
|
||||
"importScanQrCode": "QR kod tara",
|
||||
"qrCode": "QR Kodu",
|
||||
"importEnterSetupKey": "Kurulum anahtarını giriniz",
|
||||
"importAccountPageTitle": "Hesap bilgilerinizi girin",
|
||||
@@ -51,11 +51,11 @@
|
||||
"trashCode": "Kod çöpe atılsın mı?",
|
||||
"trashCodeMessage": "{account} için kodu çöpe atmak istediğinize emin misiniz?",
|
||||
"trash": "Çöp",
|
||||
"viewLogsAction": "Günlüğü görüntüle",
|
||||
"sendLogsDescription": "Günlüğünüz hatanızı çözmemize yardımcı olacaktır. Hassas bilginin kaydedilmediğine dikkat etsek de bu günlükleri paylaşmadan önce kontrol etmenizi isteriz.",
|
||||
"preparingLogsTitle": "Günlük hazırlanıyor...",
|
||||
"emailLogsTitle": "Günlüğü e-posta olarak gönder",
|
||||
"emailLogsMessage": "Lütfen günlüğünüzü {email} adresine gönderin",
|
||||
"viewLogsAction": "Kayıtları görüntüle",
|
||||
"sendLogsDescription": "Kayıtlarınız hatanızı çözmemize yardımcı olacaktır. Hassas bilginin kaydedilmediğine dikkat etsek de bu günlükleri paylaşmadan önce kontrol etmenizi isteriz.",
|
||||
"preparingLogsTitle": "Kayıtlar hazırlanıyor...",
|
||||
"emailLogsTitle": "Kayıtları e-posta olarak gönder",
|
||||
"emailLogsMessage": "Lütfen kayıtlarınızı {email} adresine gönderin",
|
||||
"@emailLogsMessage": {
|
||||
"placeholders": {
|
||||
"email": {
|
||||
@@ -64,7 +64,7 @@
|
||||
}
|
||||
},
|
||||
"copyEmailAction": "E-postayı Kopyala",
|
||||
"exportLogsAction": "Günlüğü dışa aktar",
|
||||
"exportLogsAction": "Kayıtları dışa aktar",
|
||||
"reportABug": "Hata bildirin",
|
||||
"crashAndErrorReporting": "Çökme ve hata bildirimi",
|
||||
"reportBug": "Hata bildir",
|
||||
@@ -90,7 +90,7 @@
|
||||
"welcomeBack": "Tekrar hoş geldiniz!",
|
||||
"emailAlreadyRegistered": "E-posta zaten kayıtlı.",
|
||||
"emailNotRegistered": "E-posta kayıtlı değil.",
|
||||
"madeWithLoveAtPrefix": "❤️ ile şurada yapılmıştır ",
|
||||
"madeWithLoveAtPrefix": "❤️ ile yapılmıştır ",
|
||||
"supportDevs": "Bu projeyi desteklemek için <bold-green>ente</bold-green> kanalına abone olun",
|
||||
"supportDiscount": "İlk yılda %10 indirim için \"AUTH\" kupon kodunu kullanın",
|
||||
"changeEmail": "E-posta adresini değiştir",
|
||||
@@ -173,10 +173,11 @@
|
||||
"invalidQRCode": "Geçersiz QR kodu",
|
||||
"noRecoveryKeyTitle": "Kurtarma anahtarınız yok mu?",
|
||||
"enterEmailHint": "E-posta adresinizi girin",
|
||||
"enterNewEmailHint": "Yeni e-posta adresinizi girin",
|
||||
"invalidEmailTitle": "Geçersiz e-posta adresi",
|
||||
"invalidEmailMessage": "Lütfen geçerli bir e-posta adresi girin.",
|
||||
"deleteAccount": "Hesabı sil",
|
||||
"deleteAccountQuery": "Sizin gitmenizi görmekten üzüleceğiz. Bazı problemlerle mi karşılaşıyorsunuz?",
|
||||
"deleteAccountQuery": "Sizin gittiğinizi görmekten üzüleceğiz. Bazı problemlerle mi karşılaşıyorsunuz?",
|
||||
"yesSendFeedbackAction": "Evet, geri bildirimi gönder",
|
||||
"noDeleteAccountAction": "Hayır, hesabı sil",
|
||||
"initiateAccountDeleteTitle": "Hesap silme işlemini yapabilmek için lütfen kimliğinizi doğrulayın",
|
||||
@@ -253,8 +254,8 @@
|
||||
"insecureDevice": "Güvenli olmayan cihaz",
|
||||
"sorryWeCouldNotGenerateSecureKeysOnThisDevicennplease": "Üzgünüz, bu cihazda güvenli anahtarlar oluşturamadık.\n\nlütfen farklı bir cihazdan kaydolun.",
|
||||
"howItWorks": "Nasıl çalışır",
|
||||
"ackPasswordLostWarning": "Eğer şifremi kaybedersem, verilerim <underline>uçtan uca şifrelendiğinden</underline> verilerimi kaybedebileceğimi anladım.",
|
||||
"loginTerms": "Giriş yaparak, <u-terms>kullanım şartları</u-terms>nı ve <u-policy>gizlilik politikası</u-policy>nı onaylıyorum",
|
||||
"ackPasswordLostWarning": "Eğer şifremi kaybedersem, verilerim <underline> uçtan uca şifrelendiğinden </underline> verilerimi kaybedebileceğimi anladım.",
|
||||
"loginTerms": "Giriş yaparak, <u-terms> kullanım şartları </u-terms>nı ve <u-policy> gizlilik politikası </u-policy>nı onaylıyorum",
|
||||
"logInLabel": "Giriş yapın",
|
||||
"logout": "Çıkış yap",
|
||||
"areYouSureYouWantToLogout": "Çıkış yapmak istediğinize emin misiniz?",
|
||||
@@ -277,10 +278,10 @@
|
||||
"recoveryKeyVerifyReason": "Kurtarma anahtarınız, şifrenizi unutmanız durumunda fotoğraflarınızı kurtarmanın tek yoludur. Kurtarma anahtarınızı Ayarlar > Hesap bölümünde bulabilirsiniz.\n\nDoğru kaydettiğinizi doğrulamak için lütfen kurtarma anahtarınızı buraya girin.",
|
||||
"confirmYourRecoveryKey": "Kurtarma anahtarınızı doğrulayın",
|
||||
"confirm": "Doğrula",
|
||||
"emailYourLogs": "Günlüklerinizi e-postayla gönderin",
|
||||
"pleaseSendTheLogsTo": "Lütfen günlükleri şu adrese gönderin\n{toEmail}",
|
||||
"emailYourLogs": "Kayıtlarınızı e-postayla gönderin",
|
||||
"pleaseSendTheLogsTo": "Lütfen kayıtları şu adrese gönderin\n{toEmail}",
|
||||
"copyEmailAddress": "E-posta adresini kopyala",
|
||||
"exportLogs": "Günlüğü dışa aktar",
|
||||
"exportLogs": "Kayıtları dışa aktar",
|
||||
"enterYourRecoveryKey": "Kurtarma anahtarınızı girin",
|
||||
"tempErrorContactSupportIfPersists": "Bir şeyler ters gitmiş gibi görünüyor. Lütfen bir süre sonra tekrar deneyin. Hata devam ederse, lütfen destek ekibimizle iletişime geçin.",
|
||||
"networkHostLookUpErr": "Ente'ye bağlanılamıyor, lütfen ağ ayarlarınızı kontrol edin ve hata devam ederse desteğe başvurun.",
|
||||
@@ -499,10 +500,24 @@
|
||||
"appLockOfflineModeWarning": "Yedekleme olmadan devam etmeyi seçtiniz. Eğer uygulama parolanızı unutursanız, verilerinize erişiminiz engellenir.",
|
||||
"duplicateCodes": "Yinelenen kodlar",
|
||||
"noDuplicates": "✨ Yinelenen yok",
|
||||
"youveNoDuplicateCodesThatCanBeCleared": "Temizlenebilecek herhangi bir yinelenen kodunuz yok",
|
||||
"deduplicateCodes": "Kodları tekilleştir",
|
||||
"deselectAll": "Tümünün seçimini kaldır",
|
||||
"selectAll": "Tümünü seç",
|
||||
"deleteDuplicates": "Yinelenenleri sil",
|
||||
"plainHTML": "Sade HTML",
|
||||
"supportEnte": "<bold-Green>Ente</bold-Green>'yi destekle"
|
||||
"tellUsWhatYouThink": "Bize ne düşündüğünü söyle",
|
||||
"dropReviewiOS": "App Store'da bir inceleme bırakın",
|
||||
"dropReviewAndroid": "Play Store'da bir inceleme bırakın",
|
||||
"supportEnte": "<bold-Green>Ente</bold-Green>'yi destekle",
|
||||
"giveUsAStarOnGithub": "Github'da bize bir yıldız verin",
|
||||
"free5GB": "<bold-green>ente</bold-green> Fotoğraflarında 5GB ücretsiz",
|
||||
"loginWithAuthAccount": "Kimlik Doğrulama hesabınızla giriş yapın",
|
||||
"freeStorageOffer": "<bold-green>ente</bold-green> fotoğraflarında %10 indirim",
|
||||
"freeStorageOfferDescription": "İlk yılda %10 indirim almak için \"AUTH\" kodunu kullanın",
|
||||
"advanced": "Gelişmiş",
|
||||
"algorithm": "Algoritma",
|
||||
"type": "Tür",
|
||||
"period": "Zaman Aralığı",
|
||||
"digits": "Uzunluk"
|
||||
}
|
||||
@@ -173,6 +173,7 @@
|
||||
"invalidQRCode": "二维码无效",
|
||||
"noRecoveryKeyTitle": "没有恢复密钥吗?",
|
||||
"enterEmailHint": "请输入您的电子邮件地址",
|
||||
"enterNewEmailHint": "请输入您的新电子邮件地址",
|
||||
"invalidEmailTitle": "无效的电子邮件地址",
|
||||
"invalidEmailMessage": "请输入一个有效的电子邮件地址。",
|
||||
"deleteAccount": "删除账户",
|
||||
@@ -513,5 +514,10 @@
|
||||
"free5GB": "<bold-green>ente</bold-green> Photos 上 5GB 可用空间",
|
||||
"loginWithAuthAccount": "使用您的认证账户登录",
|
||||
"freeStorageOffer": "购买 <bold-green>ente</bold-green> Photos 可享受 10% 优惠",
|
||||
"freeStorageOfferDescription": "使用优惠码“AUTH”可享受首年 10% 折扣"
|
||||
"freeStorageOfferDescription": "使用优惠码“AUTH”可享受首年 10% 折扣",
|
||||
"advanced": "高级",
|
||||
"algorithm": "算法",
|
||||
"type": "类型",
|
||||
"period": "周期",
|
||||
"digits": "数字"
|
||||
}
|
||||
@@ -173,6 +173,7 @@
|
||||
"invalidQRCode": "QR 碼無效",
|
||||
"noRecoveryKeyTitle": "沒有復原密鑰嗎?",
|
||||
"enterEmailHint": "請輸入您的電子郵件地址",
|
||||
"enterNewEmailHint": "輸入你的新電子郵件地址",
|
||||
"invalidEmailTitle": "無效的電子郵件地址",
|
||||
"invalidEmailMessage": "請輸入一個有效的電子郵件地址。",
|
||||
"deleteAccount": "刪除帳戶",
|
||||
@@ -513,5 +514,10 @@
|
||||
"free5GB": "<bold-green>ente</bold-green> Photos 上 5GB 可用空間",
|
||||
"loginWithAuthAccount": "使用您的認證帳戶登錄",
|
||||
"freeStorageOffer": "購買 <bold-green>ente</bold-green> Photos 可享受 10% 優惠",
|
||||
"freeStorageOfferDescription": "使用優惠碼“AUTH”可享受首年 10% 折扣"
|
||||
"freeStorageOfferDescription": "使用優惠碼“AUTH”可享受首年 10% 折扣",
|
||||
"advanced": "進階",
|
||||
"algorithm": "演算法",
|
||||
"type": "類型",
|
||||
"period": "期間",
|
||||
"digits": "數位"
|
||||
}
|
||||
@@ -38,28 +38,28 @@ const List<Locale> appSupportedLocales = <Locale>[
|
||||
];
|
||||
|
||||
Locale? autoDetectedLocale;
|
||||
Locale localResolutionCallBack(locales, supportedLocales) {
|
||||
Locale? languageCodeMatch;
|
||||
final Map<String, Locale> languageCodeToLocale = {
|
||||
for (Locale supportedLocale in appSupportedLocales)
|
||||
supportedLocale.languageCode: supportedLocale,
|
||||
};
|
||||
|
||||
for (Locale locale in locales) {
|
||||
// This function takes device locales and supported locales as input
|
||||
// and returns the best matching locale.
|
||||
// The device locales are sorted by priority, so the first one is the most preferred.
|
||||
Locale localResolutionCallBack(onDeviceLocales, supportedLocales) {
|
||||
final Set<String> languageSupport = {};
|
||||
for (Locale supportedLocale in appSupportedLocales) {
|
||||
languageSupport.add(supportedLocale.languageCode);
|
||||
}
|
||||
for (Locale locale in onDeviceLocales) {
|
||||
// check if exact local is supported, if yes, return it
|
||||
if (appSupportedLocales.contains(locale)) {
|
||||
autoDetectedLocale = locale;
|
||||
return locale;
|
||||
}
|
||||
|
||||
if (languageCodeMatch == null &&
|
||||
languageCodeToLocale.containsKey(locale.languageCode)) {
|
||||
languageCodeMatch = languageCodeToLocale[locale.languageCode];
|
||||
autoDetectedLocale = languageCodeMatch;
|
||||
// check if language code is supported, if yes, return it
|
||||
if (languageSupport.contains(locale.languageCode)) {
|
||||
autoDetectedLocale = locale;
|
||||
return locale;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the first language code match or default to 'en'
|
||||
return languageCodeMatch ?? const Locale('en');
|
||||
return autoDetectedLocale ?? const Locale('en');
|
||||
}
|
||||
|
||||
Future<Locale?> getLocale({
|
||||
|
||||
@@ -87,19 +87,6 @@ void main() async {
|
||||
}
|
||||
}
|
||||
|
||||
// Future<void> whiteListLetsEncryptRootCA() async {
|
||||
// try {
|
||||
// // https://stackoverflow.com/a/71090239
|
||||
// // https://github.com/ente-io/ente/issues/2178
|
||||
// ByteData data =
|
||||
// await PlatformAssetBundle().load('assets/ca/lets-encrypt-r3.pem');
|
||||
// SecurityContext.defaultContext
|
||||
// .setTrustedCertificatesBytes(data.buffer.asUint8List());
|
||||
// } catch (e) {
|
||||
// _logger.severe("Failed to whitelist Let's Encrypt Root CA", e);
|
||||
// }
|
||||
// }
|
||||
|
||||
Future<void> _runInForeground() async {
|
||||
final savedThemeMode = _themeMode(await AdaptiveTheme.getThemeMode());
|
||||
return await _runWithLogs(() async {
|
||||
@@ -116,7 +103,7 @@ Future<void> _runInForeground() async {
|
||||
AppLock(
|
||||
builder: (args) => App(locale: locale),
|
||||
lockScreen: const LockScreen(),
|
||||
enabled: await Configuration.instance.shouldShowLockScreen(),
|
||||
enabled: await LockScreenSettings.instance.shouldShowLockScreen(),
|
||||
locale: locale,
|
||||
lightTheme: lightThemeData,
|
||||
darkTheme: darkThemeData,
|
||||
|
||||
@@ -293,10 +293,6 @@ class _SetupEnterSecretKeyPageState extends State<SetupEnterSecretKeyPage> {
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
widget.code == null
|
||||
? advanceOptionWidget()
|
||||
: const SizedBox.shrink(),
|
||||
const SizedBox(height: 12),
|
||||
Wrap(
|
||||
spacing: 12,
|
||||
alignment: WrapAlignment.start,
|
||||
@@ -343,6 +339,10 @@ class _SetupEnterSecretKeyPageState extends State<SetupEnterSecretKeyPage> {
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
widget.code == null
|
||||
? advanceOptionWidget()
|
||||
: const SizedBox.shrink(),
|
||||
const SizedBox(height: 40),
|
||||
SizedBox(
|
||||
width: 400,
|
||||
@@ -525,6 +525,7 @@ class _SetupEnterSecretKeyPageState extends State<SetupEnterSecretKeyPage> {
|
||||
}
|
||||
|
||||
Widget advanceOptionWidget() {
|
||||
final l10n = context.l10n;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 16.0),
|
||||
child: Column(
|
||||
@@ -537,9 +538,7 @@ class _SetupEnterSecretKeyPageState extends State<SetupEnterSecretKeyPage> {
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text(
|
||||
'Advanced',
|
||||
),
|
||||
Text(l10n.advanced),
|
||||
ValueListenableBuilder<bool>(
|
||||
valueListenable: showAdvancedOptions,
|
||||
builder: (context, isExpanded, child) {
|
||||
@@ -583,7 +582,7 @@ class _SetupEnterSecretKeyPageState extends State<SetupEnterSecretKeyPage> {
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
const FieldLabel("Algorithm", width: 60),
|
||||
FieldLabel(l10n.algorithm, width: 60),
|
||||
AlgorithmSelectorWidget(
|
||||
currentAlgorithm: _algorithm,
|
||||
onSelected: (newAlgorithm) async {
|
||||
@@ -597,7 +596,7 @@ class _SetupEnterSecretKeyPageState extends State<SetupEnterSecretKeyPage> {
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
const FieldLabel("Type", width: 60),
|
||||
FieldLabel(l10n.type, width: 60),
|
||||
ToptSelectorWidget(
|
||||
currentTopt: _type,
|
||||
onSelected: (newTopt) async {
|
||||
@@ -610,7 +609,7 @@ class _SetupEnterSecretKeyPageState extends State<SetupEnterSecretKeyPage> {
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
const FieldLabel("Period", width: 60),
|
||||
FieldLabel(l10n.period, width: 60),
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
keyboardType: TextInputType.number,
|
||||
@@ -639,7 +638,7 @@ class _SetupEnterSecretKeyPageState extends State<SetupEnterSecretKeyPage> {
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
const FieldLabel("Digits", width: 60),
|
||||
FieldLabel(l10n.digits, width: 60),
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
keyboardType: TextInputType.number,
|
||||
|
||||
@@ -13,6 +13,7 @@ import 'package:ente_auth/models/authenticator/auth_entity.dart';
|
||||
import 'package:ente_auth/models/authenticator/auth_key.dart';
|
||||
import 'package:ente_auth/models/authenticator/entity_result.dart';
|
||||
import 'package:ente_auth/models/authenticator/local_auth_entity.dart';
|
||||
import 'package:ente_auth/services/preference_service.dart';
|
||||
import 'package:ente_auth/store/authenticator_db.dart';
|
||||
import 'package:ente_auth/store/offline_authenticator_db.dart';
|
||||
import 'package:ente_crypto_dart/ente_crypto_dart.dart';
|
||||
@@ -194,8 +195,13 @@ class AuthenticatorService {
|
||||
final int lastSyncTime = _prefs.getInt(_lastEntitySyncTime) ?? 0;
|
||||
_logger.info("Current sync is $lastSyncTime");
|
||||
const int fetchLimit = 500;
|
||||
final List<AuthEntity> result =
|
||||
late final List<AuthEntity> result;
|
||||
late final int? epochTimeInMicroseconds;
|
||||
(result, epochTimeInMicroseconds) =
|
||||
await _gateway.getDiff(lastSyncTime, limit: fetchLimit);
|
||||
PreferenceService.instance
|
||||
.computeAndStoreTimeOffset(epochTimeInMicroseconds);
|
||||
|
||||
_logger.info("${result.length} entries fetched from remote");
|
||||
if (result.isEmpty) {
|
||||
return;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:ente_auth/core/configuration.dart';
|
||||
import 'package:ente_auth/ui/settings/lock_screen/lock_screen_password.dart';
|
||||
import 'package:ente_auth/ui/settings/lock_screen/lock_screen_pin.dart';
|
||||
import 'package:ente_auth/ui/tools/app_lock.dart';
|
||||
@@ -42,7 +41,7 @@ class LocalAuthenticationService {
|
||||
isAuthenticatingForInAppChange: true,
|
||||
);
|
||||
AppLock.of(context)!.setEnabled(
|
||||
await Configuration.instance.shouldShowLockScreen(),
|
||||
await LockScreenSettings.instance.shouldShowLockScreen(),
|
||||
);
|
||||
if (!result) {
|
||||
showToast(context, infoMessage);
|
||||
@@ -114,12 +113,13 @@ class LocalAuthenticationService {
|
||||
);
|
||||
if (result) {
|
||||
AppLock.of(context)!.setEnabled(shouldEnableLockScreen);
|
||||
await Configuration.instance
|
||||
await LockScreenSettings.instance
|
||||
.setSystemLockScreen(shouldEnableLockScreen);
|
||||
return true;
|
||||
} else {
|
||||
AppLock.of(context)!
|
||||
.setEnabled(await Configuration.instance.shouldShowLockScreen());
|
||||
AppLock.of(context)!.setEnabled(
|
||||
await LockScreenSettings.instance.shouldShowLockScreen(),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// ignore: unawaited_futures
|
||||
|
||||
@@ -18,6 +18,7 @@ class PreferenceService {
|
||||
late final SharedPreferences _prefs;
|
||||
|
||||
static const kHasShownCoachMarkKey = "has_shown_coach_mark_v2";
|
||||
static const kLocalTimeOffsetKey = "local_time_offset";
|
||||
static const kShouldShowLargeIconsKey = "should_show_large_icons";
|
||||
static const kShouldHideCodesKey = "should_hide_codes";
|
||||
static const kShouldAutoFocusOnSearchBar = "should_auto_focus_on_search_bar";
|
||||
@@ -114,4 +115,24 @@ class PreferenceService {
|
||||
return installedTimeinMillis;
|
||||
}
|
||||
}
|
||||
|
||||
// localEpochOffsetInMilliSecond returns the local epoch offset in milliseconds.
|
||||
// This is used to adjust the time for TOTP calculations when device local time is not in sync with actual time.
|
||||
int timeOffsetInMilliSeconds() {
|
||||
return _prefs.getInt(kLocalTimeOffsetKey) ?? 0;
|
||||
}
|
||||
|
||||
void computeAndStoreTimeOffset(
|
||||
int? epochTimeInMicroseconds,
|
||||
) {
|
||||
if (epochTimeInMicroseconds == null) {
|
||||
_prefs.remove(kLocalTimeOffsetKey);
|
||||
return;
|
||||
}
|
||||
int serverEpochTimeInMilliSecond = epochTimeInMicroseconds ~/ 1000;
|
||||
int localEpochTimeInMilliSecond = DateTime.now().millisecondsSinceEpoch;
|
||||
int localEpochOffset =
|
||||
serverEpochTimeInMilliSecond - localEpochTimeInMilliSecond;
|
||||
_prefs.setInt(kLocalTimeOffsetKey, localEpochOffset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ class _ChangeEmailDialogState extends State<ChangeEmailDialog> {
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = context.l10n;
|
||||
return AlertDialog(
|
||||
title: Text(l10n.enterEmailHint),
|
||||
title: Text(l10n.enterNewEmailHint),
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
|
||||
@@ -7,10 +7,12 @@ import 'package:flutter/material.dart';
|
||||
class CodeTimerProgress extends StatefulWidget {
|
||||
final int period;
|
||||
final bool isCompactMode;
|
||||
final int timeOffsetInMilliseconds;
|
||||
const CodeTimerProgress({
|
||||
super.key,
|
||||
required this.period,
|
||||
this.isCompactMode = false,
|
||||
this.timeOffsetInMilliseconds = 0,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -20,7 +22,7 @@ class CodeTimerProgress extends StatefulWidget {
|
||||
class _CodeTimerProgressState extends State<CodeTimerProgress> {
|
||||
late final Timer _timer;
|
||||
late final ValueNotifier<double> _progress;
|
||||
late final int _periodInMicros;
|
||||
late final int _periodInMilii;
|
||||
|
||||
// Reduce update frequency
|
||||
final int _updateIntervalMs =
|
||||
@@ -29,29 +31,30 @@ class _CodeTimerProgressState extends State<CodeTimerProgress> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_periodInMicros = widget.period * 1000000;
|
||||
_periodInMilii = widget.period * 1000;
|
||||
_progress = ValueNotifier<double>(0.0);
|
||||
_updateTimeRemaining(DateTime.now().microsecondsSinceEpoch);
|
||||
_updateTimeRemaining(DateTime.now().millisecondsSinceEpoch);
|
||||
|
||||
_timer = Timer.periodic(Duration(milliseconds: _updateIntervalMs), (timer) {
|
||||
final now = DateTime.now().microsecondsSinceEpoch;
|
||||
final now = DateTime.now().millisecondsSinceEpoch;
|
||||
_updateTimeRemaining(now);
|
||||
});
|
||||
}
|
||||
|
||||
void _updateTimeRemaining(int currentMicros) {
|
||||
void _updateTimeRemaining(int currentMilliSeconds) {
|
||||
// More efficient time calculation using modulo
|
||||
final elapsed = (currentMicros) % _periodInMicros;
|
||||
final timeRemaining = _periodInMicros - elapsed;
|
||||
_progress.value = timeRemaining / _periodInMicros;
|
||||
final elapsed = (currentMilliSeconds + widget.timeOffsetInMilliseconds) %
|
||||
_periodInMilii;
|
||||
final timeRemaining = _periodInMilii - elapsed;
|
||||
_progress.value = timeRemaining / _periodInMilii;
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant CodeTimerProgress oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.period != widget.period) {
|
||||
_periodInMicros = widget.period * 1000000;
|
||||
_updateTimeRemaining(DateTime.now().microsecondsSinceEpoch);
|
||||
_periodInMilii = widget.period * 1000;
|
||||
_updateTimeRemaining(DateTime.now().millisecondsSinceEpoch);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -152,6 +152,8 @@ class _CodeWidgetState extends State<CodeWidget> {
|
||||
key: ValueKey('period_${widget.code.period}'),
|
||||
period: widget.code.period,
|
||||
isCompactMode: widget.isCompactMode,
|
||||
timeOffsetInMilliseconds:
|
||||
PreferenceService.instance.timeOffsetInMilliSeconds(),
|
||||
),
|
||||
widget.isCompactMode
|
||||
? const SizedBox(height: 4)
|
||||
|
||||
@@ -319,7 +319,7 @@ class _HomePageState extends State<HomePage> {
|
||||
|
||||
Future<void> navigateToLockScreen() async {
|
||||
final bool shouldShowLockScreen =
|
||||
await Configuration.instance.shouldShowLockScreen();
|
||||
await LockScreenSettings.instance.shouldShowLockScreen();
|
||||
if (shouldShowLockScreen) {
|
||||
await AppLock.of(context)!.showLockScreen();
|
||||
} else {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import "dart:async";
|
||||
import "dart:io";
|
||||
|
||||
import "package:ente_auth/core/configuration.dart";
|
||||
import "package:ente_auth/l10n/l10n.dart";
|
||||
import "package:ente_auth/services/local_authentication_service.dart";
|
||||
import "package:ente_auth/theme/ente_theme.dart";
|
||||
@@ -31,8 +30,7 @@ class LockScreenOptions extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _LockScreenOptionsState extends State<LockScreenOptions> {
|
||||
final Configuration _configuration = Configuration.instance;
|
||||
final LockScreenSettings _lockscreenSetting = LockScreenSettings.instance;
|
||||
final LockScreenSettings _lockScreenSettings = LockScreenSettings.instance;
|
||||
late bool appLock = false;
|
||||
bool isPinEnabled = false;
|
||||
bool isPasswordEnabled = false;
|
||||
@@ -43,18 +41,18 @@ class _LockScreenOptionsState extends State<LockScreenOptions> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
hideAppContent = _lockscreenSetting.getShouldHideAppContent();
|
||||
autoLockTimeInMilliseconds = _lockscreenSetting.getAutoLockTime();
|
||||
hideAppContent = _lockScreenSettings.getShouldHideAppContent();
|
||||
autoLockTimeInMilliseconds = _lockScreenSettings.getAutoLockTime();
|
||||
_initializeSettings();
|
||||
appLock = _lockscreenSetting.getIsAppLockSet();
|
||||
appLock = _lockScreenSettings.getIsAppLockSet();
|
||||
}
|
||||
|
||||
Future<void> _initializeSettings() async {
|
||||
final bool passwordEnabled = await _lockscreenSetting.isPasswordSet();
|
||||
final bool pinEnabled = await _lockscreenSetting.isPinSet();
|
||||
final bool passwordEnabled = await _lockScreenSettings.isPasswordSet();
|
||||
final bool pinEnabled = await _lockScreenSettings.isPinSet();
|
||||
final bool shouldHideAppContent =
|
||||
_lockscreenSetting.getShouldHideAppContent();
|
||||
final bool systemLockEnabled = _configuration.shouldShowSystemLockScreen();
|
||||
_lockScreenSettings.getShouldHideAppContent();
|
||||
final bool systemLockEnabled = _lockScreenSettings.shouldShowSystemLockScreen();
|
||||
setState(() {
|
||||
isPasswordEnabled = passwordEnabled;
|
||||
isPinEnabled = pinEnabled;
|
||||
@@ -66,8 +64,8 @@ class _LockScreenOptionsState extends State<LockScreenOptions> {
|
||||
Future<void> _deviceLock() async {
|
||||
if (await LocalAuthenticationService.instance
|
||||
.isLocalAuthSupportedOnDevice()) {
|
||||
await _lockscreenSetting.removePinAndPassword();
|
||||
await _configuration.setSystemLockScreen(!isSystemLockEnabled);
|
||||
await _lockScreenSettings.removePinAndPassword();
|
||||
await _lockScreenSettings.setSystemLockScreen(!isSystemLockEnabled);
|
||||
} else {
|
||||
await showDialogWidget(
|
||||
context: context,
|
||||
@@ -96,10 +94,10 @@ class _LockScreenOptionsState extends State<LockScreenOptions> {
|
||||
);
|
||||
|
||||
if (result) {
|
||||
await _configuration.setSystemLockScreen(false);
|
||||
await _lockscreenSetting.setAppLockEnabled(true);
|
||||
await _lockScreenSettings.setSystemLockScreen(false);
|
||||
await _lockScreenSettings.setAppLockEnabled(true);
|
||||
setState(() {
|
||||
appLock = _lockscreenSetting.getIsAppLockSet();
|
||||
appLock = _lockScreenSettings.getIsAppLockSet();
|
||||
});
|
||||
}
|
||||
await _initializeSettings();
|
||||
@@ -114,9 +112,9 @@ class _LockScreenOptionsState extends State<LockScreenOptions> {
|
||||
),
|
||||
);
|
||||
if (result) {
|
||||
await _configuration.setSystemLockScreen(false);
|
||||
await _lockScreenSettings.setSystemLockScreen(false);
|
||||
setState(() {
|
||||
appLock = _lockscreenSetting.getIsAppLockSet();
|
||||
appLock = _lockScreenSettings.getIsAppLockSet();
|
||||
});
|
||||
}
|
||||
await _initializeSettings();
|
||||
@@ -126,17 +124,17 @@ class _LockScreenOptionsState extends State<LockScreenOptions> {
|
||||
AppLock.of(context)!.setEnabled(!appLock);
|
||||
if (await LocalAuthenticationService.instance
|
||||
.isLocalAuthSupportedOnDevice()) {
|
||||
await _configuration.setSystemLockScreen(!appLock);
|
||||
await _lockscreenSetting.setAppLockEnabled(!appLock);
|
||||
await _lockScreenSettings.setSystemLockScreen(!appLock);
|
||||
await _lockScreenSettings.setAppLockEnabled(!appLock);
|
||||
} else {
|
||||
await _configuration.setSystemLockScreen(false);
|
||||
await _lockscreenSetting.setAppLockEnabled(false);
|
||||
await _lockScreenSettings.setSystemLockScreen(false);
|
||||
await _lockScreenSettings.setAppLockEnabled(false);
|
||||
}
|
||||
await _lockscreenSetting.removePinAndPassword();
|
||||
await _lockScreenSettings.removePinAndPassword();
|
||||
if (PlatformUtil.isMobile()) {
|
||||
await _lockscreenSetting.setHideAppContent(!appLock);
|
||||
await _lockScreenSettings.setHideAppContent(!appLock);
|
||||
setState(() {
|
||||
hideAppContent = _lockscreenSetting.getShouldHideAppContent();
|
||||
hideAppContent = _lockScreenSettings.getShouldHideAppContent();
|
||||
});
|
||||
}
|
||||
await _initializeSettings();
|
||||
@@ -152,7 +150,7 @@ class _LockScreenOptionsState extends State<LockScreenOptions> {
|
||||
).then(
|
||||
(value) {
|
||||
setState(() {
|
||||
autoLockTimeInMilliseconds = _lockscreenSetting.getAutoLockTime();
|
||||
autoLockTimeInMilliseconds = _lockScreenSettings.getAutoLockTime();
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -162,7 +160,7 @@ class _LockScreenOptionsState extends State<LockScreenOptions> {
|
||||
setState(() {
|
||||
hideAppContent = !hideAppContent;
|
||||
});
|
||||
await _lockscreenSetting.setHideAppContent(hideAppContent);
|
||||
await _lockScreenSettings.setHideAppContent(hideAppContent);
|
||||
}
|
||||
|
||||
String _formatTime(Duration duration) {
|
||||
|
||||
@@ -166,7 +166,7 @@ class _SecuritySectionWidgetState extends State<SecuritySectionWidget> {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (await Configuration.instance.shouldShowLockScreen()) {
|
||||
if (await LockScreenSettings.instance.shouldShowLockScreen()) {
|
||||
final bool result = await requestAuthentication(
|
||||
context,
|
||||
context.l10n.authToChangeLockscreenSetting,
|
||||
|
||||
@@ -3,6 +3,8 @@ import "dart:io";
|
||||
import "dart:typed_data";
|
||||
|
||||
import "package:ente_auth/core/configuration.dart";
|
||||
import "package:ente_auth/core/event_bus.dart";
|
||||
import "package:ente_auth/events/signed_out_event.dart";
|
||||
import "package:ente_auth/utils/platform_util.dart";
|
||||
import "package:ente_crypto_dart/ente_crypto_dart.dart";
|
||||
import "package:flutter/material.dart";
|
||||
@@ -26,6 +28,7 @@ class LockScreenSettings {
|
||||
static const keyHasMigratedLockScreenChanges =
|
||||
"ls_has_migrated_lock_screen_changes";
|
||||
static const keyShowOfflineModeWarning = "ls_show_offline_mode_warning";
|
||||
static const keyShouldShowLockScreen = "should_show_lock_screen";
|
||||
static const String kIsLightMode = "is_light_mode";
|
||||
|
||||
final List<Duration> autoLockDurations = const [
|
||||
@@ -52,6 +55,10 @@ class LockScreenSettings {
|
||||
await runLockScreenChangesMigration();
|
||||
|
||||
await _clearLsDataInKeychainIfFreshInstall();
|
||||
|
||||
Bus.instance.on<SignedOutEvent>().listen((event) {
|
||||
removePinAndPassword();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> setOfflineModeWarningStatus(bool value) async {
|
||||
@@ -69,8 +76,7 @@ class LockScreenSettings {
|
||||
|
||||
final bool passwordEnabled = await isPasswordSet();
|
||||
final bool pinEnabled = await isPinSet();
|
||||
final bool systemLockEnabled =
|
||||
Configuration.instance.shouldShowSystemLockScreen();
|
||||
final bool systemLockEnabled = shouldShowSystemLockScreen();
|
||||
|
||||
if (passwordEnabled || pinEnabled || systemLockEnabled) {
|
||||
await setAppLockEnabled(true);
|
||||
@@ -214,6 +220,24 @@ class LockScreenSettings {
|
||||
return await _secureStorage.containsKey(key: password);
|
||||
}
|
||||
|
||||
Future<bool> shouldShowLockScreen() async {
|
||||
final bool isPin = await isPinSet();
|
||||
final bool isPass = await isPasswordSet();
|
||||
return isPin || isPass || shouldShowSystemLockScreen();
|
||||
}
|
||||
|
||||
bool shouldShowSystemLockScreen() {
|
||||
if (_preferences.containsKey(keyShouldShowLockScreen)) {
|
||||
return _preferences.getBool(keyShouldShowLockScreen)!;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setSystemLockScreen(bool value) {
|
||||
return _preferences.setBool(keyShouldShowLockScreen, value);
|
||||
}
|
||||
|
||||
// If the app was uninstalled (without logging out if it was used with
|
||||
// backups), keychain items of the app persist in the keychain. To avoid using
|
||||
// old keychain items, we delete them on reinstall.
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import 'package:ente_auth/models/code.dart';
|
||||
import 'package:ente_auth/services/preference_service.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:otp/otp.dart' as otp;
|
||||
import 'package:steam_totp/steam_totp.dart';
|
||||
|
||||
int millisecondsSinceEpoch() {
|
||||
return DateTime.now().millisecondsSinceEpoch +
|
||||
PreferenceService.instance.timeOffsetInMilliSeconds();
|
||||
}
|
||||
|
||||
String getOTP(Code code) {
|
||||
if (code.type == Type.steam || code.issuer.toLowerCase() == 'steam') {
|
||||
return _getSteamCode(code);
|
||||
@@ -12,7 +18,7 @@ String getOTP(Code code) {
|
||||
}
|
||||
return otp.OTP.generateTOTPCodeString(
|
||||
getSanitizedSecret(code.secret),
|
||||
DateTime.now().millisecondsSinceEpoch,
|
||||
millisecondsSinceEpoch(),
|
||||
length: code.digits,
|
||||
interval: code.period,
|
||||
algorithm: _getAlgorithm(code),
|
||||
@@ -34,7 +40,7 @@ String _getSteamCode(Code code, [bool isNext = false]) {
|
||||
final SteamTOTP steamtotp = SteamTOTP(secret: code.secret);
|
||||
|
||||
return steamtotp.generate(
|
||||
DateTime.now().millisecondsSinceEpoch ~/ 1000 + (isNext ? code.period : 0),
|
||||
millisecondsSinceEpoch() ~/ 1000 + (isNext ? code.period : 0),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -44,7 +50,7 @@ String getNextTotp(Code code) {
|
||||
}
|
||||
return otp.OTP.generateTOTPCodeString(
|
||||
getSanitizedSecret(code.secret),
|
||||
DateTime.now().millisecondsSinceEpoch + code.period * 1000,
|
||||
millisecondsSinceEpoch() + code.period * 1000,
|
||||
length: code.digits,
|
||||
interval: code.period,
|
||||
algorithm: _getAlgorithm(code),
|
||||
@@ -56,9 +62,7 @@ String getNextTotp(Code code) {
|
||||
// It returns the start time and a list of future codes.
|
||||
(int, List<String>) generateFutureTotpCodes(Code code, int count) {
|
||||
final int startTime =
|
||||
((DateTime.now().millisecondsSinceEpoch ~/ 1000) ~/ code.period) *
|
||||
code.period *
|
||||
1000;
|
||||
((millisecondsSinceEpoch() ~/ 1000) ~/ code.period) * code.period * 1000;
|
||||
final String secret = getSanitizedSecret(code.secret);
|
||||
final List<String> codes = [];
|
||||
if (code.type == Type.steam || code.issuer.toLowerCase() == 'steam') {
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<releases>
|
||||
<release version="4.4.0" date="2025-05-31" />
|
||||
<release version="4.3.8" date="2025-05-20" />
|
||||
<release version="4.2.4" date="2025-01-11" />
|
||||
<release version="4.0.3" date="2024-10-08" />
|
||||
</releases>
|
||||
@@ -33,4 +35,4 @@
|
||||
<color type="primary" scheme_preference="light">#ffffff</color>
|
||||
<color type="primary" scheme_preference="dark">#000000</color>
|
||||
</branding>
|
||||
</component>
|
||||
</component>
|
||||
|
||||
@@ -34,11 +34,11 @@ PODS:
|
||||
- FlutterMacOS
|
||||
- screen_retriever (0.0.1):
|
||||
- FlutterMacOS
|
||||
- Sentry/HybridSDK (8.36.0)
|
||||
- sentry_flutter (8.9.0):
|
||||
- Sentry/HybridSDK (8.46.0)
|
||||
- sentry_flutter (8.14.2):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- Sentry/HybridSDK (= 8.36.0)
|
||||
- Sentry/HybridSDK (= 8.46.0)
|
||||
- share_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
@@ -157,33 +157,33 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/window_manager/macos
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
app_links: 10e0a0ab602ffaf34d142cd4862f29d34b303b2a
|
||||
connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db
|
||||
cupertino_http: 947a233f40cfea55167a49f2facc18434ea117ba
|
||||
device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f
|
||||
file_saver: 44e6fbf666677faf097302460e214e977fdd977b
|
||||
flutter_inappwebview_macos: bdf207b8f4ebd58e86ae06cd96b147de99a67c9b
|
||||
flutter_local_authentication: 85674893931e1c9cfa7c9e4f5973cb8c56b018b0
|
||||
flutter_local_notifications: 3805ca215b2fb7f397d78b66db91f6a747af52e4
|
||||
flutter_secure_storage_macos: 59459653abe1adb92abbc8ea747d79f8d19866c9
|
||||
app_links: 9028728e32c83a0831d9db8cf91c526d16cc5468
|
||||
connectivity_plus: 3f6c9057f4cd64198dc826edfb0542892f825343
|
||||
cupertino_http: 94ac07f5ff090b8effa6c5e2c47871d48ab7c86c
|
||||
device_info_plus: b0fafc687fb901e2af612763340f1b0d4352f8e5
|
||||
file_saver: e35bd97de451dde55ff8c38862ed7ad0f3418d0f
|
||||
flutter_inappwebview_macos: c2d68649f9f8f1831bfcd98d73fd6256366d9d1d
|
||||
flutter_local_authentication: 2f9a2682f498abcc12d7e9729b5007a947170fdc
|
||||
flutter_local_notifications: 453432cd6399a07d072885bc7828fb2307868856
|
||||
flutter_secure_storage_macos: b2d62a774c23b060f0b99d0173b0b36abb4a8632
|
||||
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||
local_auth_darwin: 66e40372f1c29f383a314c738c7446e2f7fdadc3
|
||||
objective_c: e5f8194456e8fc943e034d1af00510a1bc29c067
|
||||
local_auth_darwin: 553ce4f9b16d3fdfeafce9cf042e7c9f77c1c391
|
||||
objective_c: ec13431e45ba099cb734eb2829a5c1cd37986cba
|
||||
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
|
||||
package_info_plus: fa739dd842b393193c5ca93c26798dff6e3d0e0c
|
||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||
screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38
|
||||
Sentry: f8374b5415bc38dfb5645941b3ae31230fbeae57
|
||||
sentry_flutter: 0eb93e5279eb41e2392212afe1ccd2fecb4f8cbe
|
||||
share_plus: 36537c04ce0c3e3f5bd297ce4318b6d5ee5fd6cf
|
||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||
sodium_libs: d39bd76697736cb11ce4a0be73b9b4bc64466d6f
|
||||
sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec
|
||||
package_info_plus: a8a591e70e87ce97ce5d21b2594f69cea9e0312f
|
||||
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
||||
screen_retriever: 4f97c103641aab8ce183fa5af3b87029df167936
|
||||
Sentry: da60d980b197a46db0b35ea12cb8f39af48d8854
|
||||
sentry_flutter: 27892878729f42701297c628eb90e7c6529f3684
|
||||
share_plus: 11c7b7fa7020465584eca3ff6392c5bc1e399d6e
|
||||
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
|
||||
sodium_libs: b9459e5bfc1185349f43472e79fc5d8e526b2bda
|
||||
sqflite: c35dad70033b8862124f8337cc994a809fcd9fa3
|
||||
sqlite3: 0bb0e6389d824e40296f531b858a2a0b71c0d2fb
|
||||
sqlite3_flutter_libs: 5ca46c1a04eddfbeeb5b16566164aa7ad1616e7b
|
||||
tray_manager: 9064e219c56d75c476e46b9a21182087930baf90
|
||||
url_launcher_macos: c82c93949963e55b228a30115bd219499a6fe404
|
||||
window_manager: 3a1844359a6295ab1e47659b1a777e36773cd6e8
|
||||
sqlite3_flutter_libs: 03311aede9d32fb2d24e32bebb8cd01c3b2e6239
|
||||
tray_manager: a104b5c81b578d83f3c3d0f40a997c8b10810166
|
||||
url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673
|
||||
window_manager: 1d01fa7ac65a6e6f83b965471b1a7fdd3f06166c
|
||||
|
||||
PODFILE CHECKSUM: 6ff827273ace187339fc5d3684072a26ad85c298
|
||||
|
||||
|
||||
@@ -675,14 +675,13 @@ packages:
|
||||
source: hosted
|
||||
version: "9.2.2"
|
||||
flutter_secure_storage_linux:
|
||||
dependency: "direct overridden"
|
||||
dependency: transitive
|
||||
description:
|
||||
path: flutter_secure_storage_linux
|
||||
ref: develop
|
||||
resolved-ref: "5a5692b609b3886cdd49b2ed06b9c079ecdff996"
|
||||
url: "https://github.com/mogol/flutter_secure_storage.git"
|
||||
source: git
|
||||
version: "1.2.1"
|
||||
name: flutter_secure_storage_linux
|
||||
sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.3"
|
||||
flutter_secure_storage_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
name: ente_auth
|
||||
description: ente two-factor authenticator
|
||||
version: 4.3.5+436
|
||||
version: 4.4.0+440
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
@@ -107,12 +107,6 @@ dependencies:
|
||||
window_manager: ^0.4.2
|
||||
xdg_directories: ^1.0.4
|
||||
|
||||
dependency_overrides:
|
||||
flutter_secure_storage_linux:
|
||||
git:
|
||||
url: https://github.com/mogol/flutter_secure_storage.git
|
||||
ref: develop
|
||||
path: flutter_secure_storage_linux
|
||||
dev_dependencies:
|
||||
build_runner: ^2.1.11
|
||||
flutter_test:
|
||||
|
||||
70
auth/scripts/release_tag.sh
Executable file
@@ -0,0 +1,70 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Function to display usage
|
||||
usage() {
|
||||
echo "Usage: $0 tag"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Ensure a tag was provided
|
||||
[[ $# -eq 0 ]] && usage
|
||||
|
||||
# Exit immediately if a command exits with a non-zero status
|
||||
set -e
|
||||
|
||||
# Go to the project root directory
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
# Get the tag from the command line argument
|
||||
TAG=$1
|
||||
|
||||
# Define the appdata file path - use absolute path to avoid directory navigation issues
|
||||
PROJECT_ROOT=$(pwd)
|
||||
APPDATA_FILE="${PROJECT_ROOT}/linux/packaging/enteauth.appdata.xml"
|
||||
|
||||
# Get the version from the pubspec.yaml file and cut everything after the +
|
||||
VERSION=$(grep "^version:" pubspec.yaml | awk '{ print $2 }' | cut -d '+' -f 1)
|
||||
|
||||
PREFIX="auth-v"
|
||||
|
||||
# Ensure the tag has the correct prefix
|
||||
if [[ $TAG != $PREFIX* ]]; then
|
||||
echo "Invalid tag. tags must start with '$PREFIX'."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Ensure the tag version is in the pubspec.yaml file
|
||||
if [[ $TAG != *$VERSION ]]; then
|
||||
echo "Invalid tag."
|
||||
echo "The version $VERSION in pubspec doesn't match the version in tag $TAG"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Extract version number from the tag (remove prefix)
|
||||
TAG_VERSION=${TAG#$PREFIX}
|
||||
|
||||
# Check if this version is already in the releases section of the appdata.xml file
|
||||
if ! grep -q "<release version=\"$TAG_VERSION\"" "$APPDATA_FILE"; then
|
||||
echo "Adding release entry for version $TAG_VERSION to appdata.xml"
|
||||
|
||||
# Get today's date in YYYY-MM-DD format
|
||||
TODAY=$(date +%Y-%m-%d)
|
||||
|
||||
# Use a more reliable approach with awk instead of sed for cross-platform compatibility
|
||||
echo "Creating temporary file with updated content..."
|
||||
awk '/<releases>/{print $0; print " <release version=\"'"$TAG_VERSION"'\" date=\"'"$TODAY"'\" />"; next}1' "$APPDATA_FILE" > "${APPDATA_FILE}.tmp"
|
||||
mv "${APPDATA_FILE}.tmp" "$APPDATA_FILE"
|
||||
|
||||
echo "Added release entry for version $TAG_VERSION with date $TODAY"
|
||||
|
||||
# Stage and commit the updated appdata.xml file
|
||||
git add "$APPDATA_FILE"
|
||||
git commit -m "Add release $TAG_VERSION to appdata.xml"
|
||||
echo "Committed appdata.xml changes for version $TAG_VERSION"
|
||||
fi
|
||||
|
||||
# If all checks pass, create the tag
|
||||
git tag $TAG
|
||||
echo "Tag $TAG created."
|
||||
|
||||
exit 0
|
||||
@@ -83,13 +83,14 @@ func (c *Client) VerifySRPSession(
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func (c *Client) SendEmailOTP(
|
||||
func (c *Client) SendLoginOTP(
|
||||
ctx context.Context,
|
||||
email string,
|
||||
) error {
|
||||
var res AuthorizationResponse
|
||||
payload := map[string]interface{}{
|
||||
"email": email,
|
||||
"email": email,
|
||||
"purpose": "login",
|
||||
}
|
||||
r, err := c.restClient.R().
|
||||
SetContext(ctx).
|
||||
|
||||
@@ -167,7 +167,7 @@ func (c *ClICtrl) verifyPassKey(ctx context.Context, authResp *api.Authorization
|
||||
}
|
||||
|
||||
func (c *ClICtrl) validateEmail(ctx context.Context, email string) (*api.AuthorizationResponse, error) {
|
||||
err := c.Client.SendEmailOTP(ctx, email)
|
||||
err := c.Client.SendLoginOTP(ctx, email)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -113,6 +113,11 @@ jobs:
|
||||
APPLE_APP_SPECIFIC_PASSWORD:
|
||||
${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
# Windows Azure Trusted Signing related values
|
||||
# https://www.electron.build/code-signing-win#using-azure-trusted-signing-beta
|
||||
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
|
||||
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
|
||||
AZURE_CLIENT_SECRET: ${{ secrets.AZURE_CLIENT_SECRET }}
|
||||
# Default is "draft", but since our nightly builds update
|
||||
# existing pre-releases, set this to "prerelease".
|
||||
EP_PRE_RELEASE: true
|
||||
|
||||
@@ -1,9 +1,19 @@
|
||||
# CHANGELOG
|
||||
|
||||
## v1.7.13 (Unreleased)
|
||||
## v1.7.14 (Unreleased)
|
||||
|
||||
- Increase file size limit to 10 GB.
|
||||
- .
|
||||
|
||||
## v1.7.13
|
||||
|
||||
- Generate streams for videos (beta)
|
||||
|
||||
> Streamable videos can be enabled in Preferences. For more details, see the
|
||||
> [video streaming FAQ](https://help.ente.io/photos/faq/video-streaming).
|
||||
|
||||
- Support Turkish translations.
|
||||
|
||||
## v1.7.12
|
||||
|
||||
- Improved video player with streaming support (for already processed videos).
|
||||
|
||||
@@ -14,6 +14,11 @@ win:
|
||||
target:
|
||||
- target: nsis
|
||||
arch: [x64, arm64]
|
||||
azureSignOptions:
|
||||
publisherName: ENTE TECHNOLOGIES, INC.
|
||||
endpoint: https://eus.codesigning.azure.net/
|
||||
certificateProfileName: EnteTrustCertProfile
|
||||
codeSigningAccountName: EnteTechnologiesInc
|
||||
nsis:
|
||||
deleteAppDataOnUninstall: true
|
||||
linux:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ente",
|
||||
"version": "1.7.13-beta",
|
||||
"version": "1.7.14-beta",
|
||||
"private": true,
|
||||
"description": "Desktop client for Ente Photos",
|
||||
"repository": "github:ente-io/photos-desktop",
|
||||
@@ -33,30 +33,31 @@
|
||||
"compare-versions": "^6.1.1",
|
||||
"electron-log": "^5.4.0",
|
||||
"electron-store": "^8.2.0",
|
||||
"electron-updater": "^6.6.3",
|
||||
"electron-updater": "^6.6.5",
|
||||
"ffmpeg-static": "^5.2.0",
|
||||
"lru-cache": "^11.1.0",
|
||||
"next-electron-server": "^1.0.0",
|
||||
"node-stream-zip": "^1.15.0",
|
||||
"onnxruntime-node": "^1.20.1"
|
||||
"onnxruntime-node": "^1.20.1",
|
||||
"zod": "^3.25.51"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.25.1",
|
||||
"@tsconfig/node22": "^22.0.1",
|
||||
"@eslint/js": "^9.28.0",
|
||||
"@tsconfig/node22": "^22.0.2",
|
||||
"@types/auto-launch": "^5.0.5",
|
||||
"@types/ffmpeg-static": "^3.0.3",
|
||||
"ajv": "^8.17.1",
|
||||
"concurrently": "^9.1.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"electron": "^36.1.0",
|
||||
"electron-builder": "^26.0.14",
|
||||
"electron": "^36.4.0",
|
||||
"electron-builder": "^26.0.17",
|
||||
"eslint": "^9",
|
||||
"prettier": "3.5.3",
|
||||
"prettier-plugin-organize-imports": "^4.1.0",
|
||||
"prettier-plugin-packagejson": "^2.5.10",
|
||||
"shx": "^0.3.4",
|
||||
"prettier-plugin-packagejson": "^2.5.15",
|
||||
"shx": "^0.4.0",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.31.1"
|
||||
"typescript-eslint": "^8.33.1"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22",
|
||||
"productName": "ente"
|
||||
|
||||
@@ -78,6 +78,14 @@ export const allowWindowClose = (): void => {
|
||||
* We call this at the end of this file.
|
||||
*/
|
||||
const main = () => {
|
||||
// Workaround for Electron 36 not launching on some Linux distros. Remove
|
||||
// once fixed or otherwise mitigated upstream.
|
||||
//
|
||||
// https://github.com/electron/electron/issues/46538#issuecomment-2808806722
|
||||
if (process.platform == "linux") {
|
||||
app.commandLine.appendSwitch("gtk-version", "3");
|
||||
}
|
||||
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
if (!gotTheLock) {
|
||||
app.quit();
|
||||
|
||||
@@ -16,6 +16,7 @@ import type {
|
||||
FFmpegCommand,
|
||||
FolderWatch,
|
||||
PendingUploads,
|
||||
UtilityProcessType,
|
||||
ZipItem,
|
||||
} from "../types/ipc";
|
||||
import { logToDisk } from "./log";
|
||||
@@ -31,7 +32,7 @@ import {
|
||||
openLogDirectory,
|
||||
selectDirectory,
|
||||
} from "./services/dir";
|
||||
import { ffmpegExec } from "./services/ffmpeg";
|
||||
import { ffmpegDetermineVideoDuration, ffmpegExec } from "./services/ffmpeg";
|
||||
import {
|
||||
fsExists,
|
||||
fsFindFiles,
|
||||
@@ -47,11 +48,10 @@ import {
|
||||
} from "./services/fs";
|
||||
import { convertToJPEG, generateImageThumbnail } from "./services/image";
|
||||
import { logout } from "./services/logout";
|
||||
import { createMLWorker } from "./services/ml";
|
||||
import {
|
||||
lastShownChangelogVersion,
|
||||
masterKeyB64,
|
||||
saveMasterKeyB64,
|
||||
masterKeyFromSafeStorage,
|
||||
saveMasterKeyInSafeStorage,
|
||||
setLastShownChangelogVersion,
|
||||
} from "./services/store";
|
||||
import {
|
||||
@@ -70,6 +70,7 @@ import {
|
||||
watchUpdateIgnoredFiles,
|
||||
watchUpdateSyncedFiles,
|
||||
} from "./services/watch";
|
||||
import { triggerCreateUtilityProcess } from "./services/workers";
|
||||
|
||||
/**
|
||||
* Listen for IPC events sent/invoked by the renderer process, and route them to
|
||||
@@ -107,10 +108,12 @@ export const attachIPCHandlers = () => {
|
||||
|
||||
ipcMain.handle("selectDirectory", () => selectDirectory());
|
||||
|
||||
ipcMain.handle("masterKeyB64", () => masterKeyB64());
|
||||
ipcMain.handle("masterKeyFromSafeStorage", () =>
|
||||
masterKeyFromSafeStorage(),
|
||||
);
|
||||
|
||||
ipcMain.handle("saveMasterKeyB64", (_, masterKeyB64: string) =>
|
||||
saveMasterKeyB64(masterKeyB64),
|
||||
ipcMain.handle("saveMasterKeyInSafeStorage", (_, masterKey: string) =>
|
||||
saveMasterKeyInSafeStorage(masterKey),
|
||||
);
|
||||
|
||||
ipcMain.handle("lastShownChangelogVersion", () =>
|
||||
@@ -181,10 +184,10 @@ export const attachIPCHandlers = () => {
|
||||
"generateImageThumbnail",
|
||||
(
|
||||
_,
|
||||
dataOrPathOrZipItem: Uint8Array | string | ZipItem,
|
||||
pathOrZipItem: string | ZipItem,
|
||||
maxDimension: number,
|
||||
maxSize: number,
|
||||
) => generateImageThumbnail(dataOrPathOrZipItem, maxDimension, maxSize),
|
||||
) => generateImageThumbnail(pathOrZipItem, maxDimension, maxSize),
|
||||
);
|
||||
|
||||
ipcMain.handle(
|
||||
@@ -192,9 +195,15 @@ export const attachIPCHandlers = () => {
|
||||
(
|
||||
_,
|
||||
command: FFmpegCommand,
|
||||
dataOrPathOrZipItem: Uint8Array | string | ZipItem,
|
||||
pathOrZipItem: string | ZipItem,
|
||||
outputFileExtension: string,
|
||||
) => ffmpegExec(command, dataOrPathOrZipItem, outputFileExtension),
|
||||
) => ffmpegExec(command, pathOrZipItem, outputFileExtension),
|
||||
);
|
||||
|
||||
ipcMain.handle(
|
||||
"ffmpegDetermineVideoDuration",
|
||||
(_, pathOrZipItem: string | ZipItem) =>
|
||||
ffmpegDetermineVideoDuration(pathOrZipItem),
|
||||
);
|
||||
|
||||
// - Upload
|
||||
@@ -233,9 +242,11 @@ export const attachIPCHandlers = () => {
|
||||
* the main window to do their thing.
|
||||
*/
|
||||
export const attachMainWindowIPCHandlers = (mainWindow: BrowserWindow) => {
|
||||
// - ML
|
||||
// - Utility processes
|
||||
|
||||
ipcMain.on("createMLWorker", () => createMLWorker(mainWindow));
|
||||
ipcMain.on("triggerCreateUtilityProcess", (_, type: UtilityProcessType) =>
|
||||
triggerCreateUtilityProcess(type, mainWindow),
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
59
desktop/src/main/log-worker.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* A object that behaves similar to the default export of "./log", except this
|
||||
* can be used from within a utility process.
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* We cannot directly do
|
||||
*
|
||||
* import log from "../log";
|
||||
*
|
||||
* because that requires the Electron APIs that are not available to a utility
|
||||
* process (See: [Note: Using Electron APIs in UtilityProcess]).
|
||||
*
|
||||
* But even if that were to work, logging will still be problematic since we'd
|
||||
* try opening the log file from two different Node.js processes (this one, and
|
||||
* the main one), and I didn't find any indication in the electron-log
|
||||
* repository that the log file's integrity would be maintained in such cases.
|
||||
*
|
||||
* So instead we provide this proxy log object that uses the
|
||||
* `process.parentPort` to transport the logs over to the main process, where
|
||||
* the {@link processUtilityProcessLogMessage} function in the main process is
|
||||
* expected to handle these (sending them to the actual log).
|
||||
*/
|
||||
export default {
|
||||
error: (s: string, e?: unknown) =>
|
||||
mainProcess("log.errorString", messageWithError(s, e)),
|
||||
warn: (s: string, e?: unknown) =>
|
||||
mainProcess("log.warnString", messageWithError(s, e)),
|
||||
info: (...ms: unknown[]) => mainProcess("log.info", ms),
|
||||
/**
|
||||
* Unlike the real {@link log.debug}, this is (a) eagerly evaluated, and (b)
|
||||
* accepts only strings.
|
||||
*/
|
||||
debugString: (s: string) => mainProcess("log.debugString", s),
|
||||
};
|
||||
|
||||
/**
|
||||
* Send a message to the main process using a barebones RPC protocol.
|
||||
*/
|
||||
const mainProcess = (method: string, param: unknown) =>
|
||||
process.parentPort.postMessage({ method, p: param });
|
||||
|
||||
// Duplicated verbatim from ./log.ts
|
||||
const messageWithError = (message: string, e?: unknown) => {
|
||||
if (!e) return message;
|
||||
|
||||
let es: string;
|
||||
if (e instanceof Error) {
|
||||
// In practice, we expect ourselves to be called with Error objects, so
|
||||
// this is the happy path so to say.
|
||||
es = [`${e.name}: ${e.message}`, e.stack].filter((x) => x).join("\n");
|
||||
} else {
|
||||
// For the rest rare cases, use the default string serialization of e.
|
||||
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
||||
es = String(e);
|
||||
}
|
||||
|
||||
return `${message}: ${es}`;
|
||||
};
|
||||
@@ -83,6 +83,56 @@ const logDebug = (param: () => unknown) => {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle log messages posted from the utility process in the main process.
|
||||
*
|
||||
* See: [Note: Using Electron APIs in UtilityProcess]
|
||||
*
|
||||
* @param message The arbitrary message that was received as an argument to the
|
||||
* "message" event invoked on a {@link UtilityProcess}.
|
||||
*
|
||||
* @returns true if the message was recognized and handled, and false otherwise.
|
||||
*/
|
||||
export const processUtilityProcessLogMessage = (
|
||||
logTag: string,
|
||||
message: unknown,
|
||||
) => {
|
||||
const m = message; /* shorter alias */
|
||||
if (m && typeof m == "object" && "method" in m && "p" in m) {
|
||||
const p = m.p;
|
||||
switch (m.method) {
|
||||
case "log.errorString":
|
||||
if (typeof p == "string") {
|
||||
logError(`${logTag} ${p}`);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case "log.warnString":
|
||||
if (typeof p == "string") {
|
||||
logWarn(`${logTag} ${p}`);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case "log.info":
|
||||
if (Array.isArray(p)) {
|
||||
// Need to cast from any[] to unknown[]
|
||||
logInfo(logTag, ...(p as unknown[]));
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
case "log.debugString":
|
||||
if (typeof p == "string") {
|
||||
logDebug(() => `${logTag} ${p}`);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Ente's logger.
|
||||
*
|
||||
|
||||
1133
desktop/src/main/services/ffmpeg-worker.ts
Normal file
@@ -1,678 +1,77 @@
|
||||
import pathToFfmpeg from "ffmpeg-static";
|
||||
import { randomBytes } from "node:crypto";
|
||||
/**
|
||||
* @file A bridge to the ffmpeg utility process. This code runs in the main
|
||||
* process.
|
||||
*/
|
||||
|
||||
import { wrap } from "comlink";
|
||||
import fs from "node:fs/promises";
|
||||
import path, { basename } from "node:path";
|
||||
import type { FFmpegCommand, ZipItem } from "../../types/ipc";
|
||||
import log from "../log";
|
||||
import { execAsync } from "../utils/electron";
|
||||
import {
|
||||
deleteTempFileIgnoringErrors,
|
||||
makeFileForDataOrStreamOrPathOrZipItem,
|
||||
makeFileForStreamOrPathOrZipItem,
|
||||
makeTempFilePath,
|
||||
} from "../utils/temp";
|
||||
|
||||
/* Ditto in the web app's code (used by the Wasm FFmpeg invocation). */
|
||||
const ffmpegPathPlaceholder = "FFMPEG";
|
||||
const inputPathPlaceholder = "INPUT";
|
||||
const outputPathPlaceholder = "OUTPUT";
|
||||
import type { FFmpegUtilityProcess } from "./ffmpeg-worker";
|
||||
import { ffmpegUtilityProcessEndpoint } from "./workers";
|
||||
|
||||
/**
|
||||
* Run a FFmpeg command
|
||||
*
|
||||
* [Note: FFmpeg in Electron]
|
||||
*
|
||||
* There is a Wasm build of FFmpeg, but that is currently 10-20 times slower
|
||||
* that the native build. That is slow enough to be unusable for our purposes.
|
||||
* https://ffmpegwasm.netlify.app/docs/performance
|
||||
*
|
||||
* So the alternative is to bundle a FFmpeg executable binary with our app. e.g.
|
||||
*
|
||||
* yarn add fluent-ffmpeg ffmpeg-static ffprobe-static
|
||||
*
|
||||
* (we only use ffmpeg-static, the rest are mentioned for completeness' sake).
|
||||
*
|
||||
* Interestingly, Electron already bundles an binary FFmpeg library (it comes
|
||||
* from the ffmpeg fork maintained by Chromium).
|
||||
* https://chromium.googlesource.com/chromium/third_party/ffmpeg
|
||||
* https://stackoverflow.com/questions/53963672/what-version-of-ffmpeg-is-bundled-inside-electron
|
||||
*
|
||||
* This can be found in (e.g. on macOS) at
|
||||
*
|
||||
* $ file ente.app/Contents/Frameworks/Electron\ Framework.framework/Versions/Current/Libraries/libffmpeg.dylib
|
||||
* .../libffmpeg.dylib: Mach-O 64-bit dynamically linked shared library arm64
|
||||
*
|
||||
* But I'm not sure if our code is supposed to be able to use it, and how.
|
||||
* Return a handle to the ffmpeg utility process, starting it if needed.
|
||||
*/
|
||||
export const ffmpegUtilityProcess = () =>
|
||||
ffmpegUtilityProcessEndpoint().then((port) =>
|
||||
wrap<FFmpegUtilityProcess>(port),
|
||||
);
|
||||
|
||||
/**
|
||||
* Implement the IPC "ffmpegExec" contract, writing the input and output to
|
||||
* temporary files as needed, and then forward to the {@link ffmpegExec} running
|
||||
* in the utility process.
|
||||
*/
|
||||
export const ffmpegExec = async (
|
||||
command: FFmpegCommand,
|
||||
dataOrPathOrZipItem: Uint8Array | string | ZipItem,
|
||||
pathOrZipItem: string | ZipItem,
|
||||
outputFileExtension: string,
|
||||
): Promise<Uint8Array> => {
|
||||
): Promise<Uint8Array> =>
|
||||
withInputFile(pathOrZipItem, async (worker, inputFilePath) => {
|
||||
const outputFilePath = await makeTempFilePath(outputFileExtension);
|
||||
try {
|
||||
await worker.ffmpegExec(command, inputFilePath, outputFilePath);
|
||||
return await fs.readFile(outputFilePath);
|
||||
} finally {
|
||||
await deleteTempFileIgnoringErrors(outputFilePath);
|
||||
}
|
||||
});
|
||||
|
||||
export const withInputFile = async <T>(
|
||||
pathOrZipItem: string | ZipItem,
|
||||
f: (worker: FFmpegUtilityProcess, inputFilePath: string) => Promise<T>,
|
||||
): Promise<T> => {
|
||||
const worker = await ffmpegUtilityProcess();
|
||||
|
||||
const {
|
||||
path: inputFilePath,
|
||||
isFileTemporary: isInputFileTemporary,
|
||||
writeToTemporaryFile: writeToTemporaryInputFile,
|
||||
} = await makeFileForDataOrStreamOrPathOrZipItem(dataOrPathOrZipItem);
|
||||
} = await makeFileForStreamOrPathOrZipItem(pathOrZipItem);
|
||||
|
||||
const outputFilePath = await makeTempFilePath(outputFileExtension);
|
||||
try {
|
||||
await writeToTemporaryInputFile();
|
||||
|
||||
let resolvedCommand: string[];
|
||||
if (Array.isArray(command)) {
|
||||
resolvedCommand = command;
|
||||
} else {
|
||||
const isHDR = await isHDRVideo(inputFilePath);
|
||||
log.debug(() => [basename(inputFilePath), { isHDR }]);
|
||||
resolvedCommand = isHDR ? command.hdr : command.default;
|
||||
}
|
||||
|
||||
const cmd = substitutePlaceholders(
|
||||
resolvedCommand,
|
||||
inputFilePath,
|
||||
outputFilePath,
|
||||
);
|
||||
|
||||
await execAsync(cmd);
|
||||
|
||||
return await fs.readFile(outputFilePath);
|
||||
return await f(worker, inputFilePath);
|
||||
} finally {
|
||||
if (isInputFileTemporary)
|
||||
await deleteTempFileIgnoringErrors(inputFilePath);
|
||||
await deleteTempFileIgnoringErrors(outputFilePath);
|
||||
}
|
||||
};
|
||||
|
||||
const substitutePlaceholders = (
|
||||
command: string[],
|
||||
inputFilePath: string,
|
||||
outputFilePath: string,
|
||||
) =>
|
||||
command.map((segment) => {
|
||||
if (segment == ffmpegPathPlaceholder) {
|
||||
return ffmpegBinaryPath();
|
||||
} else if (segment == inputPathPlaceholder) {
|
||||
return inputFilePath;
|
||||
} else if (segment == outputPathPlaceholder) {
|
||||
return outputFilePath;
|
||||
} else {
|
||||
return segment;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Return the path to the `ffmpeg` binary.
|
||||
*
|
||||
* At runtime, the FFmpeg binary is present in a path like (macOS example):
|
||||
* `ente.app/Contents/Resources/app.asar.unpacked/node_modules/ffmpeg-static/ffmpeg`
|
||||
* Implement the IPC "ffmpegDetermineVideoDuration" contract, writing the input
|
||||
* to temporary files as needed, and then forward to the
|
||||
* {@link ffmpegDetermineVideoDuration} running in the utility process.
|
||||
*/
|
||||
const ffmpegBinaryPath = () => {
|
||||
// This substitution of app.asar by app.asar.unpacked is suggested by the
|
||||
// ffmpeg-static library author themselves:
|
||||
// https://github.com/eugeneware/ffmpeg-static/issues/16
|
||||
return pathToFfmpeg!.replace("app.asar", "app.asar.unpacked");
|
||||
};
|
||||
|
||||
/**
|
||||
* A variant of {@link ffmpegExec} adapted to work with streams so that it can
|
||||
* handle the MP4 conversion of large video files.
|
||||
*
|
||||
* @param inputFilePath The path to a file on the user's local file system. This
|
||||
* is the video we want to convert.
|
||||
*
|
||||
* @param outputFilePath The path to a file on the user's local file system where
|
||||
* we should write the converted MP4 video.
|
||||
*/
|
||||
export const ffmpegConvertToMP4 = async (
|
||||
inputFilePath: string,
|
||||
outputFilePath: string,
|
||||
): Promise<void> => {
|
||||
const command = [
|
||||
ffmpegPathPlaceholder,
|
||||
"-i",
|
||||
inputPathPlaceholder,
|
||||
"-preset",
|
||||
"ultrafast",
|
||||
outputPathPlaceholder,
|
||||
];
|
||||
|
||||
const cmd = substitutePlaceholders(command, inputFilePath, outputFilePath);
|
||||
|
||||
await execAsync(cmd);
|
||||
};
|
||||
|
||||
export interface FFmpegGenerateHLSPlaylistAndSegmentsResult {
|
||||
playlistPath: string;
|
||||
videoPath: string;
|
||||
dimensions: { width: number; height: number };
|
||||
videoSize: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* A bespoke variant of {@link ffmpegExec} for generation of HLS playlists for
|
||||
* videos.
|
||||
*
|
||||
* Overview of the cases:
|
||||
*
|
||||
* H.264, <= 10 MB - Skip
|
||||
* H.264, <= 4000 kb/s bitrate - Don't re-encode video stream
|
||||
* BT.709, <= 2000 kb/s bitrate - Don't apply the scale+fps filter
|
||||
* !BT.709 - Apply tonemap (zscale+tonemap+zscale)
|
||||
*
|
||||
* Example invocation:
|
||||
*
|
||||
* ffmpeg -i in.mov -vf 'scale=-2:720,fps=30,zscale=transfer=linear,tonemap=tonemap=hable:desat=0,zscale=primaries=709:transfer=709:matrix=709,format=yuv420p' -c:v libx264 -c:a aac -f hls -hls_key_info_file out.m3u8.info -hls_list_size 0 -hls_flags single_file out.m3u8
|
||||
*
|
||||
* See: [Note: Preview variant of videos]
|
||||
*
|
||||
* @param inputFilePath The path to a file on the user's local file system. This
|
||||
* is the video we want to generate an streamable HLS playlist for.
|
||||
*
|
||||
* @param outputPathPrefix The path to unique, unused and temporary prefix on
|
||||
* the user's local file system. This function will write the generated HLS
|
||||
* playlist and video segments under this prefix.
|
||||
*
|
||||
* @returns The paths to two files on the user's local file system - one
|
||||
* containing the generated HLS playlist, and the other containing the
|
||||
* transcoded and encrypted video segments that the HLS playlist refers to.
|
||||
*
|
||||
* If the video is such that it doesn't require stream generation, then this
|
||||
* function returns `undefined`.
|
||||
*/
|
||||
export const ffmpegGenerateHLSPlaylistAndSegments = async (
|
||||
inputFilePath: string,
|
||||
outputPathPrefix: string,
|
||||
): Promise<FFmpegGenerateHLSPlaylistAndSegmentsResult | undefined> => {
|
||||
const { isH264, isBT709, bitrate } =
|
||||
await detectVideoCharacteristics(inputFilePath);
|
||||
|
||||
log.debug(() => [basename(inputFilePath), { isH264, isBT709, bitrate }]);
|
||||
|
||||
// If the video is smaller than 10 MB, and already H.264 (the codec we are
|
||||
// going to use for the conversion), then a streaming variant is not much
|
||||
// use. Skip such cases.
|
||||
//
|
||||
// ---
|
||||
//
|
||||
// [Note: HEVC/H.265 issues]
|
||||
//
|
||||
// We've observed two issues out in the wild with HEVC videos:
|
||||
//
|
||||
// 1. On Linux, HEVC video streams don't play. However, since the audio
|
||||
// stream plays, the browser tells us that the "video" itself is
|
||||
// playable, but the user sees a blank screen with only audio.
|
||||
//
|
||||
// 2. HEVC + HDR videos taken on an iPhone have a rotation (`Side data:
|
||||
// displaymatrix` in the ffmpeg output) that Chrome (and thus Electron)
|
||||
// doesn't take into account, so these play upside down.
|
||||
//
|
||||
// Not fully related to this case, but mentioning here as to why both the
|
||||
// size and codec need to be checked before skipping stream generation.
|
||||
if (isH264) {
|
||||
const inputVideoSize = await fs
|
||||
.stat(inputFilePath)
|
||||
.then((st) => st.size);
|
||||
if (inputVideoSize <= 10 * 1024 * 1024 /* 10 MB */) {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// If the video is already H.264 with a bitrate less than 4000 kbps, then we
|
||||
// do not need to reencode the video stream (by _far_ the costliest part of
|
||||
// the HLS stream generation).
|
||||
const reencodeVideo = !(isH264 && bitrate && bitrate <= 4000 * 1000);
|
||||
|
||||
// If the bitrate is not too high, then we don't need to rescale the video
|
||||
// when generating the video stream. This is not a performance optimization,
|
||||
// but more for avoiding making the video size smaller unnecessarily.
|
||||
const rescaleVideo = !(bitrate && bitrate <= 2000 * 1000);
|
||||
|
||||
// [Note: Tonemapping HDR to HD]
|
||||
//
|
||||
// BT.709 ("HD") is a standard that describes things like how color is
|
||||
// encoded, the range of values, and their "meaning" - i.e. how to map the
|
||||
// values in the video to the pixels on the screen.
|
||||
//
|
||||
// It is not the only such standard, there are three common examples:
|
||||
//
|
||||
// - BT.601 ("Standard-Definition" or SD)
|
||||
// - BT.709 ("High-Definition" or HD)
|
||||
// - BT.2020 ("Ultra-High-Definition" or UHD, aka HDR^).
|
||||
//
|
||||
// ^ HDR ("High-Dynamic-Range") is an addendum to BT.2020, but for our
|
||||
// purpose here we can treat it as as alias.
|
||||
//
|
||||
// BT.709 is the most common amongst these for older files out stored on
|
||||
// computers, and they conform mostly to the standard (one notable exception
|
||||
// is that the BT.709 standard also recommends using the yuv422p pixel
|
||||
// format, but de facto yuv420p is used because many video players only
|
||||
// support yuv420p).
|
||||
//
|
||||
// Since BT.709 is the most widely supported standard, we use it when
|
||||
// generating the HLS playlist so to allow playback across the widest
|
||||
// possible hardware/OS/browser combinations.
|
||||
//
|
||||
// If we convert HDR to HD without naively, then the colors look washed out
|
||||
// compared to the original. To resolve this, we use a ffmpeg filterchain
|
||||
// that uses the tonemap filter.
|
||||
//
|
||||
// However applying this tonemap to videos that are already HD leads to a
|
||||
// brightness drop. So we conditionally apply this filter chain only if the
|
||||
// colorspace is not already BT.709.
|
||||
//
|
||||
// See also: [Note: Alternative FFmpeg command for HDR videos], although
|
||||
// that uses a allow-list based check (while here we use deny-list).
|
||||
//
|
||||
// Reference:
|
||||
// - https://trac.ffmpeg.org/wiki/colorspace
|
||||
const tonemap = !isBT709;
|
||||
|
||||
// We want the generated playlist to refer to the chunks as "output.ts".
|
||||
//
|
||||
// So we arrange things accordingly: We use the `outputPathPrefix` as our
|
||||
// working directory, and then ask ffmpeg to generate a playlist with the
|
||||
// name "output.m3u8".
|
||||
//
|
||||
// ffmpeg will automatically place the segments in a file with the same base
|
||||
// name as the playlist, but with a ".ts" extension. And since we use the
|
||||
// "single_file" option, all the segments will be placed in a file named
|
||||
// "output.ts".
|
||||
|
||||
await fs.mkdir(outputPathPrefix);
|
||||
|
||||
const playlistPath = path.join(outputPathPrefix, "output.m3u8");
|
||||
const videoPath = path.join(outputPathPrefix, "output.ts");
|
||||
|
||||
// Generate a cryptographically secure random key (16 bytes).
|
||||
const keyBytes = randomBytes(16);
|
||||
const keyB64 = keyBytes.toString("base64");
|
||||
|
||||
// Convert it to a data: URI that will be added to the playlist.
|
||||
const keyURI = `data:text/plain;base64,${keyB64}`;
|
||||
|
||||
// Determine two paths - one where we will write the key itself, and where
|
||||
// we will write the "key info" that provides ffmpeg the `keyURI` and the
|
||||
// `keyPath;.
|
||||
const keyPath = playlistPath + ".key";
|
||||
const keyInfoPath = playlistPath + ".key-info";
|
||||
|
||||
// Generate a "key info":
|
||||
//
|
||||
// - the first line specifies the key URI that is written into the playlist.
|
||||
// - the second line specifies the path to the local file system file from
|
||||
// where ffmpeg should read the key.
|
||||
const keyInfo = [keyURI, keyPath].join("\n");
|
||||
|
||||
// Overview:
|
||||
//
|
||||
// - Video H.264 HD 720p 30fps.
|
||||
// - Audio AAC 128kbps.
|
||||
// - Encrypted HLS playlist with a single file containing all the chunks.
|
||||
//
|
||||
// Reference:
|
||||
// - `man ffmpeg-all`
|
||||
// - https://trac.ffmpeg.org/wiki/Encode/H.264
|
||||
//
|
||||
const command = [
|
||||
ffmpegBinaryPath(),
|
||||
// Reduce the amount of output lines we have to parse.
|
||||
["-hide_banner"],
|
||||
// Input file. We don't need any extra options that apply to the input file.
|
||||
"-i",
|
||||
inputFilePath,
|
||||
// The remaining options apply to the next output file (`playlistPath`).
|
||||
reencodeVideo
|
||||
? [
|
||||
// `-vf` creates a filter graph for the video stream. It is a
|
||||
// comma separated list of filters chained together, e.g.
|
||||
// `filter1=key=value:key=value.filter2=key=value`.
|
||||
"-vf",
|
||||
[
|
||||
// Do the rescaling to even number of pixels always if the
|
||||
// tonemapping is going to be applied subsequently,
|
||||
// otherwise the tonemapping will fail with "image
|
||||
// dimensions must be divisible by subsampling factor".
|
||||
//
|
||||
// While we add the extra condition here for completeness,
|
||||
// it won't usually matter since a non-BT.709 video is
|
||||
// likely using a new codec, and as such would've a high
|
||||
// enough bitrate to require rescaling anyways.
|
||||
rescaleVideo || tonemap
|
||||
? [
|
||||
// Scales the video to maximum 720p height,
|
||||
// keeping aspect ratio and the calculated
|
||||
// dimension divisible by 2 (some of the other
|
||||
// operations require an even pixel count).
|
||||
"scale=-2:720",
|
||||
// Convert the video to a constant 30 fps,
|
||||
// duplicating or dropping frames as necessary.
|
||||
"fps=30",
|
||||
]
|
||||
: [],
|
||||
// Convert the colorspace if the video is not in the HD
|
||||
// color space (bt709). Before conversion, tone map colors
|
||||
// so that they work the same across the change in the
|
||||
// dyamic range.
|
||||
//
|
||||
// 1. The tonemap filter only works linear light, so we
|
||||
// first use zscale with transfer=linear to linearize
|
||||
// the input.
|
||||
//
|
||||
// 2. Then we use the tonemap, with the hable option that
|
||||
// is best for preserving details. desat=0 turns off
|
||||
// the default desaturation.
|
||||
//
|
||||
// 3. Use zscale again to "convert to BT.709" by asking it
|
||||
// to set the all three of color primaries, transfer
|
||||
// characteristics and colorspace matrix to 709 (Note:
|
||||
// the constants specified in the tonemap filter help
|
||||
// do not include the "bt" prefix)
|
||||
//
|
||||
// See: https://ffmpeg.org/ffmpeg-filters.html#tonemap-1
|
||||
//
|
||||
// See: [Note: Tonemapping HDR to HD]
|
||||
tonemap
|
||||
? [
|
||||
"zscale=transfer=linear",
|
||||
"tonemap=tonemap=hable:desat=0",
|
||||
"zscale=primaries=709:transfer=709:matrix=709",
|
||||
]
|
||||
: [],
|
||||
// Output using the well supported pixel format: 8-bit YUV
|
||||
// planar color space with 4:2:0 chroma subsampling.
|
||||
"format=yuv420p",
|
||||
]
|
||||
.flat()
|
||||
.join(","),
|
||||
]
|
||||
: [],
|
||||
reencodeVideo
|
||||
? // Video codec H.264
|
||||
//
|
||||
// - `-c:v libx264` converts the video stream to the H.264 codec.
|
||||
//
|
||||
// - We don't supply a bitrate, instead it uses the default CRF
|
||||
// ("23") as recommended in the ffmpeg trac.
|
||||
//
|
||||
// - We don't supply a preset, it'll use the default ("medium").
|
||||
["-c:v", "libx264"]
|
||||
: // Keep the video stream unchanged
|
||||
["-c:v", "copy"],
|
||||
// Audio codec AAC
|
||||
//
|
||||
// - `-c:a aac` converts the audio stream to use the AAC codec
|
||||
//
|
||||
// - We don't supply a bitrate, it'll use the AAC default 128k bps.
|
||||
["-c:a", "aac"],
|
||||
// Generate a HLS playlist.
|
||||
["-f", "hls"],
|
||||
// Tell ffmpeg where to find the key, and the URI for the key to write
|
||||
// into the generated playlist. Implies "-hls_enc 1".
|
||||
["-hls_key_info_file", keyInfoPath],
|
||||
// Generate as many playlist entries as needed (default limit is 5).
|
||||
["-hls_list_size", "0"],
|
||||
// Place all the video segments within the same .ts file (with the same
|
||||
// path as the playlist file but with a ".ts" extension).
|
||||
["-hls_flags", "single_file"],
|
||||
// Output path where the playlist should be generated.
|
||||
playlistPath,
|
||||
].flat();
|
||||
|
||||
let dimensions: ReturnType<typeof detectVideoDimensions>;
|
||||
let videoSize: number;
|
||||
|
||||
try {
|
||||
// Write the key and the keyInfo to their desired paths.
|
||||
await Promise.all([
|
||||
fs.writeFile(keyPath, keyBytes),
|
||||
fs.writeFile(keyInfoPath, keyInfo, { encoding: "utf8" }),
|
||||
]);
|
||||
|
||||
// Run the ffmpeg command to generate the HLS playlist and segments.
|
||||
//
|
||||
// Note: Depending on the size of the input file, this may take long!
|
||||
const { stderr: conversionStderr } = await execAsync(command);
|
||||
|
||||
// Determine the dimensions of the generated video from the stderr
|
||||
// output produced by ffmpeg during the conversion.
|
||||
dimensions = detectVideoDimensions(conversionStderr);
|
||||
|
||||
// Find the size of the generated video segments by reading the size of
|
||||
// the generated .ts file.
|
||||
videoSize = await fs.stat(videoPath).then((st) => st.size);
|
||||
} catch (e) {
|
||||
log.error("HLS generation failed", e);
|
||||
await Promise.all([
|
||||
deleteTempFileIgnoringErrors(playlistPath),
|
||||
deleteTempFileIgnoringErrors(videoPath),
|
||||
]);
|
||||
throw e;
|
||||
} finally {
|
||||
await Promise.all([
|
||||
deleteTempFileIgnoringErrors(keyInfoPath),
|
||||
deleteTempFileIgnoringErrors(keyPath),
|
||||
// ffmpeg writes a /path/output.ts.tmp, clear it out too.
|
||||
deleteTempFileIgnoringErrors(videoPath + ".tmp"),
|
||||
]);
|
||||
}
|
||||
|
||||
return { playlistPath, videoPath, dimensions, videoSize };
|
||||
};
|
||||
|
||||
/**
|
||||
* A regex that matches the first line of the form
|
||||
*
|
||||
* Stream #0:0: Video: h264 (High 10) ([27][0][0][0] / 0x001B), yuv420p10le(tv, bt2020nc/bt2020/arib-std-b67), 1920x1080, 30 fps, 30 tbr, 90k tbn
|
||||
*
|
||||
* The part after Video: is the first capture group.
|
||||
*
|
||||
* Another example:
|
||||
*
|
||||
* Stream #0:1[0x2](und): Video: h264 (Constrained Baseline) (avc1 / 0x31637661), yuv420p(progressive), 480x270 [SAR 1:1 DAR 16:9], 539 kb/s, 29.97 fps, 29.97 tbr, 30k tbn (default)
|
||||
*/
|
||||
const videoStreamLineRegex = /Stream #.+: Video:(.+)\n/;
|
||||
|
||||
/** {@link videoStreamLineRegex}, but global. */
|
||||
const videoStreamLinesRegex = /Stream #.+: Video:(.+)\n/g;
|
||||
|
||||
/**
|
||||
* A regex that matches "<digits> kb/s" preceded by a space. See
|
||||
* {@link videoStreamLineRegex} for the context in which it is used.
|
||||
*/
|
||||
const videoBitrateRegex = / ([1-9]\d*) kb\/s/;
|
||||
|
||||
/**
|
||||
* A regex that matches <digits>x<digits> pair preceded by a space. See
|
||||
* {@link videoStreamLineRegex} for the context in which it is used.
|
||||
*
|
||||
* We constrain the digit sequence not to begin with 0 to exclude hexadecimal
|
||||
* representations of various constants that ffmpeg prints on this line (e.g.
|
||||
* "avc1 / 0x31637661").
|
||||
*/
|
||||
const videoDimensionsRegex = / ([1-9]\d*)x([1-9]\d*)/;
|
||||
|
||||
interface VideoCharacteristics {
|
||||
isH264: boolean;
|
||||
isBT709: boolean;
|
||||
bitrate: number | undefined;
|
||||
}
|
||||
/**
|
||||
* Heuristically determine information about the video at the given
|
||||
* {@link inputFilePath}:
|
||||
*
|
||||
* - If is encoded using H.264 codec.
|
||||
* - If it uses the BT.709 colorspace.
|
||||
* - Its bitrate.
|
||||
*
|
||||
* The defaults are tailored for the cases in which these conditions are used,
|
||||
* so that even if we get the detection wrong we'll only end up encoding videos
|
||||
* that could've possibly been skipped as an optimization.
|
||||
*
|
||||
* [Note: Parsing CLI output might break on ffmpeg updates]
|
||||
*
|
||||
* This function tries to determine the these bits of information about the
|
||||
* given video by scanning the ffmpeg info output for the video stream line, and
|
||||
* doing various string matches and regex extractions.
|
||||
*
|
||||
* Needless to say, while this works currently, this is liable to break in the
|
||||
* future. So if something stops working after updating ffmpeg, look here!
|
||||
*
|
||||
* Ideally, we'd have done this using `ffprobe`, but we don't have the ffprobe
|
||||
* binary at hand, so we make do by grepping the log output of ffmpeg.
|
||||
*
|
||||
* For reference,
|
||||
*
|
||||
* - codec and colorspace are printed by the `avcodec_string` function in the
|
||||
* ffmpeg source:
|
||||
* https://github.com/FFmpeg/FFmpeg/blob/master/libavcodec/avcodec.c
|
||||
*
|
||||
* - bitrate is printed by the `dump_stream_format` function in `dump.c`.
|
||||
*/
|
||||
const detectVideoCharacteristics = async (inputFilePath: string) => {
|
||||
const videoInfo = await pseudoFFProbeVideo(inputFilePath);
|
||||
const videoStreamLine = videoStreamLineRegex.exec(videoInfo)?.at(1)?.trim();
|
||||
|
||||
// Since the checks are heuristic, start with defaults that would cause the
|
||||
// codec conversion to happen, even if it is unnecessary.
|
||||
const res: VideoCharacteristics = {
|
||||
isH264: false,
|
||||
isBT709: false,
|
||||
bitrate: undefined,
|
||||
};
|
||||
if (!videoStreamLine) return res;
|
||||
|
||||
res.isH264 = videoStreamLine.startsWith("h264 ");
|
||||
res.isBT709 = videoStreamLine.includes("bt709");
|
||||
// The regex matches "\d kb/s", but there can be other units for the
|
||||
// bitrate. However, (a) "kb/s" is the most common for videos out in the
|
||||
// wild, and (b) even if we guess wrong it we'll just do "-v:c x264" instead
|
||||
// of "-v:c copy", so only unnecessary processing but no change in output.
|
||||
const brs = videoBitrateRegex.exec(videoStreamLine)?.at(0);
|
||||
if (brs) {
|
||||
const br = parseInt(brs, 10);
|
||||
if (br) res.bitrate = br;
|
||||
}
|
||||
|
||||
return res;
|
||||
};
|
||||
|
||||
/**
|
||||
* Heuristically detect the dimensions of the given video from the log output of
|
||||
* the ffmpeg invocation during the HLS playlist generation.
|
||||
*
|
||||
* This function tries to determine the width and height of the generated video
|
||||
* from the output log written by ffmpeg on its stderr during the generation
|
||||
* process, scanning it for the last video stream line, and trying to match a
|
||||
* "<digits>x<digits>" regex.
|
||||
*
|
||||
* See: [Note: Parsing CLI output might break on ffmpeg updates].
|
||||
*/
|
||||
const detectVideoDimensions = (conversionStderr: string) => {
|
||||
// There is a nicer way to do it - by running `pseudoFFProbeVideo` on the
|
||||
// generated playlist. However, that playlist includes a data URL that
|
||||
// specifies the encryption info, and ffmpeg refuses to read that unless we
|
||||
// specify the "-allowed_extensions ALL" or something to that effect.
|
||||
//
|
||||
// Unfortunately, our current ffmpeg binary (5.x) does not support that
|
||||
// option. So we instead parse the conversion output itself.
|
||||
//
|
||||
// This is also nice, since it saves on an extra ffmpeg invocation. But we
|
||||
// now need to be careful to find the right video stream line, since the
|
||||
// conversion output includes both the input and output video stream lines.
|
||||
//
|
||||
// To match the right (output) video stream line, we use a global regex, and
|
||||
// use the last match since that'd correspond to the single video stream
|
||||
// written in the output.
|
||||
const videoStreamLine = Array.from(
|
||||
conversionStderr.matchAll(videoStreamLinesRegex),
|
||||
)
|
||||
.at(-1) /* Last Stream...: Video: line in the output */
|
||||
?.at(1); /* First capture group */
|
||||
if (videoStreamLine) {
|
||||
const [, ws, hs] = videoDimensionsRegex.exec(videoStreamLine) ?? [];
|
||||
if (ws && hs) {
|
||||
const w = parseInt(ws, 10);
|
||||
const h = parseInt(hs, 10);
|
||||
if (w && h) {
|
||||
return { width: w, height: h };
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new Error(
|
||||
`Unable to detect video dimensions from stream line [${videoStreamLine ?? ""}]`,
|
||||
export const ffmpegDetermineVideoDuration = async (
|
||||
pathOrZipItem: string | ZipItem,
|
||||
): Promise<number> =>
|
||||
withInputFile(pathOrZipItem, async (worker, inputFilePath) =>
|
||||
worker.ffmpegDetermineVideoDuration(inputFilePath),
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Heuristically detect if the file at given path is a HDR video.
|
||||
*
|
||||
* This is similar to {@link detectVideoCharacteristics}, and see that
|
||||
* function's documentation for all the caveats. However, this function uses an
|
||||
* allow-list instead, and considers any file with color transfer "smpte2084" or
|
||||
* "arib-std-b67" to be HDR. While this is in some sense a more exact check, it
|
||||
* comes with different caveats:
|
||||
*
|
||||
* - These particular constants are not guaranteed to be correct; these are just
|
||||
* what I saw on the internet as being used / recommended for detecting HDR.
|
||||
*
|
||||
* - Since we don't have ffprobe, we're not checking the color space value
|
||||
* itself but a substring of the stream line in the ffmpeg stderr output.
|
||||
*
|
||||
* In particular, we use this more exact check for places where we have less
|
||||
* leeway. e.g. when generating thumbnails, if we apply the tonemapping to any
|
||||
* non-BT.709 file (as the HLS stream generation does), we start getting the
|
||||
* "code 3074: no path between colorspaces" error during the JPEG conversion
|
||||
* (this is not a problem in the H.264 conversion).
|
||||
*
|
||||
* - See: [Note: Alternative FFmpeg command for HDR videos]
|
||||
* - See: [Note: Tonemapping HDR to HD]
|
||||
*
|
||||
* @param inputFilePath The path to a video file on the user's machine.
|
||||
*
|
||||
* @returns `true` if this file is likely a HDR video. Exceptions are treated as
|
||||
* `false` to make this function safe to invoke without breaking the happy path.
|
||||
*/
|
||||
const isHDRVideo = async (inputFilePath: string) => {
|
||||
try {
|
||||
const videoInfo = await pseudoFFProbeVideo(inputFilePath);
|
||||
const vs = videoStreamLineRegex.exec(videoInfo)?.at(1);
|
||||
if (!vs) return false;
|
||||
return vs.includes("smpte2084") || vs.includes("arib-std-b67");
|
||||
} catch (e) {
|
||||
log.warn(`Could not detect HDR status of ${inputFilePath}`, e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the stderr of ffmpeg in an attempt to gain information about the video
|
||||
* at the given {@link inputFilePath}.
|
||||
*
|
||||
* We don't have the ffprobe binary at hand, which is why we need to use this
|
||||
* alternative. See: [Note: Parsing CLI output might break on ffmpeg updates]
|
||||
*
|
||||
* @returns the stderr of ffmpeg after running it on the input file. The exact
|
||||
* command we run is:
|
||||
*
|
||||
* ffmpeg -i in.mov -an -frames:v 0 -f null - 2>info.txt
|
||||
*
|
||||
* And the returned string is the contents of the `info.txt` thus produced.
|
||||
*/
|
||||
const pseudoFFProbeVideo = async (inputFilePath: string) => {
|
||||
const command = [
|
||||
ffmpegPathPlaceholder,
|
||||
// Reduce the amount of output lines we have to parse.
|
||||
["-hide_banner"],
|
||||
["-i", inputPathPlaceholder],
|
||||
"-an",
|
||||
["-frames:v", "0"],
|
||||
["-f", "null"],
|
||||
"-",
|
||||
].flat();
|
||||
|
||||
const cmd = substitutePlaceholders(command, inputFilePath, /* NA */ "");
|
||||
|
||||
const { stderr } = await execAsync(cmd);
|
||||
|
||||
return stderr;
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import { type ZipItem } from "../../types/ipc";
|
||||
import { execAsync, isDev } from "../utils/electron";
|
||||
import {
|
||||
deleteTempFileIgnoringErrors,
|
||||
makeFileForDataOrStreamOrPathOrZipItem,
|
||||
makeFileForStreamOrPathOrZipItem,
|
||||
makeTempFilePath,
|
||||
} from "../utils/temp";
|
||||
|
||||
@@ -61,7 +61,7 @@ const vipsPath = () =>
|
||||
);
|
||||
|
||||
export const generateImageThumbnail = async (
|
||||
dataOrPathOrZipItem: Uint8Array | string | ZipItem,
|
||||
pathOrZipItem: string | ZipItem,
|
||||
maxDimension: number,
|
||||
maxSize: number,
|
||||
): Promise<Uint8Array> => {
|
||||
@@ -69,7 +69,7 @@ export const generateImageThumbnail = async (
|
||||
path: inputFilePath,
|
||||
isFileTemporary: isInputFileTemporary,
|
||||
writeToTemporaryFile: writeToTemporaryInputFile,
|
||||
} = await makeFileForDataOrStreamOrPathOrZipItem(dataOrPathOrZipItem);
|
||||
} = await makeFileForStreamOrPathOrZipItem(pathOrZipItem);
|
||||
|
||||
const outputFilePath = await makeTempFilePath("jpeg");
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import log from "../log";
|
||||
import { clearPendingVideoResults } from "../stream";
|
||||
import { clearStores } from "./store";
|
||||
import { watchReset } from "./watch";
|
||||
import { terminateUtilityProcesses } from "./workers";
|
||||
import { clearOpenZipCache } from "./zip";
|
||||
|
||||
/**
|
||||
@@ -36,4 +37,9 @@ export const logout = (watcher: FSWatcher) => {
|
||||
} catch (e) {
|
||||
ignoreError("zip cache", e);
|
||||
}
|
||||
try {
|
||||
terminateUtilityProcesses();
|
||||
} catch (e) {
|
||||
ignoreError("utility processes", e);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -15,47 +15,16 @@ import { existsSync } from "fs";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import * as ort from "onnxruntime-node";
|
||||
import { z } from "zod/v4";
|
||||
import log from "../log-worker";
|
||||
import { messagePortMainEndpoint } from "../utils/comlink";
|
||||
import { wait } from "../utils/common";
|
||||
import { writeStream } from "../utils/stream";
|
||||
import { fsStatMtime } from "./fs";
|
||||
|
||||
/**
|
||||
* We cannot do
|
||||
*
|
||||
* import log from "../log";
|
||||
*
|
||||
* because that requires the Electron APIs that are not available to a utility
|
||||
* process (See: [Note: Using Electron APIs in UtilityProcess]). But even if
|
||||
* that were to work, logging will still be problematic since we'd try opening
|
||||
* the log file from two different Node.js processes (this one, and the main
|
||||
* one), and I didn't find any indication in the electron-log repository that
|
||||
* the log file's integrity would be maintained in such cases.
|
||||
*
|
||||
* So instead we create this proxy log object that uses `process.parentPort` to
|
||||
* transport the logs over to the main process.
|
||||
*/
|
||||
const log = {
|
||||
/**
|
||||
* Unlike the real {@link log.error}, this accepts only the first string
|
||||
* argument, not the second optional error one.
|
||||
*/
|
||||
errorString: (s: string) => mainProcess("log.errorString", s),
|
||||
info: (...ms: unknown[]) => mainProcess("log.info", ms),
|
||||
/**
|
||||
* Unlike the real {@link log.debug}, this is (a) eagerly evaluated, and (b)
|
||||
* accepts only strings.
|
||||
*/
|
||||
debugString: (s: string) => mainProcess("log.debugString", s),
|
||||
};
|
||||
log.debugString("Started ML utility process");
|
||||
|
||||
/**
|
||||
* Send a message to the main process using a barebones RPC protocol.
|
||||
*/
|
||||
const mainProcess = (method: string, param: unknown) =>
|
||||
process.parentPort.postMessage({ method, p: param });
|
||||
|
||||
log.debugString(`Started ML worker process`);
|
||||
process.on("uncaughtException", (e, origin) => log.error(origin, e));
|
||||
|
||||
process.parentPort.once("message", (e) => {
|
||||
// Initialize ourselves with the data we got from our parent.
|
||||
@@ -84,17 +53,10 @@ let _userDataPath: string | undefined;
|
||||
/** Equivalent to app.getPath("userData") */
|
||||
const userDataPath = () => _userDataPath!;
|
||||
|
||||
const MLWorkerInitData = z.object({ userDataPath: z.string() });
|
||||
|
||||
const parseInitData = (data: unknown) => {
|
||||
if (
|
||||
data &&
|
||||
typeof data == "object" &&
|
||||
"userDataPath" in data &&
|
||||
typeof data.userDataPath == "string"
|
||||
) {
|
||||
_userDataPath = data.userDataPath;
|
||||
} else {
|
||||
log.errorString("Unparseable initialization data");
|
||||
}
|
||||
_userDataPath = MLWorkerInitData.parse(data).userDataPath;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -161,7 +123,7 @@ const modelPathDownloadingIfNeeded = async (
|
||||
} else {
|
||||
const size = (await fs.stat(modelPath)).size;
|
||||
if (size !== expectedByteSize) {
|
||||
log.errorString(
|
||||
log.error(
|
||||
`The size ${size} of model ${modelName} does not match the expected size, downloading again`,
|
||||
);
|
||||
await downloadModel(modelPath, modelName);
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
/**
|
||||
* @file ML related functionality. This code runs in the main process.
|
||||
*/
|
||||
|
||||
import {
|
||||
MessageChannelMain,
|
||||
type BrowserWindow,
|
||||
type UtilityProcess,
|
||||
} from "electron";
|
||||
import { app, utilityProcess } from "electron/main";
|
||||
import path from "node:path";
|
||||
import log from "../log";
|
||||
|
||||
/** The active ML worker (utility) process, if any. */
|
||||
let _child: UtilityProcess | undefined;
|
||||
|
||||
/**
|
||||
* Create a new ML worker process, terminating the older ones (if any).
|
||||
*
|
||||
* [Note: ML IPC]
|
||||
*
|
||||
* The primary reason for doing ML tasks in the Node.js layer is so that we can
|
||||
* use the binary ONNX runtime, which is 10-20x faster than the Wasm one that
|
||||
* can be used directly on the web layer.
|
||||
*
|
||||
* For this to work, the main and renderer process need to communicate with each
|
||||
* other. Further, in the web layer the ML indexing runs in a web worker (so as
|
||||
* to not get in the way of the main thread). So the communication has 2 hops:
|
||||
*
|
||||
* Node.js main <-> Renderer main <-> Renderer web worker
|
||||
*
|
||||
* This naive way works, but has a problem. The Node.js main process is in the
|
||||
* code path for delivering user events to the renderer process. The ML tasks we
|
||||
* do take in the order of 100-300 ms (possibly more) for each individual
|
||||
* inference. Thus, the Node.js main process is busy for those 100-300 ms, and
|
||||
* does not forward events to the renderer, causing the UI to jitter.
|
||||
*
|
||||
* The solution for this is to spawn an Electron UtilityProcess, which we can
|
||||
* think of a regular Node.js child process. This frees up the Node.js main
|
||||
* process, and would remove the jitter.
|
||||
* https://www.electronjs.org/docs/latest/tutorial/process-model
|
||||
*
|
||||
* It would seem that this introduces another hop in our IPC
|
||||
*
|
||||
* Node.js utility process <-> Node.js main <-> ...
|
||||
*
|
||||
* but here we can use the special bit about Electron utility processes that
|
||||
* separates them from regular Node.js child processes: their support for
|
||||
* message ports. https://www.electronjs.org/docs/latest/tutorial/message-ports
|
||||
*
|
||||
* As a brief summary, a MessagePort is a web feature that allows two contexts
|
||||
* to communicate. A pair of message ports is called a message channel. The cool
|
||||
* thing about these is that we can pass these ports themselves over IPC.
|
||||
*
|
||||
* > One caveat here is that the message ports can only be passed using the
|
||||
* > `postMessage` APIs, not the usual send/invoke APIs.
|
||||
*
|
||||
* So we
|
||||
*
|
||||
* 1. In the utility process create a message channel.
|
||||
* 2. Spawn a utility process, and send one port of the pair to it.
|
||||
* 3. Send the other port of the pair to the renderer.
|
||||
*
|
||||
* The renderer will forward that port to the web worker that is coordinating
|
||||
* the ML indexing on the web layer. Thereafter, the utility process and web
|
||||
* worker can directly talk to each other!
|
||||
*
|
||||
* Node.js utility process <-> Renderer web worker
|
||||
*
|
||||
* The RPC protocol is handled using comlink on both ends. The port itself needs
|
||||
* to be relayed using `postMessage`.
|
||||
*/
|
||||
export const createMLWorker = (window: BrowserWindow) => {
|
||||
if (_child) {
|
||||
log.debug(() => "Terminating previous ML worker process");
|
||||
_child.kill();
|
||||
_child = undefined;
|
||||
}
|
||||
|
||||
const { port1, port2 } = new MessageChannelMain();
|
||||
|
||||
const child = utilityProcess.fork(path.join(__dirname, "ml-worker.js"));
|
||||
const userDataPath = app.getPath("userData");
|
||||
child.postMessage({ userDataPath }, [port1]);
|
||||
|
||||
window.webContents.postMessage("createMLWorker/port", undefined, [port2]);
|
||||
|
||||
handleMessagesFromUtilityProcess(child);
|
||||
|
||||
_child = child;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle messages posted from the utility process.
|
||||
*
|
||||
* [Note: Using Electron APIs in UtilityProcess]
|
||||
*
|
||||
* Only a small subset of the Electron APIs are available to a UtilityProcess.
|
||||
* As of writing (Jul 2024, Electron 30), only the following are available:
|
||||
*
|
||||
* - net
|
||||
* - systemPreferences
|
||||
*
|
||||
* In particular, `app` is not available.
|
||||
*
|
||||
* We structure our code so that it doesn't need anything apart from `net`.
|
||||
*
|
||||
* For the other cases,
|
||||
*
|
||||
* - Additional parameters to the utility process are passed alongwith the
|
||||
* initial message where we provide it the message port.
|
||||
*
|
||||
* - When we need to communicate from the utility process to the main process,
|
||||
* we use the `parentPort` in the utility process.
|
||||
*/
|
||||
const handleMessagesFromUtilityProcess = (child: UtilityProcess) => {
|
||||
const logTag = "[ml-worker]";
|
||||
child.on("message", (m: unknown) => {
|
||||
if (m && typeof m == "object" && "method" in m && "p" in m) {
|
||||
const p = m.p;
|
||||
switch (m.method) {
|
||||
case "log.errorString":
|
||||
if (typeof p == "string") {
|
||||
log.error(`${logTag} ${p}`);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case "log.info":
|
||||
if (Array.isArray(p)) {
|
||||
// Need to cast from any[] to unknown[]
|
||||
log.info(logTag, ...(p as unknown[]));
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case "log.debugString":
|
||||
if (typeof p == "string") {
|
||||
log.debug(() => `${logTag} ${p}`);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
log.info("Ignoring unknown message from ML worker", m);
|
||||
});
|
||||
};
|
||||
@@ -24,17 +24,17 @@ export const clearStores = () => {
|
||||
* On macOS, `safeStorage` stores our data under a Keychain entry named
|
||||
* "<app-name> Safe Storage". In our case, "ente Safe Storage".
|
||||
*/
|
||||
export const saveMasterKeyB64 = (masterKeyB64: string) => {
|
||||
const encryptedKey = safeStorage.encryptString(masterKeyB64);
|
||||
const b64EncryptedKey = Buffer.from(encryptedKey).toString("base64");
|
||||
safeStorageStore.set("encryptionKey", b64EncryptedKey);
|
||||
export const saveMasterKeyInSafeStorage = (masterKey: string) => {
|
||||
const encryptedKeyBuffer = safeStorage.encryptString(masterKey);
|
||||
const encryptedKey = Buffer.from(encryptedKeyBuffer).toString("base64");
|
||||
safeStorageStore.set("encryptionKey", encryptedKey);
|
||||
};
|
||||
|
||||
export const masterKeyB64 = (): string | undefined => {
|
||||
const b64EncryptedKey = safeStorageStore.get("encryptionKey");
|
||||
if (!b64EncryptedKey) return undefined;
|
||||
const keyBuffer = Buffer.from(b64EncryptedKey, "base64");
|
||||
return safeStorage.decryptString(keyBuffer);
|
||||
export const masterKeyFromSafeStorage = (): string | undefined => {
|
||||
const encryptedKey = safeStorageStore.get("encryptionKey");
|
||||
if (!encryptedKey) return undefined;
|
||||
const encryptedKeyBuffer = Buffer.from(encryptedKey, "base64");
|
||||
return safeStorage.decryptString(encryptedKeyBuffer);
|
||||
};
|
||||
|
||||
export const lastShownChangelogVersion = (): number | undefined =>
|
||||
|
||||
@@ -28,6 +28,13 @@ export const createWatcher = (mainWindow: BrowserWindow) => {
|
||||
// Ask the watcher to wait for a the file size to stabilize before
|
||||
// telling us about a new file. By default, it waits for 2 seconds.
|
||||
awaitWriteFinish: true,
|
||||
// On macOS we start getting "EMFILE: too many open files" when watching
|
||||
// large folders. This is a known regression in Chokidar v4:
|
||||
// https://github.com/paulmillr/chokidar/issues/1385
|
||||
//
|
||||
// The recommended workaround for now is to enable usePolling. Since it
|
||||
// comes at a performance cost, we only do it where needed (macOS).
|
||||
...(process.platform == "darwin" ? { usePolling: true } : {}),
|
||||
});
|
||||
|
||||
watcher
|
||||
|
||||
241
desktop/src/main/services/workers.ts
Normal file
@@ -0,0 +1,241 @@
|
||||
/**
|
||||
* @file This main process code and interface for dealing with the various
|
||||
* utility processes that we create.
|
||||
*/
|
||||
|
||||
import type { Endpoint } from "comlink";
|
||||
import {
|
||||
MessageChannelMain,
|
||||
type BrowserWindow,
|
||||
type UtilityProcess,
|
||||
} from "electron";
|
||||
import { app, utilityProcess } from "electron/main";
|
||||
import path from "node:path";
|
||||
import type { UtilityProcessType } from "../../types/ipc";
|
||||
import log, { processUtilityProcessLogMessage } from "../log";
|
||||
import { messagePortMainEndpoint } from "../utils/comlink";
|
||||
|
||||
/**
|
||||
* Terminate any existing utility processes if they're running.
|
||||
*
|
||||
* This function is called during the logout sequence.
|
||||
*/
|
||||
export const terminateUtilityProcesses = () => {
|
||||
terminateMLProcessIfRunning();
|
||||
terminateFFmpegProcessIfRunning();
|
||||
};
|
||||
|
||||
/** The active ML utility process, if any. */
|
||||
let _utilityProcessML: UtilityProcess | undefined;
|
||||
|
||||
/** The active FFmpeg utility process, if any. */
|
||||
let _utilityProcessFFmpeg: UtilityProcess | undefined;
|
||||
|
||||
/**
|
||||
* A promise to a comlink {@link Endpoint} that can be used to communicate with
|
||||
* the active ffmpeg utility process (if any).
|
||||
*/
|
||||
let _utilityProcessFFmpegEndpoint: Promise<Endpoint> | undefined;
|
||||
|
||||
/**
|
||||
* Create a new utility process of the given {@link type}, terminating the older
|
||||
* ones (if any).
|
||||
*
|
||||
* Currently the only type is "ml". The following note explains the reasoning
|
||||
* why utility processes were used for the first workload (ML) that was handled
|
||||
* this way. Similar reasoning applies to subsequent workloads (ffmpeg) that
|
||||
* have been offloaded to utility processes in a slightly different manner to
|
||||
* avoid stutter in the UI.
|
||||
*
|
||||
* [Note: ML IPC]
|
||||
*
|
||||
* The primary reason for doing ML tasks in the Node.js layer is so that we can
|
||||
* use the binary ONNX runtime, which is 10-20x faster than the Wasm one that
|
||||
* can be used directly on the web layer.
|
||||
*
|
||||
* For this to work, the main and renderer process need to communicate with each
|
||||
* other. Further, in the web layer the ML indexing runs in a web worker (so as
|
||||
* to not get in the way of the main thread). So the communication has 2 hops:
|
||||
*
|
||||
* Node.js main <-> Renderer main <-> Renderer web worker
|
||||
*
|
||||
* This naive way works, but has a problem. The Node.js main process is in the
|
||||
* code path for delivering user events to the renderer process. The ML tasks we
|
||||
* do take in the order of 100-300 ms (possibly more) for each individual
|
||||
* inference. Thus, the Node.js main process is busy for those 100-300 ms, and
|
||||
* does not forward events to the renderer, causing the UI to jitter.
|
||||
*
|
||||
* The solution for this is to spawn an Electron UtilityProcess, which we can
|
||||
* think of a regular Node.js child process. This frees up the Node.js main
|
||||
* process, and would remove the jitter.
|
||||
* https://www.electronjs.org/docs/latest/tutorial/process-model
|
||||
*
|
||||
* It would seem that this introduces another hop in our IPC
|
||||
*
|
||||
* Node.js utility process <-> Node.js main <-> ...
|
||||
*
|
||||
* but here we can use the special bit about Electron utility processes that
|
||||
* separates them from regular Node.js child processes: their support for
|
||||
* message ports. https://www.electronjs.org/docs/latest/tutorial/message-ports
|
||||
*
|
||||
* As a brief summary, a MessagePort is a web feature that allows two contexts
|
||||
* to communicate. A pair of message ports is called a message channel. The cool
|
||||
* thing about these is that we can pass these ports themselves over IPC.
|
||||
*
|
||||
* > One caveat here is that the message ports can only be passed using the
|
||||
* > `postMessage` APIs, not the usual send/invoke APIs.
|
||||
*
|
||||
* So we
|
||||
*
|
||||
* 1. In the utility process create a message channel.
|
||||
* 2. Spawn a utility process, and send one port of the pair to it.
|
||||
* 3. Send the other port of the pair to the renderer.
|
||||
*
|
||||
* The renderer will forward that port to the web worker that is coordinating
|
||||
* the ML indexing on the web layer. Thereafter, the utility process and web
|
||||
* worker can directly talk to each other!
|
||||
*
|
||||
* Node.js utility process <-> Renderer web worker
|
||||
*
|
||||
* The RPC protocol is handled using comlink on both ends. The port itself needs
|
||||
* to be relayed using `postMessage`.
|
||||
*/
|
||||
export const triggerCreateUtilityProcess = (
|
||||
type: UtilityProcessType,
|
||||
window: BrowserWindow,
|
||||
) => triggerCreateMLUtilityProcess(window);
|
||||
|
||||
const terminateMLProcessIfRunning = () => {
|
||||
if (_utilityProcessML) {
|
||||
log.debug(() => "Terminating running ML utility process");
|
||||
_utilityProcessML.kill();
|
||||
_utilityProcessML = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
export const triggerCreateMLUtilityProcess = (window: BrowserWindow) => {
|
||||
terminateMLProcessIfRunning();
|
||||
|
||||
const { port1, port2 } = new MessageChannelMain();
|
||||
|
||||
const child = utilityProcess.fork(path.join(__dirname, "ml-worker.js"));
|
||||
const userDataPath = app.getPath("userData");
|
||||
child.postMessage(/* MLWorkerInitData */ { userDataPath }, [port1]);
|
||||
|
||||
window.webContents.postMessage("utilityProcessPort/ml", undefined, [port2]);
|
||||
|
||||
handleMessagesFromMLUtilityProcess(child);
|
||||
|
||||
_utilityProcessML = child;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle messages posted from the utility process.
|
||||
*
|
||||
* [Note: Using Electron APIs in UtilityProcess]
|
||||
*
|
||||
* Only a small subset of the Electron APIs are available to a UtilityProcess.
|
||||
* As of writing (Jul 2024, Electron 30), only the following are available:
|
||||
*
|
||||
* - net
|
||||
* - systemPreferences
|
||||
*
|
||||
* In particular, `app` is not available.
|
||||
*
|
||||
* We structure our code so that it doesn't need anything apart from `net`.
|
||||
*
|
||||
* For the other cases,
|
||||
*
|
||||
* - Additional parameters to the utility process are passed alongwith the
|
||||
* initial message where we provide it the message port.
|
||||
*
|
||||
* - When we need to communicate from the utility process to the main process,
|
||||
* we use the `parentPort` in the utility process.
|
||||
*/
|
||||
const handleMessagesFromMLUtilityProcess = (child: UtilityProcess) => {
|
||||
child.on("message", (m: unknown) => {
|
||||
if (processUtilityProcessLogMessage("[ml-worker]", m)) {
|
||||
return;
|
||||
}
|
||||
log.info("Ignoring unknown message from ML utility process", m);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* A comlink endpoint that can be used to communicate with the ffmpeg utility
|
||||
* process. If there is no ffmpeg utility process, a new one is created on
|
||||
* demand.
|
||||
*
|
||||
* See [Note: ML IPC] for a general outline of why utility processes are needed
|
||||
* (tl;dr; to avoid stutter on the UI).
|
||||
*
|
||||
* In the case of ffmpeg, the IPC flow is a bit different: the utility process
|
||||
* is not exposed to the web layer, and is internal to the node layer. The
|
||||
* reason for this difference is that we need to create temporary files etc, and
|
||||
* doing it a utility process requires access to the `app` module which are not
|
||||
* accessible (See: [Note: Using Electron APIs in UtilityProcess]).
|
||||
*
|
||||
* There could've been possible reasonable workarounds, but the architecture
|
||||
* we've adopted of three layers:
|
||||
*
|
||||
* Renderer (web) <-> Node.js main <-> Node.js ffmpeg utility process
|
||||
*
|
||||
* The temporary file creation etc is handled in the Node.js main process, and
|
||||
* paths to the files are forwarded to the ffmpeg utility process to act on.
|
||||
*
|
||||
* @returns an endpoint that can be used to communicate with the utility
|
||||
* process. The utility process is expected to expose an object that conforms to
|
||||
* the {@link ElectronFFmpegWorkerNode} interface on this endpoint.
|
||||
*/
|
||||
export const ffmpegUtilityProcessEndpoint = () =>
|
||||
(_utilityProcessFFmpegEndpoint ??= createFFmpegUtilityProcessEndpoint());
|
||||
|
||||
const terminateFFmpegProcessIfRunning = () => {
|
||||
if (_utilityProcessFFmpeg) {
|
||||
log.debug(() => "Terminating running FFmpeg utility process");
|
||||
_utilityProcessFFmpeg.kill();
|
||||
_utilityProcessFFmpeg = undefined;
|
||||
_utilityProcessFFmpegEndpoint = undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const createFFmpegUtilityProcessEndpoint = () => {
|
||||
if (_utilityProcessFFmpeg) {
|
||||
throw new Error("FFmpeg utility process is already running");
|
||||
}
|
||||
|
||||
// Promise.withResolvers is currently in the node available to us.
|
||||
let resolve: ((endpoint: Endpoint) => void) | undefined;
|
||||
const promise = new Promise<Endpoint>((r) => (resolve = r));
|
||||
|
||||
const { port1, port2 } = new MessageChannelMain();
|
||||
|
||||
const child = utilityProcess.fork(path.join(__dirname, "ffmpeg-worker.js"));
|
||||
// Send a handle to the port (one end of the message channel) to the utility
|
||||
// process (alongwith any other init data). The utility process will reply
|
||||
// with an "ack" when it get it.
|
||||
const appVersion = app.getVersion();
|
||||
child.postMessage(/* FFmpegWorkerInitData */ { appVersion }, [port1]);
|
||||
|
||||
child.on("message", (m: unknown) => {
|
||||
if (m && typeof m == "object" && "method" in m) {
|
||||
switch (m.method) {
|
||||
case "ack":
|
||||
resolve!(messagePortMainEndpoint(port2));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (processUtilityProcessLogMessage("[ffmpeg-worker]", m)) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("Ignoring unknown message from ffmpeg utility process", m);
|
||||
});
|
||||
|
||||
_utilityProcessFFmpeg = child;
|
||||
|
||||
// Resolve with the other end of the message channel (once we get an "ack"
|
||||
// from the utility process).
|
||||
return promise;
|
||||
};
|
||||
@@ -3,23 +3,18 @@
|
||||
*/
|
||||
import { net, protocol } from "electron/main";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import fs_ from "node:fs";
|
||||
import fs from "node:fs/promises";
|
||||
import { Readable, Writable } from "node:stream";
|
||||
import { Writable } from "node:stream";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import log from "./log";
|
||||
import {
|
||||
ffmpegConvertToMP4,
|
||||
ffmpegGenerateHLSPlaylistAndSegments,
|
||||
type FFmpegGenerateHLSPlaylistAndSegmentsResult,
|
||||
} from "./services/ffmpeg";
|
||||
import { ffmpegUtilityProcess } from "./services/ffmpeg";
|
||||
import { type FFmpegGenerateHLSPlaylistAndSegmentsResult } from "./services/ffmpeg-worker";
|
||||
import { markClosableZip, openZip } from "./services/zip";
|
||||
import { wait } from "./utils/common";
|
||||
import { writeStream } from "./utils/stream";
|
||||
import {
|
||||
deleteTempFile,
|
||||
deleteTempFileIgnoringErrors,
|
||||
makeFileForDataOrStreamOrPathOrZipItem,
|
||||
makeFileForStreamOrPathOrZipItem,
|
||||
makeTempFilePath,
|
||||
} from "./utils/temp";
|
||||
|
||||
@@ -234,12 +229,14 @@ export const clearPendingVideoResults = () => pendingVideoResults.clear();
|
||||
* See also: [Note: IPC streams]
|
||||
*/
|
||||
const handleConvertToMP4Write = async (request: Request) => {
|
||||
const worker = await ffmpegUtilityProcess();
|
||||
|
||||
const inputTempFilePath = await makeTempFilePath();
|
||||
await writeStream(inputTempFilePath, request.body!);
|
||||
|
||||
const outputTempFilePath = await makeTempFilePath("mp4");
|
||||
try {
|
||||
await ffmpegConvertToMP4(inputTempFilePath, outputTempFilePath);
|
||||
await worker.ffmpegConvertToMP4(inputTempFilePath, outputTempFilePath);
|
||||
} catch (e) {
|
||||
log.error("Conversion to MP4 failed", e);
|
||||
await deleteTempFileIgnoringErrors(outputTempFilePath);
|
||||
@@ -280,9 +277,9 @@ const handleVideoDone = async (token: string) => {
|
||||
*
|
||||
* The difference here is that we the conversion generates two streams^ - one
|
||||
* for the HLS playlist itself, and one for the file containing the encrypted
|
||||
* and transcoded video chunks. The video stream we write to the objectUploadURL
|
||||
* (provided via {@link params}), and then we return a JSON object containing
|
||||
* the token for the playlist, and other metadata for use by the renderer.
|
||||
* and transcoded video chunks. The video stream we write to the pre-signed
|
||||
* object upload URL(s), and then we return a JSON object containing the token
|
||||
* for the playlist, and other metadata for use by the renderer.
|
||||
*
|
||||
* ^ if the video doesn't require a stream to be generated (e.g. it is very
|
||||
* small and already uses a compatible codec) then a HTT 204 is returned and
|
||||
@@ -292,10 +289,12 @@ const handleGenerateHLSWrite = async (
|
||||
request: Request,
|
||||
params: URLSearchParams,
|
||||
) => {
|
||||
const objectUploadURL = params.get("objectUploadURL");
|
||||
if (!objectUploadURL) throw new Error("Missing objectUploadURL");
|
||||
const fileID = parseInt(params.get("fileID") ?? "", 10);
|
||||
const fetchURL = params.get("fetchURL");
|
||||
const authToken = params.get("authToken");
|
||||
if (!fileID || !fetchURL || !authToken) throw new Error("Missing params");
|
||||
|
||||
let inputItem: Parameters<typeof makeFileForDataOrStreamOrPathOrZipItem>[0];
|
||||
let inputItem: Parameters<typeof makeFileForStreamOrPathOrZipItem>[0];
|
||||
const path = params.get("path");
|
||||
if (path) {
|
||||
inputItem = path;
|
||||
@@ -311,20 +310,25 @@ const handleGenerateHLSWrite = async (
|
||||
}
|
||||
}
|
||||
|
||||
const worker = await ffmpegUtilityProcess();
|
||||
|
||||
const {
|
||||
path: inputFilePath,
|
||||
isFileTemporary: isInputFileTemporary,
|
||||
writeToTemporaryFile: writeToTemporaryInputFile,
|
||||
} = await makeFileForDataOrStreamOrPathOrZipItem(inputItem);
|
||||
} = await makeFileForStreamOrPathOrZipItem(inputItem);
|
||||
|
||||
const outputFilePathPrefix = await makeTempFilePath();
|
||||
let result: FFmpegGenerateHLSPlaylistAndSegmentsResult | undefined;
|
||||
try {
|
||||
await writeToTemporaryInputFile();
|
||||
|
||||
result = await ffmpegGenerateHLSPlaylistAndSegments(
|
||||
result = await worker.ffmpegGenerateHLSPlaylistAndSegments(
|
||||
inputFilePath,
|
||||
outputFilePathPrefix,
|
||||
fileID,
|
||||
fetchURL,
|
||||
authToken,
|
||||
);
|
||||
|
||||
if (!result) {
|
||||
@@ -332,94 +336,22 @@ const handleGenerateHLSWrite = async (
|
||||
return new Response(null, { status: 204 });
|
||||
}
|
||||
|
||||
const { playlistPath, videoPath } = result;
|
||||
try {
|
||||
await uploadVideoSegments(videoPath, objectUploadURL);
|
||||
const { playlistPath, dimensions, videoSize, videoObjectID } = result;
|
||||
|
||||
const playlistToken = randomUUID();
|
||||
pendingVideoResults.set(playlistToken, playlistPath);
|
||||
const playlistToken = randomUUID();
|
||||
pendingVideoResults.set(playlistToken, playlistPath);
|
||||
|
||||
const { dimensions, videoSize } = result;
|
||||
return new Response(
|
||||
JSON.stringify({ playlistToken, dimensions, videoSize }),
|
||||
{ status: 200 },
|
||||
);
|
||||
} catch (e) {
|
||||
await deleteTempFileIgnoringErrors(playlistPath);
|
||||
throw e;
|
||||
} finally {
|
||||
await deleteTempFileIgnoringErrors(videoPath);
|
||||
}
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
playlistToken,
|
||||
dimensions,
|
||||
videoSize,
|
||||
videoObjectID,
|
||||
}),
|
||||
{ status: 200 },
|
||||
);
|
||||
} finally {
|
||||
if (isInputFileTemporary)
|
||||
await deleteTempFileIgnoringErrors(inputFilePath);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Upload the file at the given {@link videoFilePath} to the provided presigned
|
||||
* {@link objectUploadURL} using a HTTP PUT request.
|
||||
*
|
||||
* In case on non-HTTP-4xx errors, retry up to 3 times with exponential backoff.
|
||||
*
|
||||
* See: [Note: Upload HLS video segment from node side].
|
||||
*
|
||||
* ---
|
||||
*
|
||||
* This is an inlined but bespoke reimplementation of `retryEnsuringHTTPOkOr4xx`
|
||||
* from `web/packages/base/http.ts` (we don't have the rest of the scaffolding
|
||||
* used by that function, which is why it is inlined bespoked).
|
||||
*
|
||||
* It handles the specific use case of uploading videos since generating the HLS
|
||||
* stream is a fairly expensive operation, so a retry to discount transient
|
||||
* network issues is called for. There are only 2 retries for a total of 3
|
||||
* attempts, and the retry gaps are more spaced out.
|
||||
*/
|
||||
export const uploadVideoSegments = async (
|
||||
videoFilePath: string,
|
||||
objectUploadURL: string,
|
||||
) => {
|
||||
const waitTimeBeforeNextTry = [5000, 20000];
|
||||
|
||||
while (true) {
|
||||
let abort = false;
|
||||
try {
|
||||
const nodeStream = fs_.createReadStream(videoFilePath);
|
||||
const webStream = Readable.toWeb(nodeStream);
|
||||
|
||||
const res = await net.fetch(objectUploadURL, {
|
||||
method: "PUT",
|
||||
// The duplex option is required since we're passing a stream.
|
||||
//
|
||||
// @ts-expect-error TypeScript's libdom.d.ts does not include
|
||||
// the "duplex" parameter, e.g. see
|
||||
// https://github.com/node-fetch/node-fetch/issues/1769.
|
||||
duplex: "half",
|
||||
body: webStream,
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
// Success.
|
||||
return;
|
||||
}
|
||||
if (res.status >= 400 && res.status < 500) {
|
||||
// HTTP 4xx.
|
||||
abort = true;
|
||||
}
|
||||
throw new Error(
|
||||
`Failed to upload generated HLS video: HTTP ${res.status} ${res.statusText}`,
|
||||
);
|
||||
} catch (e) {
|
||||
if (abort) {
|
||||
throw e;
|
||||
}
|
||||
const t = waitTimeBeforeNextTry.shift();
|
||||
if (!t) {
|
||||
throw e;
|
||||
} else {
|
||||
log.warn("Will retry potentially transient request failure", e);
|
||||
}
|
||||
await wait(t);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@ export const messagePortMainEndpoint = (mp: MessagePortMain): Endpoint => {
|
||||
const listeners = new WeakMap<NL, EL>();
|
||||
return {
|
||||
postMessage: (message, transfer) => {
|
||||
mp.postMessage(message, transfer as unknown as MessagePortMain[]);
|
||||
mp.postMessage(message, (transfer ?? []) as MessagePortMain[]);
|
||||
},
|
||||
addEventListener: (_, eh) => {
|
||||
const l: EL = (data) =>
|
||||
|
||||
@@ -15,3 +15,11 @@
|
||||
*/
|
||||
export const wait = (ms: number) =>
|
||||
new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
/**
|
||||
* Convert `null` to `undefined`, passthrough everything else unchanged.
|
||||
*
|
||||
* Duplicated from `web/packages/utils/transform.ts`.
|
||||
*/
|
||||
export const nullToUndefined = <T>(v: T | null | undefined): T | undefined =>
|
||||
v === null ? undefined : v;
|
||||
|
||||
23
desktop/src/main/utils/exec-worker.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import shellescape from "any-shell-escape";
|
||||
import { exec } from "node:child_process";
|
||||
import { promisify } from "node:util";
|
||||
import log from "../log-worker";
|
||||
|
||||
/**
|
||||
* Run a shell command asynchronously (utility process edition).
|
||||
*
|
||||
* This is an almost verbatim copy of {@link execAsync} from `electron.ts`,
|
||||
* except it is meant to be usable from a utility process where only a subset of
|
||||
* imports are available. See [Note: Using Electron APIs in UtilityProcess].
|
||||
*/
|
||||
export const execAsyncWorker = async (command: string | string[]) => {
|
||||
const escapedCommand = Array.isArray(command)
|
||||
? shellescape(command)
|
||||
: command;
|
||||
const startTime = Date.now();
|
||||
const result = await execAsync_(escapedCommand);
|
||||
log.debugString(`${escapedCommand} (${Date.now() - startTime} ms)`);
|
||||
return result;
|
||||
};
|
||||
|
||||
const execAsync_ = promisify(exec);
|
||||
31
desktop/src/main/utils/http.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
export const clientPackageName = "io.ente.photos.desktop";
|
||||
|
||||
/**
|
||||
* Reimplementation of {@link publicRequestHeaders} from the web source.
|
||||
*
|
||||
* @param desktopAppVersion The desktop app's version. This will get passed on
|
||||
* as the "X-Client-Version" header.
|
||||
*
|
||||
* We cannot directly use `app.getVersion()` to obtain this value since the
|
||||
* {@link app} module is not accessible to Electron utility processes which also
|
||||
* calls this function.
|
||||
*/
|
||||
export const publicRequestHeaders = (desktopAppVersion: string) => ({
|
||||
"X-Client-Package": clientPackageName,
|
||||
"X-Client-Version": desktopAppVersion,
|
||||
});
|
||||
|
||||
/**
|
||||
* Reimplementation of {@link authenticatedRequestHeaders} from the web source.
|
||||
*
|
||||
* This builds on top of {@link publicRequestHeaders} and takes the same
|
||||
* parameters, and additionally also requires the {@link authToken} that will be
|
||||
* passed as the "X-Auth-Token" header.
|
||||
*/
|
||||
export const authenticatedRequestHeaders = (
|
||||
desktopAppVersion: string,
|
||||
authToken: string,
|
||||
) => ({
|
||||
...publicRequestHeaders(desktopAppVersion),
|
||||
"X-Auth-Token": authToken,
|
||||
});
|
||||
@@ -80,8 +80,8 @@ export const deleteTempFileIgnoringErrors = async (tempFilePath: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
/** The result of {@link makeFileForDataOrStreamOrPathOrZipItem}. */
|
||||
interface FileForDataOrPathOrZipItem {
|
||||
/** The result of {@link makeFileForStreamOrPathOrZipItem}. */
|
||||
interface FileForStreamOrPathOrZipItem {
|
||||
/**
|
||||
* The path to the file (possibly temporary).
|
||||
*/
|
||||
@@ -107,13 +107,13 @@ interface FileForDataOrPathOrZipItem {
|
||||
* that needs to be deleted after processing, and a function to write the given
|
||||
* {@link item} into that temporary file if needed.
|
||||
*
|
||||
* @param item The contents of the file (bytes), or a {@link ReadableStream}
|
||||
* with the contents of the file, or the path to an existing file, or a (path to
|
||||
* a zip file, name of an entry within that zip file) tuple.
|
||||
* @param item A {@link ReadableStream} with the contents of the file, or the
|
||||
* path to an existing file, or a (path to a zip file, name of an entry within
|
||||
* that zip file) tuple.
|
||||
*/
|
||||
export const makeFileForDataOrStreamOrPathOrZipItem = async (
|
||||
item: Uint8Array | ReadableStream | string | ZipItem,
|
||||
): Promise<FileForDataOrPathOrZipItem> => {
|
||||
export const makeFileForStreamOrPathOrZipItem = async (
|
||||
item: ReadableStream | string | ZipItem,
|
||||
): Promise<FileForStreamOrPathOrZipItem> => {
|
||||
let path: string;
|
||||
let isFileTemporary: boolean;
|
||||
let writeToTemporaryFile = async () => {
|
||||
@@ -126,9 +126,7 @@ export const makeFileForDataOrStreamOrPathOrZipItem = async (
|
||||
} else {
|
||||
path = await makeTempFilePath();
|
||||
isFileTemporary = true;
|
||||
if (item instanceof Uint8Array) {
|
||||
writeToTemporaryFile = () => fs.writeFile(path, item);
|
||||
} else if (item instanceof ReadableStream) {
|
||||
if (item instanceof ReadableStream) {
|
||||
writeToTemporaryFile = () => writeStream(path, item);
|
||||
} else {
|
||||
writeToTemporaryFile = async () => {
|
||||
|
||||
@@ -69,6 +69,7 @@ import type {
|
||||
FFmpegCommand,
|
||||
FolderWatch,
|
||||
PendingUploads,
|
||||
UtilityProcessType,
|
||||
ZipItem,
|
||||
} from "./types/ipc";
|
||||
|
||||
@@ -112,10 +113,11 @@ const logout = () => {
|
||||
return ipcRenderer.invoke("logout");
|
||||
};
|
||||
|
||||
const masterKeyB64 = () => ipcRenderer.invoke("masterKeyB64");
|
||||
const masterKeyFromSafeStorage = () =>
|
||||
ipcRenderer.invoke("masterKeyFromSafeStorage");
|
||||
|
||||
const saveMasterKeyB64 = (masterKeyB64: string) =>
|
||||
ipcRenderer.invoke("saveMasterKeyB64", masterKeyB64);
|
||||
const saveMasterKeyInSafeStorage = (masterKey: string) =>
|
||||
ipcRenderer.invoke("saveMasterKeyInSafeStorage", masterKey);
|
||||
|
||||
const lastShownChangelogVersion = () =>
|
||||
ipcRenderer.invoke("lastShownChangelogVersion");
|
||||
@@ -192,41 +194,45 @@ const convertToJPEG = (imageData: Uint8Array) =>
|
||||
ipcRenderer.invoke("convertToJPEG", imageData);
|
||||
|
||||
const generateImageThumbnail = (
|
||||
dataOrPathOrZipItem: Uint8Array | string | ZipItem,
|
||||
pathOrZipItem: string | ZipItem,
|
||||
maxDimension: number,
|
||||
maxSize: number,
|
||||
) =>
|
||||
ipcRenderer.invoke(
|
||||
"generateImageThumbnail",
|
||||
dataOrPathOrZipItem,
|
||||
pathOrZipItem,
|
||||
maxDimension,
|
||||
maxSize,
|
||||
);
|
||||
|
||||
const ffmpegExec = (
|
||||
command: FFmpegCommand,
|
||||
dataOrPathOrZipItem: Uint8Array | string | ZipItem,
|
||||
pathOrZipItem: string | ZipItem,
|
||||
outputFileExtension: string,
|
||||
) =>
|
||||
ipcRenderer.invoke(
|
||||
"ffmpegExec",
|
||||
command,
|
||||
dataOrPathOrZipItem,
|
||||
pathOrZipItem,
|
||||
outputFileExtension,
|
||||
);
|
||||
|
||||
// - ML
|
||||
const ffmpegDetermineVideoDuration = (pathOrZipItem: string | ZipItem) =>
|
||||
ipcRenderer.invoke("ffmpegDetermineVideoDuration", pathOrZipItem);
|
||||
|
||||
const createMLWorker = () => {
|
||||
// - Utility processes
|
||||
|
||||
const triggerCreateUtilityProcess = (type: UtilityProcessType) => {
|
||||
const portEvent = `utilityProcessPort/${type}`;
|
||||
const l = (event: IpcRendererEvent) => {
|
||||
void windowLoaded.then(() => {
|
||||
// "*"" is the origin to send to.
|
||||
window.postMessage("createMLWorker/port", "*", event.ports);
|
||||
ipcRenderer.off("createMLWorker/port", l);
|
||||
window.postMessage(portEvent, "*", event.ports);
|
||||
ipcRenderer.off(portEvent, l);
|
||||
});
|
||||
};
|
||||
ipcRenderer.on("createMLWorker/port", l);
|
||||
ipcRenderer.send("createMLWorker");
|
||||
ipcRenderer.on(portEvent, l);
|
||||
ipcRenderer.send("triggerCreateUtilityProcess", type);
|
||||
};
|
||||
|
||||
// - Watch
|
||||
@@ -353,8 +359,8 @@ contextBridge.exposeInMainWorld("electron", {
|
||||
selectDirectory,
|
||||
pathForFile,
|
||||
logout,
|
||||
masterKeyB64,
|
||||
saveMasterKeyB64,
|
||||
masterKeyFromSafeStorage,
|
||||
saveMasterKeyInSafeStorage,
|
||||
lastShownChangelogVersion,
|
||||
setLastShownChangelogVersion,
|
||||
isAutoLaunchEnabled,
|
||||
@@ -390,10 +396,11 @@ contextBridge.exposeInMainWorld("electron", {
|
||||
convertToJPEG,
|
||||
generateImageThumbnail,
|
||||
ffmpegExec,
|
||||
ffmpegDetermineVideoDuration,
|
||||
|
||||
// - ML
|
||||
|
||||
createMLWorker,
|
||||
triggerCreateUtilityProcess,
|
||||
|
||||
// - Watch
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
* See [Note: types.ts <-> preload.ts <-> ipc.ts]
|
||||
*/
|
||||
|
||||
export type UtilityProcessType = "ml";
|
||||
|
||||
export interface AppUpdate {
|
||||
autoUpdatable: boolean;
|
||||
version: string;
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
ajv "^6.12.0"
|
||||
ajv-keywords "^3.4.1"
|
||||
|
||||
"@electron/asar@3.4.1":
|
||||
"@electron/asar@3.4.1", "@electron/asar@^3.3.1":
|
||||
version "3.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@electron/asar/-/asar-3.4.1.tgz#4e9196a4b54fba18c56cd8d5cac67c5bdc588065"
|
||||
integrity sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==
|
||||
@@ -34,15 +34,6 @@
|
||||
glob "^7.1.6"
|
||||
minimatch "^3.0.4"
|
||||
|
||||
"@electron/asar@^3.2.7":
|
||||
version "3.2.18"
|
||||
resolved "https://registry.yarnpkg.com/@electron/asar/-/asar-3.2.18.tgz#fa607f829209bab8b9e0ce6658d3fe81b2cba517"
|
||||
integrity sha512-2XyvMe3N3Nrs8cV39IKELRHTYUWFKrmqqSY1U+GMlc0jvqjIVnoxhNd2H4JolWQncbJi1DCvb5TNxZuI2fEjWg==
|
||||
dependencies:
|
||||
commander "^5.0.0"
|
||||
glob "^7.1.6"
|
||||
minimatch "^3.0.4"
|
||||
|
||||
"@electron/fuses@^1.8.0":
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@electron/fuses/-/fuses-1.8.0.tgz#ad34d3cc4703b1258b83f6989917052cfc1490a0"
|
||||
@@ -91,10 +82,10 @@
|
||||
fs-extra "^9.0.1"
|
||||
promise-retry "^2.0.1"
|
||||
|
||||
"@electron/osx-sign@1.3.1":
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@electron/osx-sign/-/osx-sign-1.3.1.tgz#faf7eeca7ca004a6be541dc4cf7a1bd59ec59b1c"
|
||||
integrity sha512-BAfviURMHpmb1Yb50YbCxnOY0wfwaLXH5KJ4+80zS0gUkzDX3ec23naTlEqKsN+PwYn+a1cCzM7BJ4Wcd3sGzw==
|
||||
"@electron/osx-sign@1.3.3":
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/@electron/osx-sign/-/osx-sign-1.3.3.tgz#af751510488318d9f7663694af85819690d75583"
|
||||
integrity sha512-KZ8mhXvWv2rIEgMbWZ4y33bDHyUKMXnx4M0sTyPNK/vcB81ImdeY9Ggdqy0SWbMDgmbqyQ+phgejh6V3R2QuSg==
|
||||
dependencies:
|
||||
compare-version "^0.1.2"
|
||||
debug "^4.3.4"
|
||||
@@ -123,12 +114,12 @@
|
||||
tar "^6.0.5"
|
||||
yargs "^17.0.1"
|
||||
|
||||
"@electron/universal@2.0.1":
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@electron/universal/-/universal-2.0.1.tgz#7b070ab355e02957388f3dbd68e2c3cd08c448ae"
|
||||
integrity sha512-fKpv9kg4SPmt+hY7SVBnIYULE9QJl8L3sCfcBsnqbJwwBwAeTLokJ9TRt9y7bK0JAzIW2y78TVVjvnQEms/yyA==
|
||||
"@electron/universal@2.0.3":
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@electron/universal/-/universal-2.0.3.tgz#1680df6ced8f128ca0ff24e29c2165d41d78b3ce"
|
||||
integrity sha512-Wn9sPYIVFRFl5HmwMJkARCCf7rqK/EurkfQ/rJZ14mHP3iYTjZSIOSVonEAnhWeAXwtw7zOekGRlc6yTtZ0t+g==
|
||||
dependencies:
|
||||
"@electron/asar" "^3.2.7"
|
||||
"@electron/asar" "^3.3.1"
|
||||
"@malept/cross-spawn-promise" "^2.0.0"
|
||||
debug "^4.3.1"
|
||||
dir-compare "^4.2.0"
|
||||
@@ -136,13 +127,20 @@
|
||||
minimatch "^9.0.3"
|
||||
plist "^3.1.0"
|
||||
|
||||
"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
|
||||
"@eslint-community/eslint-utils@^4.2.0":
|
||||
version "4.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
|
||||
integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==
|
||||
dependencies:
|
||||
eslint-visitor-keys "^3.3.0"
|
||||
|
||||
"@eslint-community/eslint-utils@^4.7.0":
|
||||
version "4.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz#607084630c6c033992a082de6e6fbc1a8b52175a"
|
||||
integrity sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==
|
||||
dependencies:
|
||||
eslint-visitor-keys "^3.4.3"
|
||||
|
||||
"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.11.0":
|
||||
version "4.11.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.0.tgz#b0ffd0312b4a3fd2d6f77237e7248a5ad3a680ae"
|
||||
@@ -177,10 +175,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.9.1.tgz#4a97e85e982099d6c7ee8410aacb55adaa576f06"
|
||||
integrity sha512-xIDQRsfg5hNBqHz04H1R3scSVwmI+KUbqjsQKHKQ1DAUSaUjYPReZZmS/5PNiKu1fUvzDd6H7DEDKACSEhu+TQ==
|
||||
|
||||
"@eslint/js@^9.25.1":
|
||||
version "9.25.1"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.25.1.tgz#25f5c930c2b68b5ebe7ac857f754cbd61ef6d117"
|
||||
integrity sha512-dEIwmjntEx8u3Uvv+kr3PDeeArL8Hw07H9kyYxCjnM9pBjfEhk6uLXSchxxzgiwtRhhzVzqmUSDFBOi1TuZ7qg==
|
||||
"@eslint/js@^9.28.0":
|
||||
version "9.28.0"
|
||||
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.28.0.tgz#7822ccc2f8cae7c3cd4f902377d520e9ae03f844"
|
||||
integrity sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==
|
||||
|
||||
"@eslint/object-schema@^2.1.4":
|
||||
version "2.1.4"
|
||||
@@ -202,6 +200,18 @@
|
||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.0.tgz#6d86b8cb322660f03d3f0aa94b99bdd8e172d570"
|
||||
integrity sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==
|
||||
|
||||
"@isaacs/balanced-match@^4.0.1":
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz#3081dadbc3460661b751e7591d7faea5df39dd29"
|
||||
integrity sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==
|
||||
|
||||
"@isaacs/brace-expansion@^5.0.0":
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz#4b3dabab7d8e75a429414a96bd67bf4c1d13e0f3"
|
||||
integrity sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==
|
||||
dependencies:
|
||||
"@isaacs/balanced-match" "^4.0.1"
|
||||
|
||||
"@isaacs/fs-minipass@^4.0.0":
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz#2d59ae3ab4b38fb4270bfa23d30f8e2e86c7fe32"
|
||||
@@ -268,10 +278,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
|
||||
integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==
|
||||
|
||||
"@pkgr/core@^0.1.0":
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31"
|
||||
integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==
|
||||
"@pkgr/core@^0.2.4":
|
||||
version "0.2.4"
|
||||
resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.2.4.tgz#d897170a2b0ba51f78a099edccd968f7b103387c"
|
||||
integrity sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw==
|
||||
|
||||
"@sindresorhus/is@^4.0.0":
|
||||
version "4.6.0"
|
||||
@@ -290,10 +300,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
|
||||
integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==
|
||||
|
||||
"@tsconfig/node22@^22.0.1":
|
||||
version "22.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@tsconfig/node22/-/node22-22.0.1.tgz#27e3ee9b359e31e5b94690bf2bad5a923c1d57d0"
|
||||
integrity sha512-VkgOa3n6jvs1p+r3DiwBqeEwGAwEvnVCg/hIjiANl5IEcqP3G0u5m8cBJspe1t9qjZRlZ7WFgqq5bJrGdgAKMg==
|
||||
"@tsconfig/node22@^22.0.2":
|
||||
version "22.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@tsconfig/node22/-/node22-22.0.2.tgz#1e04e2c5cc946dac787d69bb502462a851ae51b6"
|
||||
integrity sha512-Kmwj4u8sDRDrMYRoN9FDEcXD8UpBSaPQQ24Gz+Gamqfm7xxn+GBR7ge/Z7pK8OXNGyUzbSwJj+TH6B+DS/epyA==
|
||||
|
||||
"@types/auto-launch@^5.0.5":
|
||||
version "5.0.5"
|
||||
@@ -385,85 +395,101 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@typescript-eslint/eslint-plugin@8.31.1":
|
||||
version "8.31.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.31.1.tgz#62f1befe59647524994e89de4516d8dcba7a850a"
|
||||
integrity sha512-oUlH4h1ABavI4F0Xnl8/fOtML/eu8nI2A1nYd+f+55XI0BLu+RIqKoCiZKNo6DtqZBEQm5aNKA20G3Z5w3R6GQ==
|
||||
"@typescript-eslint/eslint-plugin@8.33.1":
|
||||
version "8.33.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.33.1.tgz#532641b416ed2afd5be893cddb2a58e9cd1f7a3e"
|
||||
integrity sha512-TDCXj+YxLgtvxvFlAvpoRv9MAncDLBV2oT9Bd7YBGC/b/sEURoOYuIwLI99rjWOfY3QtDzO+mk0n4AmdFExW8A==
|
||||
dependencies:
|
||||
"@eslint-community/regexpp" "^4.10.0"
|
||||
"@typescript-eslint/scope-manager" "8.31.1"
|
||||
"@typescript-eslint/type-utils" "8.31.1"
|
||||
"@typescript-eslint/utils" "8.31.1"
|
||||
"@typescript-eslint/visitor-keys" "8.31.1"
|
||||
"@typescript-eslint/scope-manager" "8.33.1"
|
||||
"@typescript-eslint/type-utils" "8.33.1"
|
||||
"@typescript-eslint/utils" "8.33.1"
|
||||
"@typescript-eslint/visitor-keys" "8.33.1"
|
||||
graphemer "^1.4.0"
|
||||
ignore "^5.3.1"
|
||||
ignore "^7.0.0"
|
||||
natural-compare "^1.4.0"
|
||||
ts-api-utils "^2.0.1"
|
||||
ts-api-utils "^2.1.0"
|
||||
|
||||
"@typescript-eslint/parser@8.31.1":
|
||||
version "8.31.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.31.1.tgz#e9b0ccf30d37dde724ee4d15f4dbc195995cce1b"
|
||||
integrity sha512-oU/OtYVydhXnumd0BobL9rkJg7wFJ9bFFPmSmB/bf/XWN85hlViji59ko6bSKBXyseT9V8l+CN1nwmlbiN0G7Q==
|
||||
"@typescript-eslint/parser@8.33.1":
|
||||
version "8.33.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.33.1.tgz#ef9a5ee6aa37a6b4f46cc36d08a14f828238afe2"
|
||||
integrity sha512-qwxv6dq682yVvgKKp2qWwLgRbscDAYktPptK4JPojCwwi3R9cwrvIxS4lvBpzmcqzR4bdn54Z0IG1uHFskW4dA==
|
||||
dependencies:
|
||||
"@typescript-eslint/scope-manager" "8.31.1"
|
||||
"@typescript-eslint/types" "8.31.1"
|
||||
"@typescript-eslint/typescript-estree" "8.31.1"
|
||||
"@typescript-eslint/visitor-keys" "8.31.1"
|
||||
"@typescript-eslint/scope-manager" "8.33.1"
|
||||
"@typescript-eslint/types" "8.33.1"
|
||||
"@typescript-eslint/typescript-estree" "8.33.1"
|
||||
"@typescript-eslint/visitor-keys" "8.33.1"
|
||||
debug "^4.3.4"
|
||||
|
||||
"@typescript-eslint/scope-manager@8.31.1":
|
||||
version "8.31.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.31.1.tgz#1eb52e76878f545e4add142e0d8e3e97e7aa443b"
|
||||
integrity sha512-BMNLOElPxrtNQMIsFHE+3P0Yf1z0dJqV9zLdDxN/xLlWMlXK/ApEsVEKzpizg9oal8bAT5Sc7+ocal7AC1HCVw==
|
||||
"@typescript-eslint/project-service@8.33.1":
|
||||
version "8.33.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.33.1.tgz#c85e7d9a44d6a11fe64e73ac1ed47de55dc2bf9f"
|
||||
integrity sha512-DZR0efeNklDIHHGRpMpR5gJITQpu6tLr9lDJnKdONTC7vvzOlLAG/wcfxcdxEWrbiZApcoBCzXqU/Z458Za5Iw==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "8.31.1"
|
||||
"@typescript-eslint/visitor-keys" "8.31.1"
|
||||
|
||||
"@typescript-eslint/type-utils@8.31.1":
|
||||
version "8.31.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.31.1.tgz#be0f438fb24b03568e282a0aed85f776409f970c"
|
||||
integrity sha512-fNaT/m9n0+dpSp8G/iOQ05GoHYXbxw81x+yvr7TArTuZuCA6VVKbqWYVZrV5dVagpDTtj/O8k5HBEE/p/HM5LA==
|
||||
dependencies:
|
||||
"@typescript-eslint/typescript-estree" "8.31.1"
|
||||
"@typescript-eslint/utils" "8.31.1"
|
||||
"@typescript-eslint/tsconfig-utils" "^8.33.1"
|
||||
"@typescript-eslint/types" "^8.33.1"
|
||||
debug "^4.3.4"
|
||||
ts-api-utils "^2.0.1"
|
||||
|
||||
"@typescript-eslint/types@8.31.1":
|
||||
version "8.31.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.31.1.tgz#478ed6f7e8aee1be7b63a60212b6bffe1423b5d4"
|
||||
integrity sha512-SfepaEFUDQYRoA70DD9GtytljBePSj17qPxFHA/h3eg6lPTqGJ5mWOtbXCk1YrVU1cTJRd14nhaXWFu0l2troQ==
|
||||
|
||||
"@typescript-eslint/typescript-estree@8.31.1":
|
||||
version "8.31.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.31.1.tgz#37792fe7ef4d3021c7580067c8f1ae66daabacdf"
|
||||
integrity sha512-kaA0ueLe2v7KunYOyWYtlf/QhhZb7+qh4Yw6Ni5kgukMIG+iP773tjgBiLWIXYumWCwEq3nLW+TUywEp8uEeag==
|
||||
"@typescript-eslint/scope-manager@8.33.1":
|
||||
version "8.33.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.33.1.tgz#d1e0efb296da5097d054bc9972e69878a2afea73"
|
||||
integrity sha512-dM4UBtgmzHR9bS0Rv09JST0RcHYearoEoo3pG5B6GoTR9XcyeqX87FEhPo+5kTvVfKCvfHaHrcgeJQc6mrDKrA==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "8.31.1"
|
||||
"@typescript-eslint/visitor-keys" "8.31.1"
|
||||
"@typescript-eslint/types" "8.33.1"
|
||||
"@typescript-eslint/visitor-keys" "8.33.1"
|
||||
|
||||
"@typescript-eslint/tsconfig-utils@8.33.1", "@typescript-eslint/tsconfig-utils@^8.33.1":
|
||||
version "8.33.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.33.1.tgz#7836afcc097a4657a5ed56670851a450d8b70ab8"
|
||||
integrity sha512-STAQsGYbHCF0/e+ShUQ4EatXQ7ceh3fBCXkNU7/MZVKulrlq1usH7t2FhxvCpuCi5O5oi1vmVaAjrGeL71OK1g==
|
||||
|
||||
"@typescript-eslint/type-utils@8.33.1":
|
||||
version "8.33.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.33.1.tgz#d73ee1a29d8a0abe60d4abbff4f1d040f0de15fa"
|
||||
integrity sha512-1cG37d9xOkhlykom55WVwG2QRNC7YXlxMaMzqw2uPeJixBFfKWZgaP/hjAObqMN/u3fr5BrTwTnc31/L9jQ2ww==
|
||||
dependencies:
|
||||
"@typescript-eslint/typescript-estree" "8.33.1"
|
||||
"@typescript-eslint/utils" "8.33.1"
|
||||
debug "^4.3.4"
|
||||
ts-api-utils "^2.1.0"
|
||||
|
||||
"@typescript-eslint/types@8.33.1", "@typescript-eslint/types@^8.33.1":
|
||||
version "8.33.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.33.1.tgz#b693111bc2180f8098b68e9958cf63761657a55f"
|
||||
integrity sha512-xid1WfizGhy/TKMTwhtVOgalHwPtV8T32MS9MaH50Cwvz6x6YqRIPdD2WvW0XaqOzTV9p5xdLY0h/ZusU5Lokg==
|
||||
|
||||
"@typescript-eslint/typescript-estree@8.33.1":
|
||||
version "8.33.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.33.1.tgz#d271beed470bc915b8764e22365d4925c2ea265d"
|
||||
integrity sha512-+s9LYcT8LWjdYWu7IWs7FvUxpQ/DGkdjZeE/GGulHvv8rvYwQvVaUZ6DE+j5x/prADUgSbbCWZ2nPI3usuVeOA==
|
||||
dependencies:
|
||||
"@typescript-eslint/project-service" "8.33.1"
|
||||
"@typescript-eslint/tsconfig-utils" "8.33.1"
|
||||
"@typescript-eslint/types" "8.33.1"
|
||||
"@typescript-eslint/visitor-keys" "8.33.1"
|
||||
debug "^4.3.4"
|
||||
fast-glob "^3.3.2"
|
||||
is-glob "^4.0.3"
|
||||
minimatch "^9.0.4"
|
||||
semver "^7.6.0"
|
||||
ts-api-utils "^2.0.1"
|
||||
ts-api-utils "^2.1.0"
|
||||
|
||||
"@typescript-eslint/utils@8.31.1":
|
||||
version "8.31.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.31.1.tgz#5628ea0393598a0b2f143d0fc6d019f0dee9dd14"
|
||||
integrity sha512-2DSI4SNfF5T4oRveQ4nUrSjUqjMND0nLq9rEkz0gfGr3tg0S5KB6DhwR+WZPCjzkZl3cH+4x2ce3EsL50FubjQ==
|
||||
"@typescript-eslint/utils@8.33.1":
|
||||
version "8.33.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.33.1.tgz#ea22f40d3553da090f928cf17907e963643d4b96"
|
||||
integrity sha512-52HaBiEQUaRYqAXpfzWSR2U3gxk92Kw006+xZpElaPMg3C4PgM+A5LqwoQI1f9E5aZ/qlxAZxzm42WX+vn92SQ==
|
||||
dependencies:
|
||||
"@eslint-community/eslint-utils" "^4.4.0"
|
||||
"@typescript-eslint/scope-manager" "8.31.1"
|
||||
"@typescript-eslint/types" "8.31.1"
|
||||
"@typescript-eslint/typescript-estree" "8.31.1"
|
||||
"@eslint-community/eslint-utils" "^4.7.0"
|
||||
"@typescript-eslint/scope-manager" "8.33.1"
|
||||
"@typescript-eslint/types" "8.33.1"
|
||||
"@typescript-eslint/typescript-estree" "8.33.1"
|
||||
|
||||
"@typescript-eslint/visitor-keys@8.31.1":
|
||||
version "8.31.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.31.1.tgz#6742b0e3ba1e0c1e35bdaf78c03e759eb8dd8e75"
|
||||
integrity sha512-I+/rgqOVBn6f0o7NDTmAPWWC6NuqhV174lfYvAm9fUaWeiefLdux9/YI3/nLugEn9L8fcSi0XmpKi/r5u0nmpw==
|
||||
"@typescript-eslint/visitor-keys@8.33.1":
|
||||
version "8.33.1"
|
||||
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.33.1.tgz#6c6e002c24d13211df3df851767f24dfdb4f42bc"
|
||||
integrity sha512-3i8NrFcZeeDHJ+7ZUuDkGT+UHq+XoFGsymNK2jZCOHcfEzRQ0BdpRtdpSx/Iyf3MHLWIcLS0COuOPibKQboIiQ==
|
||||
dependencies:
|
||||
"@typescript-eslint/types" "8.31.1"
|
||||
"@typescript-eslint/types" "8.33.1"
|
||||
eslint-visitor-keys "^4.2.0"
|
||||
|
||||
"@xmldom/xmldom@^0.8.8":
|
||||
@@ -569,44 +595,45 @@ app-builder-bin@5.0.0-alpha.12:
|
||||
resolved "https://registry.yarnpkg.com/app-builder-bin/-/app-builder-bin-5.0.0-alpha.12.tgz#2daf82f8badc698e0adcc95ba36af4ff0650dc80"
|
||||
integrity sha512-j87o0j6LqPL3QRr8yid6c+Tt5gC7xNfYo6uQIQkorAC6MpeayVMZrEDzKmJJ/Hlv7EnOQpaRm53k6ktDYZyB6w==
|
||||
|
||||
app-builder-lib@26.0.14:
|
||||
version "26.0.14"
|
||||
resolved "https://registry.yarnpkg.com/app-builder-lib/-/app-builder-lib-26.0.14.tgz#a28fbefb600cf052d1259932f32289e043573f61"
|
||||
integrity sha512-nc/A9MUd95MCc7bR4yVW7Lhs9FZTA/l8QdV8PE1vZZOOiogK4dupBfCCJG0UqLU81JS62f078/bwAeuMjt3hfQ==
|
||||
app-builder-lib@26.0.17:
|
||||
version "26.0.17"
|
||||
resolved "https://registry.yarnpkg.com/app-builder-lib/-/app-builder-lib-26.0.17.tgz#b23c3722482d3363328248967da3238a44c8da0c"
|
||||
integrity sha512-fk8edQKtNVnjBUK0kvYEmpbgD3pn3zAwpisjor0KVZLe7kDtnHkaaczjuonshTW+eK1wHhS1W5P2Vv0/u9rwHQ==
|
||||
dependencies:
|
||||
"@develar/schema-utils" "~2.6.5"
|
||||
"@electron/asar" "3.4.1"
|
||||
"@electron/fuses" "^1.8.0"
|
||||
"@electron/notarize" "2.5.0"
|
||||
"@electron/osx-sign" "1.3.1"
|
||||
"@electron/osx-sign" "1.3.3"
|
||||
"@electron/rebuild" "3.7.2"
|
||||
"@electron/universal" "2.0.1"
|
||||
"@electron/universal" "2.0.3"
|
||||
"@malept/flatpak-bundler" "^0.4.0"
|
||||
"@types/fs-extra" "9.0.13"
|
||||
async-exit-hook "^2.0.1"
|
||||
builder-util "26.0.13"
|
||||
builder-util "26.0.17"
|
||||
builder-util-runtime "9.3.2"
|
||||
chromium-pickle-js "^0.2.0"
|
||||
ci-info "^4.2.0"
|
||||
config-file-ts "0.2.8-rc1"
|
||||
debug "^4.3.4"
|
||||
dotenv "^16.4.5"
|
||||
dotenv-expand "^11.0.6"
|
||||
ejs "^3.1.8"
|
||||
electron-publish "26.0.13"
|
||||
electron-publish "26.0.17"
|
||||
fs-extra "^10.1.0"
|
||||
hosted-git-info "^4.1.0"
|
||||
is-ci "^3.0.0"
|
||||
isbinaryfile "^5.0.0"
|
||||
js-yaml "^4.1.0"
|
||||
json5 "^2.2.3"
|
||||
lazy-val "^1.0.5"
|
||||
minimatch "^10.0.0"
|
||||
minimatch "^10.0.3"
|
||||
plist "3.1.0"
|
||||
resedit "^1.7.0"
|
||||
semver "^7.3.8"
|
||||
tar "^6.1.12"
|
||||
temp-file "^3.4.0"
|
||||
tiny-async-pool "1.3.0"
|
||||
which "^5.0.0"
|
||||
|
||||
applescript@^1.0.0:
|
||||
version "1.0.0"
|
||||
@@ -736,22 +763,22 @@ builder-util-runtime@9.3.2:
|
||||
debug "^4.3.4"
|
||||
sax "^1.2.4"
|
||||
|
||||
builder-util@26.0.13:
|
||||
version "26.0.13"
|
||||
resolved "https://registry.yarnpkg.com/builder-util/-/builder-util-26.0.13.tgz#a2c11f8e89e5392719e540d610d70d8413943d74"
|
||||
integrity sha512-6b64uHzywaL2KAG+rVcqk/Prta1m3I2Jo1d4d2CrApb6EeSk2V384tmSL0EniH+P8jaNbMp6qhg7cIALw32zRA==
|
||||
builder-util@26.0.17:
|
||||
version "26.0.17"
|
||||
resolved "https://registry.yarnpkg.com/builder-util/-/builder-util-26.0.17.tgz#2f000ae7789e0c7c8479397c99d0e25dec3b1d27"
|
||||
integrity sha512-fym+vg0kegrHBSCmkYYql2EbsLvnlUhIUKRQJ7EHjyftwMz8mibpvTRll3pzK1rtWm/VRdjl7AB397jdtg/Jmw==
|
||||
dependencies:
|
||||
"7zip-bin" "~5.2.0"
|
||||
"@types/debug" "^4.1.6"
|
||||
app-builder-bin "5.0.0-alpha.12"
|
||||
builder-util-runtime "9.3.2"
|
||||
chalk "^4.1.2"
|
||||
ci-info "^4.2.0"
|
||||
cross-spawn "^7.0.6"
|
||||
debug "^4.3.4"
|
||||
fs-extra "^10.1.0"
|
||||
http-proxy-agent "^7.0.0"
|
||||
https-proxy-agent "^7.0.0"
|
||||
is-ci "^3.0.0"
|
||||
js-yaml "^4.1.0"
|
||||
sanitize-filename "^1.6.3"
|
||||
source-map-support "^0.5.19"
|
||||
@@ -841,10 +868,10 @@ chromium-pickle-js@^0.2.0:
|
||||
resolved "https://registry.yarnpkg.com/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz#04a106672c18b085ab774d983dfa3ea138f22205"
|
||||
integrity sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==
|
||||
|
||||
ci-info@^3.2.0:
|
||||
version "3.9.0"
|
||||
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4"
|
||||
integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==
|
||||
ci-info@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-4.2.0.tgz#cbd21386152ebfe1d56f280a3b5feccbd96764c7"
|
||||
integrity sha512-cYY9mypksY8NRqgDB1XD1RiJL338v/551niynFTGkZOO2LHuB2OmOYxDIe/ttN9AHwrqdum1360G3ald0W9kCg==
|
||||
|
||||
clean-stack@^2.0.0:
|
||||
version "2.2.0"
|
||||
@@ -1007,6 +1034,17 @@ cross-env@^7.0.3:
|
||||
dependencies:
|
||||
cross-spawn "^7.0.1"
|
||||
|
||||
cross-spawn@^6.0.0:
|
||||
version "6.0.6"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.6.tgz#30d0efa0712ddb7eb5a76e1e8721bffafa6b5d57"
|
||||
integrity sha512-VqCUuhcd1iB+dsv8gxPttb5iZh/D0iubSP21g36KXdEuf6I5JiioesUVjpCdHV9MZRUfVFlvwtIUyPfxo5trtw==
|
||||
dependencies:
|
||||
nice-try "^1.0.4"
|
||||
path-key "^2.0.1"
|
||||
semver "^5.5.0"
|
||||
shebang-command "^1.2.0"
|
||||
which "^1.2.9"
|
||||
|
||||
cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.6:
|
||||
version "7.0.6"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
|
||||
@@ -1087,7 +1125,7 @@ detect-libc@^2.0.1:
|
||||
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.3.tgz#f0cd503b40f9939b894697d19ad50895e30cf700"
|
||||
integrity sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==
|
||||
|
||||
detect-newline@^4.0.0:
|
||||
detect-newline@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-4.0.1.tgz#fcefdb5713e1fb8cb2839b8b6ee22e6716ab8f23"
|
||||
integrity sha512-qE3Veg1YXzGHQhlA6jzebZN2qVf6NX+A7m7qlhCGG30dJixrAQhYOsJjsnBjJkCSmuOPpCk30145fr8FV0bzog==
|
||||
@@ -1105,13 +1143,13 @@ dir-compare@^4.2.0:
|
||||
minimatch "^3.0.5"
|
||||
p-limit "^3.1.0 "
|
||||
|
||||
dmg-builder@26.0.14:
|
||||
version "26.0.14"
|
||||
resolved "https://registry.yarnpkg.com/dmg-builder/-/dmg-builder-26.0.14.tgz#ce8180da319cf3ee05d42cd460a7509207ad474b"
|
||||
integrity sha512-0l7oEj175hee7NfnaUpb0zf7fsgh1SyHeLjDA0AtOMnBUfTGxPPwrifbUxfd73qzamrGNcyeqza+m/EJx3QUug==
|
||||
dmg-builder@26.0.17:
|
||||
version "26.0.17"
|
||||
resolved "https://registry.yarnpkg.com/dmg-builder/-/dmg-builder-26.0.17.tgz#a7cf71fd0f752c8db65f196c91ede4b49d962ec4"
|
||||
integrity sha512-ls+KKvW4u4/kSkPtdjkTpqSzuVGm90JWRrEulcR/Lu8rrEXaeTcXiesuo3/MWNeiEpwfFgNa8t/paQVH59lw7A==
|
||||
dependencies:
|
||||
app-builder-lib "26.0.14"
|
||||
builder-util "26.0.13"
|
||||
app-builder-lib "26.0.17"
|
||||
builder-util "26.0.17"
|
||||
builder-util-runtime "9.3.2"
|
||||
fs-extra "^10.1.0"
|
||||
iconv-lite "^0.6.2"
|
||||
@@ -1159,18 +1197,18 @@ ejs@^3.1.8:
|
||||
dependencies:
|
||||
jake "^10.8.5"
|
||||
|
||||
electron-builder@^26.0.14:
|
||||
version "26.0.14"
|
||||
resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-26.0.14.tgz#8927c6da42a69425d15e08f351e944ea0e7866da"
|
||||
integrity sha512-YBxpWLMGj0oS7fbS3LVingeZqFunU0F8s+uB9QTd5+wN4qgrf/rSGRkqoImbWg2+F2yHq11wmaA/Xr9xzvgQ0w==
|
||||
electron-builder@^26.0.17:
|
||||
version "26.0.17"
|
||||
resolved "https://registry.yarnpkg.com/electron-builder/-/electron-builder-26.0.17.tgz#3e252d624aa16031b1c81686ca573180fa437e3d"
|
||||
integrity sha512-PJbm3XAG9qje73j4iXi043F0JnzHEDP2y/MQBprW+zizZqT2DywTkFN14ryLt8aGkGnoYBP44Smccff0A2AafQ==
|
||||
dependencies:
|
||||
app-builder-lib "26.0.14"
|
||||
builder-util "26.0.13"
|
||||
app-builder-lib "26.0.17"
|
||||
builder-util "26.0.17"
|
||||
builder-util-runtime "9.3.2"
|
||||
chalk "^4.1.2"
|
||||
dmg-builder "26.0.14"
|
||||
ci-info "^4.2.0"
|
||||
dmg-builder "26.0.17"
|
||||
fs-extra "^10.1.0"
|
||||
is-ci "^3.0.0"
|
||||
lazy-val "^1.0.5"
|
||||
simple-update-notifier "2.0.0"
|
||||
yargs "^17.6.2"
|
||||
@@ -1180,13 +1218,13 @@ electron-log@^5.4.0:
|
||||
resolved "https://registry.yarnpkg.com/electron-log/-/electron-log-5.4.0.tgz#3180bf5194b2e2efacb62ec1392f8150faf4de6b"
|
||||
integrity sha512-AXI5OVppskrWxEAmCxuv8ovX+s2Br39CpCAgkGMNHQtjYT3IiVbSQTncEjFVGPgoH35ZygRm/mvUMBDWwhRxgg==
|
||||
|
||||
electron-publish@26.0.13:
|
||||
version "26.0.13"
|
||||
resolved "https://registry.yarnpkg.com/electron-publish/-/electron-publish-26.0.13.tgz#04340520e6e9de5262fecfa011658cfcc3fc8917"
|
||||
integrity sha512-O5hfHSwli5cegQ4JS3Dp0dZcheex6UCRE/qYyRQvhB6DhSwojiwTnAGEuQCJXc8K8Zxz2lku5Du3VwYHf8d5Lw==
|
||||
electron-publish@26.0.17:
|
||||
version "26.0.17"
|
||||
resolved "https://registry.yarnpkg.com/electron-publish/-/electron-publish-26.0.17.tgz#c4ed32d39de5796b019fbe8058dd971a6b72371b"
|
||||
integrity sha512-03hz7MEbzLmZpOCHB+TvoXvn3FW+bZyfgq2gCi4AaeqU6i8Jpx584CljFP8zuDbb0nJcN0uHhpvAWjufDkgyVg==
|
||||
dependencies:
|
||||
"@types/fs-extra" "^9.0.11"
|
||||
builder-util "26.0.13"
|
||||
builder-util "26.0.17"
|
||||
builder-util-runtime "9.3.2"
|
||||
chalk "^4.1.2"
|
||||
form-data "^4.0.0"
|
||||
@@ -1202,10 +1240,10 @@ electron-store@^8.2.0:
|
||||
conf "^10.2.0"
|
||||
type-fest "^2.17.0"
|
||||
|
||||
electron-updater@^6.6.3:
|
||||
version "6.6.3"
|
||||
resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-6.6.3.tgz#a1f53671ffbb08a475d495d48f0c0d971e665d5d"
|
||||
integrity sha512-i448/SwMtqxy5wqAcXScnWjiFxZp+hmWA2jZCmojcdfodEGhi/DWTdRP01mE3lCILb8hmdE28SBaHf1oQW3+kw==
|
||||
electron-updater@^6.6.5:
|
||||
version "6.6.5"
|
||||
resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-6.6.5.tgz#6614daa2f737c294471eee7ce7b61deda0d5543a"
|
||||
integrity sha512-jnk38WfByl2Pb0cje02xls/pJkvkq3AQZI7usDCLriU23adkerLTkRrugbCPuUxUOa79nY1g/rokHPWHZFBKyA==
|
||||
dependencies:
|
||||
builder-util-runtime "9.3.2"
|
||||
fs-extra "^10.1.0"
|
||||
@@ -1216,10 +1254,10 @@ electron-updater@^6.6.3:
|
||||
semver "^7.6.3"
|
||||
tiny-typed-emitter "^2.1.0"
|
||||
|
||||
electron@^36.1.0:
|
||||
version "36.1.0"
|
||||
resolved "https://registry.yarnpkg.com/electron/-/electron-36.1.0.tgz#9919b77e61cd1400acc6dd24f9db8451fba5f8eb"
|
||||
integrity sha512-gnp3BnbKdGsVc7cm1qlEaZc8pJsR08mIs8H/yTo8gHEtFkGGJbDTVZOYNAfbQlL0aXh+ozv+CnyiNeDNkT1Upg==
|
||||
electron@^36.4.0:
|
||||
version "36.4.0"
|
||||
resolved "https://registry.yarnpkg.com/electron/-/electron-36.4.0.tgz#9463bf5fa7565ae7be3a274f7f6a46359bcfe74d"
|
||||
integrity sha512-LLOOZEuW5oqvnjC7HBQhIqjIIJAZCIFjQxltQGLfEC7XFsBoZgQ3u3iFj+Kzw68Xj97u1n57Jdt7P98qLvUibQ==
|
||||
dependencies:
|
||||
"@electron/get" "^2.0.0"
|
||||
"@types/node" "^22.7.7"
|
||||
@@ -1289,7 +1327,7 @@ eslint-scope@^8.0.2:
|
||||
esrecurse "^4.3.0"
|
||||
estraverse "^5.2.0"
|
||||
|
||||
eslint-visitor-keys@^3.3.0:
|
||||
eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.3:
|
||||
version "3.4.3"
|
||||
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800"
|
||||
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
|
||||
@@ -1372,6 +1410,19 @@ esutils@^2.0.2:
|
||||
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
|
||||
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
|
||||
|
||||
execa@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8"
|
||||
integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==
|
||||
dependencies:
|
||||
cross-spawn "^6.0.0"
|
||||
get-stream "^4.0.0"
|
||||
is-stream "^1.1.0"
|
||||
npm-run-path "^2.0.0"
|
||||
p-finally "^1.0.0"
|
||||
signal-exit "^3.0.0"
|
||||
strip-eof "^1.0.0"
|
||||
|
||||
exponential-backoff@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz#64ac7526fe341ab18a39016cd22c787d01e00bf6"
|
||||
@@ -1438,10 +1489,10 @@ fd-slicer@~1.1.0:
|
||||
dependencies:
|
||||
pend "~1.2.0"
|
||||
|
||||
fdir@^6.4.2:
|
||||
version "6.4.2"
|
||||
resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.4.2.tgz#ddaa7ce1831b161bc3657bb99cb36e1622702689"
|
||||
integrity sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==
|
||||
fdir@^6.4.4:
|
||||
version "6.4.4"
|
||||
resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.4.4.tgz#1cfcf86f875a883e19a8fab53622cfe992e8d2f9"
|
||||
integrity sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==
|
||||
|
||||
ffmpeg-static@^5.2.0:
|
||||
version "5.2.0"
|
||||
@@ -1589,10 +1640,12 @@ get-intrinsic@^1.1.3, get-intrinsic@^1.2.4:
|
||||
has-symbols "^1.0.3"
|
||||
hasown "^2.0.0"
|
||||
|
||||
get-stdin@^9.0.0:
|
||||
version "9.0.0"
|
||||
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-9.0.0.tgz#3983ff82e03d56f1b2ea0d3e60325f39d703a575"
|
||||
integrity sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==
|
||||
get-stream@^4.0.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
|
||||
integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==
|
||||
dependencies:
|
||||
pump "^3.0.0"
|
||||
|
||||
get-stream@^5.1.0:
|
||||
version "5.2.0"
|
||||
@@ -1601,10 +1654,10 @@ get-stream@^5.1.0:
|
||||
dependencies:
|
||||
pump "^3.0.0"
|
||||
|
||||
git-hooks-list@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/git-hooks-list/-/git-hooks-list-3.1.0.tgz#386dc531dcc17474cf094743ff30987a3d3e70fc"
|
||||
integrity sha512-LF8VeHeR7v+wAbXqfgRlTSX/1BJR9Q1vEMR8JAz1cEg6GX07+zyj3sAdDvYjj/xnlIfVuGgj4qBei1K3hKH+PA==
|
||||
git-hooks-list@^4.0.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/git-hooks-list/-/git-hooks-list-4.1.1.tgz#ae340b82a9312354c73b48007f33840bbd83d3c0"
|
||||
integrity sha512-cmP497iLq54AZnv4YRAEMnEyQ1eIn4tGKbmswqwmFV4GBnAqE8NLtWxxdXa++AalfgL5EBH4IxTPyquEuGY/jA==
|
||||
|
||||
glob-parent@^5.1.2:
|
||||
version "5.1.2"
|
||||
@@ -1632,7 +1685,7 @@ glob@^10.3.12, glob@^10.3.7:
|
||||
package-json-from-dist "^1.0.0"
|
||||
path-scurry "^1.11.1"
|
||||
|
||||
glob@^7.0.0, glob@^7.1.3, glob@^7.1.6:
|
||||
glob@^7.1.3, glob@^7.1.6:
|
||||
version "7.2.3"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
|
||||
integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
|
||||
@@ -1830,11 +1883,16 @@ ieee754@^1.1.13:
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
||||
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
|
||||
|
||||
ignore@^5.2.0, ignore@^5.3.1:
|
||||
ignore@^5.2.0:
|
||||
version "5.3.2"
|
||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5"
|
||||
integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==
|
||||
|
||||
ignore@^7.0.0:
|
||||
version "7.0.4"
|
||||
resolved "https://registry.yarnpkg.com/ignore/-/ignore-7.0.4.tgz#a12c70d0f2607c5bf508fb65a40c75f037d7a078"
|
||||
integrity sha512-gJzzk+PQNznz8ysRrC0aOkBNVRBDtE1n53IqyqEf3PXrYwomFs5q4pGMizBMJF+ykh03insJ27hB8gSrD2Hn8A==
|
||||
|
||||
import-fresh@^3.2.1:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
|
||||
@@ -1884,13 +1942,6 @@ ip-address@^9.0.5:
|
||||
jsbn "1.1.0"
|
||||
sprintf-js "^1.1.3"
|
||||
|
||||
is-ci@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-3.0.1.tgz#db6ecbed1bd659c43dac0f45661e7674103d1867"
|
||||
integrity sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==
|
||||
dependencies:
|
||||
ci-info "^3.2.0"
|
||||
|
||||
is-core-module@^2.13.0:
|
||||
version "2.15.1"
|
||||
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.1.tgz#a7363a25bee942fefab0de13bf6aa372c82dcc37"
|
||||
@@ -1945,6 +1996,11 @@ is-plain-obj@^4.1.0:
|
||||
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz#d65025edec3657ce032fd7db63c97883eaed71f0"
|
||||
integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==
|
||||
|
||||
is-stream@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
|
||||
integrity sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==
|
||||
|
||||
is-unicode-supported@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7"
|
||||
@@ -1965,6 +2021,11 @@ isexe@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
||||
integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
|
||||
|
||||
isexe@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/isexe/-/isexe-3.1.1.tgz#4a407e2bd78ddfb14bea0c27c6f7072dde775f0d"
|
||||
integrity sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==
|
||||
|
||||
jackspeak@2.1.1, jackspeak@^3.1.2:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.1.1.tgz#2a42db4cfbb7e55433c28b6f75d8b796af9669cd"
|
||||
@@ -2216,12 +2277,12 @@ mimic-response@^3.1.0:
|
||||
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9"
|
||||
integrity sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==
|
||||
|
||||
minimatch@^10.0.0:
|
||||
version "10.0.1"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.0.1.tgz#ce0521856b453c86e25f2c4c0d03e6ff7ddc440b"
|
||||
integrity sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==
|
||||
minimatch@^10.0.3:
|
||||
version "10.0.3"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.0.3.tgz#cf7a0314a16c4d9ab73a7730a0e8e3c3502d47aa"
|
||||
integrity sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==
|
||||
dependencies:
|
||||
brace-expansion "^2.0.1"
|
||||
"@isaacs/brace-expansion" "^5.0.0"
|
||||
|
||||
minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
|
||||
version "3.1.2"
|
||||
@@ -2244,7 +2305,7 @@ minimatch@^9.0.3, minimatch@^9.0.4:
|
||||
dependencies:
|
||||
brace-expansion "^2.0.1"
|
||||
|
||||
minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6:
|
||||
minimist@^1.2.5, minimist@^1.2.6, minimist@^1.2.8:
|
||||
version "1.2.8"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
|
||||
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
|
||||
@@ -2363,6 +2424,11 @@ next-electron-server@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/next-electron-server/-/next-electron-server-1.0.0.tgz#03e133ed64a5ef671b6c6409f908c4901b1828cb"
|
||||
integrity sha512-fTUaHwT0Jry2fbdUSIkAiIqgDAInI5BJFF4/j90/okvZCYlyx6yxpXB30KpzmOG6TN/ESwyvsFJVvS2WHT8PAA==
|
||||
|
||||
nice-try@^1.0.4:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
|
||||
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
|
||||
|
||||
node-abi@^3.45.0:
|
||||
version "3.67.0"
|
||||
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.67.0.tgz#1d159907f18d18e18809dbbb5df47ed2426a08df"
|
||||
@@ -2399,6 +2465,13 @@ normalize-url@^6.0.1:
|
||||
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a"
|
||||
integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==
|
||||
|
||||
npm-run-path@^2.0.0:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
|
||||
integrity sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==
|
||||
dependencies:
|
||||
path-key "^2.0.0"
|
||||
|
||||
object-keys@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
|
||||
@@ -2463,6 +2536,11 @@ p-cancelable@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-2.1.1.tgz#aab7fbd416582fa32a3db49859c122487c5ed2cf"
|
||||
integrity sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==
|
||||
|
||||
p-finally@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
|
||||
integrity sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==
|
||||
|
||||
p-limit@^2.0.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
|
||||
@@ -2535,6 +2613,11 @@ path-is-absolute@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
|
||||
integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==
|
||||
|
||||
path-key@^2.0.0, path-key@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
|
||||
integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==
|
||||
|
||||
path-key@^3.1.0:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
|
||||
@@ -2599,13 +2682,13 @@ prettier-plugin-organize-imports@^4.1.0:
|
||||
resolved "https://registry.yarnpkg.com/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.1.0.tgz#f3d3764046a8e7ba6491431158b9be6ffd83b90f"
|
||||
integrity sha512-5aWRdCgv645xaa58X8lOxzZoiHAldAPChljr/MT0crXVOWTZ+Svl4hIWlz+niYSlO6ikE5UXkN1JrRvIP2ut0A==
|
||||
|
||||
prettier-plugin-packagejson@^2.5.10:
|
||||
version "2.5.10"
|
||||
resolved "https://registry.yarnpkg.com/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.5.10.tgz#f47068d0aa12efcdddb802189d8adae874ba00e7"
|
||||
integrity sha512-LUxATI5YsImIVSaaLJlJ3aE6wTD+nvots18U3GuQMJpUyClChaZlQrqx3dBnbhF20OnKWZyx8EgyZypQtBDtgQ==
|
||||
prettier-plugin-packagejson@^2.5.15:
|
||||
version "2.5.15"
|
||||
resolved "https://registry.yarnpkg.com/prettier-plugin-packagejson/-/prettier-plugin-packagejson-2.5.15.tgz#7ea880d4bb1681b5331ea7044efd3d653776f469"
|
||||
integrity sha512-2QSx6y4IT6LTwXtCvXAopENW5IP/aujC8fobEM2pDbs0IGkiVjW/ipPuYAHuXigbNe64aGWF7vIetukuzM3CBw==
|
||||
dependencies:
|
||||
sort-package-json "2.15.1"
|
||||
synckit "0.9.2"
|
||||
sort-package-json "3.2.1"
|
||||
synckit "0.11.8"
|
||||
|
||||
prettier@3.5.3:
|
||||
version "3.5.3"
|
||||
@@ -2829,6 +2912,11 @@ semver@^7.3.2, semver@^7.3.5, semver@^7.3.8, semver@^7.5.3, semver@^7.6.0, semve
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
|
||||
integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
|
||||
|
||||
semver@^7.7.1:
|
||||
version "7.7.2"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.2.tgz#67d99fdcd35cec21e6f8b87a7fd515a33f982b58"
|
||||
integrity sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==
|
||||
|
||||
serialize-error@^7.0.1:
|
||||
version "7.0.1"
|
||||
resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-7.0.1.tgz#f1360b0447f61ffb483ec4157c737fab7d778e18"
|
||||
@@ -2836,6 +2924,13 @@ serialize-error@^7.0.1:
|
||||
dependencies:
|
||||
type-fest "^0.13.1"
|
||||
|
||||
shebang-command@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
|
||||
integrity sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==
|
||||
dependencies:
|
||||
shebang-regex "^1.0.0"
|
||||
|
||||
shebang-command@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
|
||||
@@ -2843,6 +2938,11 @@ shebang-command@^2.0.0:
|
||||
dependencies:
|
||||
shebang-regex "^3.0.0"
|
||||
|
||||
shebang-regex@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
|
||||
integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==
|
||||
|
||||
shebang-regex@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
|
||||
@@ -2853,24 +2953,25 @@ shell-quote@^1.8.1:
|
||||
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680"
|
||||
integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==
|
||||
|
||||
shelljs@^0.8.5:
|
||||
version "0.8.5"
|
||||
resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c"
|
||||
integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==
|
||||
shelljs@^0.9.2:
|
||||
version "0.9.2"
|
||||
resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.9.2.tgz#a8ac724434520cd7ae24d52071e37a18ac2bb183"
|
||||
integrity sha512-S3I64fEiKgTZzKCC46zT/Ib9meqofLrQVbpSswtjFfAVDW+AZ54WTnAM/3/yENoxz/V1Cy6u3kiiEbQ4DNphvw==
|
||||
dependencies:
|
||||
glob "^7.0.0"
|
||||
execa "^1.0.0"
|
||||
fast-glob "^3.3.2"
|
||||
interpret "^1.0.0"
|
||||
rechoir "^0.6.2"
|
||||
|
||||
shx@^0.3.4:
|
||||
version "0.3.4"
|
||||
resolved "https://registry.yarnpkg.com/shx/-/shx-0.3.4.tgz#74289230b4b663979167f94e1935901406e40f02"
|
||||
integrity sha512-N6A9MLVqjxZYcVn8hLmtneQWIJtp8IKzMP4eMnx+nqkvXoqinUPCbUFLp2UcWTEIUONhlk0ewxr/jaVGlc+J+g==
|
||||
shx@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/shx/-/shx-0.4.0.tgz#c6ea6ace7e778da0ab32d2eab9def59d788e9336"
|
||||
integrity sha512-Z0KixSIlGPpijKgcH6oCMCbltPImvaKy0sGH8AkLRXw1KyzpKtaCTizP2xen+hNDqVF4xxgvA0KXSb9o4Q6hnA==
|
||||
dependencies:
|
||||
minimist "^1.2.3"
|
||||
shelljs "^0.8.5"
|
||||
minimist "^1.2.8"
|
||||
shelljs "^0.9.2"
|
||||
|
||||
signal-exit@^3.0.2:
|
||||
signal-exit@^3.0.0, signal-exit@^3.0.2:
|
||||
version "3.0.7"
|
||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
|
||||
integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
|
||||
@@ -2923,19 +3024,18 @@ sort-object-keys@^1.1.3:
|
||||
resolved "https://registry.yarnpkg.com/sort-object-keys/-/sort-object-keys-1.1.3.tgz#bff833fe85cab147b34742e45863453c1e190b45"
|
||||
integrity sha512-855pvK+VkU7PaKYPc+Jjnmt4EzejQHyhhF33q31qG8x7maDzkeFhAAThdCYay11CISO+qAMwjOBP+fPZe0IPyg==
|
||||
|
||||
sort-package-json@2.15.1:
|
||||
version "2.15.1"
|
||||
resolved "https://registry.yarnpkg.com/sort-package-json/-/sort-package-json-2.15.1.tgz#e5a035fad7da277b1947b9eecc93ea09c1c2526e"
|
||||
integrity sha512-9x9+o8krTT2saA9liI4BljNjwAbvUnWf11Wq+i/iZt8nl2UGYnf3TH5uBydE7VALmP7AGwlfszuEeL8BDyb0YA==
|
||||
sort-package-json@3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/sort-package-json/-/sort-package-json-3.2.1.tgz#889f3bdf43ceeff5fa4278a7c53ae5b1520d287e"
|
||||
integrity sha512-rTfRdb20vuoAn7LDlEtCqOkYfl2X+Qze6cLbNOzcDpbmKEhJI30tTN44d5shbKJnXsvz24QQhlCm81Bag7EOKg==
|
||||
dependencies:
|
||||
detect-indent "^7.0.1"
|
||||
detect-newline "^4.0.0"
|
||||
get-stdin "^9.0.0"
|
||||
git-hooks-list "^3.0.0"
|
||||
detect-newline "^4.0.1"
|
||||
git-hooks-list "^4.0.0"
|
||||
is-plain-obj "^4.1.0"
|
||||
semver "^7.6.0"
|
||||
semver "^7.7.1"
|
||||
sort-object-keys "^1.1.3"
|
||||
tinyglobby "^0.2.9"
|
||||
tinyglobby "^0.2.12"
|
||||
|
||||
source-map-support@^0.5.19:
|
||||
version "0.5.21"
|
||||
@@ -2990,6 +3090,11 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
dependencies:
|
||||
ansi-regex "^5.0.1"
|
||||
|
||||
strip-eof@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
|
||||
integrity sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==
|
||||
|
||||
strip-json-comments@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
|
||||
@@ -3021,13 +3126,12 @@ supports-preserve-symlinks-flag@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
|
||||
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
|
||||
|
||||
synckit@0.9.2:
|
||||
version "0.9.2"
|
||||
resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.9.2.tgz#a3a935eca7922d48b9e7d6c61822ee6c3ae4ec62"
|
||||
integrity sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==
|
||||
synckit@0.11.8:
|
||||
version "0.11.8"
|
||||
resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.11.8.tgz#b2aaae998a4ef47ded60773ad06e7cb821f55457"
|
||||
integrity sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==
|
||||
dependencies:
|
||||
"@pkgr/core" "^0.1.0"
|
||||
tslib "^2.6.2"
|
||||
"@pkgr/core" "^0.2.4"
|
||||
|
||||
tar@^6.0.5, tar@^6.1.11, tar@^6.1.12, tar@^6.2.1:
|
||||
version "6.2.1"
|
||||
@@ -3078,12 +3182,12 @@ tiny-typed-emitter@^2.1.0:
|
||||
resolved "https://registry.yarnpkg.com/tiny-typed-emitter/-/tiny-typed-emitter-2.1.0.tgz#b3b027fdd389ff81a152c8e847ee2f5be9fad7b5"
|
||||
integrity sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==
|
||||
|
||||
tinyglobby@^0.2.9:
|
||||
version "0.2.10"
|
||||
resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.10.tgz#e712cf2dc9b95a1f5c5bbd159720e15833977a0f"
|
||||
integrity sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==
|
||||
tinyglobby@^0.2.12:
|
||||
version "0.2.13"
|
||||
resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.13.tgz#a0e46515ce6cbcd65331537e57484af5a7b2ff7e"
|
||||
integrity sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==
|
||||
dependencies:
|
||||
fdir "^6.4.2"
|
||||
fdir "^6.4.4"
|
||||
picomatch "^4.0.2"
|
||||
|
||||
tmp-promise@^3.0.2:
|
||||
@@ -3117,12 +3221,12 @@ truncate-utf8-bytes@^1.0.0:
|
||||
dependencies:
|
||||
utf8-byte-length "^1.0.1"
|
||||
|
||||
ts-api-utils@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.0.1.tgz#660729385b625b939aaa58054f45c058f33f10cd"
|
||||
integrity sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==
|
||||
ts-api-utils@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-2.1.0.tgz#595f7094e46eed364c13fd23e75f9513d29baf91"
|
||||
integrity sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==
|
||||
|
||||
tslib@^2.1.0, tslib@^2.6.2:
|
||||
tslib@^2.1.0:
|
||||
version "2.7.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01"
|
||||
integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==
|
||||
@@ -3149,14 +3253,14 @@ typedarray@^0.0.6:
|
||||
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
|
||||
integrity sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==
|
||||
|
||||
typescript-eslint@^8.31.1:
|
||||
version "8.31.1"
|
||||
resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.31.1.tgz#b77ab1e48ced2daab9225ff94bab54391a4af69b"
|
||||
integrity sha512-j6DsEotD/fH39qKzXTQRwYYWlt7D+0HmfpOK+DVhwJOFLcdmn92hq3mBb7HlKJHbjjI/gTOqEcc9d6JfpFf/VA==
|
||||
typescript-eslint@^8.33.1:
|
||||
version "8.33.1"
|
||||
resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.33.1.tgz#d2d59c9b24afe1f903a855b02145802e4ae930ff"
|
||||
integrity sha512-AgRnV4sKkWOiZ0Kjbnf5ytTJXMUZQ0qhSVdQtDNYLPLnjsATEYhaO94GlRQwi4t4gO8FfjM6NnikHeKjUm8D7A==
|
||||
dependencies:
|
||||
"@typescript-eslint/eslint-plugin" "8.31.1"
|
||||
"@typescript-eslint/parser" "8.31.1"
|
||||
"@typescript-eslint/utils" "8.31.1"
|
||||
"@typescript-eslint/eslint-plugin" "8.33.1"
|
||||
"@typescript-eslint/parser" "8.33.1"
|
||||
"@typescript-eslint/utils" "8.33.1"
|
||||
|
||||
typescript@^5.4.3, typescript@^5.8.3:
|
||||
version "5.8.3"
|
||||
@@ -3230,6 +3334,13 @@ wcwidth@^1.0.1:
|
||||
dependencies:
|
||||
defaults "^1.0.3"
|
||||
|
||||
which@^1.2.9:
|
||||
version "1.3.1"
|
||||
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
|
||||
integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
|
||||
dependencies:
|
||||
isexe "^2.0.0"
|
||||
|
||||
which@^2.0.1, which@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
|
||||
@@ -3237,6 +3348,13 @@ which@^2.0.1, which@^2.0.2:
|
||||
dependencies:
|
||||
isexe "^2.0.0"
|
||||
|
||||
which@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/which/-/which-5.0.0.tgz#d93f2d93f79834d4363c7d0c23e00d07c466c8d6"
|
||||
integrity sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==
|
||||
dependencies:
|
||||
isexe "^3.1.1"
|
||||
|
||||
winreg@1.2.4:
|
||||
version "1.2.4"
|
||||
resolved "https://registry.yarnpkg.com/winreg/-/winreg-1.2.4.tgz#ba065629b7a925130e15779108cf540990e98d1b"
|
||||
@@ -3311,3 +3429,8 @@ yocto-queue@^0.1.0:
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
|
||||
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
|
||||
|
||||
zod@^3.25.51:
|
||||
version "3.25.51"
|
||||
resolved "https://registry.yarnpkg.com/zod/-/zod-3.25.51.tgz#aa2cf648e54f6f060f139cf77b694819f63c9f3a"
|
||||
integrity sha512-TQSnBldh+XSGL+opiSIq0575wvDPqu09AqWe1F7JhUMKY+M91/aGlK4MhpVNO7MgYfHcVCB1ffwAUTJzllKJqg==
|
||||
|
||||
@@ -8,13 +8,6 @@ export default defineConfig({
|
||||
head: [["link", { rel: "icon", type: "image/png", href: "/favicon.png" }]],
|
||||
cleanUrls: true,
|
||||
ignoreDeadLinks: "localhostLinks",
|
||||
vite: {
|
||||
build: {
|
||||
rollupOptions: {
|
||||
external: ['client-museum-s3.png'] // Added to handle static asset import
|
||||
}
|
||||
}
|
||||
},
|
||||
themeConfig: {
|
||||
// We use the default theme (with some CSS color overrides). This
|
||||
// themeConfig block can be used to further customize the default theme.
|
||||
|
||||
@@ -52,6 +52,10 @@ export const sidebar = [
|
||||
link: "/photos/features/machine-learning",
|
||||
},
|
||||
{ text: "Map", link: "/photos/features/map" },
|
||||
{
|
||||
text: "Notifications",
|
||||
link: "/photos/features/notifications",
|
||||
},
|
||||
{
|
||||
text: "Passkeys",
|
||||
link: "/photos/features/passkeys",
|
||||
@@ -264,34 +268,27 @@ export const sidebar = [
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: "Introduction", link: "/self-hosting/guides/" },
|
||||
{
|
||||
text: "Hosting Ente from source",
|
||||
link: "/self-hosting/guides/from-source",
|
||||
},
|
||||
{
|
||||
text: "Hosting Ente without Docker",
|
||||
link: "/self-hosting/guides/standalone-ente",
|
||||
},
|
||||
{
|
||||
text: "Administering your server",
|
||||
link: "/self-hosting/guides/admin",
|
||||
},
|
||||
{
|
||||
text: "Configure CLI for self hosted instance",
|
||||
text: "Configuring CLI for your instance",
|
||||
link: "/self-hosting/guides/selfhost-cli",
|
||||
},
|
||||
{
|
||||
text: "DB migration",
|
||||
link: "/self-hosting/guides/db-migration",
|
||||
text: "Running Ente from source",
|
||||
link: "/self-hosting/guides/from-source",
|
||||
},
|
||||
{
|
||||
text: "Mobile build",
|
||||
link: "/self-hosting/guides/mobile-build",
|
||||
text: "Running Ente without Docker",
|
||||
link: "/self-hosting/guides/standalone-ente",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "Troubleshooting",
|
||||
collapsed: true,
|
||||
items: [
|
||||
{
|
||||
text: "General",
|
||||
@@ -299,7 +296,7 @@ export const sidebar = [
|
||||
},
|
||||
{
|
||||
text: "Bucket CORS",
|
||||
link: '/self-hosting/troubleshooting/bucket-cors'
|
||||
link: "/self-hosting/troubleshooting/bucket-cors",
|
||||
},
|
||||
{
|
||||
text: "Uploads",
|
||||
@@ -309,10 +306,6 @@ export const sidebar = [
|
||||
text: "Docker / quickstart",
|
||||
link: "/self-hosting/troubleshooting/docker",
|
||||
},
|
||||
{
|
||||
text: "Yarn",
|
||||
link: "/self-hosting/troubleshooting/yarn",
|
||||
},
|
||||
{
|
||||
text: "Ente CLI secrets",
|
||||
link: "/self-hosting/troubleshooting/keyring",
|
||||
@@ -321,19 +314,21 @@ export const sidebar = [
|
||||
},
|
||||
{
|
||||
text: "Community Guides",
|
||||
items :[
|
||||
collapsed: true,
|
||||
items: [
|
||||
{
|
||||
text: "Ente via Tailscale",
|
||||
link: "/self-hosting/guides/Tailscale",
|
||||
link: "/self-hosting/guides/tailscale",
|
||||
},
|
||||
{
|
||||
text: "Ente with External S3",
|
||||
link: "/self-hosting/guides/external-s3",
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "FAQ",
|
||||
collapsed: true,
|
||||
items: [
|
||||
{ text: "General", link: "/self-hosting/faq/" },
|
||||
{
|
||||
@@ -348,16 +343,12 @@ export const sidebar = [
|
||||
text: "Backups",
|
||||
link: "/self-hosting/faq/backup",
|
||||
},
|
||||
{
|
||||
text: "Environment variables",
|
||||
link: "/self-hosting/faq/environment",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
text: "About",
|
||||
link: "/about/",
|
||||
},
|
||||
{
|
||||
text: "Contribute",
|
||||
link: "/about/contribute",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
title: Contribute
|
||||
description: Details about how to contribute to Ente's docs
|
||||
---
|
||||
|
||||
# Contributing
|
||||
|
||||
To contribute to these docs, you can use the "Edit this page" button at the
|
||||
bottom of each page. This will allow you to directly edit the markdown file that
|
||||
is used to generate this documentation and open a quick pull request directly
|
||||
from GitHub's UI.
|
||||
|
||||
If you're more comfortable in contributing with your text editor, see the
|
||||
`docs/` folder of our GitHub repository,
|
||||
[github.com/ente-io/ente](https://github.com/ente-io/ente).
|
||||
@@ -1,72 +0,0 @@
|
||||
---
|
||||
title: About Ente
|
||||
description: >
|
||||
An overview of Ente: the company, and the people behind it, and the products
|
||||
that we make.
|
||||
---
|
||||
|
||||
# About
|
||||
|
||||
Ente is a end-to-end encrypted platform for privately, reliably, and securely
|
||||
storing your data on the cloud. On top of this platform, Ente offers two
|
||||
products:
|
||||
|
||||
- **Ente Photos** - An alternative to Google Photos and Apple Photos
|
||||
|
||||
- **Ente Auth** - A free 2FA alternative to Authy
|
||||
|
||||
Both these apps are available for all desktop (Linux, Mac, Windows) and mobile
|
||||
(Android, iOS and F-Droid) platforms. They also work directly in your web
|
||||
browser without you needing to install anything.
|
||||
|
||||
More products are in the pipeline.
|
||||
|
||||
## History
|
||||
|
||||
Ente was the founded by Vishnu Mohandas (he's also Ente's CEO) in response to
|
||||
privacy concerns with major tech companies. The underlying motivation was the
|
||||
understanding that big tech had no incentive to fix their act, but with
|
||||
end-to-end encrypted cross platform apps, there was a way for people to take
|
||||
back control over their own data without sacrificing on features.
|
||||
|
||||
### What does Ente mean?
|
||||
|
||||
In Malayalam, Vishnu's native language, "ente" means "mine". Thus "Ente Photos"
|
||||
has the literal meaning "my photos".
|
||||
|
||||
This was a good name, but still Vishnu looked around for better ones. But one
|
||||
day, he discovered that "ente" means "duck" in German. This unexpected
|
||||
connection sealed the deal. We should ask him why he likes ducks so much, but
|
||||
apparently he does, so this dual meaning ("mine" / "duck") led him to finalize
|
||||
the name, and also led to the adoption of "Ducky", Ente's mascot:
|
||||
|
||||
<div align="center">
|
||||
|
||||
{width=200px}
|
||||
|
||||
</div>
|
||||
|
||||
For the full origin story of Ducky you can check out
|
||||
[this blog post](https://ente.io/blog/ducky/).
|
||||
|
||||
### How do I pronounce Ente?
|
||||
|
||||
en-_tay_. Like ca<i>fe</i>.
|
||||
|
||||
## Get in touch
|
||||
|
||||
If you have a support query that is not answered by these docs, please reach out
|
||||
to our Customer Support by sending an email to support@ente.io
|
||||
|
||||
To stay up to date with new product launches, and behind the scenes details of
|
||||
how we're building Ente, you can read our [blog](https://ente.io/blog) (or
|
||||
subscribe to it via [RSS](https://ente.io/blog/rss.xml))
|
||||
|
||||
To suggest new features and/or offer your perspective on how we should design
|
||||
planned and upcoming features, use our
|
||||
[GitHub discussions](https://github.com/ente-io/ente/discussions)
|
||||
|
||||
Or if you'd just like to hang out, join our
|
||||
[Discord](https://discord.gg/z2YVKkycX3), follow us on
|
||||
[Twitter](https://twitter.com/enteio) or give us a shout out on
|
||||
[Mastodon](https://mstdn.social/@ente)
|
||||