Compare commits
138 Commits
cli-v0.2.3
...
streaming-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0982e01d7 | ||
|
|
dd52ee7763 | ||
|
|
387e4ae826 | ||
|
|
098ff4e664 | ||
|
|
00a04f18e4 | ||
|
|
b8304f0ec5 | ||
|
|
979fa5e6da | ||
|
|
91f356ceda | ||
|
|
a019aaf5fc | ||
|
|
a9df48ea5d | ||
|
|
be6ce6d639 | ||
|
|
9d421e79a7 | ||
|
|
d1d8144fd1 | ||
|
|
7302f1d4ab | ||
|
|
39788341db | ||
|
|
6c86fe0d53 | ||
|
|
30ed06cfda | ||
|
|
21788c28cf | ||
|
|
2969b5c9a5 | ||
|
|
aa74948f4a | ||
|
|
1bdbfe0580 | ||
|
|
cacf4212c7 | ||
|
|
8f540f23dc | ||
|
|
17d76e50da | ||
|
|
db26923d68 | ||
|
|
4670be9bba | ||
|
|
f8c2f4b9dd | ||
|
|
396065e80c | ||
|
|
5a755d851a | ||
|
|
286a968f65 | ||
|
|
efff97bc71 | ||
|
|
2a73de848c | ||
|
|
c7c8fd65b6 | ||
|
|
8d7eef99ad | ||
|
|
1605b44c6e | ||
|
|
578a92d4bc | ||
|
|
bf3ed6f478 | ||
|
|
92a9698df5 | ||
|
|
342ac3258a | ||
|
|
e4427d7605 | ||
|
|
6f729c01e1 | ||
|
|
0d7c319903 | ||
|
|
6d552f5190 | ||
|
|
06450a0ce0 | ||
|
|
72d6789739 | ||
|
|
3d2d0cc345 | ||
|
|
884246d2ab | ||
|
|
cf25cc40e4 | ||
|
|
7138510e48 | ||
|
|
15e7e0ae9d | ||
|
|
9dcced260f | ||
|
|
2d5dc734aa | ||
|
|
1d93d44180 | ||
|
|
0aeb9f0c82 | ||
|
|
183bbdd145 | ||
|
|
8d701d4fd5 | ||
|
|
c6f6041d24 | ||
|
|
f49ece10e6 | ||
|
|
d0f206741f | ||
|
|
87ff5c5c0b | ||
|
|
b931dac18b | ||
|
|
2b52616ba5 | ||
|
|
e66ee5bcb1 | ||
|
|
f18bcc71d3 | ||
|
|
43a7cb1223 | ||
|
|
ceb25651f2 | ||
|
|
8a8934eacd | ||
|
|
20fea517ce | ||
|
|
0d32bd55dd | ||
|
|
20bbdb131d | ||
|
|
1980cb035e | ||
|
|
bd00c27dc6 | ||
|
|
e8fa86e2ad | ||
|
|
baa72202b2 | ||
|
|
46658a26f3 | ||
|
|
6653b36764 | ||
|
|
c17d0d0087 | ||
|
|
b823a8d6a1 | ||
|
|
e06b20a566 | ||
|
|
8218bfba04 | ||
|
|
8df5831944 | ||
|
|
6e774d6758 | ||
|
|
981c74d3f1 | ||
|
|
18ee3b19f7 | ||
|
|
aa27191ddc | ||
|
|
0883fe1d05 | ||
|
|
17e59de59c | ||
|
|
bdb30d64f0 | ||
|
|
57881f34c3 | ||
|
|
6ef3c01030 | ||
|
|
d4ddc0f919 | ||
|
|
4736ec7e0a | ||
|
|
0840c66a34 | ||
|
|
eb2f6aec68 | ||
|
|
45074f85d9 | ||
|
|
c46c27d21d | ||
|
|
3ff8d04d7b | ||
|
|
437eb246b0 | ||
|
|
5e383f3844 | ||
|
|
9bce8dc878 | ||
|
|
a447d615e0 | ||
|
|
239e6a3158 | ||
|
|
5a72d62555 | ||
|
|
7aa8f6f00f | ||
|
|
5b168021f4 | ||
|
|
a407b1baad | ||
|
|
3589cc5bbf | ||
|
|
0cef0656f3 | ||
|
|
3b3ba721a2 | ||
|
|
d899be6eac | ||
|
|
17c713d3de | ||
|
|
0e9153f4ab | ||
|
|
5484a95bf4 | ||
|
|
2a1c1a30e9 | ||
|
|
f902b7e75c | ||
|
|
ac9f4e3181 | ||
|
|
b68b1a97b5 | ||
|
|
b8de2bf736 | ||
|
|
d35975b26e | ||
|
|
c2ca87d3af | ||
|
|
a41c359ae4 | ||
|
|
e00cdee92b | ||
|
|
b1ce7b6edb | ||
|
|
15d58e3446 | ||
|
|
677a473d7d | ||
|
|
9e12f35650 | ||
|
|
a7f31119fe | ||
|
|
b729b8f0ea | ||
|
|
b933a89336 | ||
|
|
016a476895 | ||
|
|
4ee6ef408e | ||
|
|
79712182af | ||
|
|
38a35696a3 | ||
|
|
bf4807da5b | ||
|
|
dc3f074588 | ||
|
|
8da160b834 | ||
|
|
2947ca2e3c | ||
|
|
59e26779b9 |
25
.github/workflows/server-publish.yml
vendored
25
.github/workflows/server-publish.yml
vendored
@@ -1,27 +1,24 @@
|
||||
name: "Publish ghcr (server)"
|
||||
|
||||
on:
|
||||
# Run manually, providing it the commit.
|
||||
#
|
||||
# To obtain the commit from the currently deployed museum, do:
|
||||
# curl -s https://api.ente.io/ping | jq -r '.id'
|
||||
#
|
||||
# See server/docs/publish.md for more details.
|
||||
# Run automatically on 15th of every month, at 05:00 UTC.
|
||||
schedule:
|
||||
- cron: '0 5 15 * *'
|
||||
# Run manually if needed to publish out of schedule.
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
commit:
|
||||
description: "Commit to publish the image from"
|
||||
type: string
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Determine commit from prod museum
|
||||
run: |
|
||||
echo "museum_commit=$(curl -s https://api.ente.io/ping | jq -r .id)" >> $GITHUB_ENV
|
||||
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ inputs.commit }}
|
||||
ref: ${{ env.museum_commit }}
|
||||
|
||||
- name: Build and push
|
||||
uses: mr-smithers-excellent/docker-build-push@v6
|
||||
@@ -34,8 +31,8 @@ jobs:
|
||||
enableBuildKit: true
|
||||
multiPlatform: true
|
||||
platform: linux/amd64,linux/arm64
|
||||
buildArgs: GIT_COMMIT=${{ inputs.commit }}
|
||||
tags: ${{ inputs.commit }}, latest
|
||||
buildArgs: GIT_COMMIT=${{ env.museum_commit }}
|
||||
tags: ${{ env.museum_commit }}, latest
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
||||
@@ -35,9 +35,18 @@
|
||||
{
|
||||
"title": "Amazon"
|
||||
},
|
||||
{
|
||||
"title": "Ankama",
|
||||
"slug": "ankama"
|
||||
},
|
||||
{
|
||||
"title": "Anycoin Direct",
|
||||
"slug": "anycoindirect"
|
||||
},
|
||||
{
|
||||
"title": "Aruba",
|
||||
"slug": "aruba",
|
||||
"hex": "ef8a33"
|
||||
},
|
||||
{
|
||||
"title": "AscendEX"
|
||||
@@ -352,6 +361,14 @@
|
||||
{
|
||||
"title": "Estateguru"
|
||||
},
|
||||
{
|
||||
"title": "EVEOnline",
|
||||
"slug": "eve_online",
|
||||
"altNames": [
|
||||
"EVE Online"
|
||||
],
|
||||
"hex": "858585"
|
||||
},
|
||||
{
|
||||
"title": "Fastmail"
|
||||
},
|
||||
@@ -760,6 +777,11 @@
|
||||
"altNames": [
|
||||
"欧易"
|
||||
]
|
||||
},
|
||||
{
|
||||
"title": "OnShape",
|
||||
"slug": "onshape",
|
||||
"hex": "7abb5e"
|
||||
},
|
||||
{
|
||||
"title": "Parqet",
|
||||
@@ -857,6 +879,11 @@
|
||||
{
|
||||
"title": "RealMe",
|
||||
"slug": "realme"
|
||||
},
|
||||
{
|
||||
"title": "RealVNC",
|
||||
"slug": "realvnc",
|
||||
"hex": "488aec"
|
||||
},
|
||||
{
|
||||
"title": "Registro br",
|
||||
@@ -901,6 +928,10 @@
|
||||
{
|
||||
"title": "Samsung"
|
||||
},
|
||||
{
|
||||
"title": "Seafile",
|
||||
"slug": "seafile"
|
||||
},
|
||||
{
|
||||
"title": "Sendgrid"
|
||||
},
|
||||
|
||||
5
auth/assets/custom-icons/icons/ankama.svg
Normal file
5
auth/assets/custom-icons/icons/ankama.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 720 720" width="720" height="720">
|
||||
<title>ankama</title>
|
||||
<path class="s0" d="m572.3 253.3c-0.3-1.4-0.5-3-1.1-4.4-15.1-46-59.7-76.2-107.9-71.5-31.2 3-55.6 18.9-73.4 44.7-3.8 5.7-7.1 12-10.1 18.4-9.4 19.5-19.5 38.7-24.7 59.7-0.5 1.6-1.1 3.5-1.9 4.9-2.5 4.6-5.5 6.3-10.1 4.1-11.7-5.7-23.8-5.2-36.1-4.4-20.3 1.1-37.9-5.7-52.9-19.5-36.1-33.1-35.3-91 1.9-123 26.8-23.3 43.3-52.1 49.3-86.8 3-16.5 3.3-33.4 0-49.9-0.5-2.7-0.8-5.5-1.4-8.7 7.4-1.4 14.3 0 20.8 2.2 42.8 12.4 69.3 40.6 81.6 82.7 0.5 2.2 1.1 4.4 1.6 6.8 0.8 3 2.7 4.9 5.7 5.5 3.3 0.5 6.3-0.5 7.9-3.5 3-6.3 5.7-12.7 8.2-19.2 3.3-9.7 3.8-10.5 14.3-10.6 58.3-0.8 111.7 15.4 159.4 49 60.5 42.9 98.4 101.4 115.7 173.4 0.8 3.5 0.8 7.6 0.5 11.6-4.6 68-26.8 129.6-68.8 183.5-49 63.1-112.4 104.4-190.7 121.4-72.6 15.8-141.7 4.9-207.1-30.1-1.4-0.8-2.7-1.6-3.8-2.5-0.3 0-0.5-0.8-1.6-2.2 8.7 3 17 5.5 24.7 8.6 13.9 5.7 28.5 8.7 43.6 8.6 14.3 0 28.2 0.8 42.5 0 63.9-3.8 119.5-27.4 166.2-70.7 23.6-21.9 32.8-50.7 30.7-82.4-1.9-32.3-16.5-58.6-42.2-78.3-18.9-14.6-32-33.1-38-56.4-4.1-15.8-4.6-32-0.8-48.2 5.7-24.7 27.7-41.7 52.3-40.6 17 0.8 29.6 9 39.5 22.5 1.4 1.9 2.7 4.1 4.1 6.3 0.5 0 0.8-0.5 1.4-0.8l0.8 0.3v-0.5zm-263.5-55.1c-14.7 0-26.3 12.4-26.3 27.7 0 15.3 11.6 27.4 26.6 27.4 15 0 26-12.4 26-27.1 0-14.7-12-27.9-26.3-27.9v-0.2z"/>
|
||||
<path class="s0" d="m168.2 314.5c7.4 1.6 14.7 3.3 22.5 4.9 10.9 2.2 14.3 6.8 12 18.1-1.6 8.2-3 16.2-4.9 24.1-0.8 3.5 0 5.7 2.5 8.2 22.8 23.6 50.1 38.4 83.8 43.6-2.2-1.4-3-2.2-4.1-2.7-19.5-10.1-27.7-25.2-25.2-46.6 1.1-9.8 0-19.2-6-27.4-3.8-5.5-9-9.7-13.9-14.3-1.6-1.6-3.8-2.5-6-4.1 5.2-3.3 10.5-3.3 15.4-3 7.4 0.3 14.7 1.4 22.2 3 11.6 2.5 21.7 8.2 30.4 16.2 6.5 6 12.4 12.4 18.7 18.4 13.9 13.6 25.2 12.8 37.2-2.7 7.6-9.8 12.4-21.4 15.8-33.1 3.8-12.7 8.2-24.9 15.4-36.1 7.4-11.6 16.5-21.7 27.4-30.1 9.7-7.4 19.7-6.8 30.7-3.3v9.7c-1.1 27.1 5.2 52.6 19.5 75.9 1.1 1.9 2.2 4.1 3.5 6 4.1 6.3 3.3 12-0.5 18.1-5.5 8.7-13.5 13.2-23.8 13.5h-6.3c-12.7 0-23.3 8.2-26.8 20.6-3 10.9 1.9 23.3 11.7 29.6 10.9 6.8 24.1 5.7 33.4-3 10.1-9.8 16.5-21.7 18.9-35.7 0.3-1.9 0.8-4.1 1.1-6 17.3-0.3 39.8 16.6 48.2 37.2 10.1 24.1 6.3 46.6-10.1 68.8-0.8-5.2-1.4-9.4-2.2-12.8-2.2-9.7-8.6-15.7-18.1-17.7-6.8-1.6-9.8 0-13.6 5.7-1.9 3-3.5 6.3-5.2 9.7-2.5 4.9-4.6 9.8-7.1 14.6-13.6 27.1-34.9 45.8-63.2 55.6-27.1 9.7-55.2 13.2-83.5 10.1-16.5-1.6-29.3-10.5-39.8-23.6 1.9-1.4 3.3-2.7 4.9-3.8 7.4-5.5 12.8-12.7 16.6-20.8 1.1-2.2 1.6-4.9 1.9-7.4 0.5-5.2-2.7-9.4-7.6-9.8-4.6-0.5-8.7 2.5-10.1 7.6-0.8 3.3-1.1 6.5-2.5 9.7-3.8 7.1-9.7 12.8-16.5 17-7.4 4.4-14.6 3.8-20-1.4-5.5-5.5-6-11.7-1.9-19.7 0.3-0.5 0.5-1.4 1.4-3-2.5 1.1-4.1 1.4-5.5 2.2-17.7 10.6-25.8 31.5-19.2 51.5 18.7 56.7-6 119.5-54 151-3.3 2.2-7.1 4.1-11.6 6.8 0-3-0.5-5.2-0.5-7.4-2.5-57.8-30.1-98.9-82.4-122.8-32.8-14.7-53.7-39.5-62.1-74-12.4-50.1 13.9-102.2 61.2-122.8 1.9-0.8 4.1-1.4 7.6-2.7-15.1 28.8-18.7 57.2-9.4 86.8 5.5 17.3 14.7 32 29 45.5 0.3-12.4-0.8-23.6 7.6-33.4 2.7 6.3 5.5 12 8.2 17.7 4.9 10.9 12.7 19.5 23.3 24.9 12 6.3 24.1 6.8 36.5 1.1 8.6-3.8 15.8-9.4 22.2-16.2 8.6-9 8.6-24.9 0.3-35.7-5.5-7.1-8.6-14.7-7.4-23.6 1.9-15.4 15.1-26.8 32-28.2 4.6-0.3 9.4 0 13.6 0.5 4.1 0.8 8.2 2.5 12.7 3.3-3.5-3.8-7.9-6.5-12.7-8.2-8.2-2.7-16.6-3.3-25.2-1.6-14.7 3.3-24.9 16.2-26.3 33.4-0.5 6.3 0 12.8 1.1 19.2 3 16.5-0.8 30.4-13.2 42.8-1.9-1.6-3.8-2.7-5.7-4.4-43.9-37.6-69.6-84.6-75.6-142.2-10.1-95.9 42.5-184.9 130.9-223 28.5-12.4 46-33.8 53.4-63.9 0.3-1.4 0.8-2.7 1.9-3.8 1.9 10.1 1.4 20.3-1.1 30.1-6 24.1-19.5 42.8-41.4 55.3-21.4 12.4-38.4 29-51.5 50.1-2.5 4.1-2.7 7.1 0 10.9 2.7 3.5 5.2 7.6 7.6 11.6 4.4 7.4 3 13.9-4.1 18.9-6.3 4.4-13.2 8.2-19.5 12.7-1.6 1.1-3.5 3.3-3.8 5.2-1.6 18.7-0.5 36.9 5.5 55.3l1.4-0.3-0.2-0.2zm192.8 132c14.3 0 25.8-11.3 25.5-25.2 0-13.6-12-25.8-25.5-25.8-13.5 0-25.2 11.3-25.5 25.5 0 14.3 11.3 25.5 25.2 25.5z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
4
auth/assets/custom-icons/icons/aruba.svg
Normal file
4
auth/assets/custom-icons/icons/aruba.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
<svg width="800px" height="800px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill="#FF8300" fill-rule="evenodd" d="M12.1099561,17.3015551 C9.03598293,17.3015551 6.50849391,14.8423766 6.50849391,11.836714 C6.50849391,8.83105139 9.03598293,6.37187289 12.1099561,6.37187289 C15.1839292,6.37187289 17.7114182,8.83105139 17.7114182,11.836714 C17.7114182,14.8423766 15.1839292,17.3015551 12.1099561,17.3015551 L12.1099561,17.3015551 Z M12.1099561,2 C6.50849391,2 2,6.4401834 2,11.836714 C2,17.3015551 6.50849391,21.673428 12.1099561,21.673428 C14.4325135,21.673428 16.5501395,20.9220123 18.2579023,19.6241126 C19.28256,21.3318754 22.2199121,21.673428 22.2199121,21.673428 L22.2199121,11.836714 C22.2199121,6.4401834 17.7114182,2 12.1099561,2 L12.1099561,2 Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 901 B |
3
auth/assets/custom-icons/icons/eve_online.svg
Normal file
3
auth/assets/custom-icons/icons/eve_online.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" width="198.4" height="198.4" xml:space="preserve">
|
||||
<path transform="translate(0, 60)" d="M 0,0 0,13.88 10.97,13.88 10.97,10.31 60.69,10.31 60.69,0 0,0 z M 65.84,0 99.22,58.09 132.6,0 120.7,0 C 120.7,0 100.5,34.91 99.22,37.16 97.92,34.91 77.75,0 77.75,0 L 65.84,0 z M 137.8,0 137.8,13.88 148.7,13.88 148.7,10.31 198.4,10.31 198.4,0 137.8,0 z M 0,19.12 0,29.47 60.69,29.47 60.69,19.12 0,19.12 z M 137.8,19.12 137.8,29.47 198.4,29.47 198.4,19.12 137.8,19.12 z M 0,34.66 0,48.59 60.69,48.59 60.69,38.25 10.97,38.25 10.97,34.66 0,34.66 z M 137.8,34.66 137.8,48.59 198.4,48.59 198.4,38.25 148.7,38.25 148.7,34.66 137.8,34.66 z M 42.19,69.72 C 41.32,69.72 40.71,69.89 40.41,70.19 40.1,70.49 39.97,71.03 39.97,71.84 L 39.97,76.56 C 39.97,77.38 40.1,77.93 40.41,78.22 40.71,78.52 41.32,78.66 42.19,78.66 L 48.72,78.66 C 49.59,78.66 50.19,78.52 50.5,78.22 50.8,77.93 50.97,77.38 50.97,76.56 L 50.97,71.84 C 50.97,71.03 50.8,70.49 50.5,70.19 50.19,69.89 49.59,69.72 48.72,69.72 L 42.19,69.72 z M 64.37,69.72 64.37,78.66 66.25,78.66 66.25,73.84 C 66.25,73.66 66.23,73.43 66.22,73.19 66.2,72.94 66.18,72.69 66.16,72.41 66.26,72.53 66.38,72.67 66.5,72.78 66.62,72.89 66.75,73.01 66.91,73.16 L 73.47,78.66 74.88,78.66 74.88,69.72 73.03,69.72 73.03,74.41 C 73.03,74.52 73.05,74.7 73.06,74.91 73.07,75.11 73.09,75.47 73.12,75.97 72.99,75.81 72.82,75.66 72.66,75.5 72.49,75.35 72.31,75.18 72.09,75 L 65.81,69.72 64.37,69.72 z M 88.53,69.72 88.53,78.66 97.31,78.66 97.31,77 90.59,77 90.59,69.72 88.53,69.72 z M 109.4,69.72 109.4,78.66 111.5,78.66 111.5,69.72 109.4,69.72 z M 125.1,69.72 125.1,78.66 127,78.66 127,73.84 C 127,73.66 127,73.43 126.9,73.19 126.9,72.94 126.9,72.69 126.9,72.41 127,72.53 127.1,72.67 127.2,72.78 127.3,72.89 127.5,73.01 127.6,73.16 L 134.2,78.66 135.6,78.66 135.6,69.72 133.8,69.72 133.8,74.41 C 133.8,74.52 133.8,74.7 133.8,74.91 133.8,75.11 133.8,75.47 133.8,75.97 133.7,75.81 133.6,75.66 133.4,75.5 133.2,75.35 133,75.18 132.8,75 L 126.5,69.72 125.1,69.72 z M 149.3,69.72 149.3,78.66 158.5,78.66 158.5,77 151.3,77 151.3,74.78 155.4,74.78 155.4,73.25 151.3,73.25 151.3,71.25 158.4,71.25 158.4,69.72 149.3,69.72 z M 42.03,71.31 48.87,71.31 48.87,77 42.03,77 42.03,71.31 z" /></svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
1
auth/assets/custom-icons/icons/onshape.svg
Normal file
1
auth/assets/custom-icons/icons/onshape.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 33 KiB |
1
auth/assets/custom-icons/icons/realvnc.svg
Normal file
1
auth/assets/custom-icons/icons/realvnc.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 18 KiB |
13
auth/assets/custom-icons/icons/seafile.svg
Normal file
13
auth/assets/custom-icons/icons/seafile.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 120" width="180" height="120">
|
||||
<title>seafile</title>
|
||||
<defs>
|
||||
<linearGradient id="g1" x2="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0,114.369,-177.525,0,89.989,2.834)">
|
||||
<stop offset="0" stop-color="#fad956"/>
|
||||
<stop offset="1" stop-color="#ffa10f"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<style>
|
||||
.s0 { fill: url(#g1) }
|
||||
</style>
|
||||
<path class="s0" d="m1.2 52.8c0-3 2.4-5.4 5.4-5.4 1.4 0 2.7 0.6 3.6 1.5q0-0.7 0-1.4c0-9.9 8-17.9 17.9-17.9 2.5 0 4.9 0.5 7.1 1.5q0-0.8 0-1.5c0-14.8 12-26.8 26.8-26.8 14.7 0 26.6 11.9 26.8 26.6-4.8 4.2-8.7 9.6-11.2 15.7-4.8-3-10.4-4.8-16.5-4.8-12.4 0-23.2 7.1-28.3 17.8h-19.1-7.1c-3 0-5.4-2.4-5.4-5.3zm141.2-16c-6.6-6.7-15.8-10.8-25.9-10.8-18.5 0-33.8 13.7-36.3 31.5-4.5-6.1-11.8-10-20-10-13.8 0-25 11.2-25 25 0 4 0.9 7.8 2.6 11.2-8.7 1.7-15.1 8.5-15.1 16.5 0 9.4 8.8 17 19.7 17 4.7 0 9.1-1.5 12.6-4l40.2-39.5c4.4-4.1 10.3-6.6 16.8-6.6 13.6 0 24.7 10.9 25.1 24.4q0 0-0.1-0.1c0.2 4-1.8 8.1-5.7 10.3-5.3 3.1-12 1.4-15-3.7-2.9-5.1-1-11.7 4.4-14.8q1.9-1.1 3.9-1.4-1.8-0.4-3.6-0.4c-9.9 0-17.9 8-17.9 17.9 0 9.9 8 17.9 17.9 17.9q0.6 0 1.3-0.1l0.5-0.1h35.1v0.2c10.7-0.5 20.9-10.4 20.9-22.5 0-12.3-10.6-22.4-22.9-22.4q-0.1 0-0.1 0c-2 3.6-4.4 5.7-7.1 7.9 2.8-5.2 4.5-11.2 4.5-17.6-0.1-10.1-4.2-19.2-10.8-25.8z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -88,6 +88,8 @@
|
||||
"useRecoveryKey": "Usa la clau de recuperació",
|
||||
"incorrectPasswordTitle": "Contrasenya incorrecta",
|
||||
"welcomeBack": "Benvingut de nou!",
|
||||
"emailAlreadyRegistered": "El correu electrònic ja està registrat.",
|
||||
"emailNotRegistered": "El correu electrònic no està registrat.",
|
||||
"madeWithLoveAtPrefix": "fet amb ❤️ a ",
|
||||
"supportDevs": "Subscriu-te a <bold-green>ente</bold-green> per donar-nos suport",
|
||||
"supportDiscount": "Usa el codi de descompte \"AUTH\" per obtenir un 10% de descompte el primer any",
|
||||
@@ -502,5 +504,13 @@
|
||||
"deselectAll": "Desselecciona-ho tot",
|
||||
"selectAll": "Seleccionar-ho tot",
|
||||
"deleteDuplicates": "Elimina duplicats",
|
||||
"plainHTML": "HTML pla"
|
||||
"plainHTML": "HTML pla",
|
||||
"tellUsWhatYouThink": "Digueu-nos què us sembla",
|
||||
"dropReview": "Deixa una ressenya a l'App/Play Store",
|
||||
"supportEnte": "Donar suport a <bold-green>ente</bold-green>",
|
||||
"giveUsAStarOnGithub": "Dona'ns una estrella a Github",
|
||||
"free5GB": "5 GB gratuïts a <bold-green>ente</bold-green> Photos",
|
||||
"loginWithAuthAccount": "Inicieu sessió amb el vostre compte Auth",
|
||||
"freeStorageOffer": "10% de descompte a <bold-green>ente</bold-green> photos",
|
||||
"freeStorageOfferDescription": "Utilitzeu el codi \"AUTH\" per obtenir un 10% de descompte el primer any"
|
||||
}
|
||||
@@ -88,6 +88,8 @@
|
||||
"useRecoveryKey": "Wiederherstellungsschlüssel verwenden",
|
||||
"incorrectPasswordTitle": "Falsches Passwort",
|
||||
"welcomeBack": "Willkommen zurück!",
|
||||
"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",
|
||||
"supportDiscount": "Benutzen Sie den Rabattcode \"AUTH\" für 10 % Rabatt im ersten Jahr",
|
||||
@@ -255,6 +257,8 @@
|
||||
"areYouSureYouWantToLogout": "Sind sie sicher, dass sie sich ausloggen möchten?",
|
||||
"yesLogout": "Ja ausloggen",
|
||||
"exit": "Schließen",
|
||||
"theme": "Theme",
|
||||
"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.",
|
||||
@@ -325,6 +329,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"manualSort": "Benutzerdefiniert",
|
||||
"activeSessions": "Aktive Sitzungen",
|
||||
"somethingWentWrongPleaseTryAgain": "Ein Fehler ist aufgetreten, bitte versuche es erneut",
|
||||
"thisWillLogYouOutOfThisDevice": "Dadurch wirst du von diesem Gerät abgemeldet!",
|
||||
@@ -478,5 +483,9 @@
|
||||
"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"
|
||||
"appLockNotEnabledDescription": "Bitte aktivieren Sie die App-Sperre über Security > App-Sperre",
|
||||
"duplicateCodes": "Doppelte Codes",
|
||||
"noDuplicates": "✨ Keine Duplikate",
|
||||
"deselectAll": "Alle abwählen",
|
||||
"selectAll": "Alles auswählen"
|
||||
}
|
||||
@@ -504,5 +504,12 @@
|
||||
"deselectAll": "Deseleccionar todo",
|
||||
"selectAll": "Seleccionar todo",
|
||||
"deleteDuplicates": "Eliminar duplicados",
|
||||
"plainHTML": "HTML plano"
|
||||
"plainHTML": "HTML plano",
|
||||
"tellUsWhatYouThink": "Cuéntanos cuál es su opinión",
|
||||
"dropReview": "Danos una reseña en la App/Play Store",
|
||||
"supportEnte": "Apoya a <bold-green>ente</bold-green>",
|
||||
"giveUsAStarOnGithub": "Danos una estrella en GitHub",
|
||||
"free5GB": "5 GB gratis en <bold-green>ente</bold-green> Fotos",
|
||||
"freeStorageOffer": "10% de descuento en <bold-green>ente</bold-green> fotos",
|
||||
"freeStorageOfferDescription": "Usa el cupón \"AUTH\" para obtener un 10% de descuento en el primer año"
|
||||
}
|
||||
@@ -504,5 +504,13 @@
|
||||
"deselectAll": "Összes kijelölés megszüntetése",
|
||||
"selectAll": "Összes kijelölése",
|
||||
"deleteDuplicates": "Ismétlődések törlése",
|
||||
"plainHTML": "Sima HTML kód"
|
||||
"plainHTML": "Sima HTML kód",
|
||||
"tellUsWhatYouThink": "Mondja el mit gondol",
|
||||
"dropReview": "Írjon véleményt az App/Play Store-ban",
|
||||
"supportEnte": "Támogassa <bold-green>ente <bold-green>",
|
||||
"giveUsAStarOnGithub": "Adj nekünk egy csillagot a Githubon",
|
||||
"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"
|
||||
}
|
||||
@@ -499,7 +499,18 @@
|
||||
"appLockOfflineModeWarning": "バックアップなしで進むことを選択しました。アプリロックを忘れると、データにアクセスできなくなります。",
|
||||
"duplicateCodes": "重複コード",
|
||||
"noDuplicates": "✨ 重複なし",
|
||||
"youveNoDuplicateCodesThatCanBeCleared": "削除できる重複コードはありません",
|
||||
"deduplicateCodes": "重複コード",
|
||||
"deselectAll": "すべての選択を解除",
|
||||
"selectAll": "すべて選択",
|
||||
"deleteDuplicates": "重複を削除",
|
||||
"plainHTML": "Plain HTML",
|
||||
"tellUsWhatYouThink": "ご意見をお聞かせください",
|
||||
"loginWithAuthAccount": "認証アカウントでログイン"
|
||||
"dropReview": "App/Playストアにレビューを投稿する",
|
||||
"supportEnte": "<bold-green>ente</bold-green>をサポートする",
|
||||
"giveUsAStarOnGithub": "Githubで星をつける",
|
||||
"free5GB": "<bold-green>ente</bold-green>フォトで5GB無料",
|
||||
"loginWithAuthAccount": "認証アカウントでログイン",
|
||||
"freeStorageOffer": "<bold-green>ente</bold-green>の写真が10%オフ",
|
||||
"freeStorageOfferDescription": "クーポンコード \"AUTH\" の使用で初年度が10%オフになります"
|
||||
}
|
||||
@@ -1 +1,28 @@
|
||||
{}
|
||||
{
|
||||
"blog": "ബ്ലോഗ്",
|
||||
"verifyPassword": "പാസ്വേഡ് സ്ഥിരീകരിക്കുക",
|
||||
"recreatePassword": "പാസ്വേഡ് പുനഃസൃഷ്ടിക്കുക",
|
||||
"incorrectPasswordTitle": "തെറ്റായ പാസ്വേഡ്",
|
||||
"welcomeBack": "വീണ്ടും സ്വാഗതം!",
|
||||
"emailAlreadyRegistered": "ഇമെയിൽ ഇതിനകം രജിസ്റ്റർ ചെയ്തിട്ടുണ്ട്.",
|
||||
"emailNotRegistered": "ഇമെയിൽ രജിസ്റ്റർ ചെയ്തിട്ടില്ല.",
|
||||
"changeEmail": "ഇമെയിൽ മാറ്റുക",
|
||||
"changePassword": "പാസ്സ്വേർഡ് മാറ്റുക",
|
||||
"ok": "ശരി",
|
||||
"cancel": "റദ്ദാക്കുക",
|
||||
"yes": "അതെ",
|
||||
"no": "അല്ല",
|
||||
"email": "ഇമെയിൽ",
|
||||
"somethingWentWrongMessage": "എന്തോ കുഴപ്പമുണ്ടായി, ദയവായി വീണ്ടും ശ്രമിക്കുക",
|
||||
"inFamilyPlanMessage": "നിങ്ങൾ ഒരു ഫാമിലി പ്ലാനിലാണ്!",
|
||||
"scan": "സ്കാൻ ചെയ്യുക",
|
||||
"scanACode": "കോഡ് സ്കാൻ ചെയ്യുക",
|
||||
"verify": "പരിശോധിക്കുക",
|
||||
"verifyEmail": "ഇമെയിൽ സ്ഥിരീകരിക്കുക",
|
||||
"enterCodeHint": "നിങ്ങളുടെ ഓതന്റിക്കേറ്റർ ആപ്പിൽ നിന്നുള്ള 6 അക്ക കോഡ് നൽകുക",
|
||||
"twoFactorAuthTitle": "ടു-ഫാക്ടർ ആധികാരികത",
|
||||
"createNewAccount": "പുതിയ അക്കൗണ്ട് സൃഷ്ടിക്കുക",
|
||||
"confirmPassword": "പാസ്വേഡ് സ്ഥിരീകരിക്കുക",
|
||||
"language": "ഭാഷ",
|
||||
"security": "സുരക്ഷ"
|
||||
}
|
||||
@@ -362,8 +362,18 @@ const createMainWindow = () => {
|
||||
// do it (Step 2) unconditionally (i.e., on macOS too).
|
||||
//
|
||||
// https://www.electronjs.org/docs/latest/tutorial/custom-title-bar#create-a-custom-title-bar
|
||||
//
|
||||
// Note that by default on Windows, the color of the WCO title bar
|
||||
// overlay (three buttons - minimize, maximize, close - on the top
|
||||
// right) is static, and unlike Linux, doesn't adapt to the theme /
|
||||
// content. Explicitly choosing a dark background, while it won't work
|
||||
// always (if the user's theme is light), is better than picking a light
|
||||
// background since the main image viewer is always dark.
|
||||
titleBarStyle: "hidden",
|
||||
titleBarOverlay: true,
|
||||
titleBarOverlay:
|
||||
process.platform == "win32"
|
||||
? { color: "black", symbolColor: "#cdcdcd" }
|
||||
: true,
|
||||
// The color to show in the window until the web content gets loaded.
|
||||
// https://www.electronjs.org/docs/latest/api/browser-window#setting-the-backgroundcolor-property
|
||||
//
|
||||
|
||||
@@ -138,6 +138,10 @@ export const sidebar = [
|
||||
text: "Machine Learning",
|
||||
link: "/photos/faq/machine-learning",
|
||||
},
|
||||
{
|
||||
text: "Video Streaming",
|
||||
link: "/photos/faq/video-streaming",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
63
docs/docs/photos/faq/video-streaming.md
Normal file
63
docs/docs/photos/faq/video-streaming.md
Normal file
@@ -0,0 +1,63 @@
|
||||
---
|
||||
title: Video Streaming FAQ
|
||||
description:
|
||||
Frequently asked questions about Ente's Video Streaming feature
|
||||
---
|
||||
|
||||
# Video Streaming
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> Video streaming is available in beta on mobile apps starting v0.9.98.
|
||||
|
||||
### How to enable video streaming?
|
||||
|
||||
- Open Settings -> General -> Advanced
|
||||
- Switch on the toggle for `Video streaming`
|
||||
|
||||
### What happens when I enable video streaming?
|
||||
|
||||
Enabling video streaming will start processing videos captured in the last 30
|
||||
days, generating streams for each. Both local and remote videos will be
|
||||
processed, so this may consume bandwidth for downloading of remote files and
|
||||
uploading of the generated streams.
|
||||
|
||||
### How can I view video streams?
|
||||
|
||||
Settings -> Backup > Backup status will show details regarding the processing
|
||||
status for videos. Processed videos will have a green play button next to them.
|
||||
You can open these videos by tapping on them.
|
||||
|
||||
Processed videos will show a `Play stream` button, clicking which will load and
|
||||
play the stream.
|
||||
|
||||
Clicking on the `Info` icon within the original video will show details
|
||||
about the generated stream.
|
||||
|
||||
### What is a stream?
|
||||
|
||||
Stream is an encrypted HLS file with an `.m3u8` playlist that helps play a video
|
||||
with support for seeking **without** downloading the full file.
|
||||
|
||||
Currently it converts videos into `720p` with `2mbps` bitrate in `H.264` format.
|
||||
The generated stream is single blob (encrypted with AES) while the playlist file
|
||||
(`.m3u8`) is another blob (encrypted using XChaCha20).
|
||||
|
||||
We cannot read the contents, duration or the number of chunks within the
|
||||
generated stream.
|
||||
|
||||
### Will streams consume space in my storage?
|
||||
|
||||
While this feature is in beta, we will not count the storage consumed by your
|
||||
streams against your storage quota. This may change in the future. If it does,
|
||||
we will provide an option to opt-in to one of the following:
|
||||
1. Original videos only
|
||||
2. Compressed streams only
|
||||
3. Both
|
||||
|
||||
### Something doesn't seem right, what to do?
|
||||
|
||||
As video streaming is still in beta, some things might not work correctly.
|
||||
Please create a thread within the `#feedback` channel on
|
||||
[Discord](https://discord.com/channels/948937918347608085/1121126215995113552)
|
||||
or reach out to [support@ente.io](mailto:support@ente.io).
|
||||
@@ -43,6 +43,10 @@ need to disable this "Optimize battery usage" mode in the system settings for
|
||||
Ente if you wish for Ente to automatically back up your photos in the
|
||||
background.
|
||||
|
||||
On Android versions 15 and later, if an app is in private space and the private
|
||||
space is locked, Android doesn’t allow the app to run any background processes.
|
||||
As a result, background sync will not work.
|
||||
|
||||
### Desktop
|
||||
|
||||
In addition to our mobile apps, the background sync also works on our desktop
|
||||
|
||||
@@ -450,84 +450,84 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/wakelock_plus/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
background_fetch: 39f11371c0dce04b001c4bfd5e782bcccb0a85e2
|
||||
battery_info: 09f5c9ee65394f2291c8c6227bedff345b8a730c
|
||||
connectivity_plus: ddd7f30999e1faaef5967c23d5b6d503d10434db
|
||||
cupertino_http: 947a233f40cfea55167a49f2facc18434ea117ba
|
||||
dart_ui_isolate: d5bcda83ca4b04f129d70eb90110b7a567aece14
|
||||
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
|
||||
background_fetch: 94b36ee293e82972852dba8ede1fbcd3bd3d9d57
|
||||
battery_info: a06b00c06a39bc94c92beebf600f1810cb6c8c87
|
||||
connectivity_plus: 3f6c9057f4cd64198dc826edfb0542892f825343
|
||||
cupertino_http: 94ac07f5ff090b8effa6c5e2c47871d48ab7c86c
|
||||
dart_ui_isolate: 46f6714abe6891313267153ef6f9748d8ecfcab1
|
||||
device_info_plus: 335f3ce08d2e174b9fdc3db3db0f4e3b1f66bd89
|
||||
ffmpeg-kit-ios-full-gpl: 80adc341962e55ef709e36baa8ed9a70cf4ea62b
|
||||
ffmpeg_kit_flutter_full_gpl: 8d15c14c0c3aba616fac04fe44b3d27d02e3c330
|
||||
file_saver: 503e386464dbe118f630e17b4c2e1190fa0cf808
|
||||
ffmpeg_kit_flutter_full_gpl: ce18b888487c05c46ed252cd2e7956812f2e3bd1
|
||||
file_saver: 6cdbcddd690cb02b0c1a0c225b37cd805c2bf8b6
|
||||
Firebase: 98e6bf5278170668a7983e12971a66b2cd57fc8c
|
||||
firebase_core: 2bedc3136ec7c7b8561c6123ed0239387b53f2af
|
||||
firebase_messaging: 15d114e1a41fc31e4fbabcd48d765a19eec94a38
|
||||
firebase_core: 085320ddfaacb80d1a96eac3a87857afcc150db1
|
||||
firebase_messaging: d398edc15fe825f832836e74f6ac61e8cd2f3ad3
|
||||
FirebaseCore: a282032ae9295c795714ded2ec9c522fc237f8da
|
||||
FirebaseCoreInternal: d98ab91e2d80a56d7b246856a8885443b302c0c2
|
||||
FirebaseInstallations: 6ef4a1c7eb2a61ee1f74727d7f6ce2e72acf1414
|
||||
FirebaseMessaging: c9ec7b90c399c7a6100297e9d16f8a27fc7f7152
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
flutter_email_sender: 02d7443217d8c41483223627972bfdc09f74276b
|
||||
flutter_image_compress: 5a5e9aee05b6553048b8df1c3bc456d0afaac433
|
||||
flutter_inappwebview_ios: 6f63631e2c62a7c350263b13fa5427aedefe81d4
|
||||
flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086
|
||||
flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778
|
||||
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
|
||||
flutter_sodium: c84426b4de738514b5b66cfdeb8a06634e72fe0b
|
||||
fluttertoast: e9a18c7be5413da53898f660530c56f35edfba9c
|
||||
flutter_email_sender: cd533cdc7ea5eda6fabb2c7f78521c71207778a4
|
||||
flutter_image_compress: 4b058288a81f76e5e80340af37c709abafff34c4
|
||||
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
|
||||
flutter_local_notifications: ad39620c743ea4c15127860f4b5641649a988100
|
||||
flutter_native_splash: 35ddbc7228eafcb3969dcc5f1fbbe27c1145a4f0
|
||||
flutter_secure_storage: 2c2ff13db9e0a5647389bff88b0ecac56e3f3418
|
||||
flutter_sodium: 152647449ba89a157fd48d7e293dcd6d29c6ab0e
|
||||
fluttertoast: 76fea30fcf04176325f6864c87306927bd7d2038
|
||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
|
||||
home_widget: 0434835a4c9a75704264feff6be17ea40e0f0d57
|
||||
image_editor_common: d6f6644ae4a6de80481e89fe6d0a8c49e30b4b43
|
||||
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
|
||||
in_app_purchase_storekit: 8c3b0b3eb1b0f04efbff401c3de6266d4258d433
|
||||
integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573
|
||||
home_widget: f169fc41fd807b4d46ab6615dc44d62adbf9f64f
|
||||
image_editor_common: 3de87e7c4804f4ae24c8f8a998362b98c105cac1
|
||||
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
|
||||
in_app_purchase_storekit: e126ef1b89e4a9fdf07e28f005f82632b4609437
|
||||
integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
|
||||
libwebp: 1786c9f4ff8a279e4dac1e8f385004d5fc253009
|
||||
local_auth_darwin: 66e40372f1c29f383a314c738c7446e2f7fdadc3
|
||||
local_auth_ios: 5046a18c018dd973247a0564496c8898dbb5adf9
|
||||
local_auth_darwin: 553ce4f9b16d3fdfeafce9cf042e7c9f77c1c391
|
||||
local_auth_ios: f7a1841beef3151d140a967c2e46f30637cdf451
|
||||
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
|
||||
maps_launcher: 2e5b6a2d664ec6c27f82ffa81b74228d770ab203
|
||||
media_extension: 6d30dc1431ebaa63f43c397c37917b1a0a597a4c
|
||||
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
|
||||
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
|
||||
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
|
||||
motion_sensors: 03f55b7c637a7e365a0b5f9697a449f9059d5d91
|
||||
motionphoto: d4a432b8c8f22fb3ad966258597c0103c9c5ff16
|
||||
move_to_background: 39a5b79b26d577b0372cbe8a8c55e7aa9fcd3a2d
|
||||
maps_launcher: edf829809ba9e894d70e569bab11c16352dedb45
|
||||
media_extension: a1fec16ee9c8241a6aef9613578ebf097d6c5e64
|
||||
media_kit_libs_ios_video: 5a18affdb97d1f5d466dc79988b13eff6c5e2854
|
||||
media_kit_native_event_loop: 5fba1a849a6c87a34985f1e178a0de5bd444a0cf
|
||||
media_kit_video: 1746e198cb697d1ffb734b1d05ec429d1fcd1474
|
||||
motion_sensors: 741e702c17467b9569a92165dda8d4d88c6167f1
|
||||
motionphoto: 584b43031ead3060225cdff08fa49818879801d2
|
||||
move_to_background: 155f7bfbd34d43ad847cb630d2d2d87c17199710
|
||||
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
||||
native_video_player: d12af78a1a4a8cf09775a5177d5b392def6fd23c
|
||||
objective_c: 77e887b5ba1827970907e10e832eec1683f3431d
|
||||
onnxruntime: e7c2ae44385191eaad5ae64c935a72debaddc997
|
||||
native_video_player: b65c58951ede2f93d103a25366bdebca95081265
|
||||
objective_c: 89e720c30d716b036faf9c9684022048eee1eee2
|
||||
onnxruntime: f9b296392c96c42882be020a59dbeac6310d81b2
|
||||
onnxruntime-c: a909204639a1f035f575127ac406f781ac797c9c
|
||||
onnxruntime-objc: b6fab0f1787aa6f7190c2013f03037df4718bd8b
|
||||
open_mail_app: 794172f6a22cd16319d3ddaf45e945b2f74952b0
|
||||
open_mail_app: 06d5a4162866388a92b1df3deb96e56be20cf45c
|
||||
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
|
||||
package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85
|
||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
||||
photo_manager: ff695c7a1dd5bc379974953a2b5c0a293f7c4c8a
|
||||
privacy_screen: 1a131c052ceb3c3659934b003b0d397c2381a24e
|
||||
package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499
|
||||
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
||||
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
|
||||
photo_manager: d2fbcc0f2d82458700ee6256a15018210a81d413
|
||||
privacy_screen: 3159a541f5d3a31bea916cfd4e58f9dc722b3fd4
|
||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||
receive_sharing_intent: df9c334dc9feadcbd3266e5cb49c8443405e1c9f
|
||||
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
|
||||
receive_sharing_intent: f6a12b7e8f7ed745f61c982de8a65de88db44a44
|
||||
screen_brightness_ios: 5ed898fa50fa82a26171c086ca5e28228f932576
|
||||
SDWebImage: 73c6079366fea25fa4bb9640d5fb58f0893facd8
|
||||
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
|
||||
Sentry: f8374b5415bc38dfb5645941b3ae31230fbeae57
|
||||
sentry_flutter: 0eb93e5279eb41e2392212afe1ccd2fecb4f8cbe
|
||||
share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad
|
||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||
sqflite_darwin: a553b1fd6fe66f53bbb0fe5b4f5bab93f08d7a13
|
||||
sentry_flutter: 0a211008f52553ba5dd81ceb71f48d78f0f1f6ab
|
||||
share_plus: 011d6fb4f9d2576b83179a3a5c5e323202cdabcf
|
||||
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
|
||||
sqflite_darwin: 44bb54cc302bff1fbe5752293aba1820b157cf1c
|
||||
sqlite3: 0bb0e6389d824e40296f531b858a2a0b71c0d2fb
|
||||
sqlite3_flutter_libs: c00457ebd31e59fa6bb830380ddba24d44fbcd3b
|
||||
system_info_plus: 5393c8da281d899950d751713575fbf91c7709aa
|
||||
sqlite3_flutter_libs: 9379996d65aa23dcda7585a5b58766cebe0aa042
|
||||
system_info_plus: 555ce7047fbbf29154726db942ae785c29211740
|
||||
Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e
|
||||
ua_client_hints: 46bb5817a868f9e397c0ba7e3f2f5c5d90c35156
|
||||
uni_links: d97da20c7701486ba192624d99bffaaffcfc298a
|
||||
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
|
||||
video_player_avfoundation: 7c6c11d8470e1675df7397027218274b6d2360b3
|
||||
video_thumbnail: c4e2a3c539e247d4de13cd545344fd2d26ffafd1
|
||||
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
|
||||
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
|
||||
ua_client_hints: 0b48eae1134283f5b131ee0871fa878377f07a01
|
||||
uni_links: ed8c961e47ed9ce42b6d91e1de8049e38a4b3152
|
||||
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
|
||||
video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b
|
||||
video_thumbnail: b637e0ad5f588ca9945f6e2c927f73a69a661140
|
||||
volume_controller: ca1cde542ee70fad77d388f82e9616488110942b
|
||||
wakelock_plus: 04623e3f525556020ebd4034310f20fe7fda8b49
|
||||
|
||||
PODFILE CHECKSUM: 20e086e6008977d43a3d40260f3f9bffcac748dd
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import 'package:photos/core/error-reporting/tunneled_transport.dart';
|
||||
import "package:photos/core/errors.dart";
|
||||
import 'package:photos/models/typedefs.dart';
|
||||
import "package:photos/utils/device_info.dart";
|
||||
import "package:photos/utils/ram_check_util.dart";
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
@@ -205,6 +206,12 @@ class SuperLogging {
|
||||
}),
|
||||
);
|
||||
|
||||
unawaited(
|
||||
checkDeviceTotalRAM().then((ram) {
|
||||
if (ram != null) $.info("Device RAM: ${ram}MB");
|
||||
}),
|
||||
);
|
||||
|
||||
if (appConfig.body == null) return;
|
||||
|
||||
if (enable && sentryIsEnabled) {
|
||||
|
||||
@@ -251,20 +251,20 @@ class CollectionsDB {
|
||||
Map<String, dynamic> _getRowForCollection(Collection collection) {
|
||||
final row = <String, dynamic>{};
|
||||
row[columnID] = collection.id;
|
||||
row[columnOwner] = collection.owner!.toJson();
|
||||
row[columnOwner] = collection.owner.toJson();
|
||||
row[columnEncryptedKey] = collection.encryptedKey;
|
||||
row[columnKeyDecryptionNonce] = collection.keyDecryptionNonce;
|
||||
row[columnName] = collection.name;
|
||||
row[columnEncryptedName] = collection.encryptedName;
|
||||
row[columnNameDecryptionNonce] = collection.nameDecryptionNonce;
|
||||
row[columnType] = Collection.typeToString(collection.type);
|
||||
row[columnType] = typeToString(collection.type);
|
||||
row[columnEncryptedPath] = collection.attributes.encryptedPath;
|
||||
row[columnPathDecryptionNonce] = collection.attributes.pathDecryptionNonce;
|
||||
row[columnVersion] = collection.attributes.version;
|
||||
row[columnSharees] =
|
||||
json.encode(collection.sharees?.map((x) => x?.toMap()).toList());
|
||||
json.encode(collection.sharees.map((x) => x.toMap()).toList());
|
||||
row[columnPublicURLs] =
|
||||
json.encode(collection.publicURLs?.map((x) => x?.toMap()).toList());
|
||||
json.encode(collection.publicURLs.map((x) => x.toMap()).toList());
|
||||
row[columnUpdationTime] = collection.updationTime;
|
||||
if (collection.isDeleted) {
|
||||
row[columnIsDeleted] = _sqlBoolTrue;
|
||||
@@ -290,7 +290,7 @@ class CollectionsDB {
|
||||
row[columnName],
|
||||
row[columnEncryptedName],
|
||||
row[columnNameDecryptionNonce],
|
||||
Collection.typeFromString(row[columnType]),
|
||||
typeFromString(row[columnType]),
|
||||
CollectionAttributes(
|
||||
encryptedPath: row[columnEncryptedPath],
|
||||
pathDecryptionNonce: row[columnPathDecryptionNonce],
|
||||
|
||||
@@ -1733,6 +1733,7 @@ class FilesDB {
|
||||
Future<List<EnteFile>> getAllFilesAfterDate({
|
||||
required FileType fileType,
|
||||
required DateTime beginDate,
|
||||
required int userID,
|
||||
}) async {
|
||||
final db = await instance.sqliteAsyncDB;
|
||||
final results = await db.getAll(
|
||||
@@ -1741,6 +1742,7 @@ class FilesDB {
|
||||
WHERE $columnFileType = ?
|
||||
AND $columnCreationTime > ?
|
||||
AND $columnUploadedFileID != -1
|
||||
AND $columnOwnerID = $userID
|
||||
ORDER BY $columnCreationTime DESC
|
||||
''',
|
||||
[getInt(fileType), beginDate.microsecondsSinceEpoch],
|
||||
|
||||
@@ -322,22 +322,20 @@ class _AddContactPage extends State<AddContactPage> {
|
||||
final int ownerID = Configuration.instance.getUserID()!;
|
||||
existingEmails.add(Configuration.instance.getEmail()!);
|
||||
for (final c in CollectionsService.instance.getActiveCollections()) {
|
||||
if (c.owner?.id == ownerID) {
|
||||
for (final User? u in c.sharees ?? []) {
|
||||
if (u != null &&
|
||||
u.id != null &&
|
||||
if (c.owner.id == ownerID) {
|
||||
for (final User u in c.sharees) {
|
||||
if (u.id != null &&
|
||||
u.email.isNotEmpty &&
|
||||
!existingEmails.contains(u.email)) {
|
||||
existingEmails.add(u.email);
|
||||
suggestedUsers.add(u);
|
||||
}
|
||||
}
|
||||
} else if (c.owner != null &&
|
||||
c.owner!.id != null &&
|
||||
c.owner!.email.isNotEmpty &&
|
||||
!existingEmails.contains(c.owner!.email)) {
|
||||
existingEmails.add(c.owner!.email);
|
||||
suggestedUsers.add(c.owner!);
|
||||
} else if (c.owner.id != null &&
|
||||
c.owner.email.isNotEmpty &&
|
||||
!existingEmails.contains(c.owner.email)) {
|
||||
existingEmails.add(c.owner.email);
|
||||
suggestedUsers.add(c.owner);
|
||||
}
|
||||
}
|
||||
final cachedUserDetails = UserService.instance.getCachedUserDetails();
|
||||
|
||||
@@ -45,7 +45,7 @@ class CreateRequest {
|
||||
map['keyDecryptionNonce'] = keyDecryptionNonce;
|
||||
map['encryptedName'] = encryptedName;
|
||||
map['nameDecryptionNonce'] = nameDecryptionNonce;
|
||||
map['type'] = Collection.typeToString(type);
|
||||
map['type'] = typeToString(type);
|
||||
if (attributes != null) {
|
||||
map['attributes'] = attributes!.toMap();
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import "package:photos/models/metadata/common_keys.dart";
|
||||
|
||||
class Collection {
|
||||
final int id;
|
||||
final User? owner;
|
||||
final User owner;
|
||||
final String encryptedKey;
|
||||
final String? keyDecryptionNonce;
|
||||
@Deprecated("Use collectionName instead")
|
||||
@@ -20,8 +20,8 @@ class Collection {
|
||||
final String? nameDecryptionNonce;
|
||||
final CollectionType type;
|
||||
final CollectionAttributes attributes;
|
||||
final List<User?>? sharees;
|
||||
final List<PublicURL?>? publicURLs;
|
||||
final List<User> sharees;
|
||||
final List<PublicURL> publicURLs;
|
||||
final int updationTime;
|
||||
final bool isDeleted;
|
||||
|
||||
@@ -95,12 +95,12 @@ class Collection {
|
||||
|
||||
// hasLink returns true if there's any link attached to the collection
|
||||
// including expired links
|
||||
bool get hasLink => publicURLs != null && publicURLs!.isNotEmpty;
|
||||
bool get hasLink => publicURLs.isNotEmpty;
|
||||
|
||||
bool get hasCover => (pubMagicMetadata.coverID ?? 0) > 0;
|
||||
|
||||
// hasSharees returns true if the collection is shared with other ente users
|
||||
bool get hasSharees => sharees != null && sharees!.isNotEmpty;
|
||||
bool get hasSharees => sharees.isNotEmpty;
|
||||
|
||||
bool get isPinned => (magicMetadata.order ?? 0) != 0;
|
||||
|
||||
@@ -121,52 +121,43 @@ class Collection {
|
||||
}
|
||||
|
||||
List<User> getSharees() {
|
||||
final List<User> result = [];
|
||||
if (sharees == null) {
|
||||
return result;
|
||||
}
|
||||
for (final User? u in sharees!) {
|
||||
if (u != null) {
|
||||
result.add(u);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return sharees;
|
||||
}
|
||||
|
||||
bool isOwner(int userID) {
|
||||
return (owner?.id ?? 0) == userID;
|
||||
return (owner.id ?? -100) == userID;
|
||||
}
|
||||
|
||||
bool isDownloadEnabledForPublicLink() {
|
||||
if (publicURLs == null || publicURLs!.isEmpty) {
|
||||
if (publicURLs.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
return publicURLs?.first?.enableDownload ?? true;
|
||||
return publicURLs.first.enableDownload;
|
||||
}
|
||||
|
||||
bool isCollectEnabledForPublicLink() {
|
||||
if (publicURLs == null || publicURLs!.isEmpty) {
|
||||
if (publicURLs.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
return publicURLs?.first?.enableCollect ?? false;
|
||||
return publicURLs.first.enableCollect;
|
||||
}
|
||||
|
||||
bool get isJoinEnabled {
|
||||
if (publicURLs == null || publicURLs!.isEmpty) {
|
||||
if (publicURLs.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
return publicURLs?.first?.enableJoin ?? false;
|
||||
return publicURLs.first.enableJoin;
|
||||
}
|
||||
|
||||
CollectionParticipantRole getRole(int userID) {
|
||||
if (isOwner(userID)) {
|
||||
return CollectionParticipantRole.owner;
|
||||
}
|
||||
if (sharees == null) {
|
||||
if (sharees.isEmpty) {
|
||||
return CollectionParticipantRole.unknown;
|
||||
}
|
||||
for (final User? u in sharees!) {
|
||||
if (u != null && u.id == userID) {
|
||||
for (final User u in sharees) {
|
||||
if (u.id == userID) {
|
||||
if (u.isViewer) {
|
||||
return CollectionParticipantRole.viewer;
|
||||
} else if (u.isCollaborator) {
|
||||
@@ -185,40 +176,8 @@ class Collection {
|
||||
}
|
||||
|
||||
void updateSharees(List<User> newSharees) {
|
||||
sharees?.clear();
|
||||
sharees?.addAll(newSharees);
|
||||
}
|
||||
|
||||
static CollectionType typeFromString(String type) {
|
||||
switch (type) {
|
||||
case "folder":
|
||||
return CollectionType.folder;
|
||||
case "favorites":
|
||||
return CollectionType.favorites;
|
||||
case "uncategorized":
|
||||
return CollectionType.uncategorized;
|
||||
case "album":
|
||||
return CollectionType.album;
|
||||
case "unknown":
|
||||
return CollectionType.unknown;
|
||||
}
|
||||
debugPrint("unexpected collection type $type");
|
||||
return CollectionType.unknown;
|
||||
}
|
||||
|
||||
static String typeToString(CollectionType type) {
|
||||
switch (type) {
|
||||
case CollectionType.folder:
|
||||
return "folder";
|
||||
case CollectionType.favorites:
|
||||
return "favorites";
|
||||
case CollectionType.album:
|
||||
return "album";
|
||||
case CollectionType.uncategorized:
|
||||
return "uncategorized";
|
||||
case CollectionType.unknown:
|
||||
return "unknown";
|
||||
}
|
||||
sharees.clear();
|
||||
sharees.addAll(newSharees);
|
||||
}
|
||||
|
||||
Collection copyWith({
|
||||
@@ -303,6 +262,38 @@ enum CollectionType {
|
||||
unknown,
|
||||
}
|
||||
|
||||
CollectionType typeFromString(String type) {
|
||||
switch (type) {
|
||||
case "folder":
|
||||
return CollectionType.folder;
|
||||
case "favorites":
|
||||
return CollectionType.favorites;
|
||||
case "uncategorized":
|
||||
return CollectionType.uncategorized;
|
||||
case "album":
|
||||
return CollectionType.album;
|
||||
case "unknown":
|
||||
return CollectionType.unknown;
|
||||
}
|
||||
debugPrint("unexpected collection type $type");
|
||||
return CollectionType.unknown;
|
||||
}
|
||||
|
||||
String typeToString(CollectionType type) {
|
||||
switch (type) {
|
||||
case CollectionType.folder:
|
||||
return "folder";
|
||||
case CollectionType.favorites:
|
||||
return "favorites";
|
||||
case CollectionType.album:
|
||||
return "album";
|
||||
case CollectionType.uncategorized:
|
||||
return "uncategorized";
|
||||
case CollectionType.unknown:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
extension CollectionTypeExtn on CollectionType {
|
||||
bool get canDelete =>
|
||||
this != CollectionType.favorites && this != CollectionType.uncategorized;
|
||||
|
||||
@@ -139,7 +139,7 @@ class CollectionsService {
|
||||
}
|
||||
}
|
||||
// remove reference for incoming collections when unshared/deleted
|
||||
if (collection.isDeleted && ownerID != collection.owner?.id) {
|
||||
if (collection.isDeleted && ownerID != collection.owner.id) {
|
||||
await _db.deleteCollection(collection.id);
|
||||
} else {
|
||||
// keep entry for deletedCollection as collectionKey may be used during
|
||||
@@ -394,7 +394,7 @@ class CollectionsService {
|
||||
final List<Collection> collections =
|
||||
getCollectionsForUI(includedShared: true);
|
||||
for (final c in collections) {
|
||||
if (c.owner!.id == Configuration.instance.getUserID()) {
|
||||
if (c.owner.id == Configuration.instance.getUserID()) {
|
||||
if (c.hasSharees || c.hasLink && !c.isQuickLinkCollection()) {
|
||||
outgoing.add(c);
|
||||
} else if (c.isQuickLinkCollection()) {
|
||||
@@ -472,8 +472,8 @@ class CollectionsService {
|
||||
if (collectionID != null) {
|
||||
final Collection? collection = getCollectionByID(collectionID);
|
||||
if (collection != null) {
|
||||
if (collection.owner?.id == userID) {
|
||||
_cachedUserIdToUser[userID] = collection.owner!;
|
||||
if (collection.owner.id == userID) {
|
||||
_cachedUserIdToUser[userID] = collection.owner;
|
||||
} else {
|
||||
final matchingUser = collection.getSharees().firstWhereOrNull(
|
||||
(u) => u.id == userID,
|
||||
@@ -698,7 +698,7 @@ class CollectionsService {
|
||||
);
|
||||
final encryptedKey = CryptoUtil.base642bin(collection.encryptedKey);
|
||||
Uint8List? collectionKey;
|
||||
if (collection.owner?.id == _config.getUserID()) {
|
||||
if (collection.owner.id == _config.getUserID()) {
|
||||
// If the collection is owned by the user, decrypt with the master key
|
||||
if (_config.getKey() == null) {
|
||||
// Possible during AppStore account migration, where SecureStorage
|
||||
@@ -767,7 +767,7 @@ class CollectionsService {
|
||||
) async {
|
||||
final int ownerID = Configuration.instance.getUserID()!;
|
||||
try {
|
||||
if (collection.owner?.id != ownerID) {
|
||||
if (collection.owner.id != ownerID) {
|
||||
throw AssertionError("cannot modify albums not owned by you");
|
||||
}
|
||||
// read the existing magic metadata and apply new updates to existing data
|
||||
@@ -798,7 +798,7 @@ class CollectionsService {
|
||||
);
|
||||
await _enteDio.put(
|
||||
"/collections/magic-metadata",
|
||||
data: params,
|
||||
data: params.toJson(),
|
||||
);
|
||||
// update the local information so that it's reflected on UI
|
||||
collection.mMdEncodedJson = jsonEncode(jsonToUpdate);
|
||||
@@ -826,7 +826,7 @@ class CollectionsService {
|
||||
) async {
|
||||
final int ownerID = Configuration.instance.getUserID()!;
|
||||
try {
|
||||
if (collection.owner?.id != ownerID) {
|
||||
if (collection.owner.id != ownerID) {
|
||||
throw AssertionError("cannot modify albums not owned by you");
|
||||
}
|
||||
// read the existing magic metadata and apply new updates to existing data
|
||||
@@ -857,7 +857,7 @@ class CollectionsService {
|
||||
);
|
||||
await _enteDio.put(
|
||||
"/collections/public-magic-metadata",
|
||||
data: params,
|
||||
data: params.toJson(),
|
||||
);
|
||||
// update the local information so that it's reflected on UI
|
||||
collection.mMdPubEncodedJson = jsonEncode(jsonToUpdate);
|
||||
@@ -885,7 +885,7 @@ class CollectionsService {
|
||||
) async {
|
||||
final int ownerID = Configuration.instance.getUserID()!;
|
||||
try {
|
||||
if (collection.owner?.id == ownerID) {
|
||||
if (collection.owner.id == ownerID) {
|
||||
throw AssertionError("cannot modify sharee settings for albums owned "
|
||||
"by you");
|
||||
}
|
||||
@@ -917,7 +917,7 @@ class CollectionsService {
|
||||
);
|
||||
await _enteDio.put(
|
||||
"/collections/sharee-magic-metadata",
|
||||
data: params,
|
||||
data: params.toJson(),
|
||||
);
|
||||
// update the local information so that it's reflected on UI
|
||||
collection.sharedMmdJson = jsonEncode(jsonToUpdate);
|
||||
@@ -952,7 +952,7 @@ class CollectionsService {
|
||||
"enableJoin": true,
|
||||
},
|
||||
);
|
||||
collection.publicURLs?.add(PublicURL.fromMap(response.data["result"]));
|
||||
collection.publicURLs.add(PublicURL.fromMap(response.data["result"]));
|
||||
await _db.insert(List.from([collection]));
|
||||
_collectionIDToCollections[collection.id] = collection;
|
||||
Bus.instance.fire(
|
||||
@@ -980,8 +980,8 @@ class CollectionsService {
|
||||
data: json.encode(prop),
|
||||
);
|
||||
// remove existing url information
|
||||
collection.publicURLs?.clear();
|
||||
collection.publicURLs?.add(PublicURL.fromMap(response.data["result"]));
|
||||
collection.publicURLs.clear();
|
||||
collection.publicURLs.add(PublicURL.fromMap(response.data["result"]));
|
||||
await _db.insert(List.from([collection]));
|
||||
_collectionIDToCollections[collection.id] = collection;
|
||||
Bus.instance.fire(
|
||||
@@ -1003,7 +1003,7 @@ class CollectionsService {
|
||||
await _enteDio.delete(
|
||||
"/collections/share-url/" + collection.id.toString(),
|
||||
);
|
||||
collection.publicURLs?.clear();
|
||||
collection.publicURLs.clear();
|
||||
await _db.insert(List.from([collection]));
|
||||
_collectionIDToCollections[collection.id] = collection;
|
||||
Bus.instance.fire(
|
||||
|
||||
@@ -230,7 +230,7 @@ class FavoritesService {
|
||||
if (_cachedFavoritesCollectionID == null) {
|
||||
final collections = _collectionsService.getActiveCollections();
|
||||
for (final collection in collections) {
|
||||
if (collection.owner!.id == _config.getUserID() &&
|
||||
if (collection.owner.id == _config.getUserID() &&
|
||||
collection.type == CollectionType.favorites) {
|
||||
_cachedFavoritesCollectionID = collection.id;
|
||||
return collection;
|
||||
|
||||
@@ -32,7 +32,7 @@ extension HiddenService on CollectionsService {
|
||||
final int userID = config.getUserID()!;
|
||||
final allDefaultHidden = collectionIDToCollections.values
|
||||
.where(
|
||||
(element) => element.isDefaultHidden() && element.owner!.id == userID,
|
||||
(element) => element.isDefaultHidden() && element.owner.id == userID,
|
||||
)
|
||||
.toList();
|
||||
|
||||
@@ -101,7 +101,7 @@ extension HiddenService on CollectionsService {
|
||||
collectionIDToCollections.values.firstWhereOrNull(
|
||||
(element) =>
|
||||
element.type == CollectionType.uncategorized &&
|
||||
element.owner!.id == userID,
|
||||
element.owner.id == userID,
|
||||
);
|
||||
if (matchedCollection != null) {
|
||||
cachedUncategorizedCollection = matchedCollection;
|
||||
@@ -166,7 +166,9 @@ extension HiddenService on CollectionsService {
|
||||
await dialog.hide();
|
||||
} on AssertionError catch (e) {
|
||||
await dialog.hide();
|
||||
unawaited(showErrorDialog(context, S.of(context).oops, e.message as String));
|
||||
unawaited(
|
||||
showErrorDialog(context, S.of(context).oops, e.message as String),
|
||||
);
|
||||
return false;
|
||||
} catch (e, s) {
|
||||
_logger.severe("Could not hide", e, s);
|
||||
|
||||
@@ -193,6 +193,28 @@ class MLIndexingIsolate extends SuperIsolate {
|
||||
}
|
||||
}
|
||||
|
||||
/// WARNING: This method is only for debugging purposes. It should not be used in production.
|
||||
Future<void> debugLoadSingleModel(MLModels model) {
|
||||
return _initModelLock.synchronized(() async {
|
||||
final modelInstance = model.model;
|
||||
if (modelInstance.isInitialized) {
|
||||
_logger.info("Model ${model.name} already loaded");
|
||||
return;
|
||||
}
|
||||
final modelName = modelInstance.modelName;
|
||||
final modelPath = await modelInstance.downloadModelSafe();
|
||||
if (modelPath == null) {
|
||||
_logger.severe("Could not download model, no wifi");
|
||||
return;
|
||||
}
|
||||
final address = await runInIsolate(IsolateOperation.loadModel, {
|
||||
"modelName": modelName,
|
||||
"modelPath": modelPath,
|
||||
}) as int;
|
||||
modelInstance.storeSessionAddress(address);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> cleanupLocalIndexingModels({bool delete = false}) async {
|
||||
if (!areModelsDownloaded) return;
|
||||
await _releaseModels();
|
||||
|
||||
@@ -135,22 +135,6 @@ class PreviewVideoStore {
|
||||
}
|
||||
}
|
||||
|
||||
final fileSize = file.lengthSync();
|
||||
FFProbeProps? props;
|
||||
|
||||
if (fileSize <= 10 * 1024 * 1024) {
|
||||
props = await getVideoPropsAsync(file);
|
||||
final videoData = List.from(props?.propData?["streams"] ?? [])
|
||||
.firstWhereOrNull((e) => e["type"] == "video");
|
||||
|
||||
final codec = videoData["codec_name"]?.toString().toLowerCase();
|
||||
final codecIsH264 = codec?.contains("h264") ?? false;
|
||||
if (codecIsH264) {
|
||||
_items.removeWhere((key, value) => value.file == enteFile);
|
||||
Bus.instance.fire(PreviewUpdatedEvent(_items));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (uploadingFileId >= 0) {
|
||||
_items[enteFile.uploadedFileID!] = PreviewItem(
|
||||
status: PreviewItemStatus.inQueue,
|
||||
@@ -174,7 +158,8 @@ class PreviewVideoStore {
|
||||
);
|
||||
Bus.instance.fire(PreviewUpdatedEvent(_items));
|
||||
|
||||
props ??= await getVideoPropsAsync(file);
|
||||
final props = await getVideoPropsAsync(file);
|
||||
final fileSize = enteFile.fileSize ?? file.lengthSync();
|
||||
|
||||
final videoData = List.from(props?.propData?["streams"] ?? [])
|
||||
.firstWhereOrNull((e) => e["type"] == "video");
|
||||
@@ -207,13 +192,11 @@ class PreviewVideoStore {
|
||||
final codecIsH264 = codec?.contains("h264") ?? false;
|
||||
|
||||
if (bitrate != null && bitrate <= 4000 * 1000 && codecIsH264) {
|
||||
// create playlist without compression, as is
|
||||
session = await FFmpegKit.execute(
|
||||
'-i "${file.path}" '
|
||||
'-metadata:s:v:0 rotate=0 ' // Adjust metadata if needed
|
||||
'-c:v copy ' // Copy the original video codec
|
||||
'-c:a copy ' // Copy the original audio codec
|
||||
'-f hls -hls_time 10 -hls_flags single_file '
|
||||
'-metadata:s:v:0 rotate=0 '
|
||||
'-c:v copy -c:a copy '
|
||||
'-f hls -hls_time 2 -hls_flags single_file '
|
||||
'-hls_list_size 0 -hls_key_info_file ${keyinfo.path} '
|
||||
'$prefix/output.m3u8',
|
||||
);
|
||||
@@ -221,16 +204,14 @@ class PreviewVideoStore {
|
||||
codec != null &&
|
||||
bitrate <= 2000 * 1000 &&
|
||||
!codecIsH264) {
|
||||
// compress video with crf=21, h264 no change in resolution or frame rate,
|
||||
// just change color scheme
|
||||
session = await FFmpegKit.execute(
|
||||
'-i "${file.path}" '
|
||||
'-metadata:s:v:0 rotate=0 ' // Keep rotation metadata
|
||||
'-vf "format=yuv420p10le,zscale=transfer=linear,tonemap=tonemap=hable:desat=0:peak=10,zscale=transfer=bt709:matrix=bt709:primaries=bt709,format=yuv420p" ' // Adjust color scheme
|
||||
'-color_primaries bt709 -color_trc bt709 -colorspace bt709 ' // Set color profile to BT.709
|
||||
'-c:v libx264 -crf 21 -preset medium ' // Compress with CRF=21 using H.264
|
||||
'-c:a copy ' // Keep original audio
|
||||
'-f hls -hls_time 10 -hls_flags single_file '
|
||||
'-metadata:s:v:0 rotate=0 '
|
||||
'-vf "format=yuv420p10le,zscale=transfer=linear,tonemap=tonemap=hable:desat=0:peak=10,zscale=transfer=bt709:matrix=bt709:primaries=bt709,format=yuv420p" '
|
||||
'-color_primaries bt709 -color_trc bt709 -colorspace bt709 '
|
||||
'-c:v libx264 -crf 23 -preset medium '
|
||||
'-c:a copy '
|
||||
'-f hls -hls_time 2 -hls_flags single_file '
|
||||
'-hls_list_size 0 -hls_key_info_file ${keyinfo.path} '
|
||||
'$prefix/output.m3u8',
|
||||
);
|
||||
@@ -241,8 +222,8 @@ class PreviewVideoStore {
|
||||
'-i "${file.path}" '
|
||||
'-metadata:s:v:0 rotate=0 '
|
||||
'-vf "scale=-2:720,fps=30" '
|
||||
'-c:v libx264 -b:v 2000k -preset medium '
|
||||
'-c:a aac -b:a 128k -f hls -hls_time 10 -hls_flags single_file '
|
||||
'-c:v libx264 -b:v 2000k -crf 23 -preset medium '
|
||||
'-c:a aac -b:a 128k -f hls -hls_time 2 -hls_flags single_file '
|
||||
'-hls_list_size 0 -hls_key_info_file ${keyinfo.path} '
|
||||
'$prefix/output.m3u8',
|
||||
);
|
||||
@@ -254,8 +235,8 @@ class PreviewVideoStore {
|
||||
'-vf "scale=-2:720,fps=30,format=yuv420p10le,zscale=transfer=linear,tonemap=tonemap=hable:desat=0:peak=10,zscale=transfer=bt709:matrix=bt709:primaries=bt709,format=yuv420p" '
|
||||
'-color_primaries bt709 -color_trc bt709 -colorspace bt709 '
|
||||
'-x264-params "colorprim=bt709:transfer=bt709:colormatrix=bt709" '
|
||||
'-c:v libx264 -b:v 2000k -preset medium '
|
||||
'-c:a aac -b:a 128k -f hls -hls_time 10 -hls_flags single_file '
|
||||
'-c:v libx264 -b:v 2000k -crf 23 -preset medium '
|
||||
'-c:a aac -b:a 128k -f hls -hls_time 2 -hls_flags single_file '
|
||||
'-hls_list_size 0 -hls_key_info_file ${keyinfo.path} '
|
||||
'$prefix/output.m3u8',
|
||||
);
|
||||
@@ -601,21 +582,49 @@ class PreviewVideoStore {
|
||||
final files = await FilesDB.instance.getAllFilesAfterDate(
|
||||
fileType: FileType.video,
|
||||
beginDate: cutoff,
|
||||
userID: Configuration.instance.getUserID()!,
|
||||
);
|
||||
|
||||
final previewIds = FileDataService.instance.previewIds;
|
||||
final allFiles = files
|
||||
.where((file) => previewIds?[file.uploadedFileID] == null)
|
||||
.toList();
|
||||
.sorted((a, b) {
|
||||
// put higher duration videos last
|
||||
final first = a.duration == null || a.duration! >= 10 * 60 ? 1 : 0;
|
||||
final second = b.duration == null || b.duration! >= 10 * 60 ? 1 : 0;
|
||||
return first.compareTo(second);
|
||||
}).toList();
|
||||
|
||||
// set all video status to be in queue
|
||||
for (final file in allFiles) {
|
||||
_items[file.uploadedFileID!] = PreviewItem(
|
||||
for (final enteFile in allFiles) {
|
||||
final fileSize = enteFile.fileSize;
|
||||
FFProbeProps? props;
|
||||
|
||||
if (fileSize != null && fileSize <= 10 * 1024 * 1024) {
|
||||
final file = await getFile(enteFile, isOrigin: true);
|
||||
if (file != null) {
|
||||
props = await getVideoPropsAsync(file);
|
||||
final videoData = List.from(props?.propData?["streams"] ?? [])
|
||||
.firstWhereOrNull((e) => e["type"] == "video");
|
||||
|
||||
final codec = videoData["codec_name"]?.toString().toLowerCase();
|
||||
final codecIsH264 = codec?.contains("h264") ?? false;
|
||||
|
||||
if (codecIsH264) {
|
||||
_items.removeWhere((key, value) => value.file == enteFile);
|
||||
Bus.instance.fire(PreviewUpdatedEvent(_items));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_items[enteFile.uploadedFileID!] = PreviewItem(
|
||||
status: PreviewItemStatus.inQueue,
|
||||
file: file,
|
||||
collectionID: file.collectionID ?? 0,
|
||||
file: enteFile,
|
||||
collectionID: enteFile.collectionID ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
Bus.instance.fire(PreviewUpdatedEvent(_items));
|
||||
|
||||
final file = allFiles.first;
|
||||
|
||||
@@ -1311,34 +1311,30 @@ class UserService {
|
||||
|
||||
for (final c in CollectionsService.instance.getActiveCollections()) {
|
||||
// Add collaborators and viewers of collections owned by user
|
||||
if (c.owner?.id == ownerID) {
|
||||
for (final User? u in c.sharees ?? []) {
|
||||
if (u != null && u.id != null && u.email.isNotEmpty) {
|
||||
if (c.owner.id == ownerID) {
|
||||
for (final User u in c.sharees) {
|
||||
if (u.id != null && u.email.isNotEmpty) {
|
||||
if (!existingEmails.contains(u.email)) {
|
||||
relevantUsers.add(u);
|
||||
existingEmails.add(u.email);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (c.owner?.id != null && c.owner!.email.isNotEmpty) {
|
||||
} else if (c.owner.id != null && c.owner.email.isNotEmpty) {
|
||||
// Add owners of collections shared with user
|
||||
if (!existingEmails.contains(c.owner!.email)) {
|
||||
relevantUsers.add(c.owner!);
|
||||
existingEmails.add(c.owner!.email);
|
||||
if (!existingEmails.contains(c.owner.email)) {
|
||||
relevantUsers.add(c.owner);
|
||||
existingEmails.add(c.owner.email);
|
||||
}
|
||||
// Add collaborators of collections shared with user where user is a
|
||||
// viewer or a collaborator
|
||||
for (final User? u in c.sharees ?? []) {
|
||||
if (u != null &&
|
||||
u.id != null &&
|
||||
for (final User u in c.sharees) {
|
||||
if (u.id != null &&
|
||||
u.email.isNotEmpty &&
|
||||
u.email == ownerEmail &&
|
||||
(u.isCollaborator || u.isViewer)) {
|
||||
for (final User? u in c.sharees ?? []) {
|
||||
if (u != null &&
|
||||
u.id != null &&
|
||||
u.email.isNotEmpty &&
|
||||
u.isCollaborator) {
|
||||
for (final User u in c.sharees) {
|
||||
if (u.id != null && u.email.isNotEmpty && u.isCollaborator) {
|
||||
if (!existingEmails.contains(u.email)) {
|
||||
relevantUsers.add(u);
|
||||
existingEmails.add(u.email);
|
||||
@@ -1392,32 +1388,28 @@ class UserService {
|
||||
|
||||
for (final c in CollectionsService.instance.getActiveCollections()) {
|
||||
// Add collaborators and viewers of collections owned by user
|
||||
if (c.owner?.id == ownerID) {
|
||||
for (final User? u in c.sharees ?? []) {
|
||||
if (u != null && u.id != null && u.email.isNotEmpty) {
|
||||
if (c.owner.id == ownerID) {
|
||||
for (final User u in c.sharees) {
|
||||
if (u.id != null && u.email.isNotEmpty) {
|
||||
if (!emailIDs.contains(u.email)) {
|
||||
emailIDs.add(u.email);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (c.owner?.id != null && c.owner!.email.isNotEmpty) {
|
||||
} else if (c.owner.id != null && c.owner.email.isNotEmpty) {
|
||||
// Add owners of collections shared with user
|
||||
if (!emailIDs.contains(c.owner!.email)) {
|
||||
emailIDs.add(c.owner!.email);
|
||||
if (!emailIDs.contains(c.owner.email)) {
|
||||
emailIDs.add(c.owner.email);
|
||||
}
|
||||
// Add collaborators of collections shared with user where user is a
|
||||
// viewer or a collaborator
|
||||
for (final User? u in c.sharees ?? []) {
|
||||
if (u != null &&
|
||||
u.id != null &&
|
||||
for (final User u in c.sharees) {
|
||||
if (u.id != null &&
|
||||
u.email.isNotEmpty &&
|
||||
u.email == ownerEmail &&
|
||||
(u.isCollaborator || u.isViewer)) {
|
||||
for (final User? u in c.sharees ?? []) {
|
||||
if (u != null &&
|
||||
u.id != null &&
|
||||
u.email.isNotEmpty &&
|
||||
u.isCollaborator) {
|
||||
for (final User u in c.sharees) {
|
||||
if (u.id != null && u.email.isNotEmpty && u.isCollaborator) {
|
||||
if (!emailIDs.contains(u.email)) {
|
||||
emailIDs.add(u.email);
|
||||
}
|
||||
|
||||
@@ -147,7 +147,7 @@ extension CollectionFileActions on CollectionActions {
|
||||
// Newly created collection might not be cached
|
||||
final Collection? c =
|
||||
CollectionsService.instance.getCollectionByID(collectionID);
|
||||
if (c != null && c.owner!.id != currentUserID) {
|
||||
if (c != null && c.owner.id != currentUserID) {
|
||||
if (!showProgressDialog) {
|
||||
dialog = createProgressDialog(
|
||||
context,
|
||||
|
||||
@@ -340,7 +340,7 @@ class CollectionActions {
|
||||
) async {
|
||||
final textTheme = getEnteTextTheme(bContext);
|
||||
final currentUserID = Configuration.instance.getUserID()!;
|
||||
if (collection.owner!.id != currentUserID) {
|
||||
if (collection.owner.id != currentUserID) {
|
||||
throw AssertionError("Can not delete album owned by others");
|
||||
}
|
||||
if (collection.hasSharees) {
|
||||
@@ -495,7 +495,7 @@ class CollectionActions {
|
||||
bool isHidden = false,
|
||||
}) async {
|
||||
final int currentUserID = Configuration.instance.getUserID()!;
|
||||
final isCollectionOwner = collection.owner!.id == currentUserID;
|
||||
final isCollectionOwner = collection.owner.id == currentUserID;
|
||||
final FilesSplit split = FilesSplit.split(
|
||||
files,
|
||||
Configuration.instance.getUserID()!,
|
||||
@@ -631,7 +631,7 @@ class CollectionActions {
|
||||
if (targetCollection == null ||
|
||||
(CollectionType.uncategorized == targetCollection.type ||
|
||||
targetCollection.type == CollectionType.favorites) ||
|
||||
targetCollection.owner!.id != userID) {
|
||||
targetCollection.owner.id != userID) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -41,7 +41,7 @@ class AlbumRowItemWidget extends StatelessWidget {
|
||||
final Widget? linkIcon = c.hasLink && isOwner
|
||||
? Icon(
|
||||
Icons.link,
|
||||
color: c.publicURLs!.first!.isExpired ? warning500 : strokeBaseDark,
|
||||
color: c.publicURLs.first.isExpired ? warning500 : strokeBaseDark,
|
||||
)
|
||||
: null;
|
||||
return GestureDetector(
|
||||
@@ -115,7 +115,7 @@ class AlbumRowItemWidget extends StatelessWidget {
|
||||
bottom: 8.0,
|
||||
),
|
||||
child: UserAvatarWidget(
|
||||
c.owner!,
|
||||
c.owner,
|
||||
thumbnailView: true,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -287,8 +287,8 @@ class AlbumVerticalListWidget extends StatelessWidget {
|
||||
CollectionActions(CollectionsService.instance);
|
||||
|
||||
if (collection.hasLink) {
|
||||
if (collection.publicURLs!.first!.enableCollect) {
|
||||
if (Configuration.instance.getUserID() == collection.owner!.id) {
|
||||
if (collection.publicURLs.first.enableCollect) {
|
||||
if (Configuration.instance.getUserID() == collection.owner.id) {
|
||||
unawaited(
|
||||
routeToPage(
|
||||
context,
|
||||
@@ -334,7 +334,7 @@ class AlbumVerticalListWidget extends StatelessWidget {
|
||||
context,
|
||||
S.of(context).collaborativeLinkCreatedFor(collection.displayName),
|
||||
);
|
||||
if (Configuration.instance.getUserID() == collection.owner!.id) {
|
||||
if (Configuration.instance.getUserID() == collection.owner.id) {
|
||||
unawaited(
|
||||
routeToPage(
|
||||
context,
|
||||
@@ -353,7 +353,7 @@ class AlbumVerticalListWidget extends StatelessWidget {
|
||||
BuildContext context,
|
||||
Collection collection,
|
||||
) {
|
||||
if (Configuration.instance.getUserID() == collection.owner!.id) {
|
||||
if (Configuration.instance.getUserID() == collection.owner.id) {
|
||||
unawaited(
|
||||
routeToPage(
|
||||
context,
|
||||
|
||||
@@ -117,28 +117,26 @@ class AdvancedSettingsScreen extends StatelessWidget {
|
||||
},
|
||||
),
|
||||
),
|
||||
if (flagService.internalUser) ...[
|
||||
const SizedBox(height: 24),
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: S.of(context).videoStreaming,
|
||||
),
|
||||
menuItemColor: colorScheme.fillFaint,
|
||||
singleBorderRadius: 8,
|
||||
alignCaptionedTextToLeft: true,
|
||||
trailingWidget: ToggleSwitchWidget(
|
||||
value: () => PreviewVideoStore
|
||||
.instance.isVideoStreamingEnabled,
|
||||
onChanged: () async {
|
||||
final isEnabled = PreviewVideoStore
|
||||
.instance.isVideoStreamingEnabled;
|
||||
|
||||
await PreviewVideoStore.instance
|
||||
.setIsVideoStreamingEnabled(!isEnabled);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
MenuItemWidget(
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: S.of(context).videoStreaming,
|
||||
),
|
||||
],
|
||||
menuItemColor: colorScheme.fillFaint,
|
||||
singleBorderRadius: 8,
|
||||
alignCaptionedTextToLeft: true,
|
||||
trailingWidget: ToggleSwitchWidget(
|
||||
value: () => PreviewVideoStore
|
||||
.instance.isVideoStreamingEnabled,
|
||||
onChanged: () async {
|
||||
final isEnabled = PreviewVideoStore
|
||||
.instance.isVideoStreamingEnabled;
|
||||
|
||||
await PreviewVideoStore.instance
|
||||
.setIsVideoStreamingEnabled(!isEnabled);
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
|
||||
@@ -5,6 +5,8 @@ import "package:photos/db/ml/base.dart";
|
||||
import "package:photos/db/ml/db.dart";
|
||||
import "package:photos/events/people_changed_event.dart";
|
||||
import "package:photos/service_locator.dart";
|
||||
import "package:photos/services/machine_learning/ml_indexing_isolate.dart";
|
||||
import "package:photos/services/machine_learning/ml_models_overview.dart";
|
||||
import "package:photos/services/machine_learning/semantic_search/semantic_search_service.dart";
|
||||
import "package:photos/theme/ente_theme.dart";
|
||||
import "package:photos/ui/components/buttons/button_widget.dart";
|
||||
@@ -104,6 +106,90 @@ class _MLUserDeveloperOptionsState extends State<MLUserDeveloperOptions> {
|
||||
isBottomBorderRadiusRemoved: true,
|
||||
isGestureDetectorDisabled: true,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
ButtonWidget(
|
||||
buttonType: ButtonType.neutral,
|
||||
labelText: "Load face detection model",
|
||||
onTap: () async {
|
||||
try {
|
||||
await MLIndexingIsolate.instance
|
||||
.debugLoadSingleModel(MLModels.faceDetection);
|
||||
} catch (e, s) {
|
||||
_logger.severe(
|
||||
"Could not load face detection model",
|
||||
e,
|
||||
s,
|
||||
);
|
||||
await showGenericErrorDialog(
|
||||
context: context,
|
||||
error: e,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
ButtonWidget(
|
||||
buttonType: ButtonType.neutral,
|
||||
labelText: "Load face recognition model",
|
||||
onTap: () async {
|
||||
try {
|
||||
await MLIndexingIsolate.instance
|
||||
.debugLoadSingleModel(MLModels.faceEmbedding);
|
||||
} catch (e, s) {
|
||||
_logger.severe(
|
||||
"Could not load face detection model",
|
||||
e,
|
||||
s,
|
||||
);
|
||||
await showGenericErrorDialog(
|
||||
context: context,
|
||||
error: e,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
ButtonWidget(
|
||||
buttonType: ButtonType.neutral,
|
||||
labelText: "Load clip image model",
|
||||
onTap: () async {
|
||||
try {
|
||||
await MLIndexingIsolate.instance
|
||||
.debugLoadSingleModel(MLModels.clipImageEncoder);
|
||||
} catch (e, s) {
|
||||
_logger.severe(
|
||||
"Could not load face detection model",
|
||||
e,
|
||||
s,
|
||||
);
|
||||
await showGenericErrorDialog(
|
||||
context: context,
|
||||
error: e,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
ButtonWidget(
|
||||
buttonType: ButtonType.neutral,
|
||||
labelText: "Load clip text model",
|
||||
onTap: () async {
|
||||
try {
|
||||
await MLIndexingIsolate.instance
|
||||
.debugLoadSingleModel(MLModels.clipTextEncoder);
|
||||
} catch (e, s) {
|
||||
_logger.severe(
|
||||
"Could not load face detection model",
|
||||
e,
|
||||
s,
|
||||
);
|
||||
await showGenericErrorDialog(
|
||||
context: context,
|
||||
error: e,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
const SafeArea(
|
||||
child: SizedBox(
|
||||
height: 12,
|
||||
|
||||
@@ -363,8 +363,8 @@ class _AddParticipantPage extends State<AddParticipantPage> {
|
||||
|
||||
List<User> _getSuggestedUser() {
|
||||
final Set<String> existingEmails = {};
|
||||
for (final User? u in widget.collection.sharees ?? []) {
|
||||
if (u != null && u.id != null && u.email.isNotEmpty) {
|
||||
for (final User u in widget.collection.sharees) {
|
||||
if (u.id != null && u.email.isNotEmpty) {
|
||||
existingEmails.add(u.email);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,11 +64,11 @@ class _AlbumParticipantsPageState extends State<AlbumParticipantsPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isOwner =
|
||||
widget.collection.owner?.id == Configuration.instance.getUserID();
|
||||
widget.collection.owner.id == Configuration.instance.getUserID();
|
||||
final colorScheme = getEnteColorScheme(context);
|
||||
final currentUserID = Configuration.instance.getUserID()!;
|
||||
final int participants = 1 + widget.collection.getSharees().length;
|
||||
final User owner = widget.collection.owner!;
|
||||
final User owner = widget.collection.owner;
|
||||
if (owner.id == currentUserID && owner.email == "") {
|
||||
owner.email = Configuration.instance.getEmail()!;
|
||||
}
|
||||
@@ -107,11 +107,9 @@ class _AlbumParticipantsPageState extends State<AlbumParticipantsPage> {
|
||||
captionedTextWidget: CaptionedTextWidget(
|
||||
title: isOwner
|
||||
? S.of(context).you
|
||||
: widget.collection.owner != null
|
||||
? _nameIfAvailableElseEmail(
|
||||
widget.collection.owner!,
|
||||
)
|
||||
: '',
|
||||
: _nameIfAvailableElseEmail(
|
||||
widget.collection.owner,
|
||||
),
|
||||
makeTextBold: isOwner,
|
||||
),
|
||||
leadingIconWidget: UserAvatarWidget(
|
||||
|
||||
@@ -47,13 +47,13 @@ class _ManageSharedLinkWidgetState extends State<ManageSharedLinkWidget> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isCollectEnabled =
|
||||
widget.collection!.publicURLs?.firstOrNull?.enableCollect ?? false;
|
||||
widget.collection!.publicURLs.firstOrNull?.enableCollect ?? false;
|
||||
final isDownloadEnabled =
|
||||
widget.collection!.publicURLs?.firstOrNull?.enableDownload ?? true;
|
||||
widget.collection!.publicURLs.firstOrNull?.enableDownload ?? true;
|
||||
final isPasswordEnabled =
|
||||
widget.collection!.publicURLs?.firstOrNull?.passwordEnabled ?? false;
|
||||
widget.collection!.publicURLs.firstOrNull?.passwordEnabled ?? false;
|
||||
final enteColorScheme = getEnteColorScheme(context);
|
||||
final PublicURL url = widget.collection!.publicURLs!.firstOrNull!;
|
||||
final PublicURL url = widget.collection!.publicURLs.firstOrNull!;
|
||||
final String collectionKey = Base58Encode(
|
||||
CollectionsService.instance.getCollectionKey(widget.collection!.id),
|
||||
);
|
||||
|
||||
@@ -72,7 +72,7 @@ class _ItemsWidgetState extends State<ItemsWidget> {
|
||||
bool isCustomLimit = false;
|
||||
@override
|
||||
void initState() {
|
||||
currentDeviceLimit = widget.collection.publicURLs!.first!.deviceLimit;
|
||||
currentDeviceLimit = widget.collection.publicURLs.first.deviceLimit;
|
||||
initialDeviceLimit = currentDeviceLimit;
|
||||
if (!publicLinkDeviceLimits.contains(currentDeviceLimit)) {
|
||||
isCustomLimit = true;
|
||||
|
||||
@@ -61,7 +61,7 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
_sharees = widget.collection.sharees ?? [];
|
||||
_sharees = widget.collection.sharees;
|
||||
final bool hasUrl = widget.collection.hasLink;
|
||||
final children = <Widget>[];
|
||||
children.add(
|
||||
@@ -136,7 +136,7 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
|
||||
}
|
||||
|
||||
final bool hasExpired =
|
||||
widget.collection.publicURLs?.firstOrNull?.isExpired ?? false;
|
||||
widget.collection.publicURLs.firstOrNull?.isExpired ?? false;
|
||||
children.addAll([
|
||||
const SizedBox(
|
||||
height: 24,
|
||||
@@ -166,7 +166,7 @@ class _ShareCollectionPageState extends State<ShareCollectionPage> {
|
||||
CollectionsService.instance.getCollectionKey(widget.collection.id),
|
||||
);
|
||||
final String url =
|
||||
"${widget.collection.publicURLs!.first!.url}#$collectionKey";
|
||||
"${widget.collection.publicURLs.first.url}#$collectionKey";
|
||||
children.addAll(
|
||||
[
|
||||
MenuItemWidget(
|
||||
|
||||
@@ -275,7 +275,7 @@ class _HomeWidgetState extends State<HomeWidget> {
|
||||
final existingCollection =
|
||||
CollectionsService.instance.getCollectionByID(collection.id);
|
||||
|
||||
if (collection.owner!.id! == Configuration.instance.getUserID() ||
|
||||
if (collection.isOwner(Configuration.instance.getUserID() ?? -1) ||
|
||||
(existingCollection != null && !existingCollection.isDeleted)) {
|
||||
await routeToPage(
|
||||
context,
|
||||
@@ -286,8 +286,8 @@ class _HomeWidgetState extends State<HomeWidget> {
|
||||
return;
|
||||
}
|
||||
final dialog = createProgressDialog(context, "Loading...");
|
||||
final publicUrl = collection.publicURLs![0];
|
||||
if (!publicUrl!.enableDownload) {
|
||||
final publicUrl = collection.publicURLs[0];
|
||||
if (!publicUrl.enableDownload) {
|
||||
await showErrorDialog(
|
||||
context,
|
||||
context.l10n.canNotOpenTitle,
|
||||
|
||||
@@ -109,7 +109,7 @@ class QuickLinkAlbumItem extends StatelessWidget {
|
||||
style: textTheme.smallMuted,
|
||||
),
|
||||
c.hasLink
|
||||
? (c.publicURLs!.first!.isExpired
|
||||
? (c.publicURLs.first.isExpired
|
||||
? Icon(
|
||||
Icons.link_outlined,
|
||||
color: colorScheme.warning500,
|
||||
|
||||
@@ -804,7 +804,7 @@ class _FileSelectionActionsWidgetState
|
||||
.getCollectionKey(_cachedCollectionForSharedLink!.id),
|
||||
);
|
||||
final String url =
|
||||
"${_cachedCollectionForSharedLink!.publicURLs?.first?.url}#$collectionKey";
|
||||
"${_cachedCollectionForSharedLink!.publicURLs.first.url}#$collectionKey";
|
||||
unawaited(Clipboard.setData(ClipboardData(text: url)));
|
||||
await shareImageAndUrl(
|
||||
placeholderBytes,
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
import "dart:io";
|
||||
|
||||
import 'package:chewie/chewie.dart';
|
||||
// import 'package:chewie/src/notifiers/player_notifier.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import "package:fluttertoast/fluttertoast.dart";
|
||||
@@ -22,6 +23,7 @@ import "package:photos/utils/dialog_util.dart";
|
||||
import 'package:photos/utils/file_util.dart';
|
||||
import 'package:photos/utils/toast_util.dart';
|
||||
import "package:photos/utils/wakelock_util.dart";
|
||||
// import "package:provider/provider.dart";
|
||||
import 'package:video_player/video_player.dart';
|
||||
import 'package:visibility_detector/visibility_detector.dart';
|
||||
|
||||
@@ -56,6 +58,8 @@ class _PreviewVideoWidgetState extends State<PreviewVideoWidget> {
|
||||
late final StreamSubscription<GuestViewEvent> _fileSwipeLockEventSubscription;
|
||||
File? previewFile;
|
||||
|
||||
bool initializedPlayerNotifier = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -237,9 +241,6 @@ class _PreviewVideoWidgetState extends State<PreviewVideoWidget> {
|
||||
_videoPlayerController!.addListener(() {
|
||||
if (_isPlaying != _videoPlayerController!.value.isPlaying) {
|
||||
_isPlaying = _videoPlayerController!.value.isPlaying;
|
||||
if (widget.playbackCallback != null) {
|
||||
widget.playbackCallback!(_isPlaying);
|
||||
}
|
||||
unawaited(_keepScreenAliveOnPlaying(_isPlaying));
|
||||
}
|
||||
});
|
||||
@@ -251,10 +252,13 @@ class _PreviewVideoWidgetState extends State<PreviewVideoWidget> {
|
||||
looping: true,
|
||||
allowMuting: true,
|
||||
allowFullScreen: false,
|
||||
customControls: VideoControls(
|
||||
file: widget.file,
|
||||
onStreamChange: widget.onStreamChange,
|
||||
),
|
||||
customControls: (hideStuff) {
|
||||
widget.playbackCallback!(hideStuff);
|
||||
return VideoControls(
|
||||
file: widget.file,
|
||||
onStreamChange: widget.onStreamChange,
|
||||
);
|
||||
},
|
||||
);
|
||||
return Container(
|
||||
color: Colors.black,
|
||||
|
||||
@@ -48,7 +48,7 @@ class _EmptyAlbumStateNewState extends State<CollectPhotosBottomButtons> {
|
||||
final String collectionKey = Base58Encode(
|
||||
CollectionsService.instance.getCollectionKey(widget.c.id),
|
||||
);
|
||||
final String url = "${widget.c.publicURLs!.first!.url}#$collectionKey";
|
||||
final String url = "${widget.c.publicURLs.first.url}#$collectionKey";
|
||||
await shareAlbumLinkWithPlaceholder(
|
||||
context,
|
||||
widget.c,
|
||||
|
||||
@@ -820,7 +820,7 @@ class _GalleryAppBarWidgetState extends State<GalleryAppBarWidget> {
|
||||
"Cannot share empty collection of type $galleryType",
|
||||
);
|
||||
}
|
||||
if (Configuration.instance.getUserID() == widget.collection!.owner!.id) {
|
||||
if (Configuration.instance.getUserID() == widget.collection!.owner.id) {
|
||||
unawaited(
|
||||
routeToPage(
|
||||
context,
|
||||
|
||||
@@ -216,7 +216,7 @@ Future<Map<String, Uint8List>?> _getFaceCrops(
|
||||
faceBoxes,
|
||||
);
|
||||
final Map<String, Uint8List> result = {};
|
||||
for (int i = 0; i < faceIds.length; i++) {
|
||||
for (int i = 0; i < faceCrop.length; i++) {
|
||||
result[faceIds[i]] = faceCrop[i];
|
||||
}
|
||||
return result;
|
||||
|
||||
@@ -170,7 +170,7 @@ Future<List<Uint8List>> generateFaceThumbnailsUsingCanvas(
|
||||
return faceThumbnails;
|
||||
} catch (e, s) {
|
||||
_logger.severe(
|
||||
'Error generating face thumbnails. cropImage problematic input argument: ${faceBoxes[i]}',
|
||||
'Error generating face thumbnails. cropImage problematic input argument: ${i}th facebox: ${faceBoxes[i].toString()}',
|
||||
e,
|
||||
s,
|
||||
);
|
||||
|
||||
@@ -259,11 +259,11 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: forked_video_player_plus
|
||||
resolved-ref: "2d8908efe9d7533ec76abe2e59444547c4031f28"
|
||||
url: "https://github.com/ente-io/chewie.git"
|
||||
ref: latest-changes
|
||||
resolved-ref: "318339620ff3262b45645aaebc77c5f50a4b6e83"
|
||||
url: "https://github.com/prateekmedia/chewie.git"
|
||||
source: git
|
||||
version: "1.7.1"
|
||||
version: "1.10.0"
|
||||
cli_util:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1835,18 +1835,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: package_info_plus
|
||||
sha256: "7e76fad405b3e4016cd39d08f455a4eb5199723cf594cd1b8916d47140d93017"
|
||||
sha256: "67eae327b1b0faf761964a1d2e5d323c797f3799db0e85aa232db8d9e922bc35"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.0"
|
||||
version: "8.2.1"
|
||||
package_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_platform_interface
|
||||
sha256: "9bc8ba46813a4cc42c66ab781470711781940780fd8beddd0c3da62506d3a6c6"
|
||||
sha256: "205ec83335c2ab9107bbba3f8997f9356d72ca3c715d2f038fc773d0366b4c76"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
version: "3.1.0"
|
||||
page_transition:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -2915,18 +2915,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: wakelock_plus
|
||||
sha256: f268ca2116db22e57577fb99d52515a24bdc1d570f12ac18bb762361d43b043d
|
||||
sha256: "36c88af0b930121941345306d259ec4cc4ecca3b151c02e3a9e71aede83c615e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.4"
|
||||
version: "1.2.10"
|
||||
wakelock_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: wakelock_plus_platform_interface
|
||||
sha256: "422d1cdbb448079a8a62a5a770b69baa489f8f7ca21aef47800c726d404f9d16"
|
||||
sha256: "70e780bc99796e1db82fe764b1e7dcb89a86f1e5b3afb1db354de50f2e41eb7a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
version: "1.2.2"
|
||||
wallpaper_manager_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
@@ -12,7 +12,7 @@ description: ente photos application
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
|
||||
version: 0.9.96+996
|
||||
version: 0.9.98+998
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
@@ -30,8 +30,8 @@ dependencies:
|
||||
cached_network_image: ^3.0.0
|
||||
chewie:
|
||||
git:
|
||||
url: https://github.com/ente-io/chewie.git
|
||||
ref: forked_video_player_plus
|
||||
url: https://github.com/prateekmedia/chewie.git
|
||||
ref: latest-changes
|
||||
collection: # dart
|
||||
computer:
|
||||
git: "https://github.com/ente-io/computer.git"
|
||||
@@ -142,7 +142,7 @@ dependencies:
|
||||
url: https://github.com/ente-io/onnxruntime.git
|
||||
ref: ios_only
|
||||
open_mail_app: ^0.4.5
|
||||
package_info_plus: ^4.1.0
|
||||
package_info_plus:
|
||||
page_transition: ^2.0.2
|
||||
panorama:
|
||||
git:
|
||||
|
||||
@@ -293,6 +293,7 @@ func main() {
|
||||
BillingCtrl: billingController,
|
||||
UserRepo: userRepo,
|
||||
UserCacheCtrl: userCacheCtrl,
|
||||
UsageRepo: usageRepo,
|
||||
}
|
||||
|
||||
publicCollectionCtrl := &controller.PublicCollectionController{
|
||||
@@ -621,6 +622,7 @@ func main() {
|
||||
familiesJwtAuthAPI.GET("/family/members", familyHandler.FetchMembers)
|
||||
familiesJwtAuthAPI.DELETE("/family/remove-member/:id", familyHandler.RemoveMember)
|
||||
familiesJwtAuthAPI.DELETE("/family/revoke-invite/:id", familyHandler.RevokeInvite)
|
||||
familiesJwtAuthAPI.POST("/family/modify-storage", familyHandler.ModifyStorageLimit)
|
||||
|
||||
emergencyHandler := &api.EmergencyHandler{
|
||||
Controller: emergencyCtrl,
|
||||
|
||||
@@ -21,26 +21,23 @@ commit to the GitHub Container Registry (GHCR) so that it can be used by folks
|
||||
without needing to clone our repository just for building an image. For more
|
||||
details about the use case, see [docker.md](docker.md).
|
||||
|
||||
To publish such an external image, firstly find the commit of the currently
|
||||
running production instance.
|
||||
These images are published automatically by the "Publish (server)" workflow on
|
||||
the 15th of every month. If needed, the workflow can also be manually triggered
|
||||
invoked to publish out of schedule. It can be triggered on the GitHub UI, or by
|
||||
|
||||
curl -s https://api.ente.io/ping | jq -r '.id'
|
||||
```sh
|
||||
gh workflow run server-publish.yml
|
||||
```
|
||||
|
||||
> We can publish from any arbitrary commit really, but by using the commit
|
||||
> that's already seen production for a few days, we avoid externally publishing
|
||||
> images with issues.
|
||||
|
||||
Then, trigger the "Publish (server)" workflow, providing it the commit. You can
|
||||
trigger it either from GitHub's UI or using the `gh cli`. With the CLI, we can
|
||||
combine both these steps too.
|
||||
|
||||
gh workflow run server-publish.yml -F commit=`curl -s https://api.ente.io/ping | jq -r '.id'`
|
||||
> It uses the commit that is deployed on production museum instances. We can
|
||||
> publish from any arbitrary commit really, but by using the commit that's
|
||||
> already seen production, we avoid externally publishing images with issues.
|
||||
|
||||
Once the workflow completes, the resultant image will be available at
|
||||
`ghcr.io/ente-io/server`. The image will be tagged by the commit SHA. The latest
|
||||
image will also be tagged, well, "latest".
|
||||
|
||||
The workflow will also update the branch `server/ghcr` to point to the commit it
|
||||
used to build the image. This branch will be overwritten on each publish, and
|
||||
thus it `server/ghcr` points to the code from which the most recent ghcr docker
|
||||
image for museum has been built.
|
||||
used to build the image. This branch will be overwritten on each publish; thus
|
||||
`server/ghcr` will always points to the code from which the most recent ghcr
|
||||
docker image for museum has been built.
|
||||
|
||||
@@ -49,6 +49,11 @@ type FamilyMember struct {
|
||||
AdminUserID int64 `json:"-"` // for internal use only, ignore from json response
|
||||
}
|
||||
|
||||
type ModifyMemberStorage struct {
|
||||
ID uuid.UUID `json:"id" binding:"required"`
|
||||
StorageLimit *int64 `json:"storageLimit"`
|
||||
}
|
||||
|
||||
type FamilyMemberResponse struct {
|
||||
Members []FamilyMember `json:"members" binding:"required"`
|
||||
// Family admin subscription storage capacity. This excludes add-on and any other bonus storage
|
||||
|
||||
@@ -120,6 +120,21 @@ func (h *FamilyHandler) AcceptInvite(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// ModifyStorageLimit allows adminUser to Modify the storage for a member in the Family.
|
||||
func (h *FamilyHandler) ModifyStorageLimit(c *gin.Context) {
|
||||
var request ente.ModifyMemberStorage
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
handler.Error(c, stacktrace.Propagate(err, "Could not bind request params"))
|
||||
return
|
||||
}
|
||||
|
||||
err := h.Controller.ModifyMemberStorage(c, auth.GetUserID(c.Request.Header), request.ID, request.StorageLimit)
|
||||
if err != nil {
|
||||
handler.Error(c, stacktrace.Propagate(err, ""))
|
||||
}
|
||||
c.JSON(http.StatusOK, nil)
|
||||
}
|
||||
|
||||
// GetInviteInfo returns basic information about invitor/admin as long as the invite is valid
|
||||
func (h *FamilyHandler) GetInviteInfo(c *gin.Context) {
|
||||
inviteToken := c.Param("token")
|
||||
|
||||
@@ -190,6 +190,56 @@ func (c *Controller) CloseFamily(ctx context.Context, adminID int64) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ModifyMemberStorage allows admin user to update the storageLimit for a member in the family
|
||||
func (c *Controller) ModifyMemberStorage(ctx context.Context, actorUserID int64, id uuid.UUID, storageLimit *int64) error {
|
||||
member, err := c.FamilyRepo.GetMemberById(ctx, id)
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "Couldn't fetch Family Member")
|
||||
}
|
||||
|
||||
if member.AdminUserID != actorUserID {
|
||||
return stacktrace.Propagate(ente.ErrPermissionDenied, "you do not have sufficient permission")
|
||||
}
|
||||
|
||||
if member.IsAdmin {
|
||||
return stacktrace.Propagate(ente.NewBadRequestWithMessage("can not limit admin storage"), "cannot modify admin storage limit")
|
||||
}
|
||||
|
||||
if member.Status != ente.ACCEPTED && member.Status != ente.INVITED {
|
||||
return stacktrace.Propagate(ente.ErrBadRequest, "user is not a part of family")
|
||||
}
|
||||
|
||||
// gets admin subscription in order to get the size of total storage quota (including bonus)
|
||||
if storageLimit != nil {
|
||||
familyMembersData, err := c.FetchMembersForAdminID(ctx, member.AdminUserID)
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(ente.ErrBadRequest, "couldn't get active subscription")
|
||||
}
|
||||
totalFamilyStorage := familyMembersData.Storage + familyMembersData.AdminBonus
|
||||
if *storageLimit > totalFamilyStorage {
|
||||
return stacktrace.Propagate(ente.ErrStorageLimitExceeded, "potential storage limit is more than subscription storage")
|
||||
}
|
||||
|
||||
// Handle if the admin user tries reducing the storage Limit
|
||||
// and the members Usage is more than the potential storage Limit
|
||||
memberUsage, memUsageErr := c.UsageRepo.GetUsage(member.MemberUserID)
|
||||
if memUsageErr != nil {
|
||||
return stacktrace.Propagate(memUsageErr, "Couldn't find members storage usage")
|
||||
}
|
||||
|
||||
if memberUsage > *storageLimit {
|
||||
return stacktrace.Propagate(ente.NewBadRequestWithMessage("Failed to reduce storage"), "User's current usage is more")
|
||||
}
|
||||
}
|
||||
|
||||
modifyStorageErr := c.FamilyRepo.ModifyMemberStorage(ctx, actorUserID, member.ID, storageLimit)
|
||||
if modifyStorageErr != nil {
|
||||
return stacktrace.Propagate(modifyStorageErr, "Failed to modify members storage")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Controller) sendNotification(ctx context.Context, adminUserID int64, memberUserID int64, newStatus ente.MemberStatus, inviteToken *string) error {
|
||||
adminUser, err := c.UserRepo.Get(adminUserID)
|
||||
if err != nil {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/ente-io/museum/pkg/controller/usercache"
|
||||
"github.com/ente-io/museum/pkg/utils/time"
|
||||
|
||||
@@ -26,6 +27,7 @@ type Controller struct {
|
||||
UserRepo *repo.UserRepository
|
||||
FamilyRepo *repo.FamilyRepository
|
||||
UserCacheCtrl *usercache.Controller
|
||||
UsageRepo *repo.UsageRepository
|
||||
}
|
||||
|
||||
// FetchMembers return list of members who are part of a family plan
|
||||
|
||||
@@ -196,6 +196,16 @@ func (repo *FamilyRepository) RemoveMember(ctx context.Context, adminID int64, m
|
||||
return stacktrace.Propagate(tx.Commit(), "failed to commit")
|
||||
}
|
||||
|
||||
// UpdateStorage is used to set Pre-existing Members Storage Limit.
|
||||
func (repo *FamilyRepository) ModifyMemberStorage(ctx context.Context, adminID int64, id uuid.UUID, storageLimit *int64) error {
|
||||
_, err := repo.DB.Exec(`UPDATE families SET storage_limit=$1 where id=$2`, storageLimit, id)
|
||||
if err != nil {
|
||||
return stacktrace.Propagate(err, "Could not update Members Storage Limit")
|
||||
}
|
||||
|
||||
return stacktrace.Propagate(err, "Failed to Modify Members Storage Limit")
|
||||
}
|
||||
|
||||
// RevokeInvite revokes the invitation invite
|
||||
func (repo *FamilyRepository) RevokeInvite(ctx context.Context, adminID int64, memberID int64) error {
|
||||
tx, err := repo.DB.BeginTx(ctx, nil)
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import { staticAppTitle } from "@/base/app";
|
||||
import { assertionFailed } from "@/base/assert";
|
||||
import { CustomHead } from "@/base/components/Head";
|
||||
import { LoadingIndicator } from "@/base/components/loaders";
|
||||
import { AttributedMiniDialog } from "@/base/components/MiniDialog";
|
||||
import { useAttributedMiniDialog } from "@/base/components/utils/dialog";
|
||||
import { useSetupI18n, useSetupLogs } from "@/base/components/utils/hooks-app";
|
||||
import { photosTheme } from "@/base/components/utils/theme";
|
||||
import { BaseContext } from "@/base/context";
|
||||
import "@fontsource-variable/inter";
|
||||
import { CssBaseline } from "@mui/material";
|
||||
import { ThemeProvider } from "@mui/material/styles";
|
||||
import { t } from "i18next";
|
||||
import type { AppProps } from "next/app";
|
||||
import React, { useMemo } from "react";
|
||||
import { AppContext } from "../types/context";
|
||||
import React, { useCallback, useMemo } from "react";
|
||||
|
||||
const App: React.FC<AppProps> = ({ Component, pageProps }) => {
|
||||
useSetupLogs({ disableDiskLogs: true });
|
||||
@@ -19,24 +20,32 @@ const App: React.FC<AppProps> = ({ Component, pageProps }) => {
|
||||
const isI18nReady = useSetupI18n();
|
||||
const { showMiniDialog, miniDialogProps } = useAttributedMiniDialog();
|
||||
|
||||
const appContext = useMemo(() => ({ showMiniDialog }), [showMiniDialog]);
|
||||
const logout = useCallback(() => {
|
||||
// No code in the accounts app is currently expected to reach a code
|
||||
// path where they would need to "logout". In any case, the accounts app
|
||||
// doesn't store any user specific persistent state that'd need to be
|
||||
// cleared, so there really isn't anything to do here.
|
||||
assertionFailed();
|
||||
}, []);
|
||||
|
||||
const baseContext = useMemo(
|
||||
() => ({ logout, showMiniDialog }),
|
||||
[logout, showMiniDialog],
|
||||
);
|
||||
|
||||
const title = isI18nReady ? t("title_accounts") : staticAppTitle;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ThemeProvider theme={photosTheme}>
|
||||
<CustomHead {...{ title }} />
|
||||
<CssBaseline enableColorScheme />
|
||||
<AttributedMiniDialog {...miniDialogProps} />
|
||||
|
||||
<ThemeProvider theme={photosTheme}>
|
||||
<CssBaseline enableColorScheme />
|
||||
<AttributedMiniDialog {...miniDialogProps} />
|
||||
|
||||
<AppContext.Provider value={appContext}>
|
||||
{!isI18nReady && <LoadingIndicator />}
|
||||
{isI18nReady && <Component {...pageProps} />}
|
||||
</AppContext.Provider>
|
||||
</ThemeProvider>
|
||||
</>
|
||||
<BaseContext value={baseContext}>
|
||||
{!isI18nReady && <LoadingIndicator />}
|
||||
{isI18nReady && <Component {...pageProps} />}
|
||||
</BaseContext>
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import { SingleInputDialog } from "@/base/components/SingleInputDialog";
|
||||
import { Titlebar } from "@/base/components/Titlebar";
|
||||
import { errorDialogAttributes } from "@/base/components/utils/dialog";
|
||||
import { useModalVisibility } from "@/base/components/utils/modal";
|
||||
import { useBaseContext } from "@/base/context";
|
||||
import log from "@/base/log";
|
||||
import SingleInputForm from "@ente/shared/components/SingleInputForm";
|
||||
import { formatDateTimeFull } from "@ente/shared/time/format";
|
||||
@@ -28,10 +29,9 @@ import {
|
||||
renamePasskey,
|
||||
type Passkey,
|
||||
} from "services/passkey";
|
||||
import { useAppContext } from "../../types/context";
|
||||
|
||||
const Page: React.FC = () => {
|
||||
const { showMiniDialog } = useAppContext();
|
||||
const { showMiniDialog } = useBaseContext();
|
||||
|
||||
const [token, setToken] = useState<string | undefined>();
|
||||
const [passkeys, setPasskeys] = useState<Passkey[]>([]);
|
||||
@@ -252,7 +252,7 @@ const ManagePasskeyDrawer: React.FC<ManagePasskeyDrawerProps> = ({
|
||||
passkey,
|
||||
onUpdateOrDeletePasskey,
|
||||
}) => {
|
||||
const { showMiniDialog } = useAppContext();
|
||||
const { showMiniDialog } = useBaseContext();
|
||||
|
||||
const { show: showRenameDialog, props: renameDialogVisibilityProps } =
|
||||
useModalVisibility();
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
import type { AccountsContextT } from "@/accounts/types/context";
|
||||
import { createContext, useContext } from "react";
|
||||
|
||||
/**
|
||||
* The type of the context for pages in the accounts app.
|
||||
*/
|
||||
type AppContextT = Omit<AccountsContextT, "logout">;
|
||||
|
||||
/**
|
||||
* The React {@link Context} available to all nodes in the React tree.
|
||||
*/
|
||||
export const AppContext = createContext<AppContextT | undefined>(undefined);
|
||||
|
||||
/**
|
||||
* Utility hook to get the {@link AppContextT} expected to be available to all
|
||||
* React components in the Accounts app's React tree.
|
||||
*/
|
||||
export const useAppContext = (): AppContextT => useContext(AppContext)!;
|
||||
@@ -2,9 +2,7 @@
|
||||
"extends": "@/build-config/tsconfig-next.json",
|
||||
"compilerOptions": {
|
||||
/* Set the base directory from which to resolve bare module names. */
|
||||
"baseUrl": "./src",
|
||||
/* MUI doesn't work with exactOptionalPropertyTypes yet. */
|
||||
"exactOptionalPropertyTypes": false
|
||||
"baseUrl": "./src"
|
||||
},
|
||||
"include": [
|
||||
"src",
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
import Page from "@/base/components/pages/404";
|
||||
|
||||
export default Page;
|
||||
export { default } from "@/base/components/pages/404";
|
||||
|
||||
@@ -13,13 +13,10 @@ import {
|
||||
useSetupLogs,
|
||||
} from "@/base/components/utils/hooks-app";
|
||||
import { authTheme } from "@/base/components/utils/theme";
|
||||
import { BaseContext } from "@/base/context";
|
||||
import { logStartupBanner } from "@/base/log-web";
|
||||
import HTTPService from "@ente/shared/network/HTTPService";
|
||||
import {
|
||||
LS_KEYS,
|
||||
getData,
|
||||
migrateKVToken,
|
||||
} from "@ente/shared/storage/localStorage";
|
||||
import { LS_KEYS, getData } from "@ente/shared/storage/localStorage";
|
||||
import type { User } from "@ente/shared/user/types";
|
||||
import "@fontsource-variable/inter";
|
||||
import { CssBaseline } from "@mui/material";
|
||||
@@ -27,7 +24,6 @@ import { ThemeProvider } from "@mui/material/styles";
|
||||
import { t } from "i18next";
|
||||
import type { AppProps } from "next/app";
|
||||
import React, { useCallback, useEffect, useMemo } from "react";
|
||||
import { AppContext } from "types/context";
|
||||
|
||||
const App: React.FC<AppProps> = ({ Component, pageProps }) => {
|
||||
useSetupLogs();
|
||||
@@ -38,7 +34,6 @@ const App: React.FC<AppProps> = ({ Component, pageProps }) => {
|
||||
|
||||
useEffect(() => {
|
||||
const user = getData(LS_KEYS.USER) as User | undefined | null;
|
||||
void migrateKVToken(user);
|
||||
logStartupBanner(user?.id);
|
||||
HTTPService.setHeaders({ "X-Client-Package": clientPackageName });
|
||||
}, []);
|
||||
@@ -47,37 +42,30 @@ const App: React.FC<AppProps> = ({ Component, pageProps }) => {
|
||||
void accountLogout().then(() => window.location.replace("/"));
|
||||
}, []);
|
||||
|
||||
const appContext = useMemo(
|
||||
() => ({
|
||||
logout,
|
||||
showMiniDialog,
|
||||
}),
|
||||
const baseContext = useMemo(
|
||||
() => ({ logout, showMiniDialog }),
|
||||
[logout, showMiniDialog],
|
||||
);
|
||||
|
||||
const title = isI18nReady ? t("title_auth") : staticAppTitle;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ThemeProvider theme={authTheme}>
|
||||
<CustomHead {...{ title }} />
|
||||
<CssBaseline enableColorScheme />
|
||||
<AttributedMiniDialog {...miniDialogProps} />
|
||||
|
||||
<ThemeProvider theme={authTheme}>
|
||||
<CssBaseline enableColorScheme />
|
||||
|
||||
<AttributedMiniDialog {...miniDialogProps} />
|
||||
|
||||
<AppContext.Provider value={appContext}>
|
||||
{!isI18nReady ? (
|
||||
<LoadingIndicator />
|
||||
) : (
|
||||
<>
|
||||
{isChangingRoute && <TranslucentLoadingOverlay />}
|
||||
<Component {...pageProps} />
|
||||
</>
|
||||
)}
|
||||
</AppContext.Provider>
|
||||
</ThemeProvider>
|
||||
</>
|
||||
<BaseContext value={baseContext}>
|
||||
{!isI18nReady ? (
|
||||
<LoadingIndicator />
|
||||
) : (
|
||||
<>
|
||||
{isChangingRoute && <TranslucentLoadingOverlay />}
|
||||
<Component {...pageProps} />
|
||||
</>
|
||||
)}
|
||||
</BaseContext>
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
OverflowMenu,
|
||||
OverflowMenuOption,
|
||||
} from "@/base/components/OverflowMenu";
|
||||
import { useBaseContext } from "@/base/context";
|
||||
import { isHTTP401Error } from "@/base/http";
|
||||
import log from "@/base/log";
|
||||
import { masterKeyFromSessionIfLoggedIn } from "@/base/session-store";
|
||||
@@ -28,10 +29,9 @@ import { useRouter } from "next/router";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { generateOTPs, type Code } from "services/code";
|
||||
import { getAuthCodes } from "services/remote";
|
||||
import { useAppContext } from "types/context";
|
||||
|
||||
const Page: React.FC = () => {
|
||||
const { logout, showMiniDialog } = useAppContext();
|
||||
const { logout, showMiniDialog } = useBaseContext();
|
||||
|
||||
const router = useRouter();
|
||||
const [codes, setCodes] = useState<Code[]>([]);
|
||||
@@ -57,7 +57,7 @@ const Page: React.FC = () => {
|
||||
setHasFetched(true);
|
||||
};
|
||||
void fetchCodes();
|
||||
}, [router, showMiniDialog, logout]);
|
||||
}, [router, logout, showMiniDialog]);
|
||||
|
||||
const lcSearch = searchTerm.toLowerCase();
|
||||
const filteredCodes = codes.filter(
|
||||
@@ -131,7 +131,7 @@ const Page: React.FC = () => {
|
||||
export default Page;
|
||||
|
||||
const AuthNavbar: React.FC = () => {
|
||||
const { logout } = useAppContext();
|
||||
const { logout } = useBaseContext();
|
||||
|
||||
return (
|
||||
<NavbarBase
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
import Page from "@/accounts/pages/change-password";
|
||||
|
||||
export default Page;
|
||||
export { default } from "@/accounts/pages/change-password";
|
||||
|
||||
@@ -1,6 +1 @@
|
||||
import Page_ from "@/accounts/pages/credentials";
|
||||
import { useAppContext } from "types/context";
|
||||
|
||||
const Page = () => <Page_ appContext={useAppContext()} />;
|
||||
|
||||
export default Page;
|
||||
export { default } from "@/accounts/pages/credentials";
|
||||
|
||||
@@ -1,6 +1 @@
|
||||
import Page_ from "@/accounts/pages/generate";
|
||||
import { useAppContext } from "types/context";
|
||||
|
||||
const Page = () => <Page_ appContext={useAppContext()} />;
|
||||
|
||||
export default Page;
|
||||
export { default } from "@/accounts/pages/generate";
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
import Page from "@/accounts/pages/login";
|
||||
|
||||
export default Page;
|
||||
export { default } from "@/accounts/pages/login";
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
import Page from "@/accounts/pages/passkeys/finish";
|
||||
|
||||
export default Page;
|
||||
export { default } from "@/accounts/pages/passkeys/finish";
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import Page_ from "@/accounts/pages/two-factor/recover";
|
||||
import { useAppContext } from "types/context";
|
||||
|
||||
const Page = () => (
|
||||
<Page_ appContext={useAppContext()} twoFactorType="passkey" />
|
||||
);
|
||||
const Page = () => <Page_ twoFactorType="passkey" />;
|
||||
|
||||
export default Page;
|
||||
|
||||
@@ -1,6 +1 @@
|
||||
import Page_ from "@/accounts/pages/recover";
|
||||
import { useAppContext } from "types/context";
|
||||
|
||||
const Page = () => <Page_ appContext={useAppContext()} />;
|
||||
|
||||
export default Page;
|
||||
export { default } from "@/accounts/pages/recover";
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
import Page from "@/accounts/pages/signup";
|
||||
|
||||
export default Page;
|
||||
export { default } from "@/accounts/pages/signup";
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import Page_ from "@/accounts/pages/two-factor/recover";
|
||||
import { useAppContext } from "types/context";
|
||||
|
||||
const Page = () => <Page_ appContext={useAppContext()} twoFactorType="totp" />;
|
||||
const Page = () => <Page_ twoFactorType="totp" />;
|
||||
|
||||
export default Page;
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
import Page from "@/accounts/pages/two-factor/setup";
|
||||
|
||||
export default Page;
|
||||
export { default } from "@/accounts/pages/two-factor/setup";
|
||||
|
||||
@@ -1,6 +1 @@
|
||||
import Page_ from "@/accounts/pages/two-factor/verify";
|
||||
import { useAppContext } from "types/context";
|
||||
|
||||
const Page = () => <Page_ appContext={useAppContext()} />;
|
||||
|
||||
export default Page;
|
||||
export { default } from "@/accounts/pages/two-factor/verify";
|
||||
|
||||
@@ -1,6 +1 @@
|
||||
import Page_ from "@/accounts/pages/verify";
|
||||
import { useAppContext } from "types/context";
|
||||
|
||||
const Page = () => <Page_ appContext={useAppContext()} />;
|
||||
|
||||
export default Page;
|
||||
export { default } from "@/accounts/pages/verify";
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
import type { AccountsContextT } from "@/accounts/types/context";
|
||||
import { createContext, useContext } from "react";
|
||||
|
||||
/**
|
||||
* Properties available via {@link AppContext} to the Auth app's React tree.
|
||||
*/
|
||||
type AppContextT = AccountsContextT;
|
||||
|
||||
/** The React {@link Context} available to all pages. */
|
||||
export const AppContext = createContext<AppContextT | undefined>(undefined);
|
||||
|
||||
/** Utility hook to reduce amount of boilerplate in account related pages. */
|
||||
export const useAppContext = () => useContext(AppContext)!;
|
||||
@@ -2,9 +2,7 @@
|
||||
"extends": "@/build-config/tsconfig-next.json",
|
||||
"compilerOptions": {
|
||||
/* Set the base directory from which to resolve bare module names. */
|
||||
"baseUrl": "./src",
|
||||
/* MUI doesn't work with exactOptionalPropertyTypes yet. */
|
||||
"exactOptionalPropertyTypes": false
|
||||
"baseUrl": "./src"
|
||||
},
|
||||
"include": [
|
||||
"src",
|
||||
|
||||
@@ -10,15 +10,14 @@ import React from "react";
|
||||
const App: React.FC<AppProps> = ({ Component, pageProps }) => {
|
||||
useSetupLogs({ disableDiskLogs: true });
|
||||
|
||||
return (
|
||||
<>
|
||||
<CustomHead title={staticAppTitle} />
|
||||
// We don't provide BaseContext. Nothing in the cast app needs it yet.
|
||||
|
||||
<ThemeProvider theme={castTheme}>
|
||||
<CssBaseline enableColorScheme />
|
||||
<Component {...pageProps} />
|
||||
</ThemeProvider>
|
||||
</>
|
||||
return (
|
||||
<ThemeProvider theme={castTheme}>
|
||||
<CustomHead title={staticAppTitle} />
|
||||
<CssBaseline enableColorScheme />
|
||||
<Component {...pageProps} />
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
"extends": "@/build-config/tsconfig-next.json",
|
||||
"compilerOptions": {
|
||||
/* Set the base directory from which to resolve bare module names. */
|
||||
"baseUrl": "./src",
|
||||
/* MUI doesn't work with exactOptionalPropertyTypes yet. */
|
||||
"exactOptionalPropertyTypes": false
|
||||
"baseUrl": "./src"
|
||||
},
|
||||
"include": [
|
||||
"src",
|
||||
|
||||
@@ -3,7 +3,7 @@ import config from "@/build-config/eslintrc-next-app.mjs";
|
||||
export default [
|
||||
...config,
|
||||
{
|
||||
ignores: ["thirdparty"],
|
||||
ignores: ["thirdparty", ".next-desktop"],
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
|
||||
@@ -3,15 +3,16 @@ import {
|
||||
TitledMiniDialog,
|
||||
type MiniDialogAttributes,
|
||||
} from "@/base/components/MiniDialog";
|
||||
import { useBaseContext } from "@/base/context";
|
||||
import log from "@/base/log";
|
||||
import { AppContext } from "@/new/photos/types/context";
|
||||
import { usePhotosAppContext } from "@/new/photos/types/context";
|
||||
import VerifyMasterPasswordForm, {
|
||||
type VerifyMasterPasswordFormProps,
|
||||
} from "@ente/shared/components/VerifyMasterPasswordForm";
|
||||
import { LS_KEYS, getData } from "@ente/shared/storage/localStorage";
|
||||
import type { KeyAttributes, User } from "@ente/shared/user/types";
|
||||
import { t } from "i18next";
|
||||
import { useCallback, useContext, useEffect, useState } from "react";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
interface Iprops {
|
||||
open: boolean;
|
||||
@@ -24,7 +25,8 @@ export default function AuthenticateUserModal({
|
||||
onClose,
|
||||
onAuthenticate,
|
||||
}: Iprops) {
|
||||
const { showMiniDialog, onGenericError, logout } = useContext(AppContext);
|
||||
const { logout, showMiniDialog } = useBaseContext();
|
||||
const { onGenericError } = usePhotosAppContext();
|
||||
const [user, setUser] = useState<User>();
|
||||
const [keyAttributes, setKeyAttributes] = useState<KeyAttributes>();
|
||||
|
||||
@@ -47,7 +49,7 @@ export default function AuthenticateUserModal({
|
||||
// potentially transient issues.
|
||||
log.warn("Ignoring error when determining session validity", e);
|
||||
}
|
||||
}, [showMiniDialog, logout]);
|
||||
}, [logout, showMiniDialog]);
|
||||
|
||||
useEffect(() => {
|
||||
const main = async () => {
|
||||
|
||||
@@ -6,6 +6,11 @@ import {
|
||||
OverflowMenuOption,
|
||||
} from "@/base/components/OverflowMenu";
|
||||
import { useModalVisibility } from "@/base/components/utils/modal";
|
||||
import { useBaseContext } from "@/base/context";
|
||||
import {
|
||||
isArchivedCollection,
|
||||
isPinnedCollection,
|
||||
} from "@/gallery/services/magic-metadata";
|
||||
import type { Collection } from "@/media/collection";
|
||||
import { ItemVisibility } from "@/media/file-metadata";
|
||||
import {
|
||||
@@ -22,11 +27,7 @@ import type {
|
||||
CollectionSummaryType,
|
||||
} from "@/new/photos/services/collection/ui";
|
||||
import { clearLocalTrash, emptyTrash } from "@/new/photos/services/collections";
|
||||
import {
|
||||
isArchivedCollection,
|
||||
isPinnedCollection,
|
||||
} from "@/new/photos/services/magic-metadata";
|
||||
import { useAppContext } from "@/new/photos/types/context";
|
||||
import { usePhotosAppContext } from "@/new/photos/types/context";
|
||||
import ArchiveOutlinedIcon from "@mui/icons-material/ArchiveOutlined";
|
||||
import DeleteOutlinedIcon from "@mui/icons-material/DeleteOutlined";
|
||||
import EditIcon from "@mui/icons-material/Edit";
|
||||
@@ -128,8 +129,9 @@ const CollectionOptions: React.FC<CollectionHeaderProps> = ({
|
||||
setFilesDownloadProgressAttributesCreator,
|
||||
isActiveCollectionDownloadInProgress,
|
||||
}) => {
|
||||
const { showLoadingBar, hideLoadingBar, onGenericError, showMiniDialog } =
|
||||
useAppContext();
|
||||
const { showMiniDialog } = useBaseContext();
|
||||
const { showLoadingBar, hideLoadingBar, onGenericError } =
|
||||
usePhotosAppContext();
|
||||
const { syncWithRemote } = useContext(GalleryContext);
|
||||
const overFlowMenuIconRef = useRef<SVGSVGElement>(null);
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
} from "@/base/components/RowButton";
|
||||
import { Titlebar } from "@/base/components/Titlebar";
|
||||
import { useModalVisibility } from "@/base/components/utils/modal";
|
||||
import { useBaseContext } from "@/base/context";
|
||||
import { sharedCryptoWorker } from "@/base/crypto";
|
||||
import log from "@/base/log";
|
||||
import { appendCollectionKeyToShareURL } from "@/gallery/services/share";
|
||||
@@ -27,7 +28,7 @@ import { COLLECTION_ROLE, type CollectionUser } from "@/media/collection";
|
||||
import { PublicLinkCreated } from "@/new/photos/components/share/PublicLinkCreated";
|
||||
import { avatarTextColor } from "@/new/photos/services/avatar";
|
||||
import type { CollectionSummary } from "@/new/photos/services/collection/ui";
|
||||
import { AppContext, useAppContext } from "@/new/photos/types/context";
|
||||
import { usePhotosAppContext } from "@/new/photos/types/context";
|
||||
import { FlexWrapper } from "@ente/shared/components/Container";
|
||||
import SingleInputForm, {
|
||||
type SingleInputFormProps,
|
||||
@@ -811,7 +812,7 @@ const ManageEmailShare: React.FC<ManageEmailShareProps> = ({
|
||||
onRootClose,
|
||||
peopleCount,
|
||||
}) => {
|
||||
const { showLoadingBar, hideLoadingBar } = useContext(AppContext);
|
||||
const { showLoadingBar, hideLoadingBar } = usePhotosAppContext();
|
||||
const galleryContext = useContext(GalleryContext);
|
||||
|
||||
const [addParticipantView, setAddParticipantView] = useState(false);
|
||||
@@ -1007,7 +1008,7 @@ const ManageParticipant: React.FC<ManageParticipantProps> = ({
|
||||
selectedParticipant,
|
||||
collectionUnshare,
|
||||
}) => {
|
||||
const { showMiniDialog } = useAppContext();
|
||||
const { showMiniDialog } = useBaseContext();
|
||||
const galleryContext = useContext(GalleryContext);
|
||||
|
||||
const handleRootClose = () => {
|
||||
@@ -1710,7 +1711,7 @@ const ManageDownloadAccess: React.FC<ManageDownloadAccessProps> = ({
|
||||
updatePublicShareURLHelper,
|
||||
collection,
|
||||
}) => {
|
||||
const { showMiniDialog } = useAppContext();
|
||||
const { showMiniDialog } = useBaseContext();
|
||||
|
||||
const handleFileDownloadSetting = () => {
|
||||
if (publicShareProp.enableDownload) {
|
||||
@@ -1758,7 +1759,7 @@ const ManageLinkPassword: React.FC<ManageLinkPasswordProps> = ({
|
||||
publicShareProp,
|
||||
updatePublicShareURLHelper,
|
||||
}) => {
|
||||
const { showMiniDialog } = useAppContext();
|
||||
const { showMiniDialog } = useBaseContext();
|
||||
const [changePasswordView, setChangePasswordView] = useState(false);
|
||||
|
||||
const closeConfigurePassword = () => setChangePasswordView(false);
|
||||
|
||||
@@ -2,12 +2,14 @@ import { assertionFailed } from "@/base/assert";
|
||||
import { TitledMiniDialog } from "@/base/components/MiniDialog";
|
||||
import { FocusVisibleButton } from "@/base/components/mui/FocusVisibleButton";
|
||||
import { LoadingButton } from "@/base/components/mui/LoadingButton";
|
||||
import type { ModalVisibilityProps } from "@/base/components/utils/modal";
|
||||
import { useBaseContext } from "@/base/context";
|
||||
import { sharedCryptoWorker } from "@/base/crypto";
|
||||
import {
|
||||
DropdownInput,
|
||||
type DropdownOption,
|
||||
} from "@/new/photos/components/DropdownInput";
|
||||
import { AppContext } from "@/new/photos/types/context";
|
||||
import { usePhotosAppContext } from "@/new/photos/types/context";
|
||||
import { initiateEmail } from "@/new/photos/utils/web";
|
||||
import { getData, LS_KEYS } from "@ente/shared/storage/localStorage";
|
||||
import { getActualKey } from "@ente/shared/user";
|
||||
@@ -28,18 +30,17 @@ import { Trans } from "react-i18next";
|
||||
import { deleteAccount, getAccountDeleteChallenge } from "services/userService";
|
||||
import * as Yup from "yup";
|
||||
|
||||
interface Iprops {
|
||||
onClose: () => void;
|
||||
open: boolean;
|
||||
}
|
||||
|
||||
interface FormValues {
|
||||
reason: string;
|
||||
feedback: string;
|
||||
}
|
||||
|
||||
const DeleteAccountModal = ({ open, onClose }: Iprops) => {
|
||||
const { showMiniDialog, onGenericError, logout } = useContext(AppContext);
|
||||
const DeleteAccountModal: React.FC<ModalVisibilityProps> = ({
|
||||
open,
|
||||
onClose,
|
||||
}) => {
|
||||
const { logout, showMiniDialog } = useBaseContext();
|
||||
const { onGenericError } = usePhotosAppContext();
|
||||
const { authenticateUser } = useContext(GalleryContext);
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
@@ -7,12 +7,12 @@ import {
|
||||
} from "@/base/components/OverflowMenu";
|
||||
import { EllipsizedTypography } from "@/base/components/Typography";
|
||||
import type { ButtonishProps } from "@/base/components/mui";
|
||||
import { DialogCloseIconButton } from "@/base/components/mui/DialogCloseIconButton";
|
||||
import type { ModalVisibilityProps } from "@/base/components/utils/modal";
|
||||
import { useBaseContext } from "@/base/context";
|
||||
import { ensureElectron } from "@/base/electron";
|
||||
import log from "@/base/log";
|
||||
import { EnteFile } from "@/media/file";
|
||||
import { DialogCloseIconButton } from "@/new/photos/components/mui/Dialog";
|
||||
import { useAppContext } from "@/new/photos/types/context";
|
||||
import { SpaceBetweenFlex } from "@ente/shared/components/Container";
|
||||
import { CustomError } from "@ente/shared/error";
|
||||
import FolderIcon from "@mui/icons-material/Folder";
|
||||
@@ -50,7 +50,7 @@ export const Export: React.FC<ExportProps> = ({
|
||||
onClose,
|
||||
collectionNameMap,
|
||||
}) => {
|
||||
const { showMiniDialog } = useAppContext();
|
||||
const { showMiniDialog } = useBaseContext();
|
||||
const [exportStage, setExportStage] = useState(ExportStage.INIT);
|
||||
const [exportFolder, setExportFolder] = useState("");
|
||||
const [continuousExport, setContinuousExport] = useState(false);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useBaseContext } from "@/base/context";
|
||||
import { Notification } from "@/new/photos/components/Notification";
|
||||
import { aboveFileViewerContentZ } from "@/new/photos/components/utils/z-index";
|
||||
import { useAppContext } from "@/new/photos/types/context";
|
||||
import { t } from "i18next";
|
||||
import { GalleryContext } from "pages/gallery";
|
||||
import { useContext } from "react";
|
||||
@@ -57,7 +57,7 @@ export const FilesDownloadProgress: React.FC<FilesDownloadProgressProps> = ({
|
||||
attributesList,
|
||||
setAttributesList,
|
||||
}) => {
|
||||
const { showMiniDialog } = useAppContext();
|
||||
const { showMiniDialog } = useBaseContext();
|
||||
const galleryContext = useContext(GalleryContext);
|
||||
|
||||
if (!attributesList) {
|
||||
|
||||
@@ -112,8 +112,8 @@ export interface PhotoFrameProps {
|
||||
/** This will be set if mode is "people". */
|
||||
activePersonID?: string | undefined;
|
||||
enableDownload?: boolean;
|
||||
fileToCollectionsMap: Map<number, number[]>;
|
||||
collectionNameMap: Map<number, string>;
|
||||
fileToCollectionsMap?: Map<number, number[]>;
|
||||
collectionNameMap?: Map<number, string>;
|
||||
showAppDownloadBanner?: boolean;
|
||||
setIsPhotoSwipeOpen?: (value: boolean) => void;
|
||||
isInHiddenSection?: boolean;
|
||||
|
||||
@@ -9,14 +9,21 @@ import {
|
||||
} from "@/base/components/utils/modal";
|
||||
import { lowercaseExtension } from "@/base/file-name";
|
||||
import log from "@/base/log";
|
||||
import {
|
||||
FileInfo,
|
||||
type FileInfoExif,
|
||||
type FileInfoProps,
|
||||
} from "@/gallery/components/FileInfo";
|
||||
import { downloadManager } from "@/gallery/services/download";
|
||||
import type { Collection } from "@/media/collection";
|
||||
import { fileLogID, type EnteFile } from "@/media/file";
|
||||
import { FileType } from "@/media/file-type";
|
||||
import { isHEICExtension, needsJPEGConversion } from "@/media/formats";
|
||||
import { ConfirmDeleteFileDialog } from "@/new/photos/components/FileViewer";
|
||||
import { ImageEditorOverlay } from "@/new/photos/components/ImageEditorOverlay";
|
||||
import { moveToTrash } from "@/new/photos/services/collection";
|
||||
import { extractRawExif, parseExif } from "@/new/photos/services/exif";
|
||||
import { AppContext } from "@/new/photos/types/context";
|
||||
import { usePhotosAppContext } from "@/new/photos/types/context";
|
||||
import AlbumOutlinedIcon from "@mui/icons-material/AlbumOutlined";
|
||||
import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
|
||||
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
||||
@@ -61,6 +68,7 @@ import {
|
||||
addToFavorites,
|
||||
removeFromFavorites,
|
||||
} from "services/collectionService";
|
||||
import uploadManager from "services/upload/uploadManager";
|
||||
import { SetFilesDownloadProgressAttributesCreator } from "types/gallery";
|
||||
import {
|
||||
copyFileToClipboard,
|
||||
@@ -68,8 +76,6 @@ import {
|
||||
getFileFromURL,
|
||||
} from "utils/file";
|
||||
import { PublicCollectionGalleryContext } from "utils/publicCollectionGallery";
|
||||
import { FileInfo, type FileInfoExif, type FileInfoProps } from "./FileInfo";
|
||||
import { ImageEditorOverlay } from "./ImageEditorOverlay";
|
||||
|
||||
export type PhotoViewerProps = Pick<
|
||||
PhotoFrameProps,
|
||||
@@ -99,8 +105,8 @@ export type PhotoViewerProps = Pick<
|
||||
isInHiddenSection: boolean;
|
||||
enableDownload: boolean;
|
||||
setFilesDownloadProgressAttributesCreator: SetFilesDownloadProgressAttributesCreator;
|
||||
fileToCollectionsMap: Map<number, number[]>;
|
||||
collectionNameMap: Map<number, string>;
|
||||
fileToCollectionsMap?: Map<number, number[]>;
|
||||
collectionNameMap?: Map<number, string>;
|
||||
onSelectPerson?: FileInfoProps["onSelectPerson"];
|
||||
};
|
||||
|
||||
@@ -137,8 +143,8 @@ export const PhotoViewer: React.FC<PhotoViewerProps> = ({
|
||||
collectionNameMap,
|
||||
onSelectPerson,
|
||||
}) => {
|
||||
const { showLoadingBar, hideLoadingBar } = usePhotosAppContext();
|
||||
const galleryContext = useContext(GalleryContext);
|
||||
const { showLoadingBar, hideLoadingBar } = useContext(AppContext);
|
||||
const publicCollectionGalleryContext = useContext(
|
||||
PublicCollectionGalleryContext,
|
||||
);
|
||||
@@ -650,6 +656,18 @@ export const PhotoViewer: React.FC<PhotoViewerProps> = ({
|
||||
setShowImageEditorOverlay(false);
|
||||
};
|
||||
|
||||
const handleSaveEditedCopy = (
|
||||
editedFile: File,
|
||||
collection: Collection,
|
||||
enteFile: EnteFile,
|
||||
) => {
|
||||
uploadManager.prepareForNewUpload();
|
||||
uploadManager.showUploadProgressDialog();
|
||||
uploadManager.uploadFile(editedFile, collection, enteFile);
|
||||
handleCloseEditor();
|
||||
handleClose();
|
||||
};
|
||||
|
||||
const downloadFileHelper = async (file: EnteFile) => {
|
||||
if (
|
||||
file &&
|
||||
@@ -717,6 +735,18 @@ export const PhotoViewer: React.FC<PhotoViewerProps> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectCollection = (collectionID: number) => {
|
||||
galleryContext.onShowCollection(collectionID);
|
||||
handleClose();
|
||||
};
|
||||
|
||||
const handleSelectPerson = onSelectPerson
|
||||
? (personID: string) => {
|
||||
onSelectPerson(personID);
|
||||
handleClose();
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const handleForceConvert = () =>
|
||||
forceConvertItem(
|
||||
photoSwipe,
|
||||
@@ -964,10 +994,10 @@ export const PhotoViewer: React.FC<PhotoViewerProps> = ({
|
||||
<FileInfo
|
||||
showInfo={showInfo}
|
||||
handleCloseInfo={handleCloseInfo}
|
||||
closePhotoViewer={handleClose}
|
||||
file={photoSwipe?.currItem as EnteFile}
|
||||
exif={exif?.value}
|
||||
shouldDisableEdits={!isOwnFile}
|
||||
allowMap={!publicCollectionGalleryContext.credentials}
|
||||
showCollectionChips={
|
||||
!isTrashCollection && isOwnFile && !isInHiddenSection
|
||||
}
|
||||
@@ -975,13 +1005,14 @@ export const PhotoViewer: React.FC<PhotoViewerProps> = ({
|
||||
refreshPhotoswipe={refreshPhotoswipe}
|
||||
fileToCollectionsMap={fileToCollectionsMap}
|
||||
collectionNameMap={collectionNameMap}
|
||||
onSelectPerson={onSelectPerson}
|
||||
onSelectCollection={handleSelectCollection}
|
||||
onSelectPerson={handleSelectPerson}
|
||||
/>
|
||||
<ImageEditorOverlay
|
||||
show={showImageEditorOverlay}
|
||||
file={photoSwipe?.currItem as EnteFile}
|
||||
open={showImageEditorOverlay}
|
||||
onClose={handleCloseEditor}
|
||||
closePhotoViewer={handleClose}
|
||||
file={photoSwipe?.currItem as EnteFile}
|
||||
onSaveEditedCopy={handleSaveEditedCopy}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
} from "@/base/components/RowButton";
|
||||
import { SpacedRow } from "@/base/components/containers";
|
||||
import { ActivityIndicator } from "@/base/components/mui/ActivityIndicator";
|
||||
import { DialogCloseIconButton } from "@/base/components/mui/DialogCloseIconButton";
|
||||
import {
|
||||
NestedSidebarDrawer,
|
||||
SidebarDrawer,
|
||||
@@ -20,6 +21,7 @@ import {
|
||||
} from "@/base/components/mui/SidebarDrawer";
|
||||
import { useIsSmallWidth } from "@/base/components/utils/hooks";
|
||||
import { useModalVisibility } from "@/base/components/utils/modal";
|
||||
import { useBaseContext } from "@/base/context";
|
||||
import { isDevBuild } from "@/base/env";
|
||||
import {
|
||||
getLocaleInUse,
|
||||
@@ -33,7 +35,6 @@ import { savedLogs } from "@/base/log-web";
|
||||
import { customAPIHost } from "@/base/origins";
|
||||
import { downloadString } from "@/base/utils/web";
|
||||
import { DropdownInput } from "@/new/photos/components/DropdownInput";
|
||||
import { DialogCloseIconButton } from "@/new/photos/components/mui/Dialog";
|
||||
import { MLSettings } from "@/new/photos/components/sidebar/MLSettings";
|
||||
import { TwoFactorSettings } from "@/new/photos/components/sidebar/TwoFactorSettings";
|
||||
import {
|
||||
@@ -75,7 +76,7 @@ import {
|
||||
userDetailsAddOnBonuses,
|
||||
type UserDetails,
|
||||
} from "@/new/photos/services/user-details";
|
||||
import { AppContext, useAppContext } from "@/new/photos/types/context";
|
||||
import { usePhotosAppContext } from "@/new/photos/types/context";
|
||||
import { initiateEmail, openURL } from "@/new/photos/utils/web";
|
||||
import {
|
||||
FlexWrapper,
|
||||
@@ -354,7 +355,7 @@ const SubscriptionStatus: React.FC<SubscriptionStatusProps> = ({
|
||||
};
|
||||
|
||||
function MemberSubscriptionManage({ open, userDetails, onClose }) {
|
||||
const { showMiniDialog } = useAppContext();
|
||||
const { showMiniDialog } = useBaseContext();
|
||||
const fullScreen = useIsSmallWidth();
|
||||
|
||||
const confirmLeaveFamily = () =>
|
||||
@@ -508,7 +509,7 @@ const UtilitySection: React.FC<Pick<SidebarProps, "closeSidebar">> = ({
|
||||
closeSidebar,
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
const { watchFolderView, setWatchFolderView } = useAppContext();
|
||||
const { watchFolderView, setWatchFolderView } = usePhotosAppContext();
|
||||
|
||||
const { show: showAccount, props: accountVisibilityProps } =
|
||||
useModalVisibility();
|
||||
@@ -562,7 +563,7 @@ const UtilitySection: React.FC<Pick<SidebarProps, "closeSidebar">> = ({
|
||||
const HelpSection: React.FC<Pick<SidebarProps, "closeSidebar">> = ({
|
||||
closeSidebar,
|
||||
}) => {
|
||||
const { showMiniDialog } = useContext(AppContext);
|
||||
const { showMiniDialog } = useBaseContext();
|
||||
const { openExportModal } = useContext(GalleryContext);
|
||||
|
||||
const { show: showHelp, props: helpVisibilityProps } = useModalVisibility();
|
||||
@@ -595,7 +596,7 @@ const HelpSection: React.FC<Pick<SidebarProps, "closeSidebar">> = ({
|
||||
};
|
||||
|
||||
const ExitSection: React.FC = () => {
|
||||
const { showMiniDialog, logout } = useContext(AppContext);
|
||||
const { logout, showMiniDialog } = useBaseContext();
|
||||
|
||||
const handleLogout = () =>
|
||||
showMiniDialog({
|
||||
@@ -648,7 +649,7 @@ const Account: React.FC<NestedSidebarDrawerVisibilityProps> = ({
|
||||
onClose,
|
||||
onRootClose,
|
||||
}) => {
|
||||
const { showMiniDialog } = useAppContext();
|
||||
const { showMiniDialog } = useBaseContext();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
@@ -908,7 +909,7 @@ const MapSettings: React.FC<NestedSidebarDrawerVisibilityProps> = ({
|
||||
onClose,
|
||||
onRootClose,
|
||||
}) => {
|
||||
const { showMiniDialog } = useAppContext();
|
||||
const { showMiniDialog } = useBaseContext();
|
||||
|
||||
const { mapEnabled } = useSettingsSnapshot();
|
||||
|
||||
@@ -1033,7 +1034,7 @@ const Help: React.FC<NestedSidebarDrawerVisibilityProps> = ({
|
||||
onClose,
|
||||
onRootClose,
|
||||
}) => {
|
||||
const { showMiniDialog } = useAppContext();
|
||||
const { showMiniDialog } = useBaseContext();
|
||||
|
||||
const handleRootClose = () => {
|
||||
onClose();
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
import { FilledIconButton } from "@/base/components/mui";
|
||||
import {
|
||||
UPLOAD_RESULT,
|
||||
type UploadPhase,
|
||||
} from "@/new/photos/services/upload/types";
|
||||
import { useAppContext } from "@/new/photos/types/context";
|
||||
import { useBaseContext } from "@/base/context";
|
||||
import { UPLOAD_RESULT, type UploadPhase } from "@/gallery/services/upload";
|
||||
import { SpaceBetweenFlex } from "@ente/shared/components/Container";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||
@@ -67,7 +64,8 @@ export const UploadProgress: React.FC<UploadProgressProps> = ({
|
||||
finishedUploads,
|
||||
cancelUploads,
|
||||
}) => {
|
||||
const { showMiniDialog } = useAppContext();
|
||||
const { showMiniDialog } = useBaseContext();
|
||||
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { SpacedRow } from "@/base/components/containers";
|
||||
import { DialogCloseIconButton } from "@/base/components/mui/DialogCloseIconButton";
|
||||
import { FocusVisibleButton } from "@/base/components/mui/FocusVisibleButton";
|
||||
import { RowButton } from "@/base/components/RowButton";
|
||||
import { useIsTouchscreen } from "@/base/components/utils/hooks";
|
||||
import { DialogCloseIconButton } from "@/new/photos/components/mui/Dialog";
|
||||
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
|
||||
import GoogleIcon from "@mui/icons-material/Google";
|
||||
import ImageOutlinedIcon from "@mui/icons-material/ImageOutlined";
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import { isDesktop } from "@/base/app";
|
||||
import { useModalVisibility } from "@/base/components/utils/modal";
|
||||
import { useBaseContext } from "@/base/context";
|
||||
import { basename } from "@/base/file-name";
|
||||
import log from "@/base/log";
|
||||
import type { CollectionMapping, Electron, ZipItem } from "@/base/types/ipc";
|
||||
import type {
|
||||
FileAndPath,
|
||||
UploadItem,
|
||||
UploadPhase,
|
||||
} from "@/gallery/services/upload";
|
||||
import type { Collection } from "@/media/collection";
|
||||
import type { EnteFile } from "@/media/file";
|
||||
import { UploaderNameInput } from "@/new/albums/components/UploaderNameInput";
|
||||
@@ -11,13 +17,8 @@ import type { CollectionSelectorAttributes } from "@/new/photos/components/Colle
|
||||
import { downloadAppDialogAttributes } from "@/new/photos/components/utils/download";
|
||||
import { getLatestCollections } from "@/new/photos/services/collections";
|
||||
import { exportMetadataDirectoryName } from "@/new/photos/services/export";
|
||||
import type {
|
||||
FileAndPath,
|
||||
UploadItem,
|
||||
UploadPhase,
|
||||
} from "@/new/photos/services/upload/types";
|
||||
import { redirectToCustomerPortal } from "@/new/photos/services/user-details";
|
||||
import { useAppContext } from "@/new/photos/types/context";
|
||||
import { usePhotosAppContext } from "@/new/photos/types/context";
|
||||
import { firstNonEmpty } from "@/utils/array";
|
||||
import { CustomError } from "@ente/shared/error";
|
||||
import DiscFullIcon from "@mui/icons-material/DiscFull";
|
||||
@@ -108,12 +109,9 @@ export default function Uploader({
|
||||
showSessionExpiredMessage,
|
||||
...props
|
||||
}: Props) {
|
||||
const {
|
||||
showMiniDialog,
|
||||
showNotification,
|
||||
onGenericError,
|
||||
watchFolderView,
|
||||
} = useAppContext();
|
||||
const { showMiniDialog } = useBaseContext();
|
||||
const { showNotification, onGenericError, watchFolderView } =
|
||||
usePhotosAppContext();
|
||||
const galleryContext = useContext(GalleryContext);
|
||||
const publicCollectionGalleryContext = useContext(
|
||||
PublicCollectionGalleryContext,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { CenteredFill, SpacedRow } from "@/base/components/containers";
|
||||
import { DialogCloseIconButton } from "@/base/components/mui/DialogCloseIconButton";
|
||||
import { FocusVisibleButton } from "@/base/components/mui/FocusVisibleButton";
|
||||
import {
|
||||
OverflowMenu,
|
||||
@@ -9,12 +10,11 @@ import {
|
||||
useModalVisibility,
|
||||
type ModalVisibilityProps,
|
||||
} from "@/base/components/utils/modal";
|
||||
import { useBaseContext } from "@/base/context";
|
||||
import { ensureElectron } from "@/base/electron";
|
||||
import { basename, dirname } from "@/base/file-name";
|
||||
import type { CollectionMapping, FolderWatch } from "@/base/types/ipc";
|
||||
import { CollectionMappingChoice } from "@/new/photos/components/CollectionMappingChoice";
|
||||
import { DialogCloseIconButton } from "@/new/photos/components/mui/Dialog";
|
||||
import { useAppContext } from "@/new/photos/types/context";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import CheckIcon from "@mui/icons-material/Check";
|
||||
import DoNotDisturbOutlinedIcon from "@mui/icons-material/DoNotDisturbOutlined";
|
||||
@@ -205,7 +205,7 @@ interface WatchEntryProps {
|
||||
}
|
||||
|
||||
const WatchEntry: React.FC<WatchEntryProps> = ({ watch, removeWatch }) => {
|
||||
const { showMiniDialog } = useAppContext();
|
||||
const { showMiniDialog } = useBaseContext();
|
||||
|
||||
const confirmStopWatching = () => {
|
||||
showMiniDialog({
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { SpacedRow } from "@/base/components/containers";
|
||||
import { useBaseContext } from "@/base/context";
|
||||
import type { Collection } from "@/media/collection";
|
||||
import type { CollectionSelectorAttributes } from "@/new/photos/components/CollectionSelector";
|
||||
import type { GalleryBarMode } from "@/new/photos/components/gallery/reducer";
|
||||
@@ -7,7 +8,6 @@ import {
|
||||
ARCHIVE_SECTION,
|
||||
TRASH_SECTION,
|
||||
} from "@/new/photos/services/collection";
|
||||
import { useAppContext } from "@/new/photos/types/context";
|
||||
import ClockIcon from "@mui/icons-material/AccessTime";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import ArchiveIcon from "@mui/icons-material/ArchiveOutlined";
|
||||
@@ -71,7 +71,7 @@ const SelectedFileOptions = ({
|
||||
isInSearchMode,
|
||||
isInHiddenSection,
|
||||
}: Props) => {
|
||||
const { showMiniDialog } = useAppContext();
|
||||
const { showMiniDialog } = useBaseContext();
|
||||
|
||||
const peopleMode = barMode == "people";
|
||||
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
import Page from "@/base/components/pages/404";
|
||||
|
||||
export default Page;
|
||||
export { default } from "@/base/components/pages/404";
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
useSetupLogs,
|
||||
} from "@/base/components/utils/hooks-app";
|
||||
import { photosTheme } from "@/base/components/utils/theme";
|
||||
import { BaseContext } from "@/base/context";
|
||||
import log from "@/base/log";
|
||||
import { logStartupBanner } from "@/base/log-web";
|
||||
import { AppUpdate } from "@/base/types/ipc";
|
||||
@@ -30,12 +31,12 @@ import { aboveFileViewerContentZ } from "@/new/photos/components/utils/z-index";
|
||||
import { runMigrations } from "@/new/photos/services/migration";
|
||||
import { initML, isMLSupported } from "@/new/photos/services/ml";
|
||||
import { getFamilyPortalRedirectURL } from "@/new/photos/services/user-details";
|
||||
import { AppContext } from "@/new/photos/types/context";
|
||||
import { PhotosAppContext } from "@/new/photos/types/context";
|
||||
import HTTPService from "@ente/shared/network/HTTPService";
|
||||
import {
|
||||
getData,
|
||||
isLocalStorageAndIndexedDBMismatch,
|
||||
LS_KEYS,
|
||||
migrateKVToken,
|
||||
} from "@ente/shared/storage/localStorage";
|
||||
import type { User } from "@ente/shared/user/types";
|
||||
import "@fontsource-variable/inter";
|
||||
@@ -68,13 +69,21 @@ const App: React.FC<AppProps> = ({ Component, pageProps }) => {
|
||||
|
||||
const [watchFolderView, setWatchFolderView] = useState(false);
|
||||
|
||||
const logout = useCallback(() => void photosLogout(), []);
|
||||
|
||||
useEffect(() => {
|
||||
const user = getData(LS_KEYS.USER) as User | undefined | null;
|
||||
void migrateKVToken(user);
|
||||
logStartupBanner(user?.id);
|
||||
HTTPService.setHeaders({ "X-Client-Package": clientPackageName });
|
||||
void runMigrations();
|
||||
}, []);
|
||||
void isLocalStorageAndIndexedDBMismatch().then((mismatch) => {
|
||||
if (mismatch) {
|
||||
log.error("Logging out (IndexedDB and local storage mismatch)");
|
||||
return logout();
|
||||
} else {
|
||||
return runMigrations();
|
||||
}
|
||||
});
|
||||
}, [logout]);
|
||||
|
||||
useEffect(() => {
|
||||
const electron = globalThis.electron;
|
||||
@@ -149,8 +158,10 @@ const App: React.FC<AppProps> = ({ Component, pageProps }) => {
|
||||
}, 0);
|
||||
}, []);
|
||||
|
||||
const logout = useCallback(() => void photosLogout(), []);
|
||||
|
||||
const baseContext = useMemo(
|
||||
() => ({ logout, showMiniDialog }),
|
||||
[logout, showMiniDialog],
|
||||
);
|
||||
const appContext = useMemo(
|
||||
() => ({
|
||||
showLoadingBar,
|
||||
@@ -176,22 +187,21 @@ const App: React.FC<AppProps> = ({ Component, pageProps }) => {
|
||||
const title = isI18nReady ? t("title_photos") : staticAppTitle;
|
||||
|
||||
return (
|
||||
<>
|
||||
<ThemeProvider theme={photosTheme}>
|
||||
<CustomHead {...{ title }} />
|
||||
<CssBaseline enableColorScheme />
|
||||
<ThemedLoadingBar ref={loadingBarRef} />
|
||||
|
||||
<ThemeProvider theme={photosTheme}>
|
||||
<CssBaseline enableColorScheme />
|
||||
<ThemedLoadingBar ref={loadingBarRef} />
|
||||
<AttributedMiniDialog
|
||||
sx={{ zIndex: aboveFileViewerContentZ }}
|
||||
{...miniDialogProps}
|
||||
/>
|
||||
|
||||
<AttributedMiniDialog
|
||||
sx={{ zIndex: aboveFileViewerContentZ }}
|
||||
{...miniDialogProps}
|
||||
/>
|
||||
<Notification {...notificationProps} />
|
||||
|
||||
<Notification {...notificationProps} />
|
||||
|
||||
{isDesktop && <WindowTitlebar>{title}</WindowTitlebar>}
|
||||
<AppContext.Provider value={appContext}>
|
||||
{isDesktop && <WindowTitlebar>{title}</WindowTitlebar>}
|
||||
<BaseContext value={baseContext}>
|
||||
<PhotosAppContext value={appContext}>
|
||||
{!isI18nReady ? (
|
||||
<LoadingIndicator />
|
||||
) : (
|
||||
@@ -200,9 +210,9 @@ const App: React.FC<AppProps> = ({ Component, pageProps }) => {
|
||||
<Component {...pageProps} />
|
||||
</>
|
||||
)}
|
||||
</AppContext.Provider>
|
||||
</ThemeProvider>
|
||||
</>
|
||||
</PhotosAppContext>
|
||||
</BaseContext>
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,3 +1 @@
|
||||
import Page from "@/accounts/pages/change-email";
|
||||
|
||||
export default Page;
|
||||
export { default } from "@/accounts/pages/change-email";
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user