From f04e54f68b2ec06747a0d461b2fb58909f3e282d Mon Sep 17 00:00:00 2001 From: ashilkn Date: Fri, 14 Jun 2024 22:30:02 +0530 Subject: [PATCH 001/130] [mob][photos] Select all MVP --- mobile/lib/ui/home/home_gallery_widget.dart | 17 ++-- .../actions/file_selection_overlay_bar.dart | 78 ++++++++++++++++--- mobile/lib/ui/viewer/gallery/gallery.dart | 5 +- .../viewer/gallery/state/selection_state.dart | 29 +++++++ 4 files changed, 110 insertions(+), 19 deletions(-) create mode 100644 mobile/lib/ui/viewer/gallery/state/selection_state.dart diff --git a/mobile/lib/ui/home/home_gallery_widget.dart b/mobile/lib/ui/home/home_gallery_widget.dart index 5d9f9c09dc..366559cb8c 100644 --- a/mobile/lib/ui/home/home_gallery_widget.dart +++ b/mobile/lib/ui/home/home_gallery_widget.dart @@ -13,6 +13,7 @@ import 'package:photos/services/collections_service.dart'; import "package:photos/services/filter/db_filters.dart"; import 'package:photos/ui/viewer/actions/file_selection_overlay_bar.dart'; import 'package:photos/ui/viewer/gallery/gallery.dart'; +import "package:photos/ui/viewer/gallery/state/selection_state.dart"; class HomeGalleryWidget extends StatelessWidget { final Widget? header; @@ -84,12 +85,16 @@ class HomeGalleryWidget extends StatelessWidget { reloadDebounceTime: const Duration(seconds: 2), reloadDebounceExecutionInterval: const Duration(seconds: 5), ); - return Stack( - alignment: Alignment.bottomCenter, - children: [ - gallery, - FileSelectionOverlayBar(GalleryType.homepage, selectedFiles), - ], + return SelectionState( + selectedFiles: selectedFiles, + // ignore: prefer_const_literals_to_create_immutables + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + gallery, + FileSelectionOverlayBar(GalleryType.homepage, selectedFiles), + ], + ), ); // return gallery; } diff --git a/mobile/lib/ui/viewer/actions/file_selection_overlay_bar.dart b/mobile/lib/ui/viewer/actions/file_selection_overlay_bar.dart index 8e2260c74d..53d835fce4 100644 --- a/mobile/lib/ui/viewer/actions/file_selection_overlay_bar.dart +++ b/mobile/lib/ui/viewer/actions/file_selection_overlay_bar.dart @@ -4,7 +4,9 @@ import 'package:photos/models/collection/collection.dart'; import 'package:photos/models/gallery_type.dart'; import 'package:photos/models/selected_files.dart'; import "package:photos/theme/effects.dart"; +import "package:photos/theme/ente_theme.dart"; import 'package:photos/ui/components/bottom_action_bar/bottom_action_bar_widget.dart'; +import "package:photos/ui/viewer/gallery/state/selection_state.dart"; class FileSelectionOverlayBar extends StatefulWidget { final GalleryType galleryType; @@ -66,18 +68,25 @@ class _FileSelectionOverlayBarState extends State { ? CrossFadeState.showFirst : CrossFadeState.showSecond, duration: const Duration(milliseconds: 400), - firstChild: BottomActionBarWidget( - selectedFiles: widget.selectedFiles, - galleryType: widget.galleryType, - collection: widget.collection, - person: widget.person, - clusterID: widget.clusterID, - onCancel: () { - if (widget.selectedFiles.files.isNotEmpty) { - widget.selectedFiles.clearAll(); - } - }, - backgroundColor: widget.backgroundColor, + firstChild: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + SelectAllButton(backgroundColor: widget.backgroundColor), + BottomActionBarWidget( + selectedFiles: widget.selectedFiles, + galleryType: widget.galleryType, + collection: widget.collection, + person: widget.person, + clusterID: widget.clusterID, + onCancel: () { + if (widget.selectedFiles.files.isNotEmpty) { + widget.selectedFiles.clearAll(); + } + }, + backgroundColor: widget.backgroundColor, + ), + ], ), secondChild: const SizedBox(width: double.infinity), ); @@ -90,3 +99,48 @@ class _FileSelectionOverlayBarState extends State { _hasSelectedFilesNotifier.value = widget.selectedFiles.files.isNotEmpty; } } + +class SelectAllButton extends StatefulWidget { + final Color? backgroundColor; + const SelectAllButton({super.key, required this.backgroundColor}); + + @override + State createState() => _SelectAllButtonState(); +} + +class _SelectAllButtonState extends State { + bool _selectAll = false; + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: () { + setState(() { + final selectionState = SelectionState.of(context); + if (_selectAll) { + selectionState?.selectedFiles.clearAll(); + } else { + selectionState?.selectedFiles + .selectAll(selectionState.allGalleryFiles!.toSet()); + } + _selectAll = !_selectAll; + }); + }, + child: Container( + color: getEnteColorScheme(context).backgroundElevated2, + padding: const EdgeInsets.all(4), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + const Text("All"), + Icon( + _selectAll ? Icons.check_circle : Icons.check_circle_outline, + color: + _selectAll ? getEnteColorScheme(context).strokeMuted : null, + ), + ], + ), + ), + ); + } +} diff --git a/mobile/lib/ui/viewer/gallery/gallery.dart b/mobile/lib/ui/viewer/gallery/gallery.dart index b255c5c375..d895bc80fe 100644 --- a/mobile/lib/ui/viewer/gallery/gallery.dart +++ b/mobile/lib/ui/viewer/gallery/gallery.dart @@ -16,6 +16,7 @@ import "package:photos/ui/viewer/gallery/component/group/type.dart"; import "package:photos/ui/viewer/gallery/component/multiple_groups_gallery_view.dart"; import 'package:photos/ui/viewer/gallery/empty_state.dart'; import "package:photos/ui/viewer/gallery/state/gallery_context_state.dart"; +import "package:photos/ui/viewer/gallery/state/selection_state.dart"; import "package:photos/utils/debouncer.dart"; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; @@ -173,7 +174,7 @@ class GalleryState extends State { if (result.hasMore) { final result = await _loadFiles(); _setFilesAndReload(result.files); - } + } else {} }); } @@ -213,6 +214,8 @@ class GalleryState extends State { // group files into multiple groups and returns `true` if it resulted in a // gallery reload bool _onFilesLoaded(List files) { + SelectionState.of(context)?.allGalleryFiles = files; + final updatedGroupedFiles = widget.enableFileGrouping && widget.groupType.timeGrouping() ? _groupBasedOnTime(files) diff --git a/mobile/lib/ui/viewer/gallery/state/selection_state.dart b/mobile/lib/ui/viewer/gallery/state/selection_state.dart new file mode 100644 index 0000000000..fa2fdfa171 --- /dev/null +++ b/mobile/lib/ui/viewer/gallery/state/selection_state.dart @@ -0,0 +1,29 @@ +import "package:flutter/material.dart"; +import "package:photos/models/file/file.dart"; +import "package:photos/models/selected_files.dart"; + +// ignore: must_be_immutable +class SelectionState extends InheritedWidget { + final SelectedFiles selectedFiles; + + ///Should be assigned later in gallery when files are loaded. + ///Note: EnteFiles in this list should be references of the same EnteFiles + ///that are grouped in gallery, so that when files are added/deleted, + ///both lists are in sync. + List? allGalleryFiles; + + SelectionState({ + Key? key, + required this.selectedFiles, + required Widget child, + }) : super(key: key, child: child); + + static SelectionState? of(BuildContext context) { + return context.dependOnInheritedWidgetOfExactType(); + } + + @override + bool updateShouldNotify(covariant InheritedWidget oldWidget) { + return false; + } +} From 1b993a617a79d3a062f5ff06cc97e7a0328dc5eb Mon Sep 17 00:00:00 2001 From: ashilkn Date: Sat, 15 Jun 2024 12:21:58 +0530 Subject: [PATCH 002/130] [mob][photos] Update select all button's state checking if all are selected or not on each select/unselect operation --- .../actions/file_selection_overlay_bar.dart | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/mobile/lib/ui/viewer/actions/file_selection_overlay_bar.dart b/mobile/lib/ui/viewer/actions/file_selection_overlay_bar.dart index 53d835fce4..8f58c1f04f 100644 --- a/mobile/lib/ui/viewer/actions/file_selection_overlay_bar.dart +++ b/mobile/lib/ui/viewer/actions/file_selection_overlay_bar.dart @@ -112,14 +112,14 @@ class _SelectAllButtonState extends State { bool _selectAll = false; @override Widget build(BuildContext context) { + final selectionState = SelectionState.of(context); return GestureDetector( onTap: () { setState(() { - final selectionState = SelectionState.of(context); if (_selectAll) { - selectionState?.selectedFiles.clearAll(); + selectionState.selectedFiles.clearAll(); } else { - selectionState?.selectedFiles + selectionState.selectedFiles .selectAll(selectionState.allGalleryFiles!.toSet()); } _selectAll = !_selectAll; @@ -133,10 +133,22 @@ class _SelectAllButtonState extends State { mainAxisSize: MainAxisSize.min, children: [ const Text("All"), - Icon( - _selectAll ? Icons.check_circle : Icons.check_circle_outline, - color: - _selectAll ? getEnteColorScheme(context).strokeMuted : null, + ListenableBuilder( + listenable: selectionState.selectedFiles, + builder: (context, _) { + if (selectionState.selectedFiles.files.length == + selectionState.allGalleryFiles!.length) { + _selectAll = true; + } else { + _selectAll = false; + } + return Icon( + _selectAll ? Icons.check_circle : Icons.check_circle_outline, + color: _selectAll + ? getEnteColorScheme(context).strokeMuted + : null, + ); + }, ), ], ), From f001812a1d28147a98348b580a597a66f680da83 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Sat, 15 Jun 2024 12:23:45 +0530 Subject: [PATCH 003/130] [mob][photos] Assert that SelectionState inherited widget is in context or not --- mobile/lib/ui/viewer/gallery/state/selection_state.dart | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/mobile/lib/ui/viewer/gallery/state/selection_state.dart b/mobile/lib/ui/viewer/gallery/state/selection_state.dart index fa2fdfa171..a67ffd449f 100644 --- a/mobile/lib/ui/viewer/gallery/state/selection_state.dart +++ b/mobile/lib/ui/viewer/gallery/state/selection_state.dart @@ -18,10 +18,16 @@ class SelectionState extends InheritedWidget { required Widget child, }) : super(key: key, child: child); - static SelectionState? of(BuildContext context) { + static SelectionState? maybeOf(BuildContext context) { return context.dependOnInheritedWidgetOfExactType(); } + static SelectionState of(BuildContext context) { + final SelectionState? result = maybeOf(context); + assert(result != null, 'No SelectionState found in context'); + return result!; + } + @override bool updateShouldNotify(covariant InheritedWidget oldWidget) { return false; From befcb72b0490c53ce34f192b6fe3482c6f719b63 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Sat, 15 Jun 2024 15:36:50 +0530 Subject: [PATCH 004/130] [mob][photos] Make Select all's UI better --- .../actions/file_selection_overlay_bar.dart | 129 +++++++++++------- 1 file changed, 76 insertions(+), 53 deletions(-) diff --git a/mobile/lib/ui/viewer/actions/file_selection_overlay_bar.dart b/mobile/lib/ui/viewer/actions/file_selection_overlay_bar.dart index 8f58c1f04f..1570a5e272 100644 --- a/mobile/lib/ui/viewer/actions/file_selection_overlay_bar.dart +++ b/mobile/lib/ui/viewer/actions/file_selection_overlay_bar.dart @@ -53,27 +53,31 @@ class _FileSelectionOverlayBarState extends State { '$runtimeType building with ${widget.selectedFiles.files.length}', ); - return Container( - decoration: BoxDecoration( - boxShadow: shadowFloatFaintLight, - ), - child: ValueListenableBuilder( - valueListenable: _hasSelectedFilesNotifier, - builder: (context, value, child) { - return AnimatedCrossFade( - firstCurve: Curves.easeInOutExpo, - secondCurve: Curves.easeInOutExpo, - sizeCurve: Curves.easeInOutExpo, - crossFadeState: _hasSelectedFilesNotifier.value - ? CrossFadeState.showFirst - : CrossFadeState.showSecond, - duration: const Duration(milliseconds: 400), - firstChild: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - SelectAllButton(backgroundColor: widget.backgroundColor), - BottomActionBarWidget( + return ValueListenableBuilder( + valueListenable: _hasSelectedFilesNotifier, + builder: (context, value, child) { + return AnimatedCrossFade( + firstCurve: Curves.easeInOutExpo, + secondCurve: Curves.easeInOutExpo, + sizeCurve: Curves.easeInOutExpo, + crossFadeState: _hasSelectedFilesNotifier.value + ? CrossFadeState.showFirst + : CrossFadeState.showSecond, + duration: const Duration(milliseconds: 400), + firstChild: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Padding( + padding: const EdgeInsets.only(right: 4), + child: SelectAllButton(backgroundColor: widget.backgroundColor), + ), + const SizedBox(height: 8), + Container( + decoration: BoxDecoration( + boxShadow: shadowFloatFaintLight, + ), + child: BottomActionBarWidget( selectedFiles: widget.selectedFiles, galleryType: widget.galleryType, collection: widget.collection, @@ -86,12 +90,12 @@ class _FileSelectionOverlayBarState extends State { }, backgroundColor: widget.backgroundColor, ), - ], - ), - secondChild: const SizedBox(width: double.infinity), - ); - }, - ), + ), + ], + ), + secondChild: const SizedBox(width: double.infinity), + ); + }, ); } @@ -113,6 +117,7 @@ class _SelectAllButtonState extends State { @override Widget build(BuildContext context) { final selectionState = SelectionState.of(context); + final colorScheme = getEnteColorScheme(context); return GestureDetector( onTap: () { setState(() { @@ -125,32 +130,50 @@ class _SelectAllButtonState extends State { _selectAll = !_selectAll; }); }, - child: Container( - color: getEnteColorScheme(context).backgroundElevated2, - padding: const EdgeInsets.all(4), - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - mainAxisSize: MainAxisSize.min, - children: [ - const Text("All"), - ListenableBuilder( - listenable: selectionState.selectedFiles, - builder: (context, _) { - if (selectionState.selectedFiles.files.length == - selectionState.allGalleryFiles!.length) { - _selectAll = true; - } else { - _selectAll = false; - } - return Icon( - _selectAll ? Icons.check_circle : Icons.check_circle_outline, - color: _selectAll - ? getEnteColorScheme(context).strokeMuted - : null, - ); - }, - ), - ], + child: Padding( + padding: const EdgeInsets.only(top: 8), + child: Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: widget.backgroundColor ?? colorScheme.backgroundElevated2, + borderRadius: BorderRadius.circular(16), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 4, + offset: const Offset(0, -1), + ), + ], + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "All", + style: getEnteTextTheme(context).miniMuted, + ), + const SizedBox(width: 4), + ListenableBuilder( + listenable: selectionState.selectedFiles, + builder: (context, _) { + if (selectionState.selectedFiles.files.length == + selectionState.allGalleryFiles?.length) { + _selectAll = true; + } else { + _selectAll = false; + } + return Icon( + _selectAll + ? Icons.check_circle + : Icons.check_circle_outline, + color: _selectAll ? null : colorScheme.strokeMuted, + size: 18, + ); + }, + ), + ], + ), ), ), ); From 533c0230e455effde389ed8436291abda4cbfb2d Mon Sep 17 00:00:00 2001 From: ialexanderbrito Date: Sun, 16 Jun 2024 00:33:48 -0300 Subject: [PATCH 005/130] feat: add and update icons --- .../custom-icons/_data/custom-icons.json | 63 ++++++------- auth/assets/custom-icons/icons/cloudamqp.svg | 19 ++++ .../assets/custom-icons/icons/nucommunity.svg | 38 ++++++++ .../assets/custom-icons/icons/registro_br.svg | 88 ++----------------- auth/assets/custom-icons/icons/render.svg | 6 ++ auth/assets/custom-icons/icons/samsung.svg | 7 ++ auth/assets/custom-icons/icons/twitch.svg | 8 ++ 7 files changed, 109 insertions(+), 120 deletions(-) create mode 100644 auth/assets/custom-icons/icons/cloudamqp.svg create mode 100644 auth/assets/custom-icons/icons/nucommunity.svg create mode 100644 auth/assets/custom-icons/icons/render.svg create mode 100644 auth/assets/custom-icons/icons/samsung.svg create mode 100644 auth/assets/custom-icons/icons/twitch.svg diff --git a/auth/assets/custom-icons/_data/custom-icons.json b/auth/assets/custom-icons/_data/custom-icons.json index a84ddd1525..9445d9fbe5 100644 --- a/auth/assets/custom-icons/_data/custom-icons.json +++ b/auth/assets/custom-icons/_data/custom-icons.json @@ -23,12 +23,6 @@ { "title": "BitMEX" }, - { - "title": "BitSkins" - }, - { - "title": "Bitstamp" - }, { "title": "Bitvavo", "hex": "0051FF" @@ -56,9 +50,6 @@ { "title": "CERN" }, - { - "title": "ChangeNOW" - }, { "title": "Channel Island Hosting", "slug": "cih", @@ -68,7 +59,11 @@ "title": "Cloudflare" }, { - "title": "ConfigCat" + "title": "CloudAMQP" + }, + { + "title": "ConfigCat", + "slug": "configcat" }, { "title": "Control D", @@ -92,9 +87,6 @@ { "title": "Discourse" }, - { - "title": "DMarket" - }, { "title": "Doppler" }, @@ -166,10 +158,6 @@ { "title": "INWX" }, - { - "title": "Itch.io", - "slug": "itch_io" - }, { "title": "IVPN", "slug": "IVPN" @@ -218,10 +206,6 @@ "slug": "local_wp", "altNames": ["LocalWP", "Local WP", "Local Wordpress"] }, - { - "title": "Marketplace.tf", - "slug": "marketplacedottf" - }, { "title": "Mastodon", "altNames": ["mstdn", "fediscience", "mathstodon", "fosstodon"], @@ -236,9 +220,6 @@ { "title": "Microsoft" }, - { - "title": "Migros" - }, { "title": "Mintos" }, @@ -254,10 +235,6 @@ "title": "MyFRITZ!Net", "slug": "myfritz" }, - { - "title": "Name.com", - "slug": "name_com" - }, { "title": "NextDNS" }, @@ -274,6 +251,10 @@ { "title": "Notion" }, + { + "title": "NuCommunity", + "slug": "nucommunity" + }, { "title": "NVIDIA" }, @@ -332,8 +313,12 @@ "slug": "real_debrid" }, { - "title": "Registro.br", - "slug": "registro_br" + "title": "Registro br", + "slug": "registro_br", + "altNames": ["Registro br", "registrobr", "Registro.br"] + }, + { + "title": "Render" }, { "title": "Revolt", @@ -352,6 +337,9 @@ "slug": "rust_language_forum", "hex": "000000" }, + { + "title": "Samsung" + }, { "title": "Sendgrid" }, @@ -368,9 +356,6 @@ "title": "Skiff", "hex": "EF5A3C" }, - { - "title": "Skinport" - }, { "title": "Snapchat" }, @@ -419,6 +404,10 @@ { "title": "Tweakers" }, + { + "title": "Twitch", + "altNames": ["Twitch.tv", "Twitch tv"] + }, { "title": "Twingate", "hex": "858585" @@ -427,10 +416,6 @@ "title": "Ubisoft", "hex": "4285f4" }, - { - "title": "Ubuntu One", - "slug": "ubuntu_one" - }, { "title": "Unity", "hex": "858585" @@ -457,7 +442,9 @@ { "title": "WorkOS", "slug": "workos", - "altNames": ["Work OS"] + "altNames": [ + "Work OS" + ] }, { "title": "X", diff --git a/auth/assets/custom-icons/icons/cloudamqp.svg b/auth/assets/custom-icons/icons/cloudamqp.svg new file mode 100644 index 0000000000..65ec3aebd2 --- /dev/null +++ b/auth/assets/custom-icons/icons/cloudamqp.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/auth/assets/custom-icons/icons/nucommunity.svg b/auth/assets/custom-icons/icons/nucommunity.svg new file mode 100644 index 0000000000..29f040cbe7 --- /dev/null +++ b/auth/assets/custom-icons/icons/nucommunity.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/auth/assets/custom-icons/icons/registro_br.svg b/auth/assets/custom-icons/icons/registro_br.svg index a719a6b975..fa5b9cb810 100644 --- a/auth/assets/custom-icons/icons/registro_br.svg +++ b/auth/assets/custom-icons/icons/registro_br.svg @@ -1,83 +1,7 @@ - - - - - - image/svg+xml - - - - - - - - - - + + + + + + diff --git a/auth/assets/custom-icons/icons/render.svg b/auth/assets/custom-icons/icons/render.svg new file mode 100644 index 0000000000..68cdd931f5 --- /dev/null +++ b/auth/assets/custom-icons/icons/render.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/auth/assets/custom-icons/icons/samsung.svg b/auth/assets/custom-icons/icons/samsung.svg new file mode 100644 index 0000000000..1c622af77d --- /dev/null +++ b/auth/assets/custom-icons/icons/samsung.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/auth/assets/custom-icons/icons/twitch.svg b/auth/assets/custom-icons/icons/twitch.svg new file mode 100644 index 0000000000..c64a9b9450 --- /dev/null +++ b/auth/assets/custom-icons/icons/twitch.svg @@ -0,0 +1,8 @@ + + + + + + + + From e1f7b04aa04dd1b4057d0372af8db8c5b57a3889 Mon Sep 17 00:00:00 2001 From: ialexanderbrito Date: Sun, 16 Jun 2024 00:46:17 -0300 Subject: [PATCH 006/130] fix: old icons import --- .../custom-icons/_data/custom-icons.json | 46 ++++++++++++++++--- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/auth/assets/custom-icons/_data/custom-icons.json b/auth/assets/custom-icons/_data/custom-icons.json index 9445d9fbe5..2a65c35aed 100644 --- a/auth/assets/custom-icons/_data/custom-icons.json +++ b/auth/assets/custom-icons/_data/custom-icons.json @@ -23,6 +23,12 @@ { "title": "BitMEX" }, + { + "title": "BitSkins" + }, + { + "title": "Bitstamp" + }, { "title": "Bitvavo", "hex": "0051FF" @@ -50,6 +56,9 @@ { "title": "CERN" }, + { + "title": "ChangeNOW" + }, { "title": "Channel Island Hosting", "slug": "cih", @@ -87,6 +96,9 @@ { "title": "Discourse" }, + { + "title": "DMarket" + }, { "title": "Doppler" }, @@ -158,6 +170,10 @@ { "title": "INWX" }, + { + "title": "Itch.io", + "slug": "itch_io" + }, { "title": "IVPN", "slug": "IVPN" @@ -206,6 +222,10 @@ "slug": "local_wp", "altNames": ["LocalWP", "Local WP", "Local Wordpress"] }, + { + "title": "Marketplace.tf", + "slug": "marketplacedottf" + }, { "title": "Mastodon", "altNames": ["mstdn", "fediscience", "mathstodon", "fosstodon"], @@ -220,6 +240,9 @@ { "title": "Microsoft" }, + { + "title": "Migros" + }, { "title": "Mintos" }, @@ -235,6 +258,10 @@ "title": "MyFRITZ!Net", "slug": "myfritz" }, + { + "title": "Name.com", + "slug": "name_com" + }, { "title": "NextDNS" }, @@ -356,6 +383,9 @@ "title": "Skiff", "hex": "EF5A3C" }, + { + "title": "Skinport" + }, { "title": "Snapchat" }, @@ -404,18 +434,22 @@ { "title": "Tweakers" }, - { - "title": "Twitch", - "altNames": ["Twitch.tv", "Twitch tv"] - }, { "title": "Twingate", "hex": "858585" }, + { + "title": "Twitch", + "altNames": ["Twitch.tv", "Twitch tv"] + }, { "title": "Ubisoft", "hex": "4285f4" }, + { + "title": "Ubuntu One", + "slug": "ubuntu_one" + }, { "title": "Unity", "hex": "858585" @@ -442,9 +476,7 @@ { "title": "WorkOS", "slug": "workos", - "altNames": [ - "Work OS" - ] + "altNames": ["Work OS"] }, { "title": "X", From 4f18fff36b11a6ebc9eb2233ef70ff15ee776631 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Mon, 17 Jun 2024 08:16:31 +0530 Subject: [PATCH 007/130] [mob][photos] Assert or log depending on the context if inherited widget holding selection state is used in a wrong way --- .../ui/viewer/actions/file_selection_overlay_bar.dart | 6 +++++- .../lib/ui/viewer/gallery/state/selection_state.dart | 11 ++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/mobile/lib/ui/viewer/actions/file_selection_overlay_bar.dart b/mobile/lib/ui/viewer/actions/file_selection_overlay_bar.dart index 1570a5e272..0cfb39c51f 100644 --- a/mobile/lib/ui/viewer/actions/file_selection_overlay_bar.dart +++ b/mobile/lib/ui/viewer/actions/file_selection_overlay_bar.dart @@ -117,6 +117,10 @@ class _SelectAllButtonState extends State { @override Widget build(BuildContext context) { final selectionState = SelectionState.of(context); + assert( + selectionState != null, + "SelectionState not found in context, SelectionState should be an ancestor of FileSelectionOverlayBar", + ); final colorScheme = getEnteColorScheme(context); return GestureDetector( onTap: () { @@ -155,7 +159,7 @@ class _SelectAllButtonState extends State { ), const SizedBox(width: 4), ListenableBuilder( - listenable: selectionState.selectedFiles, + listenable: selectionState!.selectedFiles, builder: (context, _) { if (selectionState.selectedFiles.files.length == selectionState.allGalleryFiles?.length) { diff --git a/mobile/lib/ui/viewer/gallery/state/selection_state.dart b/mobile/lib/ui/viewer/gallery/state/selection_state.dart index a67ffd449f..762b14b94a 100644 --- a/mobile/lib/ui/viewer/gallery/state/selection_state.dart +++ b/mobile/lib/ui/viewer/gallery/state/selection_state.dart @@ -1,4 +1,5 @@ import "package:flutter/material.dart"; +import "package:logging/logging.dart"; import "package:photos/models/file/file.dart"; import "package:photos/models/selected_files.dart"; @@ -22,10 +23,14 @@ class SelectionState extends InheritedWidget { return context.dependOnInheritedWidgetOfExactType(); } - static SelectionState of(BuildContext context) { + static SelectionState? of(BuildContext context) { final SelectionState? result = maybeOf(context); - assert(result != null, 'No SelectionState found in context'); - return result!; + if (result == null) { + Logger("SelectionState").warning( + "No SelectionState found in context. Ignore this if file selection is disabled in the gallery used.", + ); + } + return result; } @override From ff1e84d0d8636dff16ea2d28122ec8f57128440f Mon Sep 17 00:00:00 2001 From: ashilkn Date: Mon, 17 Jun 2024 08:41:12 +0530 Subject: [PATCH 008/130] [mob][photos] Avoid using .of(context) in initState to stop exceptions from being thrown --- mobile/lib/ui/viewer/gallery/gallery.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mobile/lib/ui/viewer/gallery/gallery.dart b/mobile/lib/ui/viewer/gallery/gallery.dart index d895bc80fe..7af50e38f5 100644 --- a/mobile/lib/ui/viewer/gallery/gallery.dart +++ b/mobile/lib/ui/viewer/gallery/gallery.dart @@ -108,6 +108,7 @@ class GalleryState extends State { final _forceReloadEventSubscriptions = >[]; late String _logTag; bool _sortOrderAsc = false; + List _allFiles = []; @override void initState() { @@ -214,7 +215,7 @@ class GalleryState extends State { // group files into multiple groups and returns `true` if it resulted in a // gallery reload bool _onFilesLoaded(List files) { - SelectionState.of(context)?.allGalleryFiles = files; + _allFiles = files; final updatedGroupedFiles = widget.enableFileGrouping && widget.groupType.timeGrouping() @@ -249,6 +250,7 @@ class GalleryState extends State { @override Widget build(BuildContext context) { _logger.finest("Building Gallery ${widget.tagPrefix}"); + SelectionState.of(context)?.allGalleryFiles = _allFiles; if (!_hasLoadedFiles) { return widget.loadingWidget; } From 9f4ce085c167a71984a3085bc560ac769b29c60b Mon Sep 17 00:00:00 2001 From: ashilkn Date: Mon, 17 Jun 2024 08:43:07 +0530 Subject: [PATCH 009/130] [mob][photos] Add select all feature to galleries (1) --- mobile/lib/ui/map/map_pull_up_gallery.dart | 57 ++++++++++--------- .../lib/ui/viewer/gallery/archive_page.dart | 22 ++++--- .../ui/viewer/gallery/collection_page.dart | 24 ++++---- .../ui/viewer/gallery/device_folder_page.dart | 22 ++++--- mobile/lib/ui/viewer/gallery/hidden_page.dart | 22 ++++--- 5 files changed, 84 insertions(+), 63 deletions(-) diff --git a/mobile/lib/ui/map/map_pull_up_gallery.dart b/mobile/lib/ui/map/map_pull_up_gallery.dart index c88d4f81ce..f52439759e 100644 --- a/mobile/lib/ui/map/map_pull_up_gallery.dart +++ b/mobile/lib/ui/map/map_pull_up_gallery.dart @@ -15,6 +15,7 @@ import "package:photos/theme/ente_theme.dart"; import "package:photos/ui/common/loading_widget.dart"; import "package:photos/ui/viewer/actions/file_selection_overlay_bar.dart"; import "package:photos/ui/viewer/gallery/gallery.dart"; +import "package:photos/ui/viewer/gallery/state/selection_state.dart"; class MapPullUpGallery extends StatefulWidget { final StreamController> visibleImages; @@ -48,33 +49,37 @@ class _MapPullUpGalleryState extends State { Widget? cachedScrollableContent; return DeferredPointerHandler( - child: Stack( - alignment: Alignment.bottomCenter, - clipBehavior: Clip.none, - children: [ - DraggableScrollableSheet( - expand: false, - initialChildSize: initialChildSize, - minChildSize: initialChildSize, - maxChildSize: 0.8, - snap: true, - snapSizes: const [0.5], - builder: (context, scrollController) { - //Must use cached widget here to avoid rebuilds when DraggableScrollableSheet - //is snapped to it's initialChildSize - cachedScrollableContent ??= - cacheScrollableContent(scrollController, context, logger); - return cachedScrollableContent!; - }, - ), - DeferPointer( - child: FileSelectionOverlayBar( - GalleryType.searchResults, - _selectedFiles, - backgroundColor: getEnteColorScheme(context).backgroundElevated2, + child: SelectionState( + selectedFiles: _selectedFiles, + child: Stack( + alignment: Alignment.bottomCenter, + clipBehavior: Clip.none, + children: [ + DraggableScrollableSheet( + expand: false, + initialChildSize: initialChildSize, + minChildSize: initialChildSize, + maxChildSize: 0.8, + snap: true, + snapSizes: const [0.5], + builder: (context, scrollController) { + //Must use cached widget here to avoid rebuilds when DraggableScrollableSheet + //is snapped to it's initialChildSize + cachedScrollableContent ??= + cacheScrollableContent(scrollController, context, logger); + return cachedScrollableContent!; + }, ), - ), - ], + DeferPointer( + child: FileSelectionOverlayBar( + GalleryType.searchResults, + _selectedFiles, + backgroundColor: + getEnteColorScheme(context).backgroundElevated2, + ), + ), + ], + ), ), ); } diff --git a/mobile/lib/ui/viewer/gallery/archive_page.dart b/mobile/lib/ui/viewer/gallery/archive_page.dart index 1d6f544def..4f0a9502f6 100644 --- a/mobile/lib/ui/viewer/gallery/archive_page.dart +++ b/mobile/lib/ui/viewer/gallery/archive_page.dart @@ -15,6 +15,7 @@ import 'package:photos/ui/viewer/actions/file_selection_overlay_bar.dart'; import "package:photos/ui/viewer/gallery/empty_state.dart"; import 'package:photos/ui/viewer/gallery/gallery.dart'; import 'package:photos/ui/viewer/gallery/gallery_app_bar_widget.dart'; +import "package:photos/ui/viewer/gallery/state/selection_state.dart"; class ArchivePage extends StatelessWidget { final String tagPrefix; @@ -86,15 +87,18 @@ class ArchivePage extends StatelessWidget { _selectedFiles, ), ), - body: Stack( - alignment: Alignment.bottomCenter, - children: [ - gallery, - FileSelectionOverlayBar( - overlayType, - _selectedFiles, - ), - ], + body: SelectionState( + selectedFiles: _selectedFiles, + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + gallery, + FileSelectionOverlayBar( + overlayType, + _selectedFiles, + ), + ], + ), ), ); } diff --git a/mobile/lib/ui/viewer/gallery/collection_page.dart b/mobile/lib/ui/viewer/gallery/collection_page.dart index 97183282b6..e0288f238b 100644 --- a/mobile/lib/ui/viewer/gallery/collection_page.dart +++ b/mobile/lib/ui/viewer/gallery/collection_page.dart @@ -16,6 +16,7 @@ import "package:photos/ui/viewer/gallery/empty_album_state.dart"; import 'package:photos/ui/viewer/gallery/empty_state.dart'; import 'package:photos/ui/viewer/gallery/gallery.dart'; import 'package:photos/ui/viewer/gallery/gallery_app_bar_widget.dart'; +import "package:photos/ui/viewer/gallery/state/selection_state.dart"; class CollectionPage extends StatelessWidget { final CollectionWithThumbnail c; @@ -98,16 +99,19 @@ class CollectionPage extends StatelessWidget { collection: c.collection, ), ), - body: Stack( - alignment: Alignment.bottomCenter, - children: [ - gallery, - FileSelectionOverlayBar( - galleryType, - _selectedFiles, - collection: c.collection, - ), - ], + body: SelectionState( + selectedFiles: _selectedFiles, + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + gallery, + FileSelectionOverlayBar( + galleryType, + _selectedFiles, + collection: c.collection, + ), + ], + ), ), ); } diff --git a/mobile/lib/ui/viewer/gallery/device_folder_page.dart b/mobile/lib/ui/viewer/gallery/device_folder_page.dart index 42d7b80a61..b1a604ed02 100644 --- a/mobile/lib/ui/viewer/gallery/device_folder_page.dart +++ b/mobile/lib/ui/viewer/gallery/device_folder_page.dart @@ -22,6 +22,7 @@ import 'package:photos/ui/components/toggle_switch_widget.dart'; import 'package:photos/ui/viewer/actions/file_selection_overlay_bar.dart'; import 'package:photos/ui/viewer/gallery/gallery.dart'; import 'package:photos/ui/viewer/gallery/gallery_app_bar_widget.dart'; +import "package:photos/ui/viewer/gallery/state/selection_state.dart"; class DeviceFolderPage extends StatelessWidget { final DeviceCollection deviceCollection; @@ -66,15 +67,18 @@ class DeviceFolderPage extends StatelessWidget { deviceCollection: deviceCollection, ), ), - body: Stack( - alignment: Alignment.bottomCenter, - children: [ - gallery, - FileSelectionOverlayBar( - GalleryType.localFolder, - _selectedFiles, - ), - ], + body: SelectionState( + selectedFiles: _selectedFiles, + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + gallery, + FileSelectionOverlayBar( + GalleryType.localFolder, + _selectedFiles, + ), + ], + ), ), ); } diff --git a/mobile/lib/ui/viewer/gallery/hidden_page.dart b/mobile/lib/ui/viewer/gallery/hidden_page.dart index f9e3525fda..1517b07460 100644 --- a/mobile/lib/ui/viewer/gallery/hidden_page.dart +++ b/mobile/lib/ui/viewer/gallery/hidden_page.dart @@ -19,6 +19,7 @@ import 'package:photos/ui/viewer/actions/file_selection_overlay_bar.dart'; import 'package:photos/ui/viewer/gallery/empty_hidden_widget.dart'; import 'package:photos/ui/viewer/gallery/gallery.dart'; import 'package:photos/ui/viewer/gallery/gallery_app_bar_widget.dart'; +import "package:photos/ui/viewer/gallery/state/selection_state.dart"; class HiddenPage extends StatefulWidget { final String tagPrefix; @@ -139,15 +140,18 @@ class _HiddenPageState extends State { _selectedFiles, ), ), - body: Stack( - alignment: Alignment.bottomCenter, - children: [ - gallery, - FileSelectionOverlayBar( - widget.overlayType, - _selectedFiles, - ), - ], + body: SelectionState( + selectedFiles: _selectedFiles, + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + gallery, + FileSelectionOverlayBar( + widget.overlayType, + _selectedFiles, + ), + ], + ), ), ); } From ffcda13a4eda80b073e3eed72e4335df7b1af737 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Mon, 17 Jun 2024 08:51:18 +0530 Subject: [PATCH 010/130] [mob][photos] Add select all feature to galleries (2) --- .../ui/viewer/gallery/large_files_page.dart | 22 ++--- mobile/lib/ui/viewer/gallery/trash_page.dart | 50 ++++++------ .../ui/viewer/gallery/uncategorized_page.dart | 22 ++--- .../ui/viewer/location/location_screen.dart | 70 ++++++++-------- mobile/lib/ui/viewer/people/cluster_page.dart | 36 +++++---- mobile/lib/ui/viewer/people/people_page.dart | 81 ++++++++++--------- 6 files changed, 155 insertions(+), 126 deletions(-) diff --git a/mobile/lib/ui/viewer/gallery/large_files_page.dart b/mobile/lib/ui/viewer/gallery/large_files_page.dart index 6b71ecba76..8668dfccae 100644 --- a/mobile/lib/ui/viewer/gallery/large_files_page.dart +++ b/mobile/lib/ui/viewer/gallery/large_files_page.dart @@ -13,6 +13,7 @@ import "package:photos/services/search_service.dart"; import 'package:photos/ui/viewer/actions/file_selection_overlay_bar.dart'; import "package:photos/ui/viewer/gallery/component/group/type.dart"; import 'package:photos/ui/viewer/gallery/gallery.dart'; +import "package:photos/ui/viewer/gallery/state/selection_state.dart"; class LargeFilesPagePage extends StatelessWidget { final String tagPrefix; @@ -84,15 +85,18 @@ class LargeFilesPagePage extends StatelessWidget { ), ), ), - body: Stack( - alignment: Alignment.bottomCenter, - children: [ - gallery, - FileSelectionOverlayBar( - overlayType, - _selectedFiles, - ), - ], + body: SelectionState( + selectedFiles: _selectedFiles, + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + gallery, + FileSelectionOverlayBar( + overlayType, + _selectedFiles, + ), + ], + ), ), ); } diff --git a/mobile/lib/ui/viewer/gallery/trash_page.dart b/mobile/lib/ui/viewer/gallery/trash_page.dart index 2f2965eea9..d247245b36 100644 --- a/mobile/lib/ui/viewer/gallery/trash_page.dart +++ b/mobile/lib/ui/viewer/gallery/trash_page.dart @@ -13,6 +13,7 @@ import 'package:photos/ui/common/bottom_shadow.dart'; import 'package:photos/ui/viewer/actions/file_selection_overlay_bar.dart'; import 'package:photos/ui/viewer/gallery/gallery.dart'; import 'package:photos/ui/viewer/gallery/gallery_app_bar_widget.dart'; +import "package:photos/ui/viewer/gallery/state/selection_state.dart"; import 'package:photos/utils/delete_file_util.dart'; class TrashPage extends StatelessWidget { @@ -65,32 +66,35 @@ class TrashPage extends StatelessWidget { _selectedFiles, ), ), - body: Stack( - alignment: Alignment.bottomCenter, - children: [ - gallery, - const BottomShadowWidget( - offsetDy: 20, - ), - AnimatedContainer( - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - height: filesAreSelected ? 0 : 80, - child: AnimatedOpacity( - duration: const Duration(milliseconds: 100), - opacity: filesAreSelected ? 0.0 : 1.0, - curve: Curves.easeIn, - child: IgnorePointer( - ignoring: filesAreSelected, - child: const SafeArea( - minimum: EdgeInsets.only(bottom: 6), - child: BottomButtonsWidget(), + body: SelectionState( + selectedFiles: _selectedFiles, + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + gallery, + const BottomShadowWidget( + offsetDy: 20, + ), + AnimatedContainer( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + height: filesAreSelected ? 0 : 80, + child: AnimatedOpacity( + duration: const Duration(milliseconds: 100), + opacity: filesAreSelected ? 0.0 : 1.0, + curve: Curves.easeIn, + child: IgnorePointer( + ignoring: filesAreSelected, + child: const SafeArea( + minimum: EdgeInsets.only(bottom: 6), + child: BottomButtonsWidget(), + ), ), ), ), - ), - FileSelectionOverlayBar(GalleryType.trash, _selectedFiles), - ], + FileSelectionOverlayBar(GalleryType.trash, _selectedFiles), + ], + ), ), ); } diff --git a/mobile/lib/ui/viewer/gallery/uncategorized_page.dart b/mobile/lib/ui/viewer/gallery/uncategorized_page.dart index 265a614a8e..7d49d2f3e5 100644 --- a/mobile/lib/ui/viewer/gallery/uncategorized_page.dart +++ b/mobile/lib/ui/viewer/gallery/uncategorized_page.dart @@ -13,6 +13,7 @@ import 'package:photos/services/ignored_files_service.dart'; import 'package:photos/ui/viewer/actions/file_selection_overlay_bar.dart'; import 'package:photos/ui/viewer/gallery/gallery.dart'; import 'package:photos/ui/viewer/gallery/gallery_app_bar_widget.dart'; +import "package:photos/ui/viewer/gallery/state/selection_state.dart"; class UnCategorizedPage extends StatelessWidget { final String tagPrefix; @@ -82,15 +83,18 @@ class UnCategorizedPage extends StatelessWidget { collection: collection, ), ), - body: Stack( - alignment: Alignment.bottomCenter, - children: [ - gallery, - FileSelectionOverlayBar( - overlayType, - _selectedFiles, - ), - ], + body: SelectionState( + selectedFiles: _selectedFiles, + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + gallery, + FileSelectionOverlayBar( + overlayType, + _selectedFiles, + ), + ], + ), ), ); } diff --git a/mobile/lib/ui/viewer/location/location_screen.dart b/mobile/lib/ui/viewer/location/location_screen.dart index 55975dd3fa..dda1d5022a 100644 --- a/mobile/lib/ui/viewer/location/location_screen.dart +++ b/mobile/lib/ui/viewer/location/location_screen.dart @@ -25,6 +25,7 @@ import "package:photos/ui/components/title_bar_title_widget.dart"; import "package:photos/ui/components/title_bar_widget.dart"; import "package:photos/ui/viewer/actions/file_selection_overlay_bar.dart"; import "package:photos/ui/viewer/gallery/gallery.dart"; +import "package:photos/ui/viewer/gallery/state/selection_state.dart"; import "package:photos/ui/viewer/location/edit_location_sheet.dart"; import "package:photos/utils/dialog_util.dart"; @@ -231,40 +232,43 @@ class _LocationGalleryWidgetState extends State { key: ValueKey("$centerPoint$selectedRadius"), builder: (context, snapshot) { if (snapshot.hasData) { - return Stack( - alignment: Alignment.bottomCenter, - children: [ - Gallery( - loadingWidget: Column( - children: [ - galleryHeaderWidget, - EnteLoadingWidget( - color: getEnteColorScheme(context).strokeMuted, - ), - ], + return SelectionState( + selectedFiles: _selectedFiles, + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + Gallery( + loadingWidget: Column( + children: [ + galleryHeaderWidget, + EnteLoadingWidget( + color: getEnteColorScheme(context).strokeMuted, + ), + ], + ), + header: galleryHeaderWidget, + asyncLoader: ( + creationStartTime, + creationEndTime, { + limit, + asc, + }) async { + return snapshot.data as FileLoadResult; + }, + reloadEvent: Bus.instance.on(), + removalEventTypes: const { + EventType.deletedFromRemote, + EventType.deletedFromEverywhere, + }, + selectedFiles: _selectedFiles, + tagPrefix: widget.tagPrefix, ), - header: galleryHeaderWidget, - asyncLoader: ( - creationStartTime, - creationEndTime, { - limit, - asc, - }) async { - return snapshot.data as FileLoadResult; - }, - reloadEvent: Bus.instance.on(), - removalEventTypes: const { - EventType.deletedFromRemote, - EventType.deletedFromEverywhere, - }, - selectedFiles: _selectedFiles, - tagPrefix: widget.tagPrefix, - ), - FileSelectionOverlayBar( - GalleryType.locationTag, - _selectedFiles, - ), - ], + FileSelectionOverlayBar( + GalleryType.locationTag, + _selectedFiles, + ), + ], + ), ); } else { return Column( diff --git a/mobile/lib/ui/viewer/people/cluster_page.dart b/mobile/lib/ui/viewer/people/cluster_page.dart index 285804f543..efca41ecab 100644 --- a/mobile/lib/ui/viewer/people/cluster_page.dart +++ b/mobile/lib/ui/viewer/people/cluster_page.dart @@ -15,6 +15,7 @@ import 'package:photos/models/selected_files.dart'; import "package:photos/services/machine_learning/face_ml/feedback/cluster_feedback.dart"; import 'package:photos/ui/viewer/actions/file_selection_overlay_bar.dart'; import 'package:photos/ui/viewer/gallery/gallery.dart'; +import "package:photos/ui/viewer/gallery/state/selection_state.dart"; import "package:photos/ui/viewer/people/add_person_action_sheet.dart"; import "package:photos/ui/viewer/people/cluster_app_bar.dart"; import "package:photos/ui/viewer/people/people_banner.dart"; @@ -57,7 +58,8 @@ class _ClusterPageState extends State { late final StreamSubscription _filesUpdatedEvent; late final StreamSubscription _peopleChangedEvent; - bool get showNamingBanner => (!userDismissedNamingBanner && widget.showNamingBanner); + bool get showNamingBanner => + (!userDismissedNamingBanner && widget.showNamingBanner); bool userDismissedNamingBanner = false; @@ -66,7 +68,8 @@ class _ClusterPageState extends State { super.initState(); ClusterFeedbackService.setLastViewedClusterID(widget.clusterID); files = widget.searchResult; - _filesUpdatedEvent = Bus.instance.on().listen((event) { + _filesUpdatedEvent = + Bus.instance.on().listen((event) { if (event.type == EventType.deletedFromDevice || event.type == EventType.deletedFromEverywhere || event.type == EventType.deletedFromRemote || @@ -111,7 +114,8 @@ class _ClusterPageState extends State { final result = files .where( (file) => - file.creationTime! >= creationStartTime && file.creationTime! <= creationEndTime, + file.creationTime! >= creationStartTime && + file.creationTime! <= creationEndTime, ) .toList(); return Future.value( @@ -148,16 +152,19 @@ class _ClusterPageState extends State { body: Column( children: [ Expanded( - child: Stack( - alignment: Alignment.bottomCenter, - children: [ - gallery, - FileSelectionOverlayBar( - ClusterPage.overlayType, - _selectedFiles, - clusterID: widget.clusterID, - ), - ], + child: SelectionState( + selectedFiles: _selectedFiles, + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + gallery, + FileSelectionOverlayBar( + ClusterPage.overlayType, + _selectedFiles, + clusterID: widget.clusterID, + ), + ], + ), ), ), showNamingBanner @@ -185,7 +192,8 @@ class _ClusterPageState extends State { context, clusterID: widget.clusterID, ); - if (result != null && result is (PersonEntity, EnteFile)) { + if (result != null && + result is (PersonEntity, EnteFile)) { Navigator.pop(context); // ignore: unawaited_futures routeToPage(context, PeoplePage(person: result.$1)); diff --git a/mobile/lib/ui/viewer/people/people_page.dart b/mobile/lib/ui/viewer/people/people_page.dart index 8b399ced0d..f857943ea3 100644 --- a/mobile/lib/ui/viewer/people/people_page.dart +++ b/mobile/lib/ui/viewer/people/people_page.dart @@ -17,6 +17,7 @@ import "package:photos/services/machine_learning/face_ml/feedback/cluster_feedba import "package:photos/services/search_service.dart"; import 'package:photos/ui/viewer/actions/file_selection_overlay_bar.dart'; import 'package:photos/ui/viewer/gallery/gallery.dart'; +import "package:photos/ui/viewer/gallery/state/selection_state.dart"; import "package:photos/ui/viewer/people/people_app_bar.dart"; import "package:photos/ui/viewer/people/people_banner.dart"; import "package:photos/ui/viewer/people/person_cluster_suggestion.dart"; @@ -127,44 +128,48 @@ class _PeoplePageState extends State { return Column( children: [ Expanded( - child: Stack( - alignment: Alignment.bottomCenter, - children: [ - Gallery( - asyncLoader: ( - creationStartTime, - creationEndTime, { - limit, - asc, - }) async { - final result = await loadPersonFiles(); - return Future.value( - FileLoadResult( - result, - false, - ), - ); - }, - reloadEvent: Bus.instance.on(), - forceReloadEvents: [ - Bus.instance.on(), - ], - removalEventTypes: const { - EventType.deletedFromRemote, - EventType.deletedFromEverywhere, - EventType.hide, - }, - tagPrefix: widget.tagPrefix + widget.tagPrefix, - selectedFiles: _selectedFiles, - initialFiles: - personFiles.isNotEmpty ? [personFiles.first] : [], - ), - FileSelectionOverlayBar( - PeoplePage.overlayType, - _selectedFiles, - person: widget.person, - ), - ], + child: SelectionState( + selectedFiles: _selectedFiles, + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + Gallery( + asyncLoader: ( + creationStartTime, + creationEndTime, { + limit, + asc, + }) async { + final result = await loadPersonFiles(); + return Future.value( + FileLoadResult( + result, + false, + ), + ); + }, + reloadEvent: + Bus.instance.on(), + forceReloadEvents: [ + Bus.instance.on(), + ], + removalEventTypes: const { + EventType.deletedFromRemote, + EventType.deletedFromEverywhere, + EventType.hide, + }, + tagPrefix: widget.tagPrefix + widget.tagPrefix, + selectedFiles: _selectedFiles, + initialFiles: + personFiles.isNotEmpty ? [personFiles.first] : [], + ), + FileSelectionOverlayBar( + PeoplePage.overlayType, + _selectedFiles, + person: widget.person, + ), + ], + ), ), ), showSuggestionBanner From 062b3f7176aec19dc117bc6ef4fadcd582fca2ab Mon Sep 17 00:00:00 2001 From: Prateek Sunal Date: Tue, 18 Jun 2024 00:58:42 +0530 Subject: [PATCH 011/130] feat(auth/linux): add pacman build --- auth/linux/packaging/pacman/make_config.yaml | 54 ++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 auth/linux/packaging/pacman/make_config.yaml diff --git a/auth/linux/packaging/pacman/make_config.yaml b/auth/linux/packaging/pacman/make_config.yaml new file mode 100644 index 0000000000..00de15e1e9 --- /dev/null +++ b/auth/linux/packaging/pacman/make_config.yaml @@ -0,0 +1,54 @@ +display_name: Auth +package_name: auth +maintainer: + name: Ente.io Developers + email: human@ente.io +licenses: + - GPLv3 +icon: assets/icons/auth-icon.png +installed_size: 36000 + +dependencies: + - c-ares + - ffmpeg + - gtk3 + - http-parser + - libevent + - libvpx + - libxslt + - libxss + - minizip + - nss + - re2 + - snappy + - libnotify + - libappindicator-gtk3 + +keywords: + - Authentication + - 2FA + +generic_name: Ente Authentication + +categories: + - Utility + +supported_mime_type: + - x-scheme-handler/enteauth + +postinstall_scripts: | + gtk-update-icon-cache -q -t -f usr/share/icons/hicolor + update-desktop-database -q + if [ ! -e /usr/lib/libsodium.so.23 ]; then + ln -s /usr/lib/libsodium.so /usr/lib/libsodium.so.23 + fi + +postuninstall_scripts: | + post_install + +postremove_scripts: | + gtk-update-icon-cache -q -t -f usr/share/icons/hicolor + update-desktop-database -q + if [ -e /usr/lib/libsodium.so.23 ]; then + rm /usr/lib/libsodium.so.23 + fi \ No newline at end of file From 077d509c231b2d37f718fbf4b099388c95b2f39f Mon Sep 17 00:00:00 2001 From: Prateek Sunal Date: Tue, 18 Jun 2024 00:58:58 +0530 Subject: [PATCH 012/130] feat(workflow/auth): update source of flutter_distributor --- .github/workflows/auth-release.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/auth-release.yml b/.github/workflows/auth-release.yml index fb9781e2db..c7ae0bb0a7 100644 --- a/.github/workflows/auth-release.yml +++ b/.github/workflows/auth-release.yml @@ -157,8 +157,10 @@ jobs: - name: Build desktop app run: | flutter config --enable-linux-desktop - dart pub global activate flutter_distributor + # dart pub global activate flutter_distributor + dart pub global activate --source git https://github.com/prateekmedia/flutter_distributor --git-ref pacman --git-path packages/flutter_distributor flutter_distributor package --platform=linux --targets=rpm --skip-clean + flutter_distributor package --platform=linux --targets=pacman --skip-clean flutter_distributor package --platform=linux --targets=appimage --skip-clean mv dist/**/*-*-linux.rpm artifacts/ente-${{ github.ref_name }}-x86_64.rpm mv dist/**/*-*-linux.AppImage artifacts/ente-${{ github.ref_name }}-x86_64.AppImage From d7a1bf3fcc9b3b5223b8d9f0c8b205d4ac347f4a Mon Sep 17 00:00:00 2001 From: ashilkn Date: Wed, 19 Jun 2024 12:21:52 +0530 Subject: [PATCH 013/130] [mob][photos] Add select all feature to galleries (3) --- .../search/result/search_result_page.dart | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/mobile/lib/ui/viewer/search/result/search_result_page.dart b/mobile/lib/ui/viewer/search/result/search_result_page.dart index 8687afe01c..218f069860 100644 --- a/mobile/lib/ui/viewer/search/result/search_result_page.dart +++ b/mobile/lib/ui/viewer/search/result/search_result_page.dart @@ -12,6 +12,7 @@ import 'package:photos/models/selected_files.dart'; import 'package:photos/ui/viewer/actions/file_selection_overlay_bar.dart'; import 'package:photos/ui/viewer/gallery/gallery.dart'; import 'package:photos/ui/viewer/gallery/gallery_app_bar_widget.dart'; +import "package:photos/ui/viewer/gallery/state/selection_state.dart"; class SearchResultPage extends StatefulWidget { final SearchResult searchResult; @@ -99,15 +100,18 @@ class _SearchResultPageState extends State { _selectedFiles, ), ), - body: Stack( - alignment: Alignment.bottomCenter, - children: [ - gallery, - FileSelectionOverlayBar( - SearchResultPage.overlayType, - _selectedFiles, - ), - ], + body: SelectionState( + selectedFiles: _selectedFiles, + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + gallery, + FileSelectionOverlayBar( + SearchResultPage.overlayType, + _selectedFiles, + ), + ], + ), ), ); } From 46b9aa259ca6638eebd5bf9faa3babc9f61825fd Mon Sep 17 00:00:00 2001 From: ashilkn Date: Wed, 19 Jun 2024 12:28:47 +0530 Subject: [PATCH 014/130] [mob][photos] Keep select all feature behind feature flag --- .../actions/file_selection_overlay_bar.dart | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/mobile/lib/ui/viewer/actions/file_selection_overlay_bar.dart b/mobile/lib/ui/viewer/actions/file_selection_overlay_bar.dart index 0cfb39c51f..735459bf82 100644 --- a/mobile/lib/ui/viewer/actions/file_selection_overlay_bar.dart +++ b/mobile/lib/ui/viewer/actions/file_selection_overlay_bar.dart @@ -3,6 +3,7 @@ import "package:photos/face/model/person.dart"; import 'package:photos/models/collection/collection.dart'; import 'package:photos/models/gallery_type.dart'; import 'package:photos/models/selected_files.dart'; +import "package:photos/service_locator.dart"; import "package:photos/theme/effects.dart"; import "package:photos/theme/ente_theme.dart"; import 'package:photos/ui/components/bottom_action_bar/bottom_action_bar_widget.dart'; @@ -68,11 +69,15 @@ class _FileSelectionOverlayBarState extends State { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.end, children: [ - Padding( - padding: const EdgeInsets.only(right: 4), - child: SelectAllButton(backgroundColor: widget.backgroundColor), - ), - const SizedBox(height: 8), + flagService.internalUser + ? Padding( + padding: const EdgeInsets.only(right: 4), + child: SelectAllButton( + backgroundColor: widget.backgroundColor, + ), + ) + : const SizedBox.shrink(), + if (flagService.internalUser) const SizedBox(height: 8), Container( decoration: BoxDecoration( boxShadow: shadowFloatFaintLight, From 5d6175050818530c185ba55778fb48c78b6a0d4c Mon Sep 17 00:00:00 2001 From: ashilkn Date: Wed, 19 Jun 2024 12:56:00 +0530 Subject: [PATCH 015/130] [mob][photos] Remove comments, clean up, use better variable name, add comments --- mobile/lib/ui/home/home_gallery_widget.dart | 2 -- .../viewer/actions/file_selection_overlay_bar.dart | 14 +++++++------- mobile/lib/ui/viewer/gallery/gallery.dart | 2 +- .../ui/viewer/gallery/state/selection_state.dart | 2 ++ 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/mobile/lib/ui/home/home_gallery_widget.dart b/mobile/lib/ui/home/home_gallery_widget.dart index 366559cb8c..7de93e71e7 100644 --- a/mobile/lib/ui/home/home_gallery_widget.dart +++ b/mobile/lib/ui/home/home_gallery_widget.dart @@ -87,7 +87,6 @@ class HomeGalleryWidget extends StatelessWidget { ); return SelectionState( selectedFiles: selectedFiles, - // ignore: prefer_const_literals_to_create_immutables child: Stack( alignment: Alignment.bottomCenter, children: [ @@ -96,6 +95,5 @@ class HomeGalleryWidget extends StatelessWidget { ], ), ); - // return gallery; } } diff --git a/mobile/lib/ui/viewer/actions/file_selection_overlay_bar.dart b/mobile/lib/ui/viewer/actions/file_selection_overlay_bar.dart index 735459bf82..608286809c 100644 --- a/mobile/lib/ui/viewer/actions/file_selection_overlay_bar.dart +++ b/mobile/lib/ui/viewer/actions/file_selection_overlay_bar.dart @@ -118,7 +118,7 @@ class SelectAllButton extends StatefulWidget { } class _SelectAllButtonState extends State { - bool _selectAll = false; + bool _allSelected = false; @override Widget build(BuildContext context) { final selectionState = SelectionState.of(context); @@ -130,13 +130,13 @@ class _SelectAllButtonState extends State { return GestureDetector( onTap: () { setState(() { - if (_selectAll) { + if (_allSelected) { selectionState.selectedFiles.clearAll(); } else { selectionState.selectedFiles .selectAll(selectionState.allGalleryFiles!.toSet()); } - _selectAll = !_selectAll; + _allSelected = !_allSelected; }); }, child: Padding( @@ -168,15 +168,15 @@ class _SelectAllButtonState extends State { builder: (context, _) { if (selectionState.selectedFiles.files.length == selectionState.allGalleryFiles?.length) { - _selectAll = true; + _allSelected = true; } else { - _selectAll = false; + _allSelected = false; } return Icon( - _selectAll + _allSelected ? Icons.check_circle : Icons.check_circle_outline, - color: _selectAll ? null : colorScheme.strokeMuted, + color: _allSelected ? null : colorScheme.strokeMuted, size: 18, ); }, diff --git a/mobile/lib/ui/viewer/gallery/gallery.dart b/mobile/lib/ui/viewer/gallery/gallery.dart index 7af50e38f5..3155617060 100644 --- a/mobile/lib/ui/viewer/gallery/gallery.dart +++ b/mobile/lib/ui/viewer/gallery/gallery.dart @@ -175,7 +175,7 @@ class GalleryState extends State { if (result.hasMore) { final result = await _loadFiles(); _setFilesAndReload(result.files); - } else {} + } }); } diff --git a/mobile/lib/ui/viewer/gallery/state/selection_state.dart b/mobile/lib/ui/viewer/gallery/state/selection_state.dart index 762b14b94a..ef97c884b1 100644 --- a/mobile/lib/ui/viewer/gallery/state/selection_state.dart +++ b/mobile/lib/ui/viewer/gallery/state/selection_state.dart @@ -3,6 +3,8 @@ import "package:logging/logging.dart"; import "package:photos/models/file/file.dart"; import "package:photos/models/selected_files.dart"; +///This is an inherited widget that needs to be wrapped around Gallery and +///FileSelectionOverlayBar to make select all work. // ignore: must_be_immutable class SelectionState extends InheritedWidget { final SelectedFiles selectedFiles; From 53cb0135765afb57afcb5427fc78532328617a0a Mon Sep 17 00:00:00 2001 From: laurenspriem Date: Wed, 19 Jun 2024 13:50:12 +0530 Subject: [PATCH 016/130] [mob][photos] Better logging --- .../machine_learning/face_ml/feedback/cluster_feedback.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart b/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart index 6ca2c33dc9..a7e5556c6c 100644 --- a/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart +++ b/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart @@ -139,6 +139,7 @@ class ClusterFeedbackService { PersonEntity p, ) async { try { + _logger.info('removeFilesFromPerson called'); // Get the relevant faces to be removed final faceIDs = await FaceMLDataDB.instance .getFaceIDsForPerson(p.remoteID) @@ -161,7 +162,7 @@ class ClusterFeedbackService { distanceThreshold: 0.20, ); if (clusterResult.isEmpty) { - _logger.warning('No clusters found or something went wrong'); + _logger.severe('No clusters found or something went wrong'); return; } final newFaceIdToClusterID = clusterResult.newFaceIdToCluster; From 8c0d21ea25c5b867e2cbe0064c0a4902b7433de9 Mon Sep 17 00:00:00 2001 From: laurenspriem Date: Wed, 19 Jun 2024 13:56:37 +0530 Subject: [PATCH 017/130] [mob][photos] More logging --- .../machine_learning/face_ml/feedback/cluster_feedback.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart b/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart index a7e5556c6c..0e1b198837 100644 --- a/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart +++ b/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart @@ -192,6 +192,7 @@ class ClusterFeedbackService { List files, int clusterID, ) async { + _logger.info('removeFilesFromCluster called'); try { // Get the relevant faces to be removed final faceIDs = await FaceMLDataDB.instance @@ -215,6 +216,7 @@ class ClusterFeedbackService { distanceThreshold: 0.20, ); if (clusterResult.isEmpty) { + _logger.severe('No clusters found or something went wrong'); return; } final newFaceIdToClusterID = clusterResult.newFaceIdToCluster; From 541ed4fdba82a6e47165cb4e19071a824d4b7816 Mon Sep 17 00:00:00 2001 From: laurenspriem Date: Wed, 19 Jun 2024 14:07:20 +0530 Subject: [PATCH 018/130] [mob][photos] Logging --- .../face_ml/feedback/cluster_feedback.dart | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart b/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart index 0e1b198837..e8e091b41e 100644 --- a/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart +++ b/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart @@ -151,6 +151,13 @@ class ClusterFeedbackService { final embeddings = await FaceMLDataDB.instance.getFaceEmbeddingMapForFaces(faceIDs); + if (faceIDs.isEmpty || embeddings.isEmpty) { + _logger.severe( + 'No faces or embeddings found for person ${p.remoteID} that match the given files', + ); + return; + } + final fileIDToCreationTime = await FilesDB.instance.getFileIDToCreationTime(); @@ -205,6 +212,13 @@ class ClusterFeedbackService { final embeddings = await FaceMLDataDB.instance.getFaceEmbeddingMapForFaces(faceIDs); + if (faceIDs.isEmpty || embeddings.isEmpty) { + _logger.severe( + 'No faces or embeddings found for cluster $clusterID that match the given files', + ); + return; + } + final fileIDToCreationTime = await FilesDB.instance.getFileIDToCreationTime(); From dfcd254668920f43eef77d170121581c54e188d4 Mon Sep 17 00:00:00 2001 From: laurenspriem Date: Wed, 19 Jun 2024 14:10:04 +0530 Subject: [PATCH 019/130] [mob][photos] Don't falsely fire PeopleChangedEvent --- .../face_ml/feedback/cluster_feedback.dart | 7 ------- .../ui/viewer/actions/file_selection_actions_widget.dart | 2 -- 2 files changed, 9 deletions(-) diff --git a/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart b/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart index e8e091b41e..1d2888dabd 100644 --- a/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart +++ b/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart @@ -247,13 +247,6 @@ class ClusterFeedbackService { source: "$clusterID", ), ); - // Bus.instance.fire( - // LocalPhotosUpdatedEvent( - // files, - // type: EventType.peopleClusterChanged, - // source: "$clusterID", - // ), - // ); return; } catch (e, s) { _logger.severe("Error in removeFilesFromCluster", e, s); diff --git a/mobile/lib/ui/viewer/actions/file_selection_actions_widget.dart b/mobile/lib/ui/viewer/actions/file_selection_actions_widget.dart index c760d88f3e..7fc020cf5d 100644 --- a/mobile/lib/ui/viewer/actions/file_selection_actions_widget.dart +++ b/mobile/lib/ui/viewer/actions/file_selection_actions_widget.dart @@ -695,7 +695,6 @@ class _FileSelectionActionsWidgetState widget.person!, ); } - Bus.instance.fire(PeopleChangedEvent()); } widget.selectedFiles.clearAll(); if (mounted) { @@ -738,7 +737,6 @@ class _FileSelectionActionsWidgetState widget.clusterID!, ); } - Bus.instance.fire(PeopleChangedEvent()); } widget.selectedFiles.clearAll(); if (mounted) { From 0588c32b526c282ece7bd6a6fb60c01eed506579 Mon Sep 17 00:00:00 2001 From: laurenspriem Date: Wed, 19 Jun 2024 14:47:40 +0530 Subject: [PATCH 020/130] [mob][photos] Don't let syncing trigger another sync --- mobile/lib/events/people_changed_event.dart | 1 + .../lib/services/machine_learning/face_ml/face_ml_service.dart | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/mobile/lib/events/people_changed_event.dart b/mobile/lib/events/people_changed_event.dart index 51f4eaeefe..47e00b2462 100644 --- a/mobile/lib/events/people_changed_event.dart +++ b/mobile/lib/events/people_changed_event.dart @@ -19,4 +19,5 @@ class PeopleChangedEvent extends Event { enum PeopleEventType { defaultType, removedFilesFromCluster, + syncDone, } \ No newline at end of file diff --git a/mobile/lib/services/machine_learning/face_ml/face_ml_service.dart b/mobile/lib/services/machine_learning/face_ml/face_ml_service.dart index e4620f6676..b50f14ebcc 100644 --- a/mobile/lib/services/machine_learning/face_ml/face_ml_service.dart +++ b/mobile/lib/services/machine_learning/face_ml/face_ml_service.dart @@ -194,6 +194,7 @@ class FaceMlService { void _listenOnPeopleChangedSync() { Bus.instance.on().listen((event) { + if (event.type == PeopleEventType.syncDone) return; _shouldSyncPeople = true; }); } @@ -367,7 +368,7 @@ class FaceMlService { _isSyncing = true; if (forceSync) { await PersonService.instance.reconcileClusters(); - Bus.instance.fire(PeopleChangedEvent()); + Bus.instance.fire(PeopleChangedEvent(type: PeopleEventType.syncDone)); _shouldSyncPeople = false; } _isSyncing = false; From 38e1208591dddda37f16af7ea81b27d2cd33d390 Mon Sep 17 00:00:00 2001 From: laurenspriem Date: Wed, 19 Jun 2024 14:48:13 +0530 Subject: [PATCH 021/130] [mob][photos] Logging --- .../machine_learning/face_ml/feedback/cluster_feedback.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart b/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart index 1d2888dabd..7af371e243 100644 --- a/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart +++ b/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart @@ -188,6 +188,7 @@ class ClusterFeedbackService { .bulkCaptureNotPersonFeedback(notClusterIdToPersonId); Bus.instance.fire(PeopleChangedEvent()); + _logger.info('removeFilesFromPerson done'); return; } catch (e, s) { _logger.severe("Error in removeFilesFromPerson", e, s); @@ -247,6 +248,7 @@ class ClusterFeedbackService { source: "$clusterID", ), ); + _logger.info('removeFilesFromCluster done'); return; } catch (e, s) { _logger.severe("Error in removeFilesFromCluster", e, s); From 3321d584553d0fc5978451263d612642fcaa5b18 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Wed, 19 Jun 2024 16:05:04 +0530 Subject: [PATCH 022/130] reduce error noise --- mobile/lib/main.dart | 2 +- mobile/lib/utils/file_uploader.dart | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mobile/lib/main.dart b/mobile/lib/main.dart index f180f2bfff..587713952d 100644 --- a/mobile/lib/main.dart +++ b/mobile/lib/main.dart @@ -330,7 +330,7 @@ Future _sync(String caller) async { await SyncService.instance.sync(); } catch (e, s) { if (!isHandledSyncError(e)) { - _logger.severe("Sync error", e, s); + _logger.warning("Sync error", e, s); } } } diff --git a/mobile/lib/utils/file_uploader.dart b/mobile/lib/utils/file_uploader.dart index 9b1b37fb4d..a5750f6b7a 100644 --- a/mobile/lib/utils/file_uploader.dart +++ b/mobile/lib/utils/file_uploader.dart @@ -286,7 +286,7 @@ class FileUploader { kFileUploadTimeout, onTimeout: () { final message = "Upload timed out for file " + file.toString(); - _logger.severe(message); + _logger.warning(message); throw TimeoutException(message); }, ); @@ -1184,7 +1184,7 @@ class FileUploader { clearQueue(error); throw error; } else { - _logger.severe("Could not fetch upload URLs", e, s); + _logger.warning("Could not fetch upload URLs", e, s); } } rethrow; From 15f4e5cae8592311d5fefe038785c4cc3376b9b3 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Wed, 19 Jun 2024 16:14:55 +0530 Subject: [PATCH 023/130] Reduce error noise --- mobile/lib/services/remote_sync_service.dart | 4 +++- mobile/lib/services/user_service.dart | 12 ++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/mobile/lib/services/remote_sync_service.dart b/mobile/lib/services/remote_sync_service.dart index eab8478a6c..993588f8ba 100644 --- a/mobile/lib/services/remote_sync_service.dart +++ b/mobile/lib/services/remote_sync_service.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:io'; import 'dart:math'; +import "package:dio/dio.dart"; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:logging/logging.dart'; @@ -180,7 +181,8 @@ class RemoteSyncService { e is WiFiUnavailableError || e is StorageLimitExceededError || e is SyncStopRequestedError || - e is NoMediaLocationAccessError) { + e is NoMediaLocationAccessError || + e is DioError) { _logger.warning("Error executing remote sync", e, s); rethrow; } else { diff --git a/mobile/lib/services/user_service.dart b/mobile/lib/services/user_service.dart index a6cb656206..4c20429b08 100644 --- a/mobile/lib/services/user_service.dart +++ b/mobile/lib/services/user_service.dart @@ -276,7 +276,7 @@ class UserService { throw Exception("delete action failed"); } } catch (e) { - _logger.severe(e); + _logger.warning(e); await showGenericErrorDialog(context: context, error: e); return null; } @@ -304,7 +304,7 @@ class UserService { throw Exception("delete action failed"); } } catch (e) { - _logger.severe(e); + _logger.warning(e); rethrow; } } @@ -329,7 +329,7 @@ class UserService { } rethrow; } catch (e, s) { - _logger.severe("unexpected error", e, s); + _logger.warning("unexpected error", e, s); rethrow; } } @@ -366,7 +366,7 @@ class UserService { Bus.instance.fire(AccountConfiguredEvent()); } } catch (e) { - _logger.severe(e); + _logger.warning(e); await dialog.hide(); await showGenericErrorDialog(context: context, error: e); } @@ -448,7 +448,7 @@ class UserService { } } catch (e) { await dialog.hide(); - _logger.severe(e); + _logger.warning(e); // ignore: unawaited_futures showErrorDialog( context, @@ -519,7 +519,7 @@ class UserService { } } catch (e) { await dialog.hide(); - _logger.severe(e); + _logger.warning(e); // ignore: unawaited_futures showErrorDialog( context, From 15baf7d0fb346dbe7f87107541eb5338aacd6e60 Mon Sep 17 00:00:00 2001 From: laurenspriem Date: Wed, 19 Jun 2024 16:15:41 +0530 Subject: [PATCH 024/130] [mob][photos] Update remote on remove feedback --- .../face_ml/feedback/cluster_feedback.dart | 4 +++ .../face_ml/person/person_service.dart | 32 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart b/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart index 7af371e243..8bce9fe0e6 100644 --- a/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart +++ b/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart @@ -187,6 +187,10 @@ class ClusterFeedbackService { await FaceMLDataDB.instance .bulkCaptureNotPersonFeedback(notClusterIdToPersonId); + // Update remote so new sync does not undo this change + await PersonService.instance + .removeFilesFromPerson(person: p, faceIDs: faceIDs.toSet()); + Bus.instance.fire(PeopleChangedEvent()); _logger.info('removeFilesFromPerson done'); return; diff --git a/mobile/lib/services/machine_learning/face_ml/person/person_service.dart b/mobile/lib/services/machine_learning/face_ml/person/person_service.dart index 682deaff0c..34f8c6b34c 100644 --- a/mobile/lib/services/machine_learning/face_ml/person/person_service.dart +++ b/mobile/lib/services/machine_learning/face_ml/person/person_service.dart @@ -201,6 +201,38 @@ class PersonService { personData.logStats(); } + Future removeFilesFromPerson({ + required PersonEntity person, + required Set faceIDs, + }) async { + final personData = person.data; + final List emptiedClusters = []; + for (final cluster in personData.assigned!) { + cluster.faces.removeWhere((faceID) => faceIDs.contains(faceID)); + if (cluster.faces.isEmpty) { + emptiedClusters.add(cluster.id); + } + } + + // Safety check to make sure we haven't created an empty cluster now, if so delete it + for (final emptyClusterID in emptiedClusters) { + personData.assigned! + .removeWhere((element) => element.id != emptyClusterID); + await faceMLDataDB.removeClusterToPerson( + personID: person.remoteID, + clusterID: emptyClusterID, + ); + } + + + await entityService.addOrUpdate( + EntityType.person, + json.encode(personData.toJson()), + id: person.remoteID, + ); + personData.logStats(); + } + Future deletePerson(String personID, {bool onlyMapping = false}) async { if (onlyMapping) { final PersonEntity? entity = await getPerson(personID); From 1a9e1d7d770a4894031c5bc87c5378d3f5fe17ac Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Wed, 19 Jun 2024 16:20:37 +0530 Subject: [PATCH 025/130] Reduce error noise --- mobile/lib/services/collections_service.dart | 2 +- mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart | 4 ++-- mobile/lib/utils/file_uploader_util.dart | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mobile/lib/services/collections_service.dart b/mobile/lib/services/collections_service.dart index 3631d00535..3510597bb1 100644 --- a/mobile/lib/services/collections_service.dart +++ b/mobile/lib/services/collections_service.dart @@ -729,7 +729,7 @@ class CollectionsService { collection.setName(newName); sync().ignore(); } catch (e, s) { - _logger.severe("failed to rename collection", e, s); + _logger.warning("failed to rename collection", e, s); rethrow; } } diff --git a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart index c62d1f7389..5edd31984a 100644 --- a/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart +++ b/mobile/lib/ui/viewer/gallery/gallery_app_bar_widget.dart @@ -179,7 +179,7 @@ class _GalleryAppBarWidgetState extends State { setState(() {}); } } catch (e, s) { - _logger.severe("Failed to rename album", e, s); + _logger.warning("Failed to rename album", e, s); rethrow; } }, @@ -610,7 +610,7 @@ class _GalleryAppBarWidgetState extends State { await dialog.hide(); Navigator.of(context).pop(); } catch (e, s) { - _logger.severe("failed to trash collection", e, s); + _logger.warning("failed to trash collection", e, s); await dialog.hide(); await showGenericErrorDialog(context: context, error: e); } diff --git a/mobile/lib/utils/file_uploader_util.dart b/mobile/lib/utils/file_uploader_util.dart index 1455ee0e9b..833c55b30d 100644 --- a/mobile/lib/utils/file_uploader_util.dart +++ b/mobile/lib/utils/file_uploader_util.dart @@ -346,7 +346,7 @@ Future _getMediaUploadDataFromAppCache(EnteFile file) async { width: dimensions?['width'], ); } catch (e, s) { - _logger.severe("failed to generate thumbnail", e, s); + _logger.warning("failed to generate thumbnail", e, s); throw InvalidFileError( "thumbnail failed for appCache fileType: ${file.fileType.toString()}", InvalidReason.thumbnailMissing, From deace2bccd958c2fc94d38d48cd76fa75b9be449 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Wed, 19 Jun 2024 16:28:38 +0530 Subject: [PATCH 026/130] Skip reporting dio error to sentry --- mobile/lib/core/error-reporting/super_logging.dart | 4 ++++ mobile/lib/services/user_service.dart | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/mobile/lib/core/error-reporting/super_logging.dart b/mobile/lib/core/error-reporting/super_logging.dart index cc9c3122c3..d1603ab20e 100644 --- a/mobile/lib/core/error-reporting/super_logging.dart +++ b/mobile/lib/core/error-reporting/super_logging.dart @@ -5,6 +5,7 @@ import 'dart:collection'; import 'dart:core'; import 'dart:io'; +import "package:dio/dio.dart"; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:http/http.dart' as http; @@ -230,6 +231,9 @@ class SuperLogging { StackTrace? stack, ) async { try { + if (error is DioError) { + return; + } await Sentry.captureException( error, stackTrace: stack, diff --git a/mobile/lib/services/user_service.dart b/mobile/lib/services/user_service.dart index 4c20429b08..57ec665628 100644 --- a/mobile/lib/services/user_service.dart +++ b/mobile/lib/services/user_service.dart @@ -205,7 +205,7 @@ class UserService { Future getActiveSessions() async { try { - final response = await _enteDio.get("/users/sessions"); + final response = await _enteDio.get("/users/sessionsx"); return Sessions.fromMap(response.data); } on DioError catch (e) { _logger.info(e); From 9bf8e16978ac61dc9b1ade9574711be26dfb21cd Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Wed, 19 Jun 2024 17:10:40 +0530 Subject: [PATCH 027/130] [mob] Undo redundant changes --- mobile/lib/services/remote_sync_service.dart | 4 +--- mobile/lib/services/user_service.dart | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/mobile/lib/services/remote_sync_service.dart b/mobile/lib/services/remote_sync_service.dart index 993588f8ba..eab8478a6c 100644 --- a/mobile/lib/services/remote_sync_service.dart +++ b/mobile/lib/services/remote_sync_service.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'dart:io'; import 'dart:math'; -import "package:dio/dio.dart"; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'package:logging/logging.dart'; @@ -181,8 +180,7 @@ class RemoteSyncService { e is WiFiUnavailableError || e is StorageLimitExceededError || e is SyncStopRequestedError || - e is NoMediaLocationAccessError || - e is DioError) { + e is NoMediaLocationAccessError) { _logger.warning("Error executing remote sync", e, s); rethrow; } else { diff --git a/mobile/lib/services/user_service.dart b/mobile/lib/services/user_service.dart index 57ec665628..4c20429b08 100644 --- a/mobile/lib/services/user_service.dart +++ b/mobile/lib/services/user_service.dart @@ -205,7 +205,7 @@ class UserService { Future getActiveSessions() async { try { - final response = await _enteDio.get("/users/sessionsx"); + final response = await _enteDio.get("/users/sessions"); return Sessions.fromMap(response.data); } on DioError catch (e) { _logger.info(e); From b9556a91a0b99538736c67dd4817f43a522c27c2 Mon Sep 17 00:00:00 2001 From: Fokke de Boer Date: Thu, 20 Jun 2024 00:45:59 +0200 Subject: [PATCH 028/130] Fix keyboard pop up when autofocus is turned on --- auth/lib/ui/home_page.dart | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/auth/lib/ui/home_page.dart b/auth/lib/ui/home_page.dart index 81fd7ed586..a2f97f07a6 100644 --- a/auth/lib/ui/home_page.dart +++ b/auth/lib/ui/home_page.dart @@ -56,7 +56,7 @@ class _HomePageState extends State { final scaffoldKey = GlobalKey(); final TextEditingController _textController = TextEditingController(); - final FocusNode searchInputFocusNode = FocusNode(); + final bool _autoFocusSearch = PreferenceService.instance.shouldAutoFocusOnSearchBar(); bool _showSearchBox = false; String _searchText = ""; List? _allCodes; @@ -87,18 +87,7 @@ class _HomePageState extends State { _iconsChangedEvent = Bus.instance.on().listen((event) { setState(() {}); }); - _showSearchBox = PreferenceService.instance.shouldAutoFocusOnSearchBar(); - if (_showSearchBox) { - WidgetsBinding.instance.addPostFrameCallback( - (_) { - // https://github.com/flutter/flutter/issues/20706#issuecomment-646328652 - FocusScope.of(context).unfocus(); - Timer(const Duration(milliseconds: 1), () { - FocusScope.of(context).requestFocus(searchInputFocusNode); - }); - }, - ); - } + _showSearchBox = _autoFocusSearch; } void _loadCodes() { @@ -240,8 +229,7 @@ class _HomePageState extends State { : TextField( autocorrect: false, enableSuggestions: false, - focusNode: searchInputFocusNode, - autofocus: _searchText.isEmpty, + autofocus: _autoFocusSearch, controller: _textController, onChanged: (val) { _searchText = val; From 492d4f0fd7e23ca5450f0cf1dc785dfeb50d4fdd Mon Sep 17 00:00:00 2001 From: Victor Benincasa Date: Thu, 20 Jun 2024 02:13:38 -0300 Subject: [PATCH 029/130] Maximize icon compatibility --- auth/assets/custom-icons/icons/hivelocity.svg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auth/assets/custom-icons/icons/hivelocity.svg b/auth/assets/custom-icons/icons/hivelocity.svg index b84451a4c2..81aa936583 100644 --- a/auth/assets/custom-icons/icons/hivelocity.svg +++ b/auth/assets/custom-icons/icons/hivelocity.svg @@ -1 +1 @@ - \ No newline at end of file + From 22e09de07a9ea8bbdd6fde2fa90f215a80a388e1 Mon Sep 17 00:00:00 2001 From: Victor Benincasa Date: Thu, 20 Jun 2024 02:34:12 -0300 Subject: [PATCH 030/130] Reduce filesize --- auth/assets/custom-icons/icons/kucoin.svg | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/auth/assets/custom-icons/icons/kucoin.svg b/auth/assets/custom-icons/icons/kucoin.svg index 076ec5c4f5..1b67b54717 100644 --- a/auth/assets/custom-icons/icons/kucoin.svg +++ b/auth/assets/custom-icons/icons/kucoin.svg @@ -1,3 +1 @@ - - - + From c3e94a405ff52c82b024ebbe6743ce91f3f1f121 Mon Sep 17 00:00:00 2001 From: Victor Benincasa Date: Thu, 20 Jun 2024 02:38:31 -0300 Subject: [PATCH 031/130] Optimize and reduce filesize --- auth/assets/custom-icons/icons/standardnotes.svg | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/auth/assets/custom-icons/icons/standardnotes.svg b/auth/assets/custom-icons/icons/standardnotes.svg index 89fb3ed578..026cf96c1f 100644 --- a/auth/assets/custom-icons/icons/standardnotes.svg +++ b/auth/assets/custom-icons/icons/standardnotes.svg @@ -1,3 +1 @@ - - - + From b798dacaf2fd625d96f02657b1438435d8ef02ed Mon Sep 17 00:00:00 2001 From: Victor Benincasa Date: Thu, 20 Jun 2024 02:56:30 -0300 Subject: [PATCH 032/130] Optimize and reduce filesize --- auth/assets/custom-icons/icons/plutus.svg | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/auth/assets/custom-icons/icons/plutus.svg b/auth/assets/custom-icons/icons/plutus.svg index 6a88dc417a..4a5d52e88c 100644 --- a/auth/assets/custom-icons/icons/plutus.svg +++ b/auth/assets/custom-icons/icons/plutus.svg @@ -1,3 +1 @@ - - - + From 933ab3ac2c0b213e0db227d962d3c6ecbd2be121 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Thu, 20 Jun 2024 15:48:43 +0530 Subject: [PATCH 033/130] Upgrade onnx dependency --- mobile/ios/Podfile.lock | 52 ++++++++++++++++++++--------------------- mobile/pubspec.lock | 4 ++-- mobile/pubspec.yaml | 2 +- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/mobile/ios/Podfile.lock b/mobile/ios/Podfile.lock index 8463f904c4..fd11b0a5b1 100644 --- a/mobile/ios/Podfile.lock +++ b/mobile/ios/Podfile.lock @@ -35,9 +35,9 @@ PODS: - FirebaseCoreInternal (~> 10.0) - GoogleUtilities/Environment (~> 7.12) - GoogleUtilities/Logger (~> 7.12) - - FirebaseCoreInternal (10.24.0): + - FirebaseCoreInternal (10.28.0): - "GoogleUtilities/NSData+zlib (~> 7.8)" - - FirebaseInstallations (10.24.0): + - FirebaseInstallations (10.28.0): - FirebaseCore (~> 10.0) - GoogleUtilities/Environment (~> 7.8) - GoogleUtilities/UserDefaults (~> 7.8) @@ -83,29 +83,29 @@ PODS: - GoogleUtilities/Environment (~> 7.7) - nanopb (< 2.30911.0, >= 2.30908.0) - PromisesObjC (< 3.0, >= 1.2) - - GoogleUtilities/AppDelegateSwizzler (7.13.0): + - GoogleUtilities/AppDelegateSwizzler (7.13.3): - GoogleUtilities/Environment - GoogleUtilities/Logger - GoogleUtilities/Network - GoogleUtilities/Privacy - - GoogleUtilities/Environment (7.13.0): + - GoogleUtilities/Environment (7.13.3): - GoogleUtilities/Privacy - PromisesObjC (< 3.0, >= 1.2) - - GoogleUtilities/Logger (7.13.0): + - GoogleUtilities/Logger (7.13.3): - GoogleUtilities/Environment - GoogleUtilities/Privacy - - GoogleUtilities/Network (7.13.0): + - GoogleUtilities/Network (7.13.3): - GoogleUtilities/Logger - "GoogleUtilities/NSData+zlib" - GoogleUtilities/Privacy - GoogleUtilities/Reachability - - "GoogleUtilities/NSData+zlib (7.13.0)": + - "GoogleUtilities/NSData+zlib (7.13.3)": - GoogleUtilities/Privacy - - GoogleUtilities/Privacy (7.13.0) - - GoogleUtilities/Reachability (7.13.0): + - GoogleUtilities/Privacy (7.13.3) + - GoogleUtilities/Reachability (7.13.3): - GoogleUtilities/Logger - GoogleUtilities/Privacy - - GoogleUtilities/UserDefaults (7.13.0): + - GoogleUtilities/UserDefaults (7.13.3): - GoogleUtilities/Logger - GoogleUtilities/Privacy - home_widget (0.0.1): @@ -157,12 +157,12 @@ PODS: - nanopb/encode (2.30910.0) - onnxruntime (0.0.1): - Flutter - - onnxruntime-objc (= 1.15.1) - - onnxruntime-c (1.15.1) - - onnxruntime-objc (1.15.1): - - onnxruntime-objc/Core (= 1.15.1) - - onnxruntime-objc/Core (1.15.1): - - onnxruntime-c (= 1.15.1) + - onnxruntime-objc (= 1.18.0) + - onnxruntime-c (1.18.0) + - onnxruntime-objc (1.18.0): + - onnxruntime-objc/Core (= 1.18.0) + - onnxruntime-objc/Core (1.18.0): + - onnxruntime-c (= 1.18.0) - open_mail_app (0.0.1): - Flutter - OrderedSet (5.0.0) @@ -181,9 +181,9 @@ PODS: - Flutter - screen_brightness_ios (0.1.0): - Flutter - - SDWebImage (5.19.1): - - SDWebImage/Core (= 5.19.1) - - SDWebImage/Core (5.19.1) + - SDWebImage (5.19.2): + - SDWebImage/Core (= 5.19.2) + - SDWebImage/Core (5.19.2) - SDWebImageWebPCoder (0.14.6): - libwebp (~> 1.0) - SDWebImage/Core (~> 5.17) @@ -427,8 +427,8 @@ SPEC CHECKSUMS: firebase_core: 66b99b4fb4e5d7cc4e88d4c195fe986681f3466a firebase_messaging: 0eb0425d28b4f4af147cdd4adcaf7c0100df28ed FirebaseCore: 11dc8a16dfb7c5e3c3f45ba0e191a33ac4f50894 - FirebaseCoreInternal: bcb5acffd4ea05e12a783ecf835f2210ce3dc6af - FirebaseInstallations: 8f581fca6478a50705d2bd2abd66d306e0f5736e + FirebaseCoreInternal: 58d07f1362fddeb0feb6a857d1d1d1c5e558e698 + FirebaseInstallations: 60c1d3bc1beef809fd1ad1189a8057a040c59f2e FirebaseMessaging: 4d52717dd820707cc4eadec5eb981b4832ec8d5d fk_user_agent: 1f47ec39291e8372b1d692b50084b0d54103c545 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 @@ -441,7 +441,7 @@ SPEC CHECKSUMS: flutter_sodium: c84426b4de738514b5b66cfdeb8a06634e72fe0b fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265 GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a - GoogleUtilities: d053d902a8edaa9904e1bd00c37535385b8ed152 + GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15 home_widget: 0434835a4c9a75704264feff6be17ea40e0f0d57 image_editor_common: d6f6644ae4a6de80481e89fe6d0a8c49e30b4b43 image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1 @@ -458,9 +458,9 @@ SPEC CHECKSUMS: motionphoto: d4a432b8c8f22fb3ad966258597c0103c9c5ff16 move_to_background: 39a5b79b26d577b0372cbe8a8c55e7aa9fcd3a2d nanopb: 438bc412db1928dac798aa6fd75726007be04262 - onnxruntime: e9346181d75b8dea8733bdae512a22c298962e00 - onnxruntime-c: ebdcfd8650bcbd10121c125262f99dea681b92a3 - onnxruntime-objc: ae7acec7a3d03eaf072d340afed7a35635c1c2a6 + onnxruntime: e7c2ae44385191eaad5ae64c935a72debaddc997 + onnxruntime-c: a909204639a1f035f575127ac406f781ac797c9c + onnxruntime-objc: b6fab0f1787aa6f7190c2013f03037df4718bd8b open_mail_app: 794172f6a22cd16319d3ddaf45e945b2f74952b0 OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 @@ -470,7 +470,7 @@ SPEC CHECKSUMS: PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 receive_sharing_intent: 6837b01768e567fe8562182397bf43d63d8c6437 screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625 - SDWebImage: 40b0b4053e36c660a764958bff99eed16610acbb + SDWebImage: dfe95b2466a9823cf9f0c6d01217c06550d7b29a SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380 Sentry: ebc12276bd17613a114ab359074096b6b3725203 sentry_flutter: 88ebea3f595b0bc16acc5bedacafe6d60c12dcd5 diff --git a/mobile/pubspec.lock b/mobile/pubspec.lock index 169dc0c245..9beaf2f2f3 100644 --- a/mobile/pubspec.lock +++ b/mobile/pubspec.lock @@ -1610,8 +1610,8 @@ packages: dependency: "direct main" description: path: "." - ref: "5f26aef45ed9f5e563c26f90c1e21b3339ed906d" - resolved-ref: "5f26aef45ed9f5e563c26f90c1e21b3339ed906d" + ref: ente_onnxruntime + resolved-ref: fb9393e36013790938b5bc995a4dca15fed3c944 url: "https://github.com/ente-io/onnxruntime.git" source: git version: "1.1.0" diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 999204d3a2..8b714db820 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -127,7 +127,7 @@ dependencies: onnxruntime: git: url: https://github.com/ente-io/onnxruntime.git - ref: 5f26aef45ed9f5e563c26f90c1e21b3339ed906d + ref: ente_onnxruntime open_mail_app: ^0.4.5 package_info_plus: ^4.1.0 page_transition: ^2.0.2 From 398ce9d44543e6cd06a02a58284b717585f110f3 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 20 Jun 2024 21:04:18 +0530 Subject: [PATCH 034/130] [desktop] Add a memory usage high water mark during uploads --- .../src/services/upload/uploadManager.ts | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/web/apps/photos/src/services/upload/uploadManager.ts b/web/apps/photos/src/services/upload/uploadManager.ts index eadb4ffc85..150b85ba69 100644 --- a/web/apps/photos/src/services/upload/uploadManager.ts +++ b/web/apps/photos/src/services/upload/uploadManager.ts @@ -520,6 +520,11 @@ class UploadManager { while (this.itemsToBeUploaded.length > 0) { this.abortIfCancelled(); + if (shouldWaitForMemoryPressureToEase()) { + await wait(2000); + continue; + } + const clusteredItem = this.itemsToBeUploaded.pop(); const { localID, collectionID } = clusteredItem; const collection = this.collections.get(collectionID); @@ -999,3 +1004,45 @@ const uploadItemSize = async (uploadItem: UploadItem): Promise => { return ensureElectron().pathOrZipItemSize(uploadItem); return uploadItem.file.size; }; + +/** + * [Note: Memory pressure when uploading video files] + * + * Some users have reported that their app runs out of memory when the app tries + * to upload multiple large videos simultaneously. For example, 4 parallel + * uploads of 4 700 MB videos. + * + * I am unable to reproduce this: tested on macOS, with videos up to 3.8 G + * uploaded in parallel. The memory usage remains constant (as expected, + * hovering around 2 G), since we don't pull the entire videos in memory and + * instead do a streaming disk read + encryption + upload. + * + * The JavaScript heap for the renderer process (when we're running in the + * context of our desktop app) is limited to 4 GB. See + * https://www.electronjs.org/blog/v8-memory-cage. + * + * Perhaps there is some distinct memory usage pattern on some systems that + * causes this limit to be reached. + * + * So as a safety check, this function returns true whenever we exceed some + * memory usage high water mark. If so, then the uploader should wait for the + * memory usage to come down before initiating a new upload. + */ +const shouldWaitForMemoryPressureToEase = () => { + if (!globalThis.electron) return false; + // performance.memory is deprecated in general as a Web standard, and is + // also not available in the DOM types provided by TypeScript. However, it + // is the method recommended by the Electron team (see the link about the V8 + // memory cage). The embedded Chromium supports it fine though, we just need + // to goad TypeScript to accept the type. + const heapSizeInBytes = (performance as any).memory.usedJSHeapSize; + const heapSizeInGB = heapSizeInBytes / (1024 * 1024 * 1024); + // 4 GB is the hard limit. Let us keep a lot of margin since uploads get + // triggered in parallel so if we're unlucky they all might get trigger when + // the memory usage is relatively low. + if (heapSizeInGB < 2.5) return false; + log.info( + `Memory usage (${heapSizeInGB} GB) exceeds the high water mark, pausing new uploads`, + ); + return true; +}; From 921ddac630d4028d6f6cb793123cfd29370c1204 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 20 Jun 2024 21:52:32 +0530 Subject: [PATCH 035/130] [desktop] Modify the upload workaround to not get in the way of Stripe payments --- desktop/src/main.ts | 47 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/desktop/src/main.ts b/desktop/src/main.ts index 50f69759da..bc5839370a 100644 --- a/desktop/src/main.ts +++ b/desktop/src/main.ts @@ -455,20 +455,51 @@ const allowExternalLinks = (webContents: WebContents) => /** * Allow uploading to arbitrary S3 buckets. * - * The files in the desktop app are served over the ente:// protocol. During - * testing or self-hosting, we might be using a S3 bucket that does not allow - * whitelisting a custom URI scheme. To avoid requiring the bucket to set an - * "Access-Control-Allow-Origin: *" or do a echo-back of `Origin`, we add a - * workaround here instead, intercepting the ACAO header and allowing `*`. + * The files in the desktop app are served over the ente:// protocol. When that + * is returned as the CORS allowed origin, "Access-Control-Allow-Origin: + * ente://app", CORS requests fail. + * + * Further, during testing or self-hosting, file uploads involve a redirection + * (This doesn't affect our production systems since we upload via a worker, + * See: [Note: Passing credentials for self-hosted file fetches]). + * + * In some cases, we might be using a S3 bucket that does not allow whitelisting + * a custom URI scheme. Echoing back the value of `Origin` (even if the bucket + * would allow us to) would also not work, since the browser sends `null` as the + * `Origin` for the redirected request (this is as per the CORS spec). So the + * only way in such cases would be to require the bucket to set an + * "Access-Control-Allow-Origin: *". + * + * To avoid these issues, we intercepting the ACAO header and set it to `*`. + * + * However, that cause problems with requests that use credentials since "*" is + * not a valid value in such cases. One such example is the HCaptcha requests + * made by Stripe when we initiate a payment within the desktop app: + * + * > Access to XMLHttpRequest at 'https://api2.hcaptcha.com/getcaptcha/xxx' from + * > origin 'https://newassets.hcaptcha.com' has been blocked by CORS policy: + * > The value of the 'Access-Control-Allow-Origin' header in the response must + * > not be the wildcard '*' when the request's credentials mode is 'include'. + * > The credentials mode of requests initiated by the XMLHttpRequest is + * > controlled by the withCredentials attribute. + * + * So we only do this workaround if there was either no ACAO specified in the + * response, or if the ACAO was "ente://app". */ const allowAllCORSOrigins = (webContents: WebContents) => webContents.session.webRequest.onHeadersReceived( ({ responseHeaders }, callback) => { const headers: NonNullable = {}; - for (const [key, value] of Object.entries(responseHeaders ?? {})) - if (key.toLowerCase() != "access-control-allow-origin") - headers[key] = value; + headers["Access-Control-Allow-Origin"] = ["*"]; + for (const [key, value] of Object.entries(responseHeaders ?? {})) + if (key.toLowerCase() == "access-control-allow-origin") { + headers["Access-Control-Allow-Origin"] = + value[0] == rendererURL ? ["*"] : value; + } else { + headers[key] = value; + } + callback({ responseHeaders: headers }); }, ); From 1e83ef0c06f57733ae610cce37ab3bcec3f95395 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 21 Jun 2024 09:09:47 +0530 Subject: [PATCH 036/130] Add contact support button --- auth/lib/utils/dialog_util.dart | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/auth/lib/utils/dialog_util.dart b/auth/lib/utils/dialog_util.dart index d24608b783..26697f0f7e 100644 --- a/auth/lib/utils/dialog_util.dart +++ b/auth/lib/utils/dialog_util.dart @@ -13,6 +13,7 @@ import 'package:ente_auth/ui/components/components_constants.dart'; import 'package:ente_auth/ui/components/dialog_widget.dart'; import 'package:ente_auth/ui/components/models/button_result.dart'; import 'package:ente_auth/ui/components/models/button_type.dart'; +import 'package:ente_auth/utils/email_util.dart'; import 'package:flutter/material.dart'; typedef DialogBuilder = DialogWidget Function(BuildContext context); @@ -80,11 +81,25 @@ Future showGenericErrorDialog({ icon: Icons.error_outline_outlined, body: context.l10n.itLooksLikeSomethingWentWrongPleaseRetryAfterSome, isDismissible: isDismissible, - buttons: const [ + buttons: [ + ButtonWidget( + buttonType: ButtonType.primary, + labelText: context.l10n.ok, + buttonAction: ButtonAction.first, + isInAlert: true, + ), ButtonWidget( buttonType: ButtonType.secondary, - labelText: "OK", - isInAlert: true, + labelText: context.l10n.contactSupport, + buttonAction: ButtonAction.second, + onTap: () async { + await sendLogs( + context, + context.l10n.contactSupport, + "support@ente.io", + postShare: () {}, + ); + }, ), ], ); From 14e0afd867617d0a6cbb23e6b6ec97e377752bd5 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 21 Jun 2024 11:21:32 +0530 Subject: [PATCH 037/130] [workers] Use yarn workspaces The duplicate disk usage of individual node_modules is starting to get prohibitive. --- infra/workers/README.md | 19 ++++++++++--------- infra/workers/cast-albums/package.json | 9 ++------- infra/workers/files/package.json | 9 ++------- .../github-discord-notifier/package.json | 9 ++------- infra/workers/health-check/package.json | 9 ++------- infra/workers/package.json | 13 +++++++++++++ infra/workers/public-albums/package.json | 9 ++------- infra/workers/sentry-reporter/package.json | 11 +++-------- infra/workers/tail/package.json | 9 ++------- infra/workers/thumbnails/package.json | 9 ++------- infra/workers/uploader/package.json | 9 ++------- 11 files changed, 42 insertions(+), 73 deletions(-) create mode 100644 infra/workers/package.json diff --git a/infra/workers/README.md b/infra/workers/README.md index 5ddd8fc393..e70ff09df0 100644 --- a/infra/workers/README.md +++ b/infra/workers/README.md @@ -3,24 +3,25 @@ Source code for our [Cloudflare Workers](https://developers.cloudflare.com/workers/). -Each worker is a self contained directory with its each `package.json`. +Workers are organized as Yarn workspaces sharing a common `package.json` and +base `tsconfig`. They can however be deployed individually. ## Deploying -- Switch to a worker directory, e.g. `cd github-discord-notifier`. +Install dependencies with `yarn`. -- Install dependencies (if needed) with `yarn` +> If you have previously deployed, then you will have an old `yarn.lock`. In +> this case it is safe to delete and recreate using `rm yarn.lock && yarn`. - > If you have previously deployed, then you will have an old `yarn.lock`. In - > this case it is safe to delete and recreate using `rm yarn.lock && yarn`. +Then, to deploy an individual worker -- Login into wrangler (if needed) using `yarn wrangler login` +- Login into wrangler (if needed) using `yarn workspace health-check wrangler login` -- Deploy! `yarn wrangler deploy` +- Deploy! `yarn workspace health-check wrangler deploy` Wrangler is the CLI provided by Cloudflare to manage workers. Apart from -deploying, it also allows us to stream logs from running workers by using -`yarn wrangler tail`. +deploying, it also allows us to stream logs from running workers by using `yarn +workspace wrangler tail`. ## Creating a new worker diff --git a/infra/workers/cast-albums/package.json b/infra/workers/cast-albums/package.json index 63995e4592..8cfb5ffc8b 100644 --- a/infra/workers/cast-albums/package.json +++ b/infra/workers/cast-albums/package.json @@ -1,10 +1,5 @@ { "name": "cast-albums", - "private": true, - "devDependencies": { - "@cloudflare/workers-types": "^4.20240614.0", - "typescript": "^5", - "wrangler": "^3" - }, - "packageManager": "yarn@1.22.22" + "version": "0.0.0", + "private": true } diff --git a/infra/workers/files/package.json b/infra/workers/files/package.json index 4ddcb5f853..11decac65e 100644 --- a/infra/workers/files/package.json +++ b/infra/workers/files/package.json @@ -1,10 +1,5 @@ { "name": "files", - "private": true, - "devDependencies": { - "@cloudflare/workers-types": "^4.20240614.0", - "typescript": "^5", - "wrangler": "^3" - }, - "packageManager": "yarn@1.22.22" + "version": "0.0.0", + "private": true } diff --git a/infra/workers/github-discord-notifier/package.json b/infra/workers/github-discord-notifier/package.json index 133c633fbb..84476af12c 100644 --- a/infra/workers/github-discord-notifier/package.json +++ b/infra/workers/github-discord-notifier/package.json @@ -1,10 +1,5 @@ { "name": "github-discord-notifier", - "private": true, - "devDependencies": { - "@cloudflare/workers-types": "^4.20240614.0", - "typescript": "^5", - "wrangler": "^3" - }, - "packageManager": "yarn@1.22.22" + "version": "0.0.0", + "private": true } diff --git a/infra/workers/health-check/package.json b/infra/workers/health-check/package.json index 73802a826b..2f3126e72c 100644 --- a/infra/workers/health-check/package.json +++ b/infra/workers/health-check/package.json @@ -1,10 +1,5 @@ { "name": "health-check", - "private": true, - "devDependencies": { - "@cloudflare/workers-types": "^4.20240614.0", - "typescript": "^5", - "wrangler": "^3" - }, - "packageManager": "yarn@1.22.22" + "version": "0.0.0", + "private": true } diff --git a/infra/workers/package.json b/infra/workers/package.json new file mode 100644 index 0000000000..f754c98504 --- /dev/null +++ b/infra/workers/package.json @@ -0,0 +1,13 @@ +{ + "name": "workers", + "private": true, + "devDependencies": { + "@cloudflare/workers-types": "^4.20240614.0", + "typescript": "^5", + "wrangler": "^3" + }, + "workspaces": [ + "*" + ], + "packageManager": "yarn@1.22.22" +} diff --git a/infra/workers/public-albums/package.json b/infra/workers/public-albums/package.json index 946f42689f..f3e54c8d64 100644 --- a/infra/workers/public-albums/package.json +++ b/infra/workers/public-albums/package.json @@ -1,10 +1,5 @@ { "name": "public-albums", - "private": true, - "devDependencies": { - "@cloudflare/workers-types": "^4.20240614.0", - "typescript": "^5", - "wrangler": "^3" - }, - "packageManager": "yarn@1.22.22" + "version": "0.0.0", + "private": true } diff --git a/infra/workers/sentry-reporter/package.json b/infra/workers/sentry-reporter/package.json index 4ddcb5f853..be9f3ca908 100644 --- a/infra/workers/sentry-reporter/package.json +++ b/infra/workers/sentry-reporter/package.json @@ -1,10 +1,5 @@ { - "name": "files", - "private": true, - "devDependencies": { - "@cloudflare/workers-types": "^4.20240614.0", - "typescript": "^5", - "wrangler": "^3" - }, - "packageManager": "yarn@1.22.22" + "name": "sentry-reporter", + "version": "0.0.0", + "private": true } diff --git a/infra/workers/tail/package.json b/infra/workers/tail/package.json index 2ec6898e4f..733bd57b0e 100644 --- a/infra/workers/tail/package.json +++ b/infra/workers/tail/package.json @@ -1,10 +1,5 @@ { "name": "tail", - "private": true, - "devDependencies": { - "@cloudflare/workers-types": "^4.20240614.0", - "typescript": "^5", - "wrangler": "^3" - }, - "packageManager": "yarn@1.22.22" + "version": "0.0.0", + "private": true } diff --git a/infra/workers/thumbnails/package.json b/infra/workers/thumbnails/package.json index e5107655bc..73e529c402 100644 --- a/infra/workers/thumbnails/package.json +++ b/infra/workers/thumbnails/package.json @@ -1,10 +1,5 @@ { "name": "thumbnails", - "private": true, - "devDependencies": { - "@cloudflare/workers-types": "^4.20240614.0", - "typescript": "^5", - "wrangler": "^3" - }, - "packageManager": "yarn@1.22.22" + "version": "0.0.0", + "private": true } diff --git a/infra/workers/uploader/package.json b/infra/workers/uploader/package.json index e22b4eb1fc..d7d955eea4 100644 --- a/infra/workers/uploader/package.json +++ b/infra/workers/uploader/package.json @@ -1,10 +1,5 @@ { "name": "uploader", - "private": true, - "devDependencies": { - "@cloudflare/workers-types": "^4.20240614.0", - "typescript": "^5", - "wrangler": "^3" - }, - "packageManager": "yarn@1.22.22" + "version": "0.0.0", + "private": true } From 37296806d6dc9b4c620820cf4846b77f5a725c07 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 21 Jun 2024 11:35:24 +0530 Subject: [PATCH 038/130] Format --- infra/workers/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/infra/workers/README.md b/infra/workers/README.md index e70ff09df0..dd4da132d5 100644 --- a/infra/workers/README.md +++ b/infra/workers/README.md @@ -15,13 +15,14 @@ Install dependencies with `yarn`. Then, to deploy an individual worker -- Login into wrangler (if needed) using `yarn workspace health-check wrangler login` +- Login into wrangler (if needed) using + `yarn workspace health-check wrangler login` - Deploy! `yarn workspace health-check wrangler deploy` Wrangler is the CLI provided by Cloudflare to manage workers. Apart from -deploying, it also allows us to stream logs from running workers by using `yarn -workspace wrangler tail`. +deploying, it also allows us to stream logs from running workers by using +`yarn workspace wrangler tail`. ## Creating a new worker From 29539d9db2cb61e5f45875d852270af1a5430191 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 21 Jun 2024 12:08:26 +0530 Subject: [PATCH 039/130] [auth] Show contact support button on error dialogs --- auth/lib/l10n/arb/app_en.arb | 2 + auth/lib/services/passkey_service.dart | 2 +- auth/lib/services/user_service.dart | 13 ++-- auth/lib/ui/account/password_entry_page.dart | 15 ++++- auth/lib/ui/account/verify_recovery_page.dart | 10 ++- .../ui/components/buttons/button_widget.dart | 5 +- auth/lib/ui/home_page.dart | 8 ++- auth/lib/ui/passkey_page.dart | 6 +- .../ui/settings/account_section_widget.dart | 5 +- .../ui/settings/security_section_widget.dart | 5 +- auth/lib/utils/dialog_util.dart | 64 ++++++++++++++++++- 11 files changed, 115 insertions(+), 20 deletions(-) diff --git a/auth/lib/l10n/arb/app_en.arb b/auth/lib/l10n/arb/app_en.arb index 58ad079a0b..0c2f9a0de4 100644 --- a/auth/lib/l10n/arb/app_en.arb +++ b/auth/lib/l10n/arb/app_en.arb @@ -263,6 +263,8 @@ "exportLogs": "Export logs", "enterYourRecoveryKey": "Enter your recovery key", "tempErrorContactSupportIfPersists": "It looks like something went wrong. Please retry after some time. If the error persists, please contact our support team.", + "networkHostLookUpErr": "Unable to connect to Ente, please check your network settings and contact support if the error persists.", + "networkConnectionRefusedErr": "Unable to connect to Ente, please retry after sometime. If the error persists, please contact support.", "itLooksLikeSomethingWentWrongPleaseRetryAfterSome": "It looks like something went wrong. Please retry after some time. If the error persists, please contact our support team.", "about": "About", "weAreOpenSource": "We are open source!", diff --git a/auth/lib/services/passkey_service.dart b/auth/lib/services/passkey_service.dart index 2be2bb1a02..4d7e9badd5 100644 --- a/auth/lib/services/passkey_service.dart +++ b/auth/lib/services/passkey_service.dart @@ -49,7 +49,7 @@ class PasskeyService { ); } catch (e) { Logger('PasskeyService').severe("failed to open passkey page", e); - showGenericErrorDialog(context: context).ignore(); + showGenericErrorDialog(context: context, error: e).ignore(); } } } diff --git a/auth/lib/services/user_service.dart b/auth/lib/services/user_service.dart index 182dee2d25..ca88796f15 100644 --- a/auth/lib/services/user_service.dart +++ b/auth/lib/services/user_service.dart @@ -101,7 +101,7 @@ class UserService { ); return; } - unawaited(showGenericErrorDialog(context: context)); + unawaited(showGenericErrorDialog(context: context, error: null)); } on DioException catch (e) { await dialog.hide(); _logger.info(e); @@ -114,12 +114,12 @@ class UserService { ), ); } else { - unawaited(showGenericErrorDialog(context: context)); + unawaited(showGenericErrorDialog(context: context, error: e)); } } catch (e) { await dialog.hide(); _logger.severe(e); - unawaited(showGenericErrorDialog(context: context)); + unawaited(showGenericErrorDialog(context: context, error: e)); } } @@ -227,7 +227,7 @@ class UserService { //to close and only then to show the error dialog. Future.delayed( const Duration(milliseconds: 150), - () => showGenericErrorDialog(context: context), + () => showGenericErrorDialog(context: context, error: e), ); rethrow; } @@ -248,7 +248,10 @@ class UserService { } } catch (e) { _logger.severe(e); - await showGenericErrorDialog(context: context); + await showGenericErrorDialog( + context: context, + error: e, + ); return null; } } diff --git a/auth/lib/ui/account/password_entry_page.dart b/auth/lib/ui/account/password_entry_page.dart index 9b1ce61812..b7e3e14694 100644 --- a/auth/lib/ui/account/password_entry_page.dart +++ b/auth/lib/ui/account/password_entry_page.dart @@ -412,7 +412,10 @@ class _PasswordEntryPageState extends State { _logger.severe(e, s); await dialog.hide(); // ignore: unawaited_futures - showGenericErrorDialog(context: context); + showGenericErrorDialog( + context: context, + error: e, + ); } } @@ -472,7 +475,10 @@ class _PasswordEntryPageState extends State { _logger.severe(e, s); await dialog.hide(); // ignore: unawaited_futures - showGenericErrorDialog(context: context); + showGenericErrorDialog( + context: context, + error: e, + ); } } @@ -500,7 +506,10 @@ class _PasswordEntryPageState extends State { ); } else { // ignore: unawaited_futures - showGenericErrorDialog(context: context); + showGenericErrorDialog( + context: context, + error: e, + ); } } } diff --git a/auth/lib/ui/account/verify_recovery_page.dart b/auth/lib/ui/account/verify_recovery_page.dart index 03ed81fdf0..bb4ebb068a 100644 --- a/auth/lib/ui/account/verify_recovery_page.dart +++ b/auth/lib/ui/account/verify_recovery_page.dart @@ -47,7 +47,10 @@ class _VerifyRecoveryPageState extends State { "Please check your internet connection and try again.", ); } else { - await showGenericErrorDialog(context: context); + await showGenericErrorDialog( + context: context, + error: e, + ); } return; } @@ -107,7 +110,10 @@ class _VerifyRecoveryPageState extends State { ); } catch (e) { // ignore: unawaited_futures - showGenericErrorDialog(context: context); + showGenericErrorDialog( + context: context, + error: e, + ); return; } } diff --git a/auth/lib/ui/components/buttons/button_widget.dart b/auth/lib/ui/components/buttons/button_widget.dart index 1932cc02bd..9e589cd7a0 100644 --- a/auth/lib/ui/components/buttons/button_widget.dart +++ b/auth/lib/ui/components/buttons/button_widget.dart @@ -485,7 +485,10 @@ class _ButtonChildWidgetState extends State { } else if (exception != null) { //This is to show the execution was unsuccessful if the dialog is manually //closed before the execution completes. - showGenericErrorDialog(context: context); + showGenericErrorDialog( + context: context, + error: exception, + ); } } diff --git a/auth/lib/ui/home_page.dart b/auth/lib/ui/home_page.dart index a2f97f07a6..ee78399944 100644 --- a/auth/lib/ui/home_page.dart +++ b/auth/lib/ui/home_page.dart @@ -56,7 +56,8 @@ class _HomePageState extends State { final scaffoldKey = GlobalKey(); final TextEditingController _textController = TextEditingController(); - final bool _autoFocusSearch = PreferenceService.instance.shouldAutoFocusOnSearchBar(); + final bool _autoFocusSearch = + PreferenceService.instance.shouldAutoFocusOnSearchBar(); bool _showSearchBox = false; String _searchText = ""; List? _allCodes; @@ -448,7 +449,10 @@ class _HomePageState extends State { CodeStore.instance.addCode(newCode); _focusNewCode(newCode); } catch (e, s) { - showGenericErrorDialog(context: context); + showGenericErrorDialog( + context: context, + error: e, + ); _logger.severe("error while handling deeplink", e, s); } } diff --git a/auth/lib/ui/passkey_page.dart b/auth/lib/ui/passkey_page.dart index eab0b72f99..4181c474e9 100644 --- a/auth/lib/ui/passkey_page.dart +++ b/auth/lib/ui/passkey_page.dart @@ -69,7 +69,7 @@ class _PasskeyPageState extends State { return; } catch (e, s) { _logger.severe("failed to check status", e, s); - showGenericErrorDialog(context: context).ignore(); + showGenericErrorDialog(context: context, error: e).ignore(); return; } await UserService.instance.onPassKeyVerified(context, response); @@ -111,7 +111,7 @@ class _PasskeyPageState extends State { } } catch (e, s) { _logger.severe('passKey: failed to handle deeplink', e, s); - showGenericErrorDialog(context: context).ignore(); + showGenericErrorDialog(context: context, error: e).ignore(); } } @@ -169,7 +169,7 @@ class _PasskeyPageState extends State { await checkStatus(); } catch (e) { debugPrint('failed to check status %e'); - showGenericErrorDialog(context: context).ignore(); + showGenericErrorDialog(context: context, error: e).ignore(); } }, shouldSurfaceExecutionStates: true, diff --git a/auth/lib/ui/settings/account_section_widget.dart b/auth/lib/ui/settings/account_section_widget.dart index d51b2dd873..47e639c2e7 100644 --- a/auth/lib/ui/settings/account_section_widget.dart +++ b/auth/lib/ui/settings/account_section_widget.dart @@ -111,7 +111,10 @@ class AccountSectionWidget extends StatelessWidget { CryptoUtil.bin2hex(Configuration.instance.getRecoveryKey()); } catch (e) { // ignore: unawaited_futures - showGenericErrorDialog(context: context); + showGenericErrorDialog( + context: context, + error: e, + ); return; } // ignore: unawaited_futures diff --git a/auth/lib/ui/settings/security_section_widget.dart b/auth/lib/ui/settings/security_section_widget.dart index 49672b0368..678a4ddfa1 100644 --- a/auth/lib/ui/settings/security_section_widget.dart +++ b/auth/lib/ui/settings/security_section_widget.dart @@ -182,7 +182,10 @@ class _SecuritySectionWidgetState extends State { PasskeyService.instance.openPasskeyPage(buildContext).ignore(); } catch (e, s) { _logger.severe("failed to open passkey page", e, s); - await showGenericErrorDialog(context: context); + await showGenericErrorDialog( + context: context, + error: e, + ); } } diff --git a/auth/lib/utils/dialog_util.dart b/auth/lib/utils/dialog_util.dart index 26697f0f7e..24636bf889 100644 --- a/auth/lib/utils/dialog_util.dart +++ b/auth/lib/utils/dialog_util.dart @@ -14,6 +14,7 @@ import 'package:ente_auth/ui/components/dialog_widget.dart'; import 'package:ente_auth/ui/components/models/button_result.dart'; import 'package:ente_auth/ui/components/models/button_type.dart'; import 'package:ente_auth/utils/email_util.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; typedef DialogBuilder = DialogWidget Function(BuildContext context); @@ -70,16 +71,77 @@ Future showErrorDialogForException({ ); } +String parseErrorForUI( + BuildContext context, + String genericError, { + Object? error, + bool surfaceError = kDebugMode, +}) { + try { + if (error == null) { + return genericError; + } + if (error is DioException) { + final DioException dioError = error; + if (dioError.type == DioExceptionType.unknown) { + if (dioError.error.toString().contains('Failed host lookup')) { + return context.l10n.networkHostLookUpErr; + } else if (dioError.error.toString().contains('SocketException')) { + return context.l10n.networkConnectionRefusedErr; + } + } + } + // return generic error if the user is not internal and the error is not in debug mode + if (!kDebugMode) { + return genericError; + } + String errorInfo = ""; + if (error is DioException) { + final DioException dioError = error; + if (dioError.type == DioExceptionType.badResponse) { + if (dioError.response?.data["code"] != null) { + errorInfo = "Reason: " + dioError.response!.data["code"]; + } else { + errorInfo = "Reason: " + dioError.response!.data.toString(); + } + } else if (dioError.type == DioExceptionType.unknown) { + errorInfo = "Reason: " + dioError.error.toString(); + } else { + errorInfo = "Reason: " + dioError.type.toString(); + } + } else { + if (kDebugMode) { + errorInfo = error.toString(); + } else { + errorInfo = error.toString().split('Source stack')[0]; + } + } + if (errorInfo.isNotEmpty) { + return "$genericError\n\n$errorInfo"; + } + return genericError; + } catch (e) { + return genericError; + } +} + ///Will return null if dismissed by tapping outside Future showGenericErrorDialog({ required BuildContext context, bool isDismissible = true, + required Object? error, }) async { + final errorBody = parseErrorForUI( + context, + context.l10n.itLooksLikeSomethingWentWrongPleaseRetryAfterSome, + error: error, + ); + return showDialogWidget( context: context, title: context.l10n.error, icon: Icons.error_outline_outlined, - body: context.l10n.itLooksLikeSomethingWentWrongPleaseRetryAfterSome, + body: errorBody, isDismissible: isDismissible, buttons: [ ButtonWidget( From da9262da7966d008956778ce0645b895b500e57a Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 21 Jun 2024 12:10:35 +0530 Subject: [PATCH 040/130] [auth] Bump version v3.0.14 --- auth/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auth/pubspec.yaml b/auth/pubspec.yaml index 26cfb90996..ba335b6960 100644 --- a/auth/pubspec.yaml +++ b/auth/pubspec.yaml @@ -1,6 +1,6 @@ name: ente_auth description: ente two-factor authenticator -version: 3.0.13+313 +version: 3.0.14+314 publish_to: none environment: From 662210b168ee04f40c6dc019cdb8747771e5532e Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 21 Jun 2024 13:35:50 +0530 Subject: [PATCH 041/130] [auth] Potential fix for invalid handshake error --- auth/lib/core/network.dart | 15 +++++++ auth/lib/core/win_http_client.dart | 63 ++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 auth/lib/core/win_http_client.dart diff --git a/auth/lib/core/network.dart b/auth/lib/core/network.dart index c14c9e758b..0efa09fb5d 100644 --- a/auth/lib/core/network.dart +++ b/auth/lib/core/network.dart @@ -1,8 +1,10 @@ import 'dart:io'; import 'package:dio/dio.dart'; +import 'package:dio/io.dart'; import 'package:ente_auth/core/configuration.dart'; import 'package:ente_auth/core/event_bus.dart'; +import 'package:ente_auth/core/win_http_client.dart'; import 'package:ente_auth/events/endpoint_updated_event.dart'; import 'package:ente_auth/utils/package_info_util.dart'; import 'package:ente_auth/utils/platform_util.dart'; @@ -50,6 +52,19 @@ class Network { }, ), ); + if (Platform.isWindows) { + final customHttpClient = windowsHttpClient(); + _enteDio.httpClientAdapter = IOHttpClientAdapter( + createHttpClient: () { + return customHttpClient; + }, + ); + _dio.httpClientAdapter = IOHttpClientAdapter( + createHttpClient: () { + return customHttpClient; + }, + ); + } _setupInterceptors(endpoint); Bus.instance.on().listen((event) { diff --git a/auth/lib/core/win_http_client.dart b/auth/lib/core/win_http_client.dart new file mode 100644 index 0000000000..a193af1174 --- /dev/null +++ b/auth/lib/core/win_http_client.dart @@ -0,0 +1,63 @@ +import 'dart:convert'; +import 'dart:io'; + +/* +Reference from +https://github.com/realm/realm-dart/blob/main/packages/realm_dart/lib/src/handles/native/default_client.dart +https://github.com/realm/realm-dart/pull/1378 + */ +HttpClient windowsHttpClient() { + const isrgRootX1CertPEM = // The root certificate used by lets encrypt + ''' +subject=CN=ISRG Root X1,O=Internet Security Research Group,C=US +issuer=CN=DST Root CA X3,O=Digital Signature Trust Co. +-----BEGIN CERTIFICATE----- +MIIFYDCCBEigAwIBAgIQQAF3ITfU6UK47naqPGQKtzANBgkqhkiG9w0BAQsFADA/ +MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT +DkRTVCBSb290IENBIFgzMB4XDTIxMDEyMDE5MTQwM1oXDTI0MDkzMDE4MTQwM1ow +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQCt6CRz9BQ385ueK1coHIe+3LffOJCMbjzmV6B493XC +ov71am72AE8o295ohmxEk7axY/0UEmu/H9LqMZshftEzPLpI9d1537O4/xLxIZpL +wYqGcWlKZmZsj348cL+tKSIG8+TA5oCu4kuPt5l+lAOf00eXfJlII1PoOK5PCm+D +LtFJV4yAdLbaL9A4jXsDcCEbdfIwPPqPrt3aY6vrFk/CjhFLfs8L6P+1dy70sntK +4EwSJQxwjQMpoOFTJOwT2e4ZvxCzSow/iaNhUd6shweU9GNx7C7ib1uYgeGJXDR5 +bHbvO5BieebbpJovJsXQEOEO3tkQjhb7t/eo98flAgeYjzYIlefiN5YNNnWe+w5y +sR2bvAP5SQXYgd0FtCrWQemsAXaVCg/Y39W9Eh81LygXbNKYwagJZHduRze6zqxZ +Xmidf3LWicUGQSk+WT7dJvUkyRGnWqNMQB9GoZm1pzpRboY7nn1ypxIFeFntPlF4 +FQsDj43QLwWyPntKHEtzBRL8xurgUBN8Q5N0s8p0544fAQjQMNRbcTa0B7rBMDBc +SLeCO5imfWCKoqMpgsy6vYMEG6KDA0Gh1gXxG8K28Kh8hjtGqEgqiNx2mna/H2ql +PRmP6zjzZN7IKw0KKP/32+IVQtQi0Cdd4Xn+GOdwiK1O5tmLOsbdJ1Fu/7xk9TND +TwIDAQABo4IBRjCCAUIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYw +SwYIKwYBBQUHAQEEPzA9MDsGCCsGAQUFBzAChi9odHRwOi8vYXBwcy5pZGVudHJ1 +c3QuY29tL3Jvb3RzL2RzdHJvb3RjYXgzLnA3YzAfBgNVHSMEGDAWgBTEp7Gkeyxx ++tvhS5B1/8QVYIWJEDBUBgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEB +ATAwMC4GCCsGAQUFBwIBFiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQu +b3JnMDwGA1UdHwQ1MDMwMaAvoC2GK2h0dHA6Ly9jcmwuaWRlbnRydXN0LmNvbS9E +U1RST09UQ0FYM0NSTC5jcmwwHQYDVR0OBBYEFHm0WeZ7tuXkAXOACIjIGlj26Ztu +MA0GCSqGSIb3DQEBCwUAA4IBAQAKcwBslm7/DlLQrt2M51oGrS+o44+/yQoDFVDC +5WxCu2+b9LRPwkSICHXM6webFGJueN7sJ7o5XPWioW5WlHAQU7G75K/QosMrAdSW +9MUgNTP52GE24HGNtLi1qoJFlcDyqSMo59ahy2cI2qBDLKobkx/J3vWraV0T9VuG +WCLKTVXkcGdtwlfFRjlBz4pYg1htmf5X6DYO8A4jqv2Il9DjXA6USbW1FzXSLr9O +he8Y4IWS6wY7bCkjCWDcRQJMEhg76fsO3txE+FiYruq9RUWhiF1myv4Q6W+CyBFC +Dfvp7OOGAN6dEOM4+qR9sdjoSYKEBpsr6GtPAQw4dy753ec5 +-----END CERTIFICATE-----'''; + + if (Platform.isWindows) { + final context = SecurityContext(withTrustedRoots: true); + try { + context.setTrustedCertificatesBytes( + const AsciiEncoder().convert(isrgRootX1CertPEM), + ); + return HttpClient(context: context); + } on TlsException catch (e) { + // certificate is already trusted. Nothing to do here + if (e.osError?.message.contains("CERT_ALREADY_IN_HASH_TABLE") != true) { + rethrow; + } else { + return HttpClient(); + } + } + } + throw UnsupportedError("This platform is not supported"); +} From f8f43b8ab730f20a0a26f85086995c88ff02e0a1 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Fri, 21 Jun 2024 14:18:20 +0530 Subject: [PATCH 042/130] Bump version --- mobile/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index 8b714db820..ab7962e182 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -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.1+901 +version: 0.9.2+902 publish_to: none environment: From e7b15b67d812ac8b2be8584ea8b3f2934ade8faa Mon Sep 17 00:00:00 2001 From: Victor Benincasa Date: Fri, 21 Jun 2024 14:50:07 -0300 Subject: [PATCH 043/130] Additional optimizations in file size and icon quality. --- auth/assets/custom-icons/icons/kick.svg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/auth/assets/custom-icons/icons/kick.svg b/auth/assets/custom-icons/icons/kick.svg index 29122ec7e7..67b065a96c 100644 --- a/auth/assets/custom-icons/icons/kick.svg +++ b/auth/assets/custom-icons/icons/kick.svg @@ -1,3 +1,3 @@ - - + + From 02a4e2b1ca45cf26efd46830d4b11352728d4dac Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 22 Jun 2024 11:30:20 +0530 Subject: [PATCH 044/130] [accounts] Make it easier for self hosters to use passkeys Solves a problem someone was facing on our Discord: https://discord.com/channels/948937918347608085/1215252276911018014/1253766354402545754 --- web/apps/accounts/src/services/passkey.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/web/apps/accounts/src/services/passkey.ts b/web/apps/accounts/src/services/passkey.ts index 4f4ab55099..5d056901fa 100644 --- a/web/apps/accounts/src/services/passkey.ts +++ b/web/apps/accounts/src/services/passkey.ts @@ -343,8 +343,18 @@ const authenticatorAttestationResponse = (credential: Credential) => { * Return `true` if the given {@link redirectURL} (obtained from the redirect * query parameter passed around during the passkey verification flow) is one of * the whitelisted URLs that we allow redirecting to on success. + * + * This check is likely not necessary but we've only kept it just to be on the + * safer side. However, this gets in the way of people who are self hosting + * Ente. So only do this check if we're running on our production servers (or + * localhost). */ export const isWhitelistedRedirect = (redirectURL: URL) => + shouldRestrictToWhitelistedRedirect() + ? _isWhitelistedRedirect(redirectURL) + : true; + +const _isWhitelistedRedirect = (redirectURL: URL) => (isDevBuild && redirectURL.hostname.endsWith("localhost")) || redirectURL.host.endsWith(".ente.io") || redirectURL.host.endsWith(".ente.sh") || @@ -352,6 +362,16 @@ export const isWhitelistedRedirect = (redirectURL: URL) => redirectURL.protocol == "enteauth:" || redirectURL.protocol == "ente-cli:"; +export const shouldRestrictToWhitelistedRedirect = () => { + // host includes port, hostname is sans port + const hostname = new URL(window.location.origin).hostname; + return ( + hostname.endsWith("localhost") || + hostname.endsWith(".ente.io") || + hostname.endsWith(".ente.sh") + ); +}; + export interface BeginPasskeyAuthenticationResponse { /** * An identifier for this authentication ceremony / session. From 2e53dcca0027cca0530539966a24eded668f0ce3 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Sat, 22 Jun 2024 16:47:53 +0530 Subject: [PATCH 045/130] Add logs --- auth/lib/core/win_http_client.dart | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/auth/lib/core/win_http_client.dart b/auth/lib/core/win_http_client.dart index a193af1174..61072d47a0 100644 --- a/auth/lib/core/win_http_client.dart +++ b/auth/lib/core/win_http_client.dart @@ -1,6 +1,8 @@ import 'dart:convert'; import 'dart:io'; +import 'package:flutter/foundation.dart'; + /* Reference from https://github.com/realm/realm-dart/blob/main/packages/realm_dart/lib/src/handles/native/default_client.dart @@ -49,8 +51,11 @@ Dfvp7OOGAN6dEOM4+qR9sdjoSYKEBpsr6GtPAQw4dy753ec5 context.setTrustedCertificatesBytes( const AsciiEncoder().convert(isrgRootX1CertPEM), ); + debugPrint("Certificate added to trusted certificates"); return HttpClient(context: context); } on TlsException catch (e) { + debugPrint( + "Error adding certificate to trusted certificates: ${e.osError?.message}"); // certificate is already trusted. Nothing to do here if (e.osError?.message.contains("CERT_ALREADY_IN_HASH_TABLE") != true) { rethrow; From 316a5e72091d786791aab6baae9b612d1cea508e Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Sat, 22 Jun 2024 16:48:16 +0530 Subject: [PATCH 046/130] [auth] Bump version --- auth/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auth/pubspec.yaml b/auth/pubspec.yaml index ba335b6960..fceca0c112 100644 --- a/auth/pubspec.yaml +++ b/auth/pubspec.yaml @@ -1,6 +1,6 @@ name: ente_auth description: ente two-factor authenticator -version: 3.0.14+314 +version: 3.0.15+315 publish_to: none environment: From 411e444295a5d231eb5fd4487e94f97100399e62 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Sat, 22 Jun 2024 16:54:58 +0530 Subject: [PATCH 047/130] [auth] Lint fix --- auth/lib/core/win_http_client.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/auth/lib/core/win_http_client.dart b/auth/lib/core/win_http_client.dart index 61072d47a0..46f1e70e5a 100644 --- a/auth/lib/core/win_http_client.dart +++ b/auth/lib/core/win_http_client.dart @@ -55,7 +55,8 @@ Dfvp7OOGAN6dEOM4+qR9sdjoSYKEBpsr6GtPAQw4dy753ec5 return HttpClient(context: context); } on TlsException catch (e) { debugPrint( - "Error adding certificate to trusted certificates: ${e.osError?.message}"); + "Error adding certificate to trusted certificates: ${e.osError?.message}", + ); // certificate is already trusted. Nothing to do here if (e.osError?.message.contains("CERT_ALREADY_IN_HASH_TABLE") != true) { rethrow; From e77ebef801e391ad3c633dfb5edf9181c26cfd64 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Sat, 22 Jun 2024 17:54:06 +0530 Subject: [PATCH 048/130] [cli] Add admin cmd to disable passkey --- cli/cmd/admin.go | 25 ++++++++++++++++++++++++- cli/internal/api/admin.go | 23 +++++++++++++++++++++++ cli/pkg/admin_actions.go | 21 +++++++++++++++++++++ 3 files changed, 68 insertions(+), 1 deletion(-) diff --git a/cli/cmd/admin.go b/cli/cmd/admin.go index 8a2d7f006b..f56fea06eb 100644 --- a/cli/cmd/admin.go +++ b/cli/cmd/admin.go @@ -58,6 +58,27 @@ var _disable2faCmd = &cobra.Command{ }, } +var _disablePasskeyCmd = &cobra.Command{ + Use: "disable-passkey", + Short: "Disable passkey for a user", + RunE: func(cmd *cobra.Command, args []string) error { + recoverWithLog() + var flags = &model.AdminActionForUser{} + cmd.Flags().VisitAll(func(f *pflag.Flag) { + if f.Name == "admin-user" { + flags.AdminEmail = f.Value.String() + } + if f.Name == "user" { + flags.UserEmail = f.Value.String() + } + }) + if flags.UserEmail == "" { + return fmt.Errorf("user email is required") + } + return ctrl.DisablePasskeys(context.Background(), *flags) + }, +} + var _deleteUser = &cobra.Command{ Use: "delete-user", Short: "Delete a user", @@ -130,11 +151,13 @@ func init() { _listUsers.Flags().StringP("admin-user", "a", "", "The email of the admin user. ") _disable2faCmd.Flags().StringP("admin-user", "a", "", "The email of the admin user. ") _disable2faCmd.Flags().StringP("user", "u", "", "The email of the user to disable 2FA for. (required)") + _disablePasskeyCmd.Flags().StringP("admin-user", "a", "", "The email of the admin user. ") + _disablePasskeyCmd.Flags().StringP("user", "u", "", "The email of the user to disable passkey for. (required)") _deleteUser.Flags().StringP("admin-user", "a", "", "The email of the admin user. ") _deleteUser.Flags().StringP("user", "u", "", "The email of the user to delete. (required)") _updateFreeUserStorage.Flags().StringP("admin-user", "a", "", "The email of the admin user.") _updateFreeUserStorage.Flags().StringP("user", "u", "", "The email of the user to update subscription for. (required)") // add a flag with no value --no-limit _updateFreeUserStorage.Flags().String("no-limit", "True", "When true, sets 100TB as storage limit, and expiry to current date + 100 years") - _adminCmd.AddCommand(_userDetailsCmd, _disable2faCmd, _updateFreeUserStorage, _listUsers, _deleteUser) + _adminCmd.AddCommand(_userDetailsCmd, _disable2faCmd, _disablePasskeyCmd, _updateFreeUserStorage, _listUsers, _deleteUser) } diff --git a/cli/internal/api/admin.go b/cli/internal/api/admin.go index 9e0bcb90a7..3511876cd1 100644 --- a/cli/internal/api/admin.go +++ b/cli/internal/api/admin.go @@ -88,6 +88,29 @@ func (c *Client) Disable2Fa(ctx context.Context, userID int64) error { return nil } +func (c *Client) DisablePassKeyMFA(ctx context.Context, userID int64) error { + var res interface{} + + payload := map[string]interface{}{ + "userID": userID, + } + r, err := c.restClient.R(). + SetContext(ctx). + SetResult(&res). + SetBody(payload). + Post("/admin/user/disable-passkeys") + if err != nil { + return err + } + if r.IsError() { + return &ApiError{ + StatusCode: r.StatusCode(), + Message: r.String(), + } + } + return nil +} + func (c *Client) UpdateFreePlanSub(ctx context.Context, userDetails *models.UserDetails, storageInBytes int64, expiryTimeInMicro int64) error { var res interface{} if userDetails.Subscription.ProductID != "free" { diff --git a/cli/pkg/admin_actions.go b/cli/pkg/admin_actions.go index 0105cdc199..44af3c27a9 100644 --- a/cli/pkg/admin_actions.go +++ b/cli/pkg/admin_actions.go @@ -82,6 +82,27 @@ func (c *ClICtrl) Disable2FA(ctx context.Context, params model.AdminActionForUse return nil } +func (c *ClICtrl) DisablePasskeys(ctx context.Context, params model.AdminActionForUser) error { + accountCtx, err := c.buildAdminContext(ctx, params.AdminEmail) + if err != nil { + return err + } + userDetails, err := c.Client.GetUserIdFromEmail(accountCtx, params.UserEmail) + if err != nil { + return err + } + err = c.Client.DisablePassKeyMFA(accountCtx, userDetails.User.ID) + if err != nil { + if apiErr, ok := err.(*api.ApiError); ok && apiErr.StatusCode == 400 && strings.Contains(apiErr.Message, "Token is too old") { + fmt.Printf("Error: Old admin token, please re-authenticate using `ente account add` \n") + return nil + } + return err + } + fmt.Println("Successfully disabled passkey for user") + return nil +} + func (c *ClICtrl) UpdateFreeStorage(ctx context.Context, params model.AdminActionForUser, noLimit bool) error { accountCtx, err := c.buildAdminContext(ctx, params.AdminEmail) if err != nil { From 05e490aa91ed689192b428e606dec4b9c46124ac Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 22 Jun 2024 18:44:21 +0530 Subject: [PATCH 049/130] [web] Build the staging/web branch when deploying if it exists This allows us to temporarily deploy arbitrary branches to staging by pushing to a staging/web branch. Removing that branch reverts to the existing and default behaviour of deploying main. Untested (need to deploy and trigger) Refs: - https://github.com/NLnetLabs/krill/commit/942f6a9fe9b43c2ddcd5adec261964359133d8e0 - https://docs.github.com/en/actions/learn-github-actions/contexts#steps-context - https://stackoverflow.com/questions/57819539/github-actions-how-to-share-a-calculated-value-between-job-steps --- .github/workflows/web-deploy-staging.yml | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/.github/workflows/web-deploy-staging.yml b/.github/workflows/web-deploy-staging.yml index ca3a6142b2..014afed10f 100644 --- a/.github/workflows/web-deploy-staging.yml +++ b/.github/workflows/web-deploy-staging.yml @@ -1,5 +1,7 @@ name: "Deploy staging (web)" +# Builds the "staging/web" branch if it exists, "main" otherwise. + on: schedule: # Run everyday at ~3:00 PM IST @@ -18,9 +20,19 @@ jobs: working-directory: web steps: - - name: Checkout code + - name: Determine branch to build + id: select-branch + run: | + if git ls-remote --exit-code --heads https://github.com/ente-io/ente refs/heads/staging/web; then + echo "branch=staging/web" >> $GITHUB_OUTPUT + else + echo "branch=main" >> $GITHUB_OUTPUT + fi + + - name: Checkout ${{ steps.select-branch.outputs.branch }} uses: actions/checkout@v4 with: + ref: ${{ steps.select-branch.outputs.branch }} submodules: recursive - name: Setup node and enable yarn caching From be82595e82d4882040d35c65deaf7e691ac711fd Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 22 Jun 2024 18:57:18 +0530 Subject: [PATCH 050/130] [web] Fix for the staging deployment change Fix for https://github.com/ente-io/ente/pull/2252 > Error: An error occurred trying to start process '/usr/bin/bash' with working directory '/home/runner/work/ente/ente/web'. No such file or directory --- .github/workflows/web-deploy-staging.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/web-deploy-staging.yml b/.github/workflows/web-deploy-staging.yml index 014afed10f..4323ee34c4 100644 --- a/.github/workflows/web-deploy-staging.yml +++ b/.github/workflows/web-deploy-staging.yml @@ -22,6 +22,7 @@ jobs: steps: - name: Determine branch to build id: select-branch + working-directory: ${{ github.workspace }} run: | if git ls-remote --exit-code --heads https://github.com/ente-io/ente refs/heads/staging/web; then echo "branch=staging/web" >> $GITHUB_OUTPUT From 18fb24fcd223b18fcefb474f92366f130f3d0816 Mon Sep 17 00:00:00 2001 From: Victor Benincasa Date: Fri, 21 Jun 2024 14:41:18 -0300 Subject: [PATCH 051/130] Additional optimizations in file size and icon quality. --- auth/assets/custom-icons/icons/instagram.svg | 1563 +----------------- 1 file changed, 1 insertion(+), 1562 deletions(-) diff --git a/auth/assets/custom-icons/icons/instagram.svg b/auth/assets/custom-icons/icons/instagram.svg index c068893628..65ca0e53d9 100644 --- a/auth/assets/custom-icons/icons/instagram.svg +++ b/auth/assets/custom-icons/icons/instagram.svg @@ -1,1562 +1 @@ - - + From de36d1f9fb74637fc9a9710b7f751903cf0a4b11 Mon Sep 17 00:00:00 2001 From: Victor Benincasa Date: Fri, 21 Jun 2024 14:42:43 -0300 Subject: [PATCH 052/130] Additional optimizations in file size and icon quality. --- auth/assets/custom-icons/icons/kraken.svg | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/auth/assets/custom-icons/icons/kraken.svg b/auth/assets/custom-icons/icons/kraken.svg index f38ee454c4..f46c926724 100644 --- a/auth/assets/custom-icons/icons/kraken.svg +++ b/auth/assets/custom-icons/icons/kraken.svg @@ -1,3 +1 @@ - - - + From 4042a5876e46b53b1ff4304581803a3b564f1a5b Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Sat, 22 Jun 2024 20:48:02 +0530 Subject: [PATCH 053/130] [cli] improve log --- cli/pkg/store.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/pkg/store.go b/cli/pkg/store.go index aa866d5952..98121e80ac 100644 --- a/cli/pkg/store.go +++ b/cli/pkg/store.go @@ -12,9 +12,9 @@ import ( ) func GetDB(path string) (*bolt.DB, error) { - db, err := bolt.Open(path, 0600, &bolt.Options{Timeout: 1 * time.Second}) + db, err := bolt.Open(path, 0600, &bolt.Options{Timeout: 5 * time.Second}) if err != nil { - log.Fatal(err) + log.Fatal(fmt.Sprintf("Failed to open db %s ", path), err) } return db, err } From fccf7e314919eaf345e9a3652e7fe4e5a4ec034d Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Sat, 22 Jun 2024 20:48:21 +0530 Subject: [PATCH 054/130] [cli] Update docs --- cli/docs/generated/ente.md | 2 +- cli/docs/generated/ente_account.md | 2 +- cli/docs/generated/ente_account_add.md | 2 +- cli/docs/generated/ente_account_get-token.md | 2 +- cli/docs/generated/ente_account_list.md | 2 +- cli/docs/generated/ente_account_update.md | 2 +- cli/docs/generated/ente_admin.md | 3 ++- cli/docs/generated/ente_admin_delete-user.md | 2 +- cli/docs/generated/ente_admin_disable-2fa.md | 2 +- .../generated/ente_admin_disable-passkey.md | 21 +++++++++++++++++++ cli/docs/generated/ente_admin_get-user-id.md | 2 +- cli/docs/generated/ente_admin_list-users.md | 2 +- .../ente_admin_update-subscription.md | 2 +- cli/docs/generated/ente_auth.md | 2 +- cli/docs/generated/ente_auth_decrypt.md | 2 +- cli/docs/generated/ente_export.md | 2 +- cli/docs/generated/ente_version.md | 2 +- 17 files changed, 38 insertions(+), 16 deletions(-) create mode 100644 cli/docs/generated/ente_admin_disable-passkey.md diff --git a/cli/docs/generated/ente.md b/cli/docs/generated/ente.md index 4f85dd0980..c3f4a11338 100644 --- a/cli/docs/generated/ente.md +++ b/cli/docs/generated/ente.md @@ -25,4 +25,4 @@ ente [flags] * [ente export](ente_export.md) - Starts the export process * [ente version](ente_version.md) - Prints the current version -###### Auto generated by spf13/cobra on 6-May-2024 +###### Auto generated by spf13/cobra on 22-Jun-2024 diff --git a/cli/docs/generated/ente_account.md b/cli/docs/generated/ente_account.md index 41c37b0547..d254b29685 100644 --- a/cli/docs/generated/ente_account.md +++ b/cli/docs/generated/ente_account.md @@ -16,4 +16,4 @@ Manage account settings * [ente account list](ente_account_list.md) - list configured accounts * [ente account update](ente_account_update.md) - Update an existing account's export directory -###### Auto generated by spf13/cobra on 6-May-2024 +###### Auto generated by spf13/cobra on 22-Jun-2024 diff --git a/cli/docs/generated/ente_account_add.md b/cli/docs/generated/ente_account_add.md index 1e86ae12f7..26a314a9e3 100644 --- a/cli/docs/generated/ente_account_add.md +++ b/cli/docs/generated/ente_account_add.md @@ -20,4 +20,4 @@ ente account add [flags] * [ente account](ente_account.md) - Manage account settings -###### Auto generated by spf13/cobra on 6-May-2024 +###### Auto generated by spf13/cobra on 22-Jun-2024 diff --git a/cli/docs/generated/ente_account_get-token.md b/cli/docs/generated/ente_account_get-token.md index 3d8814d7d1..40a21f1439 100644 --- a/cli/docs/generated/ente_account_get-token.md +++ b/cli/docs/generated/ente_account_get-token.md @@ -18,4 +18,4 @@ ente account get-token [flags] * [ente account](ente_account.md) - Manage account settings -###### Auto generated by spf13/cobra on 6-May-2024 +###### Auto generated by spf13/cobra on 22-Jun-2024 diff --git a/cli/docs/generated/ente_account_list.md b/cli/docs/generated/ente_account_list.md index a7677eb855..3c9ded6737 100644 --- a/cli/docs/generated/ente_account_list.md +++ b/cli/docs/generated/ente_account_list.md @@ -16,4 +16,4 @@ ente account list [flags] * [ente account](ente_account.md) - Manage account settings -###### Auto generated by spf13/cobra on 6-May-2024 +###### Auto generated by spf13/cobra on 22-Jun-2024 diff --git a/cli/docs/generated/ente_account_update.md b/cli/docs/generated/ente_account_update.md index 8d9c8d7e54..a1837529c3 100644 --- a/cli/docs/generated/ente_account_update.md +++ b/cli/docs/generated/ente_account_update.md @@ -19,4 +19,4 @@ ente account update [flags] * [ente account](ente_account.md) - Manage account settings -###### Auto generated by spf13/cobra on 6-May-2024 +###### Auto generated by spf13/cobra on 22-Jun-2024 diff --git a/cli/docs/generated/ente_admin.md b/cli/docs/generated/ente_admin.md index 5ac72489d6..29f136f75b 100644 --- a/cli/docs/generated/ente_admin.md +++ b/cli/docs/generated/ente_admin.md @@ -17,8 +17,9 @@ Commands for admin actions like disable or enabling 2fa, bumping up the storage * [ente](ente.md) - CLI tool for exporting your photos from ente.io * [ente admin delete-user](ente_admin_delete-user.md) - Delete a user * [ente admin disable-2fa](ente_admin_disable-2fa.md) - Disable 2fa for a user +* [ente admin disable-passkey](ente_admin_disable-passkey.md) - Disable passkey for a user * [ente admin get-user-id](ente_admin_get-user-id.md) - Get user id * [ente admin list-users](ente_admin_list-users.md) - List all users * [ente admin update-subscription](ente_admin_update-subscription.md) - Update subscription for user -###### Auto generated by spf13/cobra on 6-May-2024 +###### Auto generated by spf13/cobra on 22-Jun-2024 diff --git a/cli/docs/generated/ente_admin_delete-user.md b/cli/docs/generated/ente_admin_delete-user.md index a1d52a73d2..901430ff09 100644 --- a/cli/docs/generated/ente_admin_delete-user.md +++ b/cli/docs/generated/ente_admin_delete-user.md @@ -18,4 +18,4 @@ ente admin delete-user [flags] * [ente admin](ente_admin.md) - Commands for admin actions -###### Auto generated by spf13/cobra on 6-May-2024 +###### Auto generated by spf13/cobra on 22-Jun-2024 diff --git a/cli/docs/generated/ente_admin_disable-2fa.md b/cli/docs/generated/ente_admin_disable-2fa.md index 23cd330800..40aedd38da 100644 --- a/cli/docs/generated/ente_admin_disable-2fa.md +++ b/cli/docs/generated/ente_admin_disable-2fa.md @@ -18,4 +18,4 @@ ente admin disable-2fa [flags] * [ente admin](ente_admin.md) - Commands for admin actions -###### Auto generated by spf13/cobra on 6-May-2024 +###### Auto generated by spf13/cobra on 22-Jun-2024 diff --git a/cli/docs/generated/ente_admin_disable-passkey.md b/cli/docs/generated/ente_admin_disable-passkey.md new file mode 100644 index 0000000000..c315aeae04 --- /dev/null +++ b/cli/docs/generated/ente_admin_disable-passkey.md @@ -0,0 +1,21 @@ +## ente admin disable-passkey + +Disable passkey for a user + +``` +ente admin disable-passkey [flags] +``` + +### Options + +``` + -a, --admin-user string The email of the admin user. + -h, --help help for disable-passkey + -u, --user string The email of the user to disable passkey for. (required) +``` + +### SEE ALSO + +* [ente admin](ente_admin.md) - Commands for admin actions + +###### Auto generated by spf13/cobra on 22-Jun-2024 diff --git a/cli/docs/generated/ente_admin_get-user-id.md b/cli/docs/generated/ente_admin_get-user-id.md index 47d632abb6..3cbe42f2d7 100644 --- a/cli/docs/generated/ente_admin_get-user-id.md +++ b/cli/docs/generated/ente_admin_get-user-id.md @@ -18,4 +18,4 @@ ente admin get-user-id [flags] * [ente admin](ente_admin.md) - Commands for admin actions -###### Auto generated by spf13/cobra on 6-May-2024 +###### Auto generated by spf13/cobra on 22-Jun-2024 diff --git a/cli/docs/generated/ente_admin_list-users.md b/cli/docs/generated/ente_admin_list-users.md index 635e8ec3cd..519dcaccd2 100644 --- a/cli/docs/generated/ente_admin_list-users.md +++ b/cli/docs/generated/ente_admin_list-users.md @@ -17,4 +17,4 @@ ente admin list-users [flags] * [ente admin](ente_admin.md) - Commands for admin actions -###### Auto generated by spf13/cobra on 6-May-2024 +###### Auto generated by spf13/cobra on 22-Jun-2024 diff --git a/cli/docs/generated/ente_admin_update-subscription.md b/cli/docs/generated/ente_admin_update-subscription.md index d0fadcd2ba..341ccf5fe5 100644 --- a/cli/docs/generated/ente_admin_update-subscription.md +++ b/cli/docs/generated/ente_admin_update-subscription.md @@ -23,4 +23,4 @@ ente admin update-subscription [flags] * [ente admin](ente_admin.md) - Commands for admin actions -###### Auto generated by spf13/cobra on 6-May-2024 +###### Auto generated by spf13/cobra on 22-Jun-2024 diff --git a/cli/docs/generated/ente_auth.md b/cli/docs/generated/ente_auth.md index e0e97d84fc..2fd67cb0f1 100644 --- a/cli/docs/generated/ente_auth.md +++ b/cli/docs/generated/ente_auth.md @@ -13,4 +13,4 @@ Authenticator commands * [ente](ente.md) - CLI tool for exporting your photos from ente.io * [ente auth decrypt](ente_auth_decrypt.md) - Decrypt authenticator export -###### Auto generated by spf13/cobra on 6-May-2024 +###### Auto generated by spf13/cobra on 22-Jun-2024 diff --git a/cli/docs/generated/ente_auth_decrypt.md b/cli/docs/generated/ente_auth_decrypt.md index c9db6ea545..682ba1560e 100644 --- a/cli/docs/generated/ente_auth_decrypt.md +++ b/cli/docs/generated/ente_auth_decrypt.md @@ -16,4 +16,4 @@ ente auth decrypt [input] [output] [flags] * [ente auth](ente_auth.md) - Authenticator commands -###### Auto generated by spf13/cobra on 6-May-2024 +###### Auto generated by spf13/cobra on 22-Jun-2024 diff --git a/cli/docs/generated/ente_export.md b/cli/docs/generated/ente_export.md index d809e06e46..e3f07bb3c3 100644 --- a/cli/docs/generated/ente_export.md +++ b/cli/docs/generated/ente_export.md @@ -16,4 +16,4 @@ ente export [flags] * [ente](ente.md) - CLI tool for exporting your photos from ente.io -###### Auto generated by spf13/cobra on 6-May-2024 +###### Auto generated by spf13/cobra on 22-Jun-2024 diff --git a/cli/docs/generated/ente_version.md b/cli/docs/generated/ente_version.md index 08f384b52f..e1a3d8163b 100644 --- a/cli/docs/generated/ente_version.md +++ b/cli/docs/generated/ente_version.md @@ -16,4 +16,4 @@ ente version [flags] * [ente](ente.md) - CLI tool for exporting your photos from ente.io -###### Auto generated by spf13/cobra on 6-May-2024 +###### Auto generated by spf13/cobra on 22-Jun-2024 From bf926fe4b6a4a5e307bb6d66be3393f8948ccb75 Mon Sep 17 00:00:00 2001 From: Prateek Sunal Date: Sat, 22 Jun 2024 22:13:00 +0530 Subject: [PATCH 055/130] feat(auth): add appdata for ente_auth --- auth/linux/packaging/ente_auth.appdata.xml | 31 ++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 auth/linux/packaging/ente_auth.appdata.xml diff --git a/auth/linux/packaging/ente_auth.appdata.xml b/auth/linux/packaging/ente_auth.appdata.xml new file mode 100644 index 0000000000..c218b5ce48 --- /dev/null +++ b/auth/linux/packaging/ente_auth.appdata.xml @@ -0,0 +1,31 @@ + + + ente_auth + CC0-1.0 + AGPL-3.0 + Ente Auth + Open source 2FA authenticator, with end-to-end encrypted backups + +

Auth provides end-to-end encrypted cloud backups so you don't have to worry about losing your tokens. Our cryptography has been externally audited.

+

Auth has an app for every platform. Mobile, desktop and web. Your codes sync across all your devices, end-to-end encrypted.

+

Auth also comes with Offline mode, tags, icons, pins, import/export and more

+ + ente_auth.desktop + https://ente.io/auth + + + https://raw.githubusercontent.com/ente-io/ente/main/.github/assets/auth.png + + + + + + + ente_auth.desktop + + + + Ente.io Developers + + human@ente.io + \ No newline at end of file From 65770ff58b2ca1cfad1301c8e313b657c446439c Mon Sep 17 00:00:00 2001 From: Prateek Sunal Date: Sat, 22 Jun 2024 22:13:15 +0530 Subject: [PATCH 056/130] fix(auth): update script for pacman --- auth/linux/packaging/pacman/make_config.yaml | 30 +++++++++++--------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/auth/linux/packaging/pacman/make_config.yaml b/auth/linux/packaging/pacman/make_config.yaml index 00de15e1e9..2d83a4d493 100644 --- a/auth/linux/packaging/pacman/make_config.yaml +++ b/auth/linux/packaging/pacman/make_config.yaml @@ -36,19 +36,21 @@ categories: supported_mime_type: - x-scheme-handler/enteauth -postinstall_scripts: | - gtk-update-icon-cache -q -t -f usr/share/icons/hicolor - update-desktop-database -q - if [ ! -e /usr/lib/libsodium.so.23 ]; then - ln -s /usr/lib/libsodium.so /usr/lib/libsodium.so.23 - fi +postinstall_scripts: + - gtk-update-icon-cache -q -t -f usr/share/icons/hicolor + - update-desktop-database -q + - if [ ! -e /usr/lib/libsodium.so.23 ]; then + - ln -s /usr/lib/libsodium.so /usr/lib/libsodium.so.23 + - fi -postuninstall_scripts: | - post_install +postuninstall_scripts: + - post_install -postremove_scripts: | - gtk-update-icon-cache -q -t -f usr/share/icons/hicolor - update-desktop-database -q - if [ -e /usr/lib/libsodium.so.23 ]; then - rm /usr/lib/libsodium.so.23 - fi \ No newline at end of file +postremove_scripts: + - gtk-update-icon-cache -q -t -f usr/share/icons/hicolor + - update-desktop-database -q + - if [ -e /usr/lib/libsodium.so.23 ]; then + - rm /usr/lib/libsodium.so.23 + - fi + +startup_notify: false \ No newline at end of file From 81b07e772db3a353c7c0ddd50c5d3bd92468ee1a Mon Sep 17 00:00:00 2001 From: Kermina Awad Date: Sat, 22 Jun 2024 12:49:35 -0400 Subject: [PATCH 057/130] make replication.worker-url optional --- server/configurations/local.yaml | 2 +- server/pkg/controller/replication3.go | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/server/configurations/local.yaml b/server/configurations/local.yaml index fff43906c7..f392663c0e 100644 --- a/server/configurations/local.yaml +++ b/server/configurations/local.yaml @@ -317,7 +317,7 @@ internal: replication: enabled: false # The Cloudflare worker to use to download files from the primary hot - # bucket. Must be specified if replication is enabled. + # bucket. If this isn't specified, files will be downloaded directly. worker-url: # Number of go routines to spawn for replication # This is not related to the worker-url above. diff --git a/server/pkg/controller/replication3.go b/server/pkg/controller/replication3.go index ec949cf4c3..4fad173ea2 100644 --- a/server/pkg/controller/replication3.go +++ b/server/pkg/controller/replication3.go @@ -87,10 +87,11 @@ func (c *ReplicationController3) StartReplication() error { workerURL := viper.GetString("replication.worker-url") if workerURL == "" { - return fmt.Errorf("replication.worker-url was not defined") + log.Infof("replication.worker-url was not defined, files will downloaded directly during replication") + } else { + log.Infof("Worker URL to download objects for replication v3 is: %s", workerURL) } c.workerURL = workerURL - log.Infof("Worker URL to download objects for replication v3 is: %s", workerURL) c.createMetrics() err := c.createTemporaryStorage() @@ -414,7 +415,7 @@ func (c *ReplicationController3) downloadFromB2ViaWorker(objectKey string, file q.Add("src", presignedEncodedURL) request.URL.RawQuery = q.Encode() - if c.S3Config.AreLocalBuckets() { + if c.S3Config.AreLocalBuckets() || c.workerURL == "" { originalURL := request.URL request, err = http.NewRequest("GET", presignedURL, nil) if err != nil { From 19383ad360b3dc0c9220c3ea6e5bacf0cf795488 Mon Sep 17 00:00:00 2001 From: Victor Benincasa Date: Fri, 21 Jun 2024 12:30:54 -0300 Subject: [PATCH 058/130] Warning about icon sizes and instructions on matching the icon to the issuer. --- auth/docs/adding-icons.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/auth/docs/adding-icons.md b/auth/docs/adding-icons.md index 6a50fb9f77..ba008fe2d4 100644 --- a/auth/docs/adding-icons.md +++ b/auth/docs/adding-icons.md @@ -7,6 +7,15 @@ If you would like to add your own custom icon, please open a pull-request with the relevant SVG placed within `assets/custom-icons/icons` and add the corresponding entry within `assets/custom-icons/_data/custom-icons.json`. +Please be careful to upload small and optimized icon files. If your icon file +is over 50KB, it is certainly not optimized. + +Note that the correspondence between the icon and the issuer is based on the name +of the issuer provided by the user, excluding spaces. Only the text before the +first dot "." or left parentheses "(" will be used for icon matching. +e.g. Issuer name provided: "github.com (Main account)" - Then "github" will be +used for matching. + This JSON file contains the following attributes: | Attribute | Usecase | Required | From 7bce4e25aeaf5506a215f669077146f5336220b9 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta Date: Sat, 22 Jun 2024 22:33:45 +0530 Subject: [PATCH 059/130] Update adding-icons.md --- auth/docs/adding-icons.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auth/docs/adding-icons.md b/auth/docs/adding-icons.md index ba008fe2d4..2d4c4f2c0d 100644 --- a/auth/docs/adding-icons.md +++ b/auth/docs/adding-icons.md @@ -8,7 +8,7 @@ the relevant SVG placed within `assets/custom-icons/icons` and add the corresponding entry within `assets/custom-icons/_data/custom-icons.json`. Please be careful to upload small and optimized icon files. If your icon file -is over 50KB, it is certainly not optimized. +is over 50KB, it is likely not optimized. Note that the correspondence between the icon and the issuer is based on the name of the issuer provided by the user, excluding spaces. Only the text before the From 11b443a3f98da309a5c63058dc9264458884eb25 Mon Sep 17 00:00:00 2001 From: Prateek Sunal Date: Sat, 22 Jun 2024 22:37:29 +0530 Subject: [PATCH 060/130] fix(auth): bundle metainfo linux --- auth/linux/packaging/appimage/make_config.yaml | 2 ++ auth/linux/packaging/deb/make_config.yaml | 2 ++ auth/linux/packaging/pacman/make_config.yaml | 2 ++ auth/linux/packaging/rpm/make_config.yaml | 2 ++ 4 files changed, 8 insertions(+) diff --git a/auth/linux/packaging/appimage/make_config.yaml b/auth/linux/packaging/appimage/make_config.yaml index 0b52ddf9e2..a41658fd17 100644 --- a/auth/linux/packaging/appimage/make_config.yaml +++ b/auth/linux/packaging/appimage/make_config.yaml @@ -1,6 +1,8 @@ display_name: Auth license: GPLv3 +metainfo: linux/packaging/ente_auth.appdata.xml + icon: assets/icons/auth-icon.png keywords: diff --git a/auth/linux/packaging/deb/make_config.yaml b/auth/linux/packaging/deb/make_config.yaml index 755024d2a8..7092994bd4 100644 --- a/auth/linux/packaging/deb/make_config.yaml +++ b/auth/linux/packaging/deb/make_config.yaml @@ -10,6 +10,8 @@ license: GPLv3 icon: assets/icons/auth-icon.png installed_size: 36000 +metainfo: linux/packaging/ente_auth.appdata.xml + dependencies: - libwebkit2gtk-4.0-37 - libsqlite3-0 diff --git a/auth/linux/packaging/pacman/make_config.yaml b/auth/linux/packaging/pacman/make_config.yaml index 2d83a4d493..723b5ad540 100644 --- a/auth/linux/packaging/pacman/make_config.yaml +++ b/auth/linux/packaging/pacman/make_config.yaml @@ -8,6 +8,8 @@ licenses: icon: assets/icons/auth-icon.png installed_size: 36000 +metainfo: linux/packaging/ente_auth.appdata.xml + dependencies: - c-ares - ffmpeg diff --git a/auth/linux/packaging/rpm/make_config.yaml b/auth/linux/packaging/rpm/make_config.yaml index 495f6482c6..c285b90b30 100644 --- a/auth/linux/packaging/rpm/make_config.yaml +++ b/auth/linux/packaging/rpm/make_config.yaml @@ -9,6 +9,8 @@ url: https://github.com/ente-io/ente display_name: Auth +metainfo: linux/packaging/ente_auth.appdata.xml + requires: - libsqlite3x - webkit2gtk4.0 From f5a31397f3b96f9ca5ac2f80591decd1c07b2dbe Mon Sep 17 00:00:00 2001 From: Prateek Sunal Date: Sat, 22 Jun 2024 22:37:50 +0530 Subject: [PATCH 061/130] chore(auth): bump to v3.0.16 --- auth/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auth/pubspec.yaml b/auth/pubspec.yaml index fceca0c112..046543037e 100644 --- a/auth/pubspec.yaml +++ b/auth/pubspec.yaml @@ -1,6 +1,6 @@ name: ente_auth description: ente two-factor authenticator -version: 3.0.15+315 +version: 3.0.16+316 publish_to: none environment: From c8451ecc641807b7937136e24bc3e9dc7fd4409c Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Sat, 22 Jun 2024 22:47:38 +0530 Subject: [PATCH 062/130] [auth] Update logs --- auth/lib/core/win_http_client.dart | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/auth/lib/core/win_http_client.dart b/auth/lib/core/win_http_client.dart index 46f1e70e5a..c5c6e0d360 100644 --- a/auth/lib/core/win_http_client.dart +++ b/auth/lib/core/win_http_client.dart @@ -1,7 +1,7 @@ import 'dart:convert'; import 'dart:io'; -import 'package:flutter/foundation.dart'; +import 'package:logging/logging.dart'; /* Reference from @@ -9,6 +9,7 @@ https://github.com/realm/realm-dart/blob/main/packages/realm_dart/lib/src/handle https://github.com/realm/realm-dart/pull/1378 */ HttpClient windowsHttpClient() { + final logger = Logger("WindowsHttpClient"); const isrgRootX1CertPEM = // The root certificate used by lets encrypt ''' subject=CN=ISRG Root X1,O=Internet Security Research Group,C=US @@ -48,13 +49,14 @@ Dfvp7OOGAN6dEOM4+qR9sdjoSYKEBpsr6GtPAQw4dy753ec5 if (Platform.isWindows) { final context = SecurityContext(withTrustedRoots: true); try { + logger.info('Adding certificate to trusted certificates'); context.setTrustedCertificatesBytes( const AsciiEncoder().convert(isrgRootX1CertPEM), ); - debugPrint("Certificate added to trusted certificates"); + logger.info("Certificate added to trusted certificates"); return HttpClient(context: context); } on TlsException catch (e) { - debugPrint( + logger.warning( "Error adding certificate to trusted certificates: ${e.osError?.message}", ); // certificate is already trusted. Nothing to do here From 3ac4294c2c297a49b6777a962cbb25979e4389b2 Mon Sep 17 00:00:00 2001 From: Prateek Sunal Date: Sat, 22 Jun 2024 22:59:49 +0530 Subject: [PATCH 063/130] fix(auth): only run pacman build on beta, fix deps --- .github/workflows/auth-release.yml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/auth-release.yml b/.github/workflows/auth-release.yml index c7ae0bb0a7..d4bdae0c0e 100644 --- a/.github/workflows/auth-release.yml +++ b/.github/workflows/auth-release.yml @@ -145,7 +145,7 @@ jobs: - name: Install dependencies for desktop build run: | sudo apt-get update -y - sudo apt-get install -y libsecret-1-dev libsodium-dev libwebkit2gtk-4.0-dev libfuse2 ninja-build libgtk-3-dev dpkg-dev pkg-config rpm patchelf libsqlite3-dev locate libayatana-appindicator3-dev libffi-dev libtiff5 + sudo apt-get install -y libsecret-1-dev libsodium-dev libwebkit2gtk-4.0-dev libfuse2 ninja-build libgtk-3-dev dpkg-dev pkg-config rpm patchelf libsqlite3-dev locate libayatana-appindicator3-dev libffi-dev libtiff5 xz-utils libarchive-tools sudo updatedb --localpaths='/usr/lib/x86_64-linux-gnu' - name: Install appimagetool @@ -159,8 +159,11 @@ jobs: flutter config --enable-linux-desktop # dart pub global activate flutter_distributor dart pub global activate --source git https://github.com/prateekmedia/flutter_distributor --git-ref pacman --git-path packages/flutter_distributor + # Run below command if it is a beta or nightly + if [[ ${{ github.ref }} =~ beta|nightly ]]; then + flutter_distributor package --platform=linux --targets=pacman --skip-clean + fi flutter_distributor package --platform=linux --targets=rpm --skip-clean - flutter_distributor package --platform=linux --targets=pacman --skip-clean flutter_distributor package --platform=linux --targets=appimage --skip-clean mv dist/**/*-*-linux.rpm artifacts/ente-${{ github.ref_name }}-x86_64.rpm mv dist/**/*-*-linux.AppImage artifacts/ente-${{ github.ref_name }}-x86_64.AppImage From e7970df6cf8eef77b85ac210efda1a209dae3a0a Mon Sep 17 00:00:00 2001 From: Prateek Sunal Date: Sat, 22 Jun 2024 23:00:36 +0530 Subject: [PATCH 064/130] chore: bump version --- auth/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auth/pubspec.yaml b/auth/pubspec.yaml index 046543037e..211159b768 100644 --- a/auth/pubspec.yaml +++ b/auth/pubspec.yaml @@ -1,6 +1,6 @@ name: ente_auth description: ente two-factor authenticator -version: 3.0.16+316 +version: 3.0.17+317 publish_to: none environment: From a59ca2bdf0aefa615bdcc60e9d773cf682200f03 Mon Sep 17 00:00:00 2001 From: Prateek Sunal Date: Sat, 22 Jun 2024 23:32:54 +0530 Subject: [PATCH 065/130] fix(auth): also move the pacman build to artifacts --- .github/workflows/auth-release.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/auth-release.yml b/.github/workflows/auth-release.yml index d4bdae0c0e..3cd5333ac3 100644 --- a/.github/workflows/auth-release.yml +++ b/.github/workflows/auth-release.yml @@ -162,10 +162,11 @@ jobs: # Run below command if it is a beta or nightly if [[ ${{ github.ref }} =~ beta|nightly ]]; then flutter_distributor package --platform=linux --targets=pacman --skip-clean + mv dist/**/*-*-linux.pacman artifacts/ente-${{ github.ref_name }}-x86_64.pacman fi flutter_distributor package --platform=linux --targets=rpm --skip-clean - flutter_distributor package --platform=linux --targets=appimage --skip-clean mv dist/**/*-*-linux.rpm artifacts/ente-${{ github.ref_name }}-x86_64.rpm + flutter_distributor package --platform=linux --targets=appimage --skip-clean mv dist/**/*-*-linux.AppImage artifacts/ente-${{ github.ref_name }}-x86_64.AppImage - name: Generate checksums From 3c5ea83f8a0eaa050e1a9e4b9d8573594afbf947 Mon Sep 17 00:00:00 2001 From: Prateek Sunal Date: Sun, 23 Jun 2024 00:01:17 +0530 Subject: [PATCH 066/130] fix: pacman scripts --- auth/linux/packaging/pacman/make_config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/auth/linux/packaging/pacman/make_config.yaml b/auth/linux/packaging/pacman/make_config.yaml index 723b5ad540..c27c1c703f 100644 --- a/auth/linux/packaging/pacman/make_config.yaml +++ b/auth/linux/packaging/pacman/make_config.yaml @@ -42,10 +42,10 @@ postinstall_scripts: - gtk-update-icon-cache -q -t -f usr/share/icons/hicolor - update-desktop-database -q - if [ ! -e /usr/lib/libsodium.so.23 ]; then - - ln -s /usr/lib/libsodium.so /usr/lib/libsodium.so.23 + - " ln -s /usr/lib/libsodium.so /usr/lib/libsodium.so.23" - fi -postuninstall_scripts: +postupgrade_scripts: - post_install postremove_scripts: From 53140de8791fc70ee86ffbf435ecafc85092cc11 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 22 Jun 2024 23:45:34 +0530 Subject: [PATCH 067/130] [server] Tweak verification email Despite all of our efforts, gmail insists on marking our verification emails to new users as spam. We have already changed our mail delivery providers; non-gmail users don't face this problem; and even for gmail, (a) existing Ente users also get these mails correctly with SPF/DKIM/DMARC PASS, and (b) non-verification emails get delivered (in the anecdotal reports we've received). As an attempt at some voodoo, try changing the subject and content of the mail, to try and rule out some faulty gmail classifier that uses the email body. --- server/ente/user.go | 3 --- server/mail-templates/ott_auth.html | 6 +++--- server/mail-templates/ott_photos.html | 6 +++--- server/pkg/controller/user/userauth.go | 5 +++-- 4 files changed, 9 insertions(+), 11 deletions(-) diff --git a/server/ente/user.go b/server/ente/user.go index 387d2627b3..79c7cf96bd 100644 --- a/server/ente/user.go +++ b/server/ente/user.go @@ -9,9 +9,6 @@ const ( EmailChangedTemplate = "email_changed.html" EmailChangedSubject = "Email address updated" - // OTTEmailSubject is the subject of the OTT mail - OTTEmailSubject = "ente Verification Code" - ChangeEmailOTTPurpose = "change" ) diff --git a/server/mail-templates/ott_auth.html b/server/mail-templates/ott_auth.html index 19b9826d64..9c89cb12ab 100644 --- a/server/mail-templates/ott_auth.html +++ b/server/mail-templates/ott_auth.html @@ -159,7 +159,7 @@ - +
Paste this code into the app to verify your email address
Use this code to verify your email address
@@ -188,7 +188,7 @@
- +
Please respond to this email if you are facing any issues
ente.io
@@ -212,4 +212,4 @@ - \ No newline at end of file + diff --git a/server/mail-templates/ott_photos.html b/server/mail-templates/ott_photos.html index 5774eb4f5e..1c7e63d946 100644 --- a/server/mail-templates/ott_photos.html +++ b/server/mail-templates/ott_photos.html @@ -159,7 +159,7 @@ - +
Paste this code into the app to verify your email address
Use this code to verify your email address
@@ -188,7 +188,7 @@
- +
Please respond to this email if you are facing any issues
ente.io
@@ -212,4 +212,4 @@ - \ No newline at end of file + diff --git a/server/pkg/controller/user/userauth.go b/server/pkg/controller/user/userauth.go index 5d9664e997..5548dd23ad 100644 --- a/server/pkg/controller/user/userauth.go +++ b/server/pkg/controller/user/userauth.go @@ -302,8 +302,9 @@ func emailOTT(c *gin.Context, to string, ott string, client string, purpose stri inlineImage["content"] = "iVBORw0KGgoAAAANSUhEUgAAALAAAACwCAYAAACvt+ReAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAABHlSURBVHgB7Z1tjFxV/ce/U7ZGxa3bEsrflr+d5o+x5W9tN/ZJAu60PFi1ursm9SnSbuWFwZIAMTFAom6jKdEX0iYg+AK7SzRqfNHWmABa2qmokW61iwq7QA23L7oiaLvsQhG3MJ7vnXOXu7t3Zu7MfTrn3PNpTnd3Zpru7nznN9/fwzm3AEtoKpVKUXzwr2VidchVlA/zvg5ivMY6LZbjrUKh4MASigIscxBCpQDXyLVafiyitjCTYBhVQR+Tnw8LYY/DMgMrYEwLtgdVoXbjrWiqGsNyHUJV0A5yTm4FLERbQlW0XagKV0coZkbog0LMZeSQXAnYJ9odSNcOpIEjVlmswTyJ2XgBGy7aWjioinmfEPMwDMZIAUtP24eqny0h31DAFPIADMQoAUvh3irWbchPtA2Lg2pU3m1S8meEgGV9th9Vm2BpzAAMEbLWApbC3Q9rE1plAJoLeR40hMIVa0B8+jyseKPQJ9bz4ne5XwYD7dAqAluPmzj9qCZ82nT8tBGwEC9LYfdA3S6ZKTio2ooBaIDyArY+NzMGoIE/VtoDC/HSLpyEFW8W9Il1UjwH34TCKBmBbdRVjrJYO1WMxspFYBt1laSEajTug2IoI2BWGMRikrYXtsKgInxOWG67R1aDlEAJCyEtw1HYCoMuOGJtUsFSZB6BZXmMlqEIiy4UUbUUPciYTAUsM9wDsJZBR/icHci6SpGZhZB+9zZYTGCvsBO3IwNSF7BMABh1S7CYxEFUS22ptqFTFbBM1viDrobBjIyM4Pjx4zhz5oz79dKlS7F+/XqsXLkShsPh+d40k7vUBJyHSgNFe++997ofg6CQb7nlFvT29sJgHKRYoUhFwKaLd2JiAnfddRcOHz4c6vEUMIVMQRuKg5REnLiATRfv6Ogodu3aNW0XwkLxPvTQQ1bEEUm0jGa6eGkVbrzxxqbFS/hvtm/f7vplQymKdTTpQfnEBCyrDUzYijCQwcFBV4CTk5NoFYqYduLAgQMwlCKqteLE6vyJWQjxTfNZybxTkwRM1LjihJ6Yy1DKwkpsQgIkEoFlk8JI8e7Zsyd28ZIkXhQKUZKaiJ3YI7BsLfbDMFhpuPvuuxN/u6ctYUXDUPpFJN6NGIlVwHK4wzhDR/Hu2LEjtYTruuuucyP9ggULYCBsdBxETMQmYJltcqrMqMEcr1rQSqUhCuza0VIYWGZjq7kzrvJaLAKWWaZxI5FZidfD4Fqxg6qII89NxJXE0fcWYRBsUPT09GQmXpL1CyhBiqhqJjKRBSz3SRk1FvnYY4+5DYooNd648GrFBjY8bhPaiaybSBbCRN/LKsOdd94JFWEVxLBBoMh+OKqA2SYuwRB0qMUa2PCI1ORo2ULIem8JhqBLI8HAhkcpipVoKQKbZh1Yc2W2rxOGReKWrUSrAh6AIYdJ0+/qOkxDP0xfbAgtWYmmBSyrDvuhOWl315KCDQ9OxhnStWu6S9eUgE1pWLA0xbdfU0pTBjU8HDTZ4Gg2ieO5ZUVojImD5AY1PIposqcQOgLLxO15aIzBnS0XQyJxUwldMxG4HxrD1rDJ4iVe167WrmhNoE0N3WYOFYF1j758QrnxUoXWcFoY0LVbHiYKh43A/dAUlsii7l3TEZYHNW94hKp0NYzAOkdflpcMqpO2hOYNj4ZROEwE7oeGMPrkXbxE89ZzQy9cNwLrGn0N3yDZEhp37RbWqws3isD90Iykdg3rDnMBipgdSM2oWxduFIEZfYvQBJ3nGtJCw1oxo+/yWlG4ZgSWMw9FaACjiuEn3MSGhs0c1oX7at1Zz0LcCg3gE2HCUE6aaCji7lp3BFoIEX3XoDq0ozSmt4aThhNsLDVqcvA2T7osz76xVgRWfpOmFW90NLNegUeV1YrASidvVrzxo0HDY1xE4IWzb5wTgYV4S1BYvCqc12AiGtTOO6Q2ZxBkIfqgKBSvKuc1mIgGIp5jI+ZYCFXtA30amxRWvMmjcNdujo2YIWAZoo9CMZI8bKS9vR3bd2x3nzTdBsH5jsTy4X333he7peJlwRiNFdxrN6MaMVvAe6FY/TfJtzUKdvChQe33krmD7D29sb87KXpC5j4h4Okq2WwP3AWFSNqT7bpllxEnP/JnYDMnbhjdFaz2zNDotIDl5NkaKEIaCcWKFStgCj29yVzRQcGS5Rr/RWP8EThX4iUmXfo1yXcSBXdyT79a5wXdmCV2HFJNvJkTHj2rANPB1i/gTC/APTVxwRWubmeU5Qm2nu+44w48dWQELw2ddZ+zjJge7nGrENJTnENGnD/zGo7d9IT7Kn/lQ2fxsyd+gnOVs0iakVGzJthWrkjWEn1kQxd6Orbh/NGp6duWbL4Ma762Eu9c8g6kjLtTwxNwCRnWfw9v+y3Gn5lZAjr5xnEcefPRRIVsBRwO1spv+sCX8Z5ni5ianJpz/6VrF6HrhxuQMm49uE1+kVkC9+R3R+aIl3RetN5daQjZUpvuDb24+vy1eP2PU+CfIF46IezE5AXMb29DilCz0wIuIQPGn5nAcz9y6j6GIl4+7woceeNR/OlNrU+c0YrFCy7Dje/9EhaeXIzXawjXz0tD/3LtRIq4OZsn4GXIgKfvPxXqcR2FRfh02+exufJRPHjhPhuNE+ZjV27Fxn904aJnw0fU/0ymntC5riFTC/HyaHM7ZCnkr87/urUVCfH/l6/CDee34pJTi6EBRf7VJjtwWmFtRby8vfAObOnYig+/1oWpVxrbBUXgfHCREbgIDbG2Ih6uvrwL17+2FRdNttVM0hRGXwF7WFvxFix3hZ1IY5LGAHD5i5mkP3Ghv4A9rK2o7jJuJGCK/Kq2LmyetyWwpqsZ5giYeLai613XYuDcD6ytmAXtwsfbevDmGHS0C0Eso4CNuUysxyWvLHZtxdDFv8eRC48ERiVDruoTis6Vndh80RYsfHYx3oRRLKSA342M+M8rydYO1716Fd5XWYEj8+baCr6VmgZHKv1zu/wZt733C3j/C6tMsAtBvDvTCDw1kfwv1bMVH7y4E4fGf54LW0Hhdm/sxeoX1uP1Z6ZMsQtBFI20EEFc8eoKfGPZHjwy/ks8KpaJMAJvve6Twi581J0Ye91c4U6TGwGTV8dewzW4Fqvmd+JfG8ZgEgXx59NXbsM/fzzh/pw5oSNXAvagrej45SKM/O8pXPHFYtpTVLHDeeoTX/+LOxWWMzr0fuYiwmGi04fOYOXNV2BZt367kxl1n77/OTz3o9OmJmkNybWACd9uveh1pRByBjsLWuKfQ2fd7ztHdiGQ3AvYg5GYolA9Gl+YuICnHzjVcI46L1gB+/CiMcW89turlIvGp4RVoO3Jq10Iotmr1ecC2omHtxzDSMiB+6RhkvabLx13t19Z8c6EAh6HJRBGu0eEkJsdvI8Lblvni+jhjx3LY4UhDOO0EBRw7kppYaGtOPyZ37u+OM0kzyZpoRi3HjgkaSV5jLonvvFnjB15EZaGuAJ2YNBIZZIkXXKzSVrTuAJ+GZamYDTmoogZkaPCczH+/J0R63Ob52WbxEXAS/LOt+hTvSTtsW2/s+JtjXOehbC0CG0FS27NJnk2SYuF01bAMRE2ybNJWqw4VsAx4iV5Y0f/gdUBJzbaJC12rICTgNGVy0vybJKWGE5boVBwKpWKbWYkgDeuaX1uMlC73iyEA0siWPEmxjD/8gT8JCwWvTjNvzwBD8Ni0Ysy/7ICtujKDAthBZwA4ziHn1YGxS/3BCyx42rWnUbj1V5EJcKBHeqJjXLl1/gDfoN/iz+jlb+6Iu4pfFaUehbCEplhapaf+HdkHIIlMg7+hgcq9wiD9itXvP7b91b2uMK2RGbaMbQF3WhpHoq1XPmViLqP130chT1cGcKWwqewAh+ApSWmg61fwAfF2g9L09AePFI5NCPi1sPzxmuwFqXCDdZWNM/cCCx9MO9Q5qLfqkMhHqz8zLUHrUDhO5W/uSKmmC2hoP91vC9mbyk6BivghjDS/qHy+HSSFgXvRUBr0Ve42UbjxhzzfzF7W/1BWOpSTdK+NydJiwqF7CV5/4ZtP9dhhkZnRGBee9YO9gRDsdLnJl3T9ZI8aysCcahR/w1Bu5IHxboVlmloF+KOuPXwe2ub5M2gPPuGIAEzRFsBC17AmIi6v2g5SYuKl+RtLHwEG3E1LG5wncEcAVsb8VaSxqibNYzGtC5MGHOe5M2xD6TW2WiDyCn+JE0lvCSP7wj8PIeUg26sdTJP7myEF+lG8RRUhp0+zlbkMMnbF3RjoICljSiLT0vIAWknaVHJYZLH5kXgqEO9s9HYby7BYCgA1l2zStKiwiRvuHJCPEk3CCFfD4PZV+uOeucDD8DQU3uqNd1fYKDygLbi9cN3D/rjF/B3GAiTt4Fad9YUsJy3rKl8XaHHZZLWaGpMN2gr+HPRWhiW5JXr3dnoeNW9Yn0TBhB18EYXDBwQ2l3vzrqXGJBRWPuSGn0uo5Pp4vXwXqwcrNc8Gg/4J8+CCHONjH5oyvQuCI0qDHHCTqLmu0B2N3pAwxPa5ck9ZSRQkZi/YH4iF/wOuzsiL2g6INQw+pKwlxjYKdbziJm3vastdgHrVtNNCw1rxw2jLwklYBmF6YV3QFGyHrzRBSZ5o5WnsBHXxFo7vjjeyy2Eir6kmYu89IvVDcWGfFQavNEFDsx7tuJzhZ34H7wHihEq+pLQFzqUrwil6sKqDt7oQpy1444VCxATu8NGX9LsZbZYF6aNKCJD8lLTTYuoteP57fPFiuWKbQ6qGgtNU/+r3Ll8u/j0ADLCf+KNJT78m0t5glAR/xf633a8vx0xsds7cScsTV8rWfwHHLUsIwaWbL4s9GNrnXhjiRcKmTMizdiKS9ddghgYqDfzUIsCWkBE4aL4cBIRE7qXhs7i2E1P1H2MrelmS5jDV7oe3CBEvAgRYNTtbMb7erRkXGRZjZniPYgAf+hL1y4KvHZEnGcvWFrHG9mkkD++tBtvG3v7jPtZPosoXrKvFfGSliKwhxDxUUTs0M2Owm+0X8DjE0etcBXlqqXX4BNLujF1ouJ+vaz7cqz71ipEoCzEuwktElXARcRgJR7eUsaLS/6Onw/91FYWNOH6dTe4g/RX3bw+SgRu2Tp4NJ3E+ZH/ceiicy0++OD73OTMilcfLl7yTqz99qqo9uH2KOIlkSKwh4jErN1F3gR6ZuwM7v/+/Tg+dBxjY2OwqMe6devwlZu/4n6MCH3vbYhIXAKmhaCVKCImhoaGMPrMKCYnJ2HJhvb2dnd5bN68GQvaY+m4Oahah8hb1mIRMInLD1uMJ7Lv9RPJA/uR39BOWCz12RmXeElsAiaySxc5qbMYy26pkdiIzUL4iSupsxhFLEnbbBIRMImjyWExhoNCvL1IgCQFzGSOIraXLMg3vA53KY6KQxCxemA/8hvmq86BJa84YvUkJV6SWAT2kOU1RuIiLHnCEWtTnBWHIBIXMLEizh0OUhAvSUXAxIo4NzhISbwkMQ88G/kDcWzOXtLWXJiwpSZekpqAiU/E9np05lFGtdrgIEVSFTBhRiprgsYd3Zpj2KTYlGS1oRapC9hDdmVs21l/difRYQtLaklcLURy1yM+7IedYtMNRtudcc82NEvmAia2QqEdDlJO1mqRmYXwI38RnbC+WAf4HHWqIF6iRAT2I6JxH6rb9a2lUAtaBvrdpo5+ShrlBEykpaAvLsGiAmXEPIgeF0pYiNnwFyXPCmCVwshLfWkCf/e3yxKZAwVRMgL7kdG4Hwofrm0oZSgadf0oGYH9yGjch+p+OweWpHHE6lU56vpRPgL7kUPyLJobce06xfAubLk3i45aq2glYA9rK2JnAE2ejK4KWgrYwwo5MmVo4HProbwHrofPHy+HAVcUTZEBsZbr4nProXUEno0vInfBtqVno6XHbYRRAvYjO3o8myLvu6LLYh1C9Qh/42rqxgrYQwiZAmblIk9R2btIO89jKMNgjBewHyHmkvjQBzPFnBvR+smVgP1IMXMWmWLW1WZwf+Ex5Ey0fnIrYD8y+aOIKejVUFfQDqp+lsI9aKKnbRYr4ABkx2+NXJ7dSFPUFKaDqlCflB+HrWDnYgXcBDJS+9cyVOeWg1YQ43hrus7xfX1afu0u3WuzafJf05durhLhbZAAAAAASUVORK5CYII=" } inlineImages = append(inlineImages, inlineImage) - err := emailUtil.SendTemplatedEmail([]string{to}, "ente", "verify@ente.io", - ente.OTTEmailSubject, templateName, map[string]interface{}{ + subject := fmt.Sprintf("Email verification code: %s", ott) + err := emailUtil.SendTemplatedEmail([]string{to}, "Ente", "verify@ente.io", + subject, templateName, map[string]interface{}{ "VerificationCode": ott, }, inlineImages) if err != nil { From 306430d67d32173d329bd290fe4b3c4a81f69eb0 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 19 Jun 2024 14:36:21 +0530 Subject: [PATCH 068/130] Sketch --- web/apps/photos/src/pages/index.tsx | 36 +++++++++- .../new/photos/components/DevSettings.tsx | 67 +++++++++++++++++++ .../new/photos/components/SlideTransition.tsx | 16 +++++ .../new/photos/components/WhatsNew.tsx | 18 ++--- 4 files changed, 121 insertions(+), 16 deletions(-) create mode 100644 web/packages/new/photos/components/DevSettings.tsx create mode 100644 web/packages/new/photos/components/SlideTransition.tsx diff --git a/web/apps/photos/src/pages/index.tsx b/web/apps/photos/src/pages/index.tsx index 8abad43975..26a4e64b3d 100644 --- a/web/apps/photos/src/pages/index.tsx +++ b/web/apps/photos/src/pages/index.tsx @@ -1,3 +1,4 @@ +import { DevSettings } from "@/new/photos/components/DevSettings"; import log from "@/next/log"; import { Login } from "@ente/accounts/components/Login"; import { SignUp } from "@ente/accounts/components/SignUp"; @@ -26,10 +27,12 @@ import { useAppContext } from "./_app"; export default function LandingPage() { const { appName, showNavBar, setDialogMessage } = useAppContext(); - const router = useRouter(); + const [loading, setLoading] = useState(true); const [showLogin, setShowLogin] = useState(true); + const router = useRouter(); + useEffect(() => { showNavBar(false); const currentURL = new URL(window.location.href); @@ -145,7 +148,36 @@ export default function LandingPage() { ); } -const Container = styled("div")` +const Container: React.FC = ({ children }) => { + // [Note: Configuring custom server] + // + // Allow the user to tap 7 times anywhere on the onboarding screen to bring + // up a page where they can configure the endpoint that the app should + // connect to. + // + // See: https://help.ente.io/self-hosting/guides/custom-server/ + const [tapCount, setTapCount] = useState(0); + const [showDevSettings, setShowDevSettings] = useState(false); + + const handleClick = () => { + setTapCount(tapCount + 1); + if (tapCount + 1 == 7) setShowDevSettings(true); + }; + + return ( + + <> + setShowDevSettings(false)} + /> + {children} + + + ); +}; + +const Container_ = styled("div")` display: flex; flex: 1; align-items: center; diff --git a/web/packages/new/photos/components/DevSettings.tsx b/web/packages/new/photos/components/DevSettings.tsx new file mode 100644 index 0000000000..926d339993 --- /dev/null +++ b/web/packages/new/photos/components/DevSettings.tsx @@ -0,0 +1,67 @@ +import ArrowForward from "@mui/icons-material/ArrowForward"; +import { + Button, + Dialog, + DialogActions, + DialogContent, + DialogContentText, + DialogTitle, + styled, + useMediaQuery, +} from "@mui/material"; +import React from "react"; +import { SlideTransition } from "./SlideTransition"; + +interface DevSettingsProps { + /** If `true`, then the dialog is shown. */ + open: boolean; + /** Called when the dialog wants to be closed. */ + onClose: () => void; +} + +/** + * A dialog allowing the user to set the API origin that the app connects to. + * See: [Note: Configuring custom server]. + */ +export const DevSettings: React.FC = ({ open, onClose }) => { + const fullScreen = useMediaQuery("(max-width: 428px)"); + + return ( + + {"Developer settings"} + + WIP + + + } + > + {"Continue"} + + + + ); +}; + +const StyledButton = styled(Button)` + /* Show an outline when the button gains keyboard focus, e.g. when the user + tabs to it. */ + &.Mui-focusVisible { + outline: 1px solid #aaa; + } +`; + +const ButtonContents = styled("div")` + /* Make the button text fill the entire space so the endIcon shows at the + trailing edge of the button. */ + width: 100%; + text-align: left; +`; diff --git a/web/packages/new/photos/components/SlideTransition.tsx b/web/packages/new/photos/components/SlideTransition.tsx new file mode 100644 index 0000000000..d5d2fda5c0 --- /dev/null +++ b/web/packages/new/photos/components/SlideTransition.tsx @@ -0,0 +1,16 @@ +import Slide from "@mui/material/Slide"; +import type { TransitionProps } from "@mui/material/transitions"; +import React from "react"; + +/** + * A React component that can be passed as the `TransitionComponent` props to a + * MUI {@link Dialog} to get it to use a slide transition (default is fade). + */ +export const SlideTransition = React.forwardRef(function Transition( + props: TransitionProps & { + children: React.ReactElement; + }, + ref: React.Ref, +) { + return ; +}); diff --git a/web/packages/new/photos/components/WhatsNew.tsx b/web/packages/new/photos/components/WhatsNew.tsx index 9a98ee5e48..76a81e7b70 100644 --- a/web/packages/new/photos/components/WhatsNew.tsx +++ b/web/packages/new/photos/components/WhatsNew.tsx @@ -10,21 +10,20 @@ import { styled, useMediaQuery, } from "@mui/material"; -import Slide from "@mui/material/Slide"; -import type { TransitionProps } from "@mui/material/transitions"; import React, { useEffect } from "react"; import { didShowWhatsNew } from "../services/changelog"; +import { SlideTransition } from "./SlideTransition"; interface WhatsNewProps { /** If `true`, then the dialog is shown. */ open: boolean; - /** Callback to invoke when the dialog wants to be closed. */ + /** Called when the dialog wants to be closed. */ onClose: () => void; } /** - * Show a dialog showing a short summary of interesting-for-the-user things - * since the last time this dialog was shown. + * A dialog showing a short summary of interesting-for-the-user things since the + * last time this dialog was shown. */ export const WhatsNew: React.FC = ({ open, onClose }) => { const fullScreen = useMediaQuery("(max-width: 428px)"); @@ -60,15 +59,6 @@ export const WhatsNew: React.FC = ({ open, onClose }) => { ); }; -const SlideTransition = React.forwardRef(function Transition( - props: TransitionProps & { - children: React.ReactElement; - }, - ref: React.Ref, -) { - return ; -}); - const ChangelogContent: React.FC = () => { // NOTE: Remember to update changelogVersion when changing the content // below. From 0f076e19becd41b4cfe7b15f0a28055d2e7b6a45 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 19 Jun 2024 14:38:54 +0530 Subject: [PATCH 069/130] Reset --- web/apps/photos/src/pages/index.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/web/apps/photos/src/pages/index.tsx b/web/apps/photos/src/pages/index.tsx index 26a4e64b3d..b532d2262b 100644 --- a/web/apps/photos/src/pages/index.tsx +++ b/web/apps/photos/src/pages/index.tsx @@ -161,7 +161,10 @@ const Container: React.FC = ({ children }) => { const handleClick = () => { setTapCount(tapCount + 1); - if (tapCount + 1 == 7) setShowDevSettings(true); + if (tapCount + 1 == 7) { + setTapCount(0); + setShowDevSettings(true); + } }; return ( From 35c8970d20958330d059f334b4c3c614073acd9a Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 19 Jun 2024 15:10:24 +0530 Subject: [PATCH 070/130] Ignore button taps --- web/apps/photos/src/pages/index.tsx | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/web/apps/photos/src/pages/index.tsx b/web/apps/photos/src/pages/index.tsx index b532d2262b..48de2c184e 100644 --- a/web/apps/photos/src/pages/index.tsx +++ b/web/apps/photos/src/pages/index.tsx @@ -159,20 +159,34 @@ const Container: React.FC = ({ children }) => { const [tapCount, setTapCount] = useState(0); const [showDevSettings, setShowDevSettings] = useState(false); - const handleClick = () => { + const handleClick: React.MouseEventHandler = (event) => { + console.log("click", tapCount, event, event.target); + + // Ignore clicks on buttons when counting up towards 7. + if (event.target instanceof HTMLButtonElement) return; + + // Otherwise increase the tap count, setTapCount(tapCount + 1); + // And show the dev settings dialog when it reaches 7. if (tapCount + 1 == 7) { setTapCount(0); setShowDevSettings(true); } }; + const handleDevSettingsClose = () => { + // Reset the count again to ignore any taps when we were open. + setTapCount(0); + // Hide the dialog. + setShowDevSettings(false); + }; + return ( <> setShowDevSettings(false)} + onClose={handleDevSettingsClose} /> {children} From 3aa3fbba6d4df2bbfc177e8c794609daeb3b2276 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 19 Jun 2024 15:16:28 +0530 Subject: [PATCH 071/130] Close on esc but not backdrop clicks --- web/packages/new/photos/components/DevSettings.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/web/packages/new/photos/components/DevSettings.tsx b/web/packages/new/photos/components/DevSettings.tsx index 926d339993..0c9399a64b 100644 --- a/web/packages/new/photos/components/DevSettings.tsx +++ b/web/packages/new/photos/components/DevSettings.tsx @@ -8,6 +8,7 @@ import { DialogTitle, styled, useMediaQuery, + type ModalProps, } from "@mui/material"; import React from "react"; import { SlideTransition } from "./SlideTransition"; @@ -26,9 +27,18 @@ interface DevSettingsProps { export const DevSettings: React.FC = ({ open, onClose }) => { const fullScreen = useMediaQuery("(max-width: 428px)"); + const handleDialogClose: ModalProps["onClose"] = ( + event: Event, + reason: string, + ) => { + // Don't close on backdrop clicks. + if (reason != "backdropClick") onClose(); + }; + return ( From c2d2612f33e098cf57ce2c6b3426b12716de7b53 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Wed, 19 Jun 2024 15:18:54 +0530 Subject: [PATCH 072/130] Simplify --- web/apps/photos/src/pages/index.tsx | 12 ++++-------- web/packages/new/photos/components/DevSettings.tsx | 5 +---- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/web/apps/photos/src/pages/index.tsx b/web/apps/photos/src/pages/index.tsx index 48de2c184e..2e4c07e577 100644 --- a/web/apps/photos/src/pages/index.tsx +++ b/web/apps/photos/src/pages/index.tsx @@ -165,6 +165,9 @@ const Container: React.FC = ({ children }) => { // Ignore clicks on buttons when counting up towards 7. if (event.target instanceof HTMLButtonElement) return; + // Ignore clicks when the dialog is already open. + if (showDevSettings) return; + // Otherwise increase the tap count, setTapCount(tapCount + 1); // And show the dev settings dialog when it reaches 7. @@ -174,19 +177,12 @@ const Container: React.FC = ({ children }) => { } }; - const handleDevSettingsClose = () => { - // Reset the count again to ignore any taps when we were open. - setTapCount(0); - // Hide the dialog. - setShowDevSettings(false); - }; - return ( <> setShowDevSettings(false)} /> {children} diff --git a/web/packages/new/photos/components/DevSettings.tsx b/web/packages/new/photos/components/DevSettings.tsx index 0c9399a64b..370135b72a 100644 --- a/web/packages/new/photos/components/DevSettings.tsx +++ b/web/packages/new/photos/components/DevSettings.tsx @@ -27,10 +27,7 @@ interface DevSettingsProps { export const DevSettings: React.FC = ({ open, onClose }) => { const fullScreen = useMediaQuery("(max-width: 428px)"); - const handleDialogClose: ModalProps["onClose"] = ( - event: Event, - reason: string, - ) => { + const handleDialogClose: ModalProps["onClose"] = (_, reason: string) => { // Don't close on backdrop clicks. if (reason != "backdropClick") onClose(); }; From a9dc8da07c5be1d3431feaee99d2c4b2bbc037c7 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 20 Jun 2024 09:56:40 +0530 Subject: [PATCH 073/130] Update formik and move it to @/new Read throught the release notes, saw only backwards compatible minor and patch changes. https://github.com/jaredpalmer/formik/releases --- web/apps/photos/package.json | 1 - web/packages/new/package.json | 1 + web/yarn.lock | 39 ++++++++++++++++++++++++++++------- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/web/apps/photos/package.json b/web/apps/photos/package.json index 9c07cc7aa6..77a63ec007 100644 --- a/web/apps/photos/package.json +++ b/web/apps/photos/package.json @@ -21,7 +21,6 @@ "exifr": "^7.1.3", "fast-srp-hap": "^2.0.4", "ffmpeg-wasm": "file:./thirdparty/ffmpeg-wasm", - "formik": "^2.1.5", "hdbscan": "0.0.1-alpha.5", "idb": "^8", "leaflet": "^1.9.4", diff --git a/web/packages/new/package.json b/web/packages/new/package.json index 72c80268f6..8ce5399055 100644 --- a/web/packages/new/package.json +++ b/web/packages/new/package.json @@ -6,6 +6,7 @@ "@/next": "*", "@/utils": "*", "@ente/shared": "*", + "formik": "^2.4", "zod": "^3" }, "devDependencies": {} diff --git a/web/yarn.lock b/web/yarn.lock index a87928bacf..be1c37c63d 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -2611,10 +2611,10 @@ form-data@^4.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" -formik@^2.1.5: - version "2.4.5" - resolved "https://registry.yarnpkg.com/formik/-/formik-2.4.5.tgz#f899b5b7a6f103a8fabb679823e8fafc7e0ee1b4" - integrity sha512-Gxlht0TD3vVdzMDHwkiNZqJ7Mvg77xQNfmBRrNtvzcHZs72TJppSTDKHpImCMJZwcWPBJ8jSQQ95GJzXFf1nAQ== +formik@^2.4: + version "2.4.6" + resolved "https://registry.yarnpkg.com/formik/-/formik-2.4.6.tgz#4da75ca80f1a827ab35b08fd98d5a76e928c9686" + integrity sha512-A+2EI7U7aG296q2TLGvNapDNTZp1khVt5Vk0Q/fyfSROss0V/V6+txt2aJnwEos44IxTCW/LYAi/zgWzlevj+g== dependencies: "@types/hoist-non-react-statics" "^3.3.1" deepmerge "^2.1.1" @@ -4375,7 +4375,16 @@ streamsearch@^1.1.0: resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764" integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg== -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -4495,7 +4504,14 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -4904,7 +4920,16 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== From 7f573f21816f82294478227002afe354fa31125a Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 20 Jun 2024 10:03:35 +0530 Subject: [PATCH 074/130] Doc and roll --- web/docs/dependencies.md | 54 ++++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/web/docs/dependencies.md b/web/docs/dependencies.md index a0eee351fa..2025fe5471 100644 --- a/web/docs/dependencies.md +++ b/web/docs/dependencies.md @@ -46,9 +46,7 @@ The root `package.json` also has a convenience dev dependency: - [concurrently](https://github.com/open-cli-tools/concurrently) for spawning parallel tasks when we invoke various yarn scripts. -## Utils - -### Crypto +## Crypto We use [libsodium](https://libsodium.gitbook.io/doc/) for encryption, key generation etc. Specifically, we use its WebAssembly and JS wrappers made using @@ -70,6 +68,27 @@ builds (See this [issue](https://github.com/jedisct1/libsodium.js/issues/326)). Updating it is not a big problem, it is just a pending chore - we want to test a bit more exhaustively when changing the crypto layer. +## Meta frameworks + +### Next.js + +[Next.js](https://nextjs.org) ("next") provides the meta framework for both the +Photos and the Auth app, and also for some of the sidecar apps like accounts and +cast. + +We use a limited subset of Next. The main thing we get out of it is a reasonable +set of defaults for bundling our app into a static export which we can then +deploy to our webserver. In addition, the Next.js page router is convenient. +Apart from this, while we use a few tidbits from Next.js here and there, overall +our apps are regular React SPAs, and are not particularly tied to Next. + +### Vite + +For some of our newer code, we have started to use [Vite](https://vitejs.dev). +It is more lower level than Next, but the bells and whistles it doesn't have are +the bells and whistles (and the accompanying complexity) that we don't need in +some cases. + ## UI ### React @@ -133,28 +152,13 @@ with Next.js. For more details, see [translations.md](translations.md). -## Meta frameworks +### Others -### Next.js +- [formik](https://github.com/jaredpalmer/formik) provides an easier to use + abstraction for dealing with form state, validation and submission states + when using React. -[Next.js](https://nextjs.org) ("next") provides the meta framework for both the -Photos and the Auth app, and also for some of the sidecar apps like accounts and -cast. - -We use a limited subset of Next. The main thing we get out of it is a reasonable -set of defaults for bundling our app into a static export which we can then -deploy to our webserver. In addition, the Next.js page router is convenient. -Apart from this, while we use a few tidbits from Next.js here and there, overall -our apps are regular React SPAs, and are not particularly tied to Next. - -### Vite - -For some of our newer code, we have started to use [Vite](https://vitejs.dev). -It is more lower level than Next, but the bells and whistles it doesn't have are -the bells and whistles (and the accompanying complexity) that we don't need in -some cases. - -## General +## Infrastructure - [comlink](https://github.com/GoogleChromeLabs/comlink) provides a minimal layer on top of Web Workers to make them more easier to use. @@ -182,6 +186,8 @@ some cases. ## Photos app specific +### General + - [react-dropzone](https://github.com/react-dropzone/react-dropzone/) is a React hook to create a drag-and-drop input zone. @@ -189,7 +195,7 @@ some cases. for converting arbitrary strings into strings that are suitable for being used as filenames. -## Face search +### Face search - [transformation-matrix](https://github.com/chrvadala/transformation-matrix) is used for performing 2D affine transformations using transformation From 445af59829907620ffbee7fcfadf1f54cde74b57 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 20 Jun 2024 10:23:00 +0530 Subject: [PATCH 075/130] Form --- .../new/photos/components/DevSettings.tsx | 68 +++++++++++++++---- 1 file changed, 53 insertions(+), 15 deletions(-) diff --git a/web/packages/new/photos/components/DevSettings.tsx b/web/packages/new/photos/components/DevSettings.tsx index 370135b72a..c8e2b4a7cd 100644 --- a/web/packages/new/photos/components/DevSettings.tsx +++ b/web/packages/new/photos/components/DevSettings.tsx @@ -7,9 +7,11 @@ import { DialogContentText, DialogTitle, styled, + TextField, useMediaQuery, type ModalProps, } from "@mui/material"; +import { useFormik } from "formik"; import React from "react"; import { SlideTransition } from "./SlideTransition"; @@ -32,6 +34,16 @@ export const DevSettings: React.FC = ({ open, onClose }) => { if (reason != "backdropClick") onClose(); }; + const formik = useFormik({ + initialValues: { apiOrigin: "" }, + onSubmit: (values, { setSubmitting }) => { + setTimeout(() => { + alert(JSON.stringify(values)); + setSubmitting(false); + }, 400); + }, + }); + return ( = ({ open, onClose }) => { TransitionComponent={SlideTransition} maxWidth="xs" > - {"Developer settings"} - - WIP - - - } - > - {"Continue"} - - +
+ {"Developer settings"} + + + + + + + } + > + {"Save"} + + } + > + {"Continue"} + + +
); }; From c54c4022add4f4e576d5e3540e1a8c335023c32d Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 20 Jun 2024 10:36:49 +0530 Subject: [PATCH 076/130] Errors --- .../new/photos/components/DevSettings.tsx | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/web/packages/new/photos/components/DevSettings.tsx b/web/packages/new/photos/components/DevSettings.tsx index c8e2b4a7cd..826dab11b3 100644 --- a/web/packages/new/photos/components/DevSettings.tsx +++ b/web/packages/new/photos/components/DevSettings.tsx @@ -34,11 +34,14 @@ export const DevSettings: React.FC = ({ open, onClose }) => { if (reason != "backdropClick") onClose(); }; - const formik = useFormik({ + const form = useFormik({ initialValues: { apiOrigin: "" }, - onSubmit: (values, { setSubmitting }) => { + onSubmit: (values, { setSubmitting, setErrors }) => { setTimeout(() => { alert(JSON.stringify(values)); + if (values.apiOrigin.startsWith("test")) { + setErrors({ apiOrigin: "Testing indeed" }); + } setSubmitting(false); }, 400); }, @@ -51,7 +54,7 @@ export const DevSettings: React.FC = ({ open, onClose }) => { TransitionComponent={SlideTransition} maxWidth="xs" > -
+ {"Developer settings"} @@ -60,12 +63,16 @@ export const DevSettings: React.FC = ({ open, onClose }) => { id="apiOrigin" name="apiOrigin" label="Server endpoint" - value={formik.values.apiOrigin} - onChange={formik.handleChange} - onBlur={formik.handleBlur} + placeholder="http://localhost:8080" + value={form.values.apiOrigin} + onChange={form.handleChange} + onBlur={form.handleBlur} error={ - formik.touched.apiOrigin && - !!formik.errors.apiOrigin + form.touched.apiOrigin && + !!form.errors.apiOrigin + } + helperText={ + form.touched.apiOrigin && form.errors.apiOrigin } /> @@ -75,7 +82,7 @@ export const DevSettings: React.FC = ({ open, onClose }) => { type="submit" color="accent" fullWidth - disabled={formik.isSubmitting} + disabled={form.isSubmitting} disableRipple endIcon={} > From 6ea003a9a1d60be8d27108f4e26b7b2ecf1e8819 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 20 Jun 2024 10:47:59 +0530 Subject: [PATCH 077/130] Styling --- .../new/photos/components/DevSettings.tsx | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/web/packages/new/photos/components/DevSettings.tsx b/web/packages/new/photos/components/DevSettings.tsx index 826dab11b3..8c7d9356fe 100644 --- a/web/packages/new/photos/components/DevSettings.tsx +++ b/web/packages/new/photos/components/DevSettings.tsx @@ -1,4 +1,3 @@ -import ArrowForward from "@mui/icons-material/ArrowForward"; import { Button, Dialog, @@ -6,8 +5,8 @@ import { DialogContent, DialogContentText, DialogTitle, - styled, TextField, + styled, useMediaQuery, type ModalProps, } from "@mui/material"; @@ -84,18 +83,16 @@ export const DevSettings: React.FC = ({ open, onClose }) => { fullWidth disabled={form.isSubmitting} disableRipple - endIcon={} > - {"Save"} + {"Save"} } > - {"Continue"} + {"Cancel"} @@ -110,10 +107,3 @@ const StyledButton = styled(Button)` outline: 1px solid #aaa; } `; - -const ButtonContents = styled("div")` - /* Make the button text fill the entire space so the endIcon shows at the - trailing edge of the button. */ - width: 100%; - text-align: left; -`; From 7a2f08f49aabfc8a44b9559671a35a2ed9a61c93 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 20 Jun 2024 10:53:43 +0530 Subject: [PATCH 078/130] ex --- .../new/photos/components/DevSettings.tsx | 19 +++++-------------- .../photos/components/FocusVisibleButton.tsx | 10 ++++++++++ .../new/photos/components/WhatsNew.tsx | 14 +++----------- 3 files changed, 18 insertions(+), 25 deletions(-) create mode 100644 web/packages/new/photos/components/FocusVisibleButton.tsx diff --git a/web/packages/new/photos/components/DevSettings.tsx b/web/packages/new/photos/components/DevSettings.tsx index 8c7d9356fe..bf65559851 100644 --- a/web/packages/new/photos/components/DevSettings.tsx +++ b/web/packages/new/photos/components/DevSettings.tsx @@ -1,17 +1,16 @@ import { - Button, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, TextField, - styled, useMediaQuery, type ModalProps, } from "@mui/material"; import { useFormik } from "formik"; import React from "react"; +import { FocusVisibleButton } from "./FocusVisibleButton"; import { SlideTransition } from "./SlideTransition"; interface DevSettingsProps { @@ -77,7 +76,7 @@ export const DevSettings: React.FC = ({ open, onClose }) => {
- = ({ open, onClose }) => { disableRipple > {"Save"} - - + {"Cancel"} - +
); }; - -const StyledButton = styled(Button)` - /* Show an outline when the button gains keyboard focus, e.g. when the user - tabs to it. */ - &.Mui-focusVisible { - outline: 1px solid #aaa; - } -`; diff --git a/web/packages/new/photos/components/FocusVisibleButton.tsx b/web/packages/new/photos/components/FocusVisibleButton.tsx new file mode 100644 index 0000000000..306b62e173 --- /dev/null +++ b/web/packages/new/photos/components/FocusVisibleButton.tsx @@ -0,0 +1,10 @@ +import { Button, styled } from "@mui/material"; + +/** A MUI {@link Button} that shows a keyboard focus indicator. */ +export const FocusVisibleButton = styled(Button)` + /* Show an outline when the button gains keyboard focus, e.g. when the user + tabs to it. */ + &.Mui-focusVisible { + outline: 1px solid #aaa; + } +`; diff --git a/web/packages/new/photos/components/WhatsNew.tsx b/web/packages/new/photos/components/WhatsNew.tsx index 76a81e7b70..681360a454 100644 --- a/web/packages/new/photos/components/WhatsNew.tsx +++ b/web/packages/new/photos/components/WhatsNew.tsx @@ -1,6 +1,5 @@ import ArrowForward from "@mui/icons-material/ArrowForward"; import { - Button, Dialog, DialogActions, DialogContent, @@ -12,6 +11,7 @@ import { } from "@mui/material"; import React, { useEffect } from "react"; import { didShowWhatsNew } from "../services/changelog"; +import { FocusVisibleButton } from "./FocusVisibleButton"; import { SlideTransition } from "./SlideTransition"; interface WhatsNewProps { @@ -45,7 +45,7 @@ export const WhatsNew: React.FC = ({ open, onClose }) => { - = ({ open, onClose }) => { endIcon={} > {"Continue"} - + ); @@ -92,14 +92,6 @@ const StyledUL = styled("ul")` } `; -const StyledButton = styled(Button)` - /* Show an outline when the button gains keyboard focus, e.g. when the user - tabs to it. */ - &.Mui-focusVisible { - outline: 1px solid #aaa; - } -`; - const ButtonContents = styled("div")` /* Make the button text fill the entire space so the endIcon shows at the trailing edge of the button. */ From a170acb28bc4cd1c070b9dd348cd4c5d17b9123b Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 20 Jun 2024 10:58:32 +0530 Subject: [PATCH 079/130] ut --- web/packages/next/i18n.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/web/packages/next/i18n.ts b/web/packages/next/i18n.ts index cdc60e27ca..da63bc8c51 100644 --- a/web/packages/next/i18n.ts +++ b/web/packages/next/i18n.ts @@ -261,3 +261,15 @@ export const setLocaleInUse = async (locale: SupportedLocale) => { localStorage.setItem("locale", locale); return i18n.changeLanguage(locale); }; + +/** + * A no-op marker for strings that, for various reasons, are not translated. + * + * This function does nothing, it just returns back the passed it string + * verbatim. It is only kept as a way for us to keep track of strings that are + * not translated (and for some reason, are currently not meant to be), but + * still are user visible. + * + * It is the sibling of the {@link t} function provided by i18next. + */ +export const ut = (s: string) => s; From c79507a5d4826594e3371380f0b3628b597de251 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 20 Jun 2024 11:01:17 +0530 Subject: [PATCH 080/130] use --- .../new/photos/components/WhatsNew.tsx | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/web/packages/new/photos/components/WhatsNew.tsx b/web/packages/new/photos/components/WhatsNew.tsx index 681360a454..f6b02b1f3a 100644 --- a/web/packages/new/photos/components/WhatsNew.tsx +++ b/web/packages/new/photos/components/WhatsNew.tsx @@ -1,3 +1,4 @@ +import { ut } from "@/next/i18n"; import ArrowForward from "@mui/icons-material/ArrowForward"; import { Dialog, @@ -38,7 +39,7 @@ export const WhatsNew: React.FC = ({ open, onClose }) => { TransitionComponent={SlideTransition} maxWidth="xs" > - {"What's new"} + {ut("What's new")} @@ -52,7 +53,7 @@ export const WhatsNew: React.FC = ({ open, onClose }) => { disableRipple endIcon={} > - {"Continue"} + {ut("Continue")} @@ -68,16 +69,19 @@ const ChangelogContent: React.FC = () => {
  • - Support for Passkeys + {ut("Support for Passkeys")} - Passkeys can now be used as a second factor authentication - mechanism. + {ut( + "Passkeys can now be used as a second factor authentication mechanism.", + )}
  • - Window size + {ut("Window size")} - {"The app's window will remember its size and position."} + {ut( + "The app's window will remember its size and position.", + )}
  • From 93dec0a9f480579bdd33540f62c4dc0cbb2099ea Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 20 Jun 2024 11:05:16 +0530 Subject: [PATCH 081/130] Tweaks --- .../new/photos/components/DevSettings.tsx | 39 +++++++++---------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/web/packages/new/photos/components/DevSettings.tsx b/web/packages/new/photos/components/DevSettings.tsx index bf65559851..0a7c437e5a 100644 --- a/web/packages/new/photos/components/DevSettings.tsx +++ b/web/packages/new/photos/components/DevSettings.tsx @@ -2,13 +2,13 @@ import { Dialog, DialogActions, DialogContent, - DialogContentText, DialogTitle, TextField, useMediaQuery, type ModalProps, } from "@mui/material"; import { useFormik } from "formik"; +import { t } from "i18next"; import React from "react"; import { FocusVisibleButton } from "./FocusVisibleButton"; import { SlideTransition } from "./SlideTransition"; @@ -55,25 +55,22 @@ export const DevSettings: React.FC = ({ open, onClose }) => {
    {"Developer settings"} - - - + = ({ open, onClose }) => { fullWidth disableRipple > - {"Cancel"} + {t("CANCEL")}
    From 46644d9d4d86144188ca2f9e730ca7609933dcf9 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 20 Jun 2024 11:23:30 +0530 Subject: [PATCH 082/130] Save --- .../new/photos/components/DevSettings.tsx | 52 ++++++++++++++++--- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/web/packages/new/photos/components/DevSettings.tsx b/web/packages/new/photos/components/DevSettings.tsx index 0a7c437e5a..328fd41595 100644 --- a/web/packages/new/photos/components/DevSettings.tsx +++ b/web/packages/new/photos/components/DevSettings.tsx @@ -1,3 +1,4 @@ +import log from "@/next/log"; import { Dialog, DialogActions, @@ -34,14 +35,15 @@ export const DevSettings: React.FC = ({ open, onClose }) => { const form = useFormik({ initialValues: { apiOrigin: "" }, - onSubmit: (values, { setSubmitting, setErrors }) => { - setTimeout(() => { - alert(JSON.stringify(values)); - if (values.apiOrigin.startsWith("test")) { - setErrors({ apiOrigin: "Testing indeed" }); - } + onSubmit: async (values, { setSubmitting, setErrors }) => { + const res = await updateAPIOrigin(values.apiOrigin); + if (typeof res == "string") { + setErrors({ apiOrigin: res }); + } else { setSubmitting(false); - }, 400); + // Add a bit of delay to acknowledge the update better. + setTimeout(onClose, 200); + } }, }); @@ -95,3 +97,39 @@ export const DevSettings: React.FC = ({ open, onClose }) => { ); }; + +/** + * Save {@link origin} to local storage after verifying it with a ping. + * + * The given {@link origin} will be verifying by making an API call to the + * `/ping` endpoint. If that succeeds, then it will be saved to local storage, + * and all subsequent API calls will use it as the {@link apiOrigin}. + * + * See: [Note: Configuring custom server]. + * + * @param origin The new API origin to use. Pass an empty string to clear the + * previously saved API origin (if any). + * + * @returns true on success, and the user visible error message string + * otherwise. + */ +const updateAPIOrigin = async (origin: string): Promise => { + if (!origin) { + localStorage.removeItem("apiOrigin"); + return true; + } + + const url = `${origin}/ping`; + try { + const res = await fetch(url); + if (!res.ok) + throw new Error(`Failed to fetch ${url}: HTTP ${res.status}`); + localStorage.setItem("apiOrigin", origin); + return true; + } catch (e) { + log.error("Failed to ping the provided origin", e); + // The person using this is likely a developer, just give them the + // original error itself, they might find it helpful. + return e instanceof Error ? e.message : t("ERROR"); + } +}; From 94cd1991ce7d319e8102d688bb0383c5e9be688e Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sun, 23 Jun 2024 08:50:53 +0530 Subject: [PATCH 083/130] autoFocus --- web/packages/new/photos/components/DevSettings.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/web/packages/new/photos/components/DevSettings.tsx b/web/packages/new/photos/components/DevSettings.tsx index 328fd41595..5c65b2be7d 100644 --- a/web/packages/new/photos/components/DevSettings.tsx +++ b/web/packages/new/photos/components/DevSettings.tsx @@ -59,6 +59,7 @@ export const DevSettings: React.FC = ({ open, onClose }) => { Date: Sun, 23 Jun 2024 09:08:27 +0530 Subject: [PATCH 084/130] document @mui/material-icons --- web/docs/dependencies.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/web/docs/dependencies.md b/web/docs/dependencies.md index 2025fe5471..56b6712e00 100644 --- a/web/docs/dependencies.md +++ b/web/docs/dependencies.md @@ -96,13 +96,21 @@ some cases. [React](https://react.dev) ("react") is our core framework. It also has a sibling "react-dom" package that renders JSX to the DOM. -### MUI and Emotion +### MUI and Material Icons -We use [MUI](https://mui.com) ("@mui/material"), which is a React component -library, to get a base set of components. +We use [MUI](https://mui.com)'s + +- [@mui/material](https://mui.com/material-ui/getting-started/installation/), + which is a React component library, to get a base set of components; and + +- [@mui/material-icons](https://mui.com/material-ui/material-icons/). which + provides Material icons exported as React components (a `SvgIcon`). + +### Emotion MUI uses [Emotion](https://emotion.sh/) (a styled-component variant) as its -preferred CSS-in-JS library. +preferred CSS-in-JS library, and we use the same in our code too to reduce +moving parts. Emotion itself comes in many parts, of which we need the following: From 0f1d45587e9e4a77a17bb523918296140a0609db Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sun, 23 Jun 2024 09:22:51 +0530 Subject: [PATCH 085/130] [server] Reduce DB healthcheck interval in starter Docker compose template The museum container depends on the postgres container being up and the DB being ready to accept connections. To enforce this dependency, we use the healthcheck attribute. See: https://docs.docker.com/compose/startup-order/ The value of the healthcheck interval was set to 1s since the default (30s) caused each `docker compose up` to require at least 30 seconds on each startup, which was prohibitive. The downside is that the healthchecks continue to run beyond the startup phase too, and for small VMs, this caused a lot of unnecessary CPU usage. Thankfully, now Docker has a new option for a different healthcheck during the start phase: > start interval is the time between health checks during the start period. This option requires Docker Engine version 25.0 or later. They were added in Docker compose 2.20.2, released an year ago (2023-07-19). https://docs.docker.com/compose/release-notes/#2202 --- server/compose.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/server/compose.yaml b/server/compose.yaml index a7d5a2c39e..f20ebe2b8f 100644 --- a/server/compose.yaml +++ b/server/compose.yaml @@ -49,9 +49,8 @@ services: "-U", "pguser" ] - interval: 1s - timeout: 5s - retries: 20 + start_period: 40s + start_interval: 1s volumes: - postgres-data:/var/lib/postgresql/data networks: From 169e70cc0fce252fbbaac7b9fa3e411b8f59035f Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Sun, 23 Jun 2024 10:38:00 +0530 Subject: [PATCH 086/130] [cli] Bump version cli-v0.1.17 --- cli/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/main.go b/cli/main.go index be14a8686c..5b65929aea 100644 --- a/cli/main.go +++ b/cli/main.go @@ -15,7 +15,7 @@ import ( "strings" ) -var AppVersion = "0.1.16" +var AppVersion = "0.1.17" func main() { cliDBPath, err := GetCLIConfigPath() From 567dfb7e6b9fc38d62e426c480da0a298e7966a4 Mon Sep 17 00:00:00 2001 From: atyabbin Date: Sun, 23 Jun 2024 11:13:59 +0530 Subject: [PATCH 087/130] Added options for delete account and update subscription --- infra/staff/package.json | 5 +- infra/staff/src/App.css | 333 ++++++++++++++++++ infra/staff/src/App.tsx | 12 +- infra/staff/src/components/Sidebar.tsx | 99 +++++- .../src/components/UpdateSubscription.tsx | 183 ++++++++++ 5 files changed, 611 insertions(+), 21 deletions(-) create mode 100644 infra/staff/src/components/UpdateSubscription.tsx diff --git a/infra/staff/package.json b/infra/staff/package.json index d81c891868..6939e38937 100644 --- a/infra/staff/package.json +++ b/infra/staff/package.json @@ -11,12 +11,15 @@ "preview": "vite preview" }, "dependencies": { + "date-fns": "^3.6.0", "react": "^18", + "react-datepicker": "^7.1.0", "react-dom": "^18", "react-toastify": "^10.0.5", "zod": "^3" }, "devDependencies": { + "@rollup/plugin-node-resolve": "^15.2.3", "@types/react": "^18", "@types/react-dom": "^18", "@typescript-eslint/eslint-plugin": "^7", @@ -29,7 +32,7 @@ "prettier": "^3", "prettier-plugin-organize-imports": "^3.2", "prettier-plugin-packagejson": "^2.5", - "typescript": "^5.4.5", + "typescript": "^5", "vite": "^5.2" }, "packageManager": "yarn@1.22.21" diff --git a/infra/staff/src/App.css b/infra/staff/src/App.css index 4e7c66ce7a..8e702df72d 100644 --- a/infra/staff/src/App.css +++ b/infra/staff/src/App.css @@ -19,6 +19,21 @@ border-radius: 5px; margin-top: 20px; } +#submitbtn{ + + padding: 10px 20px; + font-size: 16px; + cursor: pointer; + background-color: #009879; + color: white; + border: none; + border-radius: 5px; + margin-top: 20px; +} + +#submitbtn:hover { + background-color: #007c6c; +} .fetch-button-container button:hover { background-color: #007c6c; @@ -122,3 +137,321 @@ button { .dropdown-menu button:hover { background-color: #f0f0f0; } +.modal { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: #ffffff; + padding: 20px; + border: 1px solid #ccc; + border-radius: 5px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + z-index: 1000; + max-width: 80%; + max-height: 80%; + overflow: auto; +} + +.modal-header { + display: flex; + justify-content: space-between; + align-items: center; + padding-bottom: 10px; + border-bottom: 1px solid #ccc; +} + +.modal-header .close-btn { + cursor: pointer; + color: #777; + font-size: 20px; +} + +.modal-content { + margin-top: 10px; +} + +/* Styles for draggable modal */ +.modal.draggable { + cursor: move; +} + +.modal.draggable .modal-header { + cursor: move; +} +.popup { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: lightgreen; + padding: 20px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); + z-index: 1000; +} + +.popup-content { + display: flex; + flex-direction: column; +} + +.popup-content div { + margin-bottom: 10px; +} +:root { + --popup-bg-color-light: #fff; + --popup-bg-color-dark: #2c2c2c; + --popup-border-color-light: #ccc; + --popup-border-color-dark: #444; + --popup-text-color-light: #000; + --popup-text-color-dark: #fff; + --popup-shadow-light: rgba(0, 0, 0, 0.1); + --popup-shadow-dark: rgba(255, 255, 255, 0.1); +} + +.update-subscription-popup { + position: fixed; + top: 50%; + left: 50%; + width: 400px; + transform: translate(-50%, -50%); + background-color: var(--popup-bg-color-light); + border: 1px solid var(--popup-border-color-light); + padding: 20px; + z-index: 1000; + box-shadow: 0px 0px 10px var(--popup-shadow-light); +} + +.popup-content { + display: flex; + flex-direction: column; +} + +.close-button { + align-self: flex-end; + background: none; + border: none; + font-size: 1.5rem; + cursor: pointer; + color: var(--popup-text-color-light); +} + +.popup-content h2 { + margin-top: 0; +} + +.popup-content form label { + display: block; + margin-bottom: 10px; +} + +.popup-content form input, +.popup-content form select { + width: 100%; + padding: 8px; + margin-top: 5px; + background-color: var(--popup-bg-color-light); + color: var(--popup-text-color-light); + border: 1px solid var(--popup-border-color-light); +} + +.popup-content form button { + padding: 10px 15px; + margin-top: 10px; + cursor: pointer; + background-color: var(--popup-bg-color-light); + color: var(--popup-text-color-light); + border: 1px solid var(--popup-border-color-light); +} + +.custom-select { + position: relative; + width: 100%; +} + +.custom-select select { + width: 100%; + padding: 8px; + cursor: pointer; + background-color: var(--popup-bg-color-light); + color: var(--popup-text-color-light); + border: 1px solid var(--popup-border-color-light); + appearance: none; +} + +.custom-select::after { + content: "\25BC"; + position: absolute; + top: 50%; + right: 10px; + transform: translateY(-50%); + pointer-events: none; +} + +.message { + margin-top: 10px; + padding: 10px; + border-radius: 5px; +} + +.message.error { + background-color: #f8d7da; + color: #721c24; +} + +.message.success { + background-color: #d4edda; + color: #155724; +} + +@media (prefers-color-scheme: dark) { + .update-subscription-popup { + background-color: var(--popup-bg-color-dark); + border-color: var(--popup-border-color-dark); + color: var(--popup-text-color-dark); + box-shadow: 0px 0px 10px var(--popup-shadow-dark); + } + + .close-button { + color: var(--popup-text-color-dark); + } + + .popup-content form input, + .popup-content form select { + background-color: var(--popup-bg-color-dark); + color: var(--popup-text-color-dark); + border: 1px solid var(--popup-border-color-dark); + } + + .popup-content form button { + background-color: var(--popup-bg-color-dark); + color: var(--popup-text-color-dark); + border: 1px solid var(--popup-border-color-dark); + } +} +:root { + --popup-bg-color-light: #fff; + --popup-bg-color-dark: #2c2c2c; + --popup-border-color-light: #ccc; + --popup-border-color-dark: #444; + --popup-text-color-light: #000; + --popup-text-color-dark: #fff; + --popup-shadow-light: rgba(0, 0, 0, 0.1); + --popup-shadow-dark: rgba(255, 255, 255, 0.1); +} + +.update-subscription-popup { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: var(--popup-bg-color-light); + border: 1px solid var(--popup-border-color-light); + padding: 20px; + z-index: 1000; + box-shadow: 0px 0px 10px var(--popup-shadow-light); +} + +.popup-content { + display: flex; + flex-direction: column; +} + +.close-button { + align-self: flex-end; + background: none; + border: none; + font-size: 1.5rem; + cursor: pointer; + color: var(--popup-text-color-light); +} + +.popup-content h2 { + margin-top: 0; +} + +.form-group { + margin-bottom: 20px; +} + +.form-group label { + display: block; + margin-bottom: 5px; +} + +.form-group input, +.form-group select { + width: 100%; + padding: 8px; + background-color: var(--popup-bg-color-light); + color: var(--popup-text-color-light); + border: 1px solid var(--popup-border-color-light); + border-radius: 5px; + margin-top: 5px; +} + +.custom-select { + position: relative; + width: 100%; +} + +.custom-select select { + width: 100%; + padding: 8px; + cursor: pointer; + background-color: var(--popup-bg-color-light); + color: var(--popup-text-color-light); + border: 1px solid var(--popup-border-color-light); + appearance: none; +} + +.custom-select::after { + content: "\25BC"; + position: absolute; + top: 50%; + right: 10px; + transform: translateY(-50%); + pointer-events: none; +} + +.message { + margin-top: 10px; + padding: 10px; + border-radius: 5px; +} + +.message.error { + background-color: #f8d7da; + color: #721c24; +} + +.message.success { + background-color: #d4edda; + color: #155724; +} + +@media (prefers-color-scheme: dark) { + .update-subscription-popup { + background-color: var(--popup-bg-color-dark); + border-color: var(--popup-border-color-dark); + color: var(--popup-text-color-dark); + box-shadow: 0px 0px 10px var(--popup-shadow-dark); + } + + .close-button { + color: var(--popup-text-color-dark); + } + + .form-group input, + .form-group select { + background-color: var(--popup-bg-color-dark); + color: var(--popup-text-color-dark); + border: 1px solid var(--popup-border-color-dark); + } + + .form-group button { + background-color: var(--popup-bg-color-dark); + color: var(--popup-text-color-dark); + border: 1px solid var(--popup-border-color-dark); + } +} diff --git a/infra/staff/src/App.tsx b/infra/staff/src/App.tsx index e27c24ccbf..bad145673b 100644 --- a/infra/staff/src/App.tsx +++ b/infra/staff/src/App.tsx @@ -15,7 +15,7 @@ export const App: React.FC = () => { const [email, setEmail] = useState(""); const [userData, setUserData] = useState(null); const [error, setError] = useState(null); - const [isDataFetched, setIsDataFetched] = useState(false); // Track if data has been fetched successfully + const [isDataFetched, setIsDataFetched] = useState(false); useEffect(() => { const storedToken = localStorage.getItem("token"); @@ -46,11 +46,11 @@ export const App: React.FC = () => { console.log("API Response:", userDataResponse); setUserData(userDataResponse); setError(null); - setIsDataFetched(true); // Set to true when data is successfully fetched + setIsDataFetched(true); } catch (error) { console.error("Error fetching data:", error); setError((error as Error).message); - setIsDataFetched(false); // Set to false if there's an error fetching data + setIsDataFetched(false); } }; @@ -113,7 +113,7 @@ export const App: React.FC = () => { displayValue = value; } } else if (typeof value === "object" && value !== null) { - displayValue = JSON.stringify(value, null, 2); // Pretty print JSON + displayValue = JSON.stringify(value, null, 2); } else if (value === null) { displayValue = "null"; } else if ( @@ -124,7 +124,7 @@ export const App: React.FC = () => { } else if (typeof value === "undefined") { displayValue = "undefined"; } else { - displayValue = value as string; // Fallback for any other types + displayValue = value as string; } return ( @@ -157,7 +157,7 @@ export const App: React.FC = () => { const handleKeyPress = (event: React.KeyboardEvent) => { if (event.key === "Enter") { - event.preventDefault(); // Prevent form submission + event.preventDefault(); fetchData().catch((error: unknown) => console.error("Fetch data error:", error), ); diff --git a/infra/staff/src/components/Sidebar.tsx b/infra/staff/src/components/Sidebar.tsx index 8e60b98f7c..a16aebf238 100644 --- a/infra/staff/src/components/Sidebar.tsx +++ b/infra/staff/src/components/Sidebar.tsx @@ -1,6 +1,7 @@ -import React, { useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; import "../App.css"; import { apiOrigin } from "../services/support"; +import UpdateSubscription from "./UpdateSubscription"; // Import the UpdateSubscription component interface SidebarProps { token: string; @@ -12,18 +13,36 @@ interface UserData { ID: string; }; } +interface ActionResponse { + success?: boolean; + message?: string; +} export const Sidebar: React.FC = ({ token, email }) => { - const [, /*userId*/ setUserId] = useState(null); + const [userId, setUserId] = useState(null); const [error, setError] = useState(null); const [message, setMessage] = useState(null); const [dropdownVisible, setDropdownVisible] = useState(false); + const [showUpdateSubscription, setShowUpdateSubscription] = + useState(false); // State to control UpdateSubscription popup - interface ApiResponse { - data: { - userId: string; + const dropdownRef = useRef(null); + + useEffect(() => { + document.addEventListener("mousedown", handleClickOutside); + return () => { + document.removeEventListener("mousedown", handleClickOutside); }; - } + }, []); + + const handleClickOutside = (event: MouseEvent) => { + if ( + dropdownRef.current && + !dropdownRef.current.contains(event.target as Node) + ) { + setDropdownVisible(false); + } + }; const fetchData = async (): Promise => { if (!email || !token) { @@ -32,9 +51,7 @@ export const Sidebar: React.FC = ({ token, email }) => { } try { - const url = `${apiOrigin}/admin/user?email=${encodeURIComponent( - email, - )}&token=${encodeURIComponent(token)}`; + const url = `${apiOrigin}/admin/user?email=${encodeURIComponent(email)}&token=${encodeURIComponent(token)}`; const response = await fetch(url); if (!response.ok) { throw new Error("Network response was not ok"); @@ -70,9 +87,7 @@ export const Sidebar: React.FC = ({ token, email }) => { Closefamily: "/admin/user/close-family", }; - const url = `${apiOrigin}${actionUrls[action]}?id=${encodeURIComponent( - userId, - )}&token=${encodeURIComponent(token)}`; + const url = `${apiOrigin}${actionUrls[action]}?id=${encodeURIComponent(userId)}&token=${encodeURIComponent(token)}`; const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, @@ -85,7 +100,7 @@ export const Sidebar: React.FC = ({ token, email }) => { ); } - const result = (await response.json()) as ApiResponse; + const result = (await response.json()) as ActionResponse; console.log("API Response:", result); setMessage(`${action} completed successfully`); @@ -109,8 +124,55 @@ export const Sidebar: React.FC = ({ token, email }) => { } }; + const deleteUser = async () => { + try { + const url = `${apiOrigin}/admin/user/delete?email=${encodeURIComponent(email)}&token=${encodeURIComponent(token)}`; + const response = await fetch(url, { + method: "DELETE", + }); + + if (!response.ok) { + throw new Error( + `Network response was not ok: ${response.status}`, + ); + } + + setMessage("Delete Account completed successfully"); + setError(null); + setTimeout(() => { + setMessage(null); + }, 1000); + setDropdownVisible(false); + } catch (error) { + console.error(`Error deleting account:`, error); + setError( + error instanceof Error && typeof error.message === "string" + ? error.message + : "An unexpected error occurred", + ); + + setTimeout(() => { + setError(null); + }, 1000); + setMessage(null); + } + }; + const handleActionClick = async (action: string) => { try { + if (action === "UpdateSubscription") { + const fetchedUserId = await fetchData(); + if (fetchedUserId) { + setShowUpdateSubscription(true); + } + return; + } + + if (action === "DeleteAccount") { + await deleteUser(); + return; + } + const fetchedUserId = await fetchData(); if (!fetchedUserId) { throw new Error("Incorrect email id or token"); @@ -140,6 +202,8 @@ export const Sidebar: React.FC = ({ token, email }) => { { value: "Disable2FA", label: "Disable 2FA" }, { value: "Closefamily", label: "Close Family" }, { value: "DisablePasskeys", label: "Disable Passkeys" }, + { value: "DeleteAccount", label: "Delete Account" }, + { value: "UpdateSubscription", label: "Update Subscription" }, // New option added here ]; return ( @@ -149,7 +213,7 @@ export const Sidebar: React.FC = ({ token, email }) => { MORE {dropdownVisible && ( -
    +
      {dropdownOptions.map((option) => (
    • @@ -178,6 +242,13 @@ export const Sidebar: React.FC = ({ token, email }) => { {error ? `Error: ${error}` : `Success: ${message}`}
    )} + {showUpdateSubscription && userId && ( + setShowUpdateSubscription(false)} + /> + )}
    ); }; diff --git a/infra/staff/src/components/UpdateSubscription.tsx b/infra/staff/src/components/UpdateSubscription.tsx new file mode 100644 index 0000000000..2794bdb6b8 --- /dev/null +++ b/infra/staff/src/components/UpdateSubscription.tsx @@ -0,0 +1,183 @@ +import React, { useEffect, useState } from "react"; +import DatePicker from "react-datepicker"; +import "react-datepicker/dist/react-datepicker.css"; +import "../App.css"; +interface UpdateSubscriptionProps { + token: string; + userId: string; + onClose: () => void; +} + +export const UpdateSubscription: React.FC = ({ + token, + userId, + onClose, +}) => { + const [expiryTime, setExpiryTime] = useState(null); + const [productId, setProductId] = useState("50gb_monthly"); + const [paymentProvider, setPaymentProvider] = useState("bitpay"); + const [transactionId, setTransactionId] = useState(""); + const [message, setMessage] = useState(null); + const [error, setError] = useState(null); + const [storage, setStorage] = useState(""); + + + useEffect(() => { + if (productId === "50gb_yearly" || productId === "50gb_monthly") { + setStorage(50 * 1024 * 1024 * 1024); + } else if ( + productId === "200gb_yearly" || + productId === "200gb_monthly" + ) { + setStorage(200 * 1024 * 1024 * 1024); + } else if ( + productId === "500gb_yearly" || + productId === "500gb_monthly" + ) { + setStorage(500 * 1024 * 1024 * 1024); + } else if ( + productId === "2000gb_yearly" || + productId === "2000gb_monthly" + ) { + setStorage(2000 * 1024 * 1024 * 1024); + } else { + setStorage(""); + } + }, [productId]); + + const handleSubmit = async (event: React.FormEvent) => { + event.preventDefault(); + + + const expiryTimeTimestamp = expiryTime + ? expiryTime.getTime() * 1000 + : ""; + + const url = `http://localhost:8080/admin/user/subscription`; + const body = { + userId, + storage, + expiryTime: expiryTimeTimestamp, + productId, + paymentProvider, + transactionId, + }; + + try { + const response = await fetch(url, { + method: "PUT", + headers: { + "Content-Type": "application/json", + "X-AUTH-TOKEN": token, + }, + body: JSON.stringify(body), + }); + + if (!response.ok) { + throw new Error( + `Network response was not ok: ${response.status}`, + ); + } + + setMessage("Subscription updated successfully"); + setError(null); + setTimeout(() => { + setMessage(null); + onClose(); + }, 1000); + } catch (error) { + console.error("Error updating subscription:", error); + setError( + error instanceof Error && typeof error.message === "string" + ? error.message + : "An unexpected error occurred", + ); + setTimeout(() => { + setError(null); + }, 1000); + } + }; + + const handleSubmitWrapper = (event: React.FormEvent) => { + handleSubmit(event).catch((error: unknown) => { + console.error("Error in handleSubmit:", error); + }); + }; + + return ( +
    +
    + +

    Update Subscription

    +
    +
    + + setExpiryTime(date)} + dateFormat="dd/MM/yyyy" + showYearDropdown + scrollableYearDropdown + yearDropdownItemNumber={15} + /> +
    +
    + + +
    +
    + + +
    +
    + + setTransactionId(e.target.value)} + /> +
    + +
    + {(error ?? message) && ( +
    + {error ? `Error: ${error}` : `Success: ${message}`} +
    + )} +
    +
    + ); +}; + +export default UpdateSubscription; From 206e387834a5a4946e052416d0dd2b5084c01b5f Mon Sep 17 00:00:00 2001 From: atyabbin Date: Sun, 23 Jun 2024 11:37:20 +0530 Subject: [PATCH 088/130] Modified calender for date picking --- infra/staff/src/App.css | 5 +- infra/staff/src/App.tsx | 12 +- .../src/components/UpdateSubscription.tsx | 17 +- infra/staff/yarn.lock | 361 ++++++++++++++++-- 4 files changed, 342 insertions(+), 53 deletions(-) diff --git a/infra/staff/src/App.css b/infra/staff/src/App.css index 8e702df72d..e080f6b3b2 100644 --- a/infra/staff/src/App.css +++ b/infra/staff/src/App.css @@ -19,8 +19,7 @@ border-radius: 5px; margin-top: 20px; } -#submitbtn{ - +#submitbtn { padding: 10px 20px; font-size: 16px; cursor: pointer; @@ -30,7 +29,7 @@ border-radius: 5px; margin-top: 20px; } - + #submitbtn:hover { background-color: #007c6c; } diff --git a/infra/staff/src/App.tsx b/infra/staff/src/App.tsx index bad145673b..aaac53273d 100644 --- a/infra/staff/src/App.tsx +++ b/infra/staff/src/App.tsx @@ -15,7 +15,7 @@ export const App: React.FC = () => { const [email, setEmail] = useState(""); const [userData, setUserData] = useState(null); const [error, setError] = useState(null); - const [isDataFetched, setIsDataFetched] = useState(false); + const [isDataFetched, setIsDataFetched] = useState(false); useEffect(() => { const storedToken = localStorage.getItem("token"); @@ -46,11 +46,11 @@ export const App: React.FC = () => { console.log("API Response:", userDataResponse); setUserData(userDataResponse); setError(null); - setIsDataFetched(true); + setIsDataFetched(true); } catch (error) { console.error("Error fetching data:", error); setError((error as Error).message); - setIsDataFetched(false); + setIsDataFetched(false); } }; @@ -113,7 +113,7 @@ export const App: React.FC = () => { displayValue = value; } } else if (typeof value === "object" && value !== null) { - displayValue = JSON.stringify(value, null, 2); + displayValue = JSON.stringify(value, null, 2); } else if (value === null) { displayValue = "null"; } else if ( @@ -124,7 +124,7 @@ export const App: React.FC = () => { } else if (typeof value === "undefined") { displayValue = "undefined"; } else { - displayValue = value as string; + displayValue = value as string; } return ( @@ -157,7 +157,7 @@ export const App: React.FC = () => { const handleKeyPress = (event: React.KeyboardEvent) => { if (event.key === "Enter") { - event.preventDefault(); + event.preventDefault(); fetchData().catch((error: unknown) => console.error("Fetch data error:", error), ); diff --git a/infra/staff/src/components/UpdateSubscription.tsx b/infra/staff/src/components/UpdateSubscription.tsx index 2794bdb6b8..292d2947a5 100644 --- a/infra/staff/src/components/UpdateSubscription.tsx +++ b/infra/staff/src/components/UpdateSubscription.tsx @@ -1,7 +1,7 @@ import React, { useEffect, useState } from "react"; import DatePicker from "react-datepicker"; import "react-datepicker/dist/react-datepicker.css"; -import "../App.css"; +import "../App.css"; interface UpdateSubscriptionProps { token: string; userId: string; @@ -14,14 +14,13 @@ export const UpdateSubscription: React.FC = ({ onClose, }) => { const [expiryTime, setExpiryTime] = useState(null); - const [productId, setProductId] = useState("50gb_monthly"); + const [productId, setProductId] = useState("50gb_monthly"); const [paymentProvider, setPaymentProvider] = useState("bitpay"); const [transactionId, setTransactionId] = useState(""); const [message, setMessage] = useState(null); const [error, setError] = useState(null); const [storage, setStorage] = useState(""); - useEffect(() => { if (productId === "50gb_yearly" || productId === "50gb_monthly") { setStorage(50 * 1024 * 1024 * 1024); @@ -29,26 +28,25 @@ export const UpdateSubscription: React.FC = ({ productId === "200gb_yearly" || productId === "200gb_monthly" ) { - setStorage(200 * 1024 * 1024 * 1024); + setStorage(200 * 1024 * 1024 * 1024); } else if ( productId === "500gb_yearly" || productId === "500gb_monthly" ) { - setStorage(500 * 1024 * 1024 * 1024); + setStorage(500 * 1024 * 1024 * 1024); } else if ( productId === "2000gb_yearly" || productId === "2000gb_monthly" ) { setStorage(2000 * 1024 * 1024 * 1024); } else { - setStorage(""); + setStorage(""); } }, [productId]); const handleSubmit = async (event: React.FormEvent) => { event.preventDefault(); - const expiryTimeTimestamp = expiryTime ? expiryTime.getTime() * 1000 : ""; @@ -163,10 +161,7 @@ export const UpdateSubscription: React.FC = ({ onChange={(e) => setTransactionId(e.target.value)} /> - diff --git a/infra/staff/yarn.lock b/infra/staff/yarn.lock index 928775dcf0..6e982d4676 100644 --- a/infra/staff/yarn.lock +++ b/infra/staff/yarn.lock @@ -23,7 +23,7 @@ resolved "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.6.tgz" integrity sha512-aC2DGhBq5eEdyXWqrDInSqQjO0k8xtPRf5YylULqx8MCd6jBtzqfta/3ETMRpuKIc5hyswfO80ObyA1MvkCcUQ== -"@babel/core@^7.0.0", "@babel/core@^7.0.0-0", "@babel/core@^7.24.5": +"@babel/core@^7.24.5": version "7.24.6" resolved "https://registry.npmjs.org/@babel/core/-/core-7.24.6.tgz" integrity sha512-qAHSfAdVyFmIvl0VHELib8xar7ONuSHrE2hLnsaWkYNTI68dmi1x8GYDhJjMI/e7XWal9QBlZkwbOnkcw7Z8gQ== @@ -208,11 +208,121 @@ "@babel/helper-validator-identifier" "^7.24.6" to-fast-properties "^2.0.0" +"@esbuild/aix-ppc64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz#a70f4ac11c6a1dfc18b8bbb13284155d933b9537" + integrity sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g== + +"@esbuild/android-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz#db1c9202a5bc92ea04c7b6840f1bbe09ebf9e6b9" + integrity sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg== + +"@esbuild/android-arm@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.20.2.tgz#3b488c49aee9d491c2c8f98a909b785870d6e995" + integrity sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w== + +"@esbuild/android-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.20.2.tgz#3b1628029e5576249d2b2d766696e50768449f98" + integrity sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg== + +"@esbuild/darwin-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz#6e8517a045ddd86ae30c6608c8475ebc0c4000bb" + integrity sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA== + "@esbuild/darwin-x64@0.20.2": version "0.20.2" resolved "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz" integrity sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA== +"@esbuild/freebsd-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz#d71502d1ee89a1130327e890364666c760a2a911" + integrity sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw== + +"@esbuild/freebsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz#aa5ea58d9c1dd9af688b8b6f63ef0d3d60cea53c" + integrity sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw== + +"@esbuild/linux-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz#055b63725df678379b0f6db9d0fa85463755b2e5" + integrity sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A== + +"@esbuild/linux-arm@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz#76b3b98cb1f87936fbc37f073efabad49dcd889c" + integrity sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg== + +"@esbuild/linux-ia32@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz#c0e5e787c285264e5dfc7a79f04b8b4eefdad7fa" + integrity sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig== + +"@esbuild/linux-loong64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz#a6184e62bd7cdc63e0c0448b83801001653219c5" + integrity sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ== + +"@esbuild/linux-mips64el@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz#d08e39ce86f45ef8fc88549d29c62b8acf5649aa" + integrity sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA== + +"@esbuild/linux-ppc64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz#8d252f0b7756ffd6d1cbde5ea67ff8fd20437f20" + integrity sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg== + +"@esbuild/linux-riscv64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz#19f6dcdb14409dae607f66ca1181dd4e9db81300" + integrity sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg== + +"@esbuild/linux-s390x@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz#3c830c90f1a5d7dd1473d5595ea4ebb920988685" + integrity sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ== + +"@esbuild/linux-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz#86eca35203afc0d9de0694c64ec0ab0a378f6fff" + integrity sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw== + +"@esbuild/netbsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz#e771c8eb0e0f6e1877ffd4220036b98aed5915e6" + integrity sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ== + +"@esbuild/openbsd-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz#9a795ae4b4e37e674f0f4d716f3e226dd7c39baf" + integrity sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ== + +"@esbuild/sunos-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz#7df23b61a497b8ac189def6e25a95673caedb03f" + integrity sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w== + +"@esbuild/win32-arm64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz#f1ae5abf9ca052ae11c1bc806fb4c0f519bacf90" + integrity sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ== + +"@esbuild/win32-ia32@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz#241fe62c34d8e8461cd708277813e1d0ba55ce23" + integrity sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ== + +"@esbuild/win32-x64@0.20.2": + version "0.20.2" + resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz#9c907b21e30a52db959ba4f80bb01a0cc403d5cc" + integrity sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ== + "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz" @@ -245,6 +355,42 @@ resolved "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz" integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== +"@floating-ui/core@^1.0.0": + version "1.6.2" + resolved "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.2.tgz" + integrity sha512-+2XpQV9LLZeanU4ZevzRnGFg2neDeKHgFLjP6YLW+tly0IvrhqT4u8enLGjLH3qeh85g19xY5rsAusfwTdn5lg== + dependencies: + "@floating-ui/utils" "^0.2.0" + +"@floating-ui/dom@^1.0.0": + version "1.6.5" + resolved "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.5.tgz" + integrity sha512-Nsdud2X65Dz+1RHjAIP0t8z5e2ff/IRbei6BqFrl1urT8sDVzM1HMQ+R0XcU5ceRfyO3I6ayeqIfh+6Wb8LGTw== + dependencies: + "@floating-ui/core" "^1.0.0" + "@floating-ui/utils" "^0.2.0" + +"@floating-ui/react-dom@^2.1.0": + version "2.1.0" + resolved "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.0.tgz" + integrity sha512-lNzj5EQmEKn5FFKc04+zasr09h/uX8RtJRNj5gUXsSQIXHVWTVh+hVAg1vOMCexkX8EgvemMvIFpQfkosnVNyA== + dependencies: + "@floating-ui/dom" "^1.0.0" + +"@floating-ui/react@^0.26.2": + version "0.26.17" + resolved "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.17.tgz" + integrity sha512-ESD+jYWwqwVzaIgIhExrArdsCL1rOAzryG/Sjlu8yaD3Mtqi3uVyhbE2V7jD58Mo52qbzKz2eUY/Xgh5I86FCQ== + dependencies: + "@floating-ui/react-dom" "^2.1.0" + "@floating-ui/utils" "^0.2.0" + tabbable "^6.0.0" + +"@floating-ui/utils@^0.2.0": + version "0.2.2" + resolved "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.2.tgz" + integrity sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw== + "@humanwhocodes/config-array@^0.11.14": version "0.11.14" resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz" @@ -304,7 +450,7 @@ "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5": +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": version "2.0.5" resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== @@ -322,11 +468,107 @@ resolved "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz" integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA== +"@rollup/plugin-node-resolve@^15.2.3": + version "15.2.3" + resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.2.3.tgz#e5e0b059bd85ca57489492f295ce88c2d4b0daf9" + integrity sha512-j/lym8nf5E21LwBT4Df1VD6hRO2L2iwUeUmP7litikRsVp1H6NWx20NEp0Y7su+7XGc476GnXXc4kFeZNGmaSQ== + dependencies: + "@rollup/pluginutils" "^5.0.1" + "@types/resolve" "1.20.2" + deepmerge "^4.2.2" + is-builtin-module "^3.2.1" + is-module "^1.0.0" + resolve "^1.22.1" + +"@rollup/pluginutils@^5.0.1": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.1.0.tgz#7e53eddc8c7f483a4ad0b94afb1f7f5fd3c771e0" + integrity sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g== + dependencies: + "@types/estree" "^1.0.0" + estree-walker "^2.0.2" + picomatch "^2.3.1" + +"@rollup/rollup-android-arm-eabi@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz#bbd0e616b2078cd2d68afc9824d1fadb2f2ffd27" + integrity sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ== + +"@rollup/rollup-android-arm64@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz#97255ef6384c5f73f4800c0de91f5f6518e21203" + integrity sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA== + +"@rollup/rollup-darwin-arm64@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz#b6dd74e117510dfe94541646067b0545b42ff096" + integrity sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w== + "@rollup/rollup-darwin-x64@4.18.0": version "4.18.0" resolved "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz" integrity sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA== +"@rollup/rollup-linux-arm-gnueabihf@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz#9f1a6d218b560c9d75185af4b8bb42f9f24736b8" + integrity sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA== + +"@rollup/rollup-linux-arm-musleabihf@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz#53618b92e6ffb642c7b620e6e528446511330549" + integrity sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A== + +"@rollup/rollup-linux-arm64-gnu@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz#99a7ba5e719d4f053761a698f7b52291cefba577" + integrity sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw== + +"@rollup/rollup-linux-arm64-musl@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz#f53db99a45d9bc00ce94db8a35efa7c3c144a58c" + integrity sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ== + +"@rollup/rollup-linux-powerpc64le-gnu@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz#cbb0837408fe081ce3435cf3730e090febafc9bf" + integrity sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA== + +"@rollup/rollup-linux-riscv64-gnu@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz#8ed09c1d1262ada4c38d791a28ae0fea28b80cc9" + integrity sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg== + +"@rollup/rollup-linux-s390x-gnu@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz#938138d3c8e0c96f022252a28441dcfb17afd7ec" + integrity sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg== + +"@rollup/rollup-linux-x64-gnu@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz#1a7481137a54740bee1ded4ae5752450f155d942" + integrity sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w== + +"@rollup/rollup-linux-x64-musl@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz#f1186afc601ac4f4fc25fac4ca15ecbee3a1874d" + integrity sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg== + +"@rollup/rollup-win32-arm64-msvc@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz#ed6603e93636a96203c6915be4117245c1bd2daf" + integrity sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA== + +"@rollup/rollup-win32-ia32-msvc@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz#14e0b404b1c25ebe6157a15edb9c46959ba74c54" + integrity sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg== + +"@rollup/rollup-win32-x64-msvc@4.18.0": + version "4.18.0" + resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz#5d694d345ce36b6ecf657349e03eb87297e68da4" + integrity sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g== + "@types/babel__core@^7.20.5": version "7.20.5" resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz" @@ -360,7 +602,7 @@ dependencies: "@babel/types" "^7.20.7" -"@types/estree@1.0.5": +"@types/estree@1.0.5", "@types/estree@^1.0.0": version "1.0.5" resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz" integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== @@ -385,6 +627,11 @@ "@types/prop-types" "*" csstype "^3.0.2" +"@types/resolve@1.20.2": + version "1.20.2" + resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.2.tgz#97d26e00cd4a0423b4af620abecf3e6f442b7975" + integrity sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q== + "@typescript-eslint/eslint-plugin@^7": version "7.11.0" resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.11.0.tgz" @@ -400,7 +647,7 @@ natural-compare "^1.4.0" ts-api-utils "^1.3.0" -"@typescript-eslint/parser@^7", "@typescript-eslint/parser@^7.0.0": +"@typescript-eslint/parser@^7": version "7.11.0" resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.11.0.tgz" integrity sha512-yimw99teuaXVWsBcPO1Ais02kwJ1jmNA1KxE7ng0aT7ndr1pT1wqj0OJnsYVGKKlc4QJai86l/025L6z8CljOg== @@ -487,7 +734,7 @@ acorn-jsx@^5.3.2: resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== -"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.9.0: +acorn@^8.9.0: version "8.11.3" resolved "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz" integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== @@ -652,7 +899,7 @@ braces@^3.0.3: dependencies: fill-range "^7.1.1" -browserslist@^4.22.2, "browserslist@>= 4.21.0": +browserslist@^4.22.2: version "4.23.0" resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz" integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ== @@ -662,6 +909,11 @@ browserslist@^4.22.2, "browserslist@>= 4.21.0": node-releases "^2.0.14" update-browserslist-db "^1.0.13" +builtin-modules@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" + integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== + call-bind@^1.0.2, call-bind@^1.0.5, call-bind@^1.0.6, call-bind@^1.0.7: version "1.0.7" resolved "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz" @@ -719,16 +971,16 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" -color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - color-name@1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" @@ -780,6 +1032,11 @@ data-view-byte-offset@^1.0.0: es-errors "^1.3.0" is-data-view "^1.0.1" +date-fns@^3.3.1, date-fns@^3.6.0: + version "3.6.0" + resolved "https://registry.npmjs.org/date-fns/-/date-fns-3.6.0.tgz" + integrity sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww== + debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: version "4.3.5" resolved "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz" @@ -792,6 +1049,11 @@ deep-is@^0.1.3: resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== +deepmerge@^4.2.2: + version "4.3.1" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" + integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== + define-data-property@^1.0.1, define-data-property@^1.1.4: version "1.1.4" resolved "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz" @@ -1053,7 +1315,7 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4 resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -"eslint@^3 || ^4 || ^5 || ^6 || ^7 || ^8", "eslint@^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", eslint@^8, eslint@^8.56.0, eslint@>=7: +eslint@^8: version "8.57.0" resolved "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz" integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== @@ -1125,6 +1387,11 @@ estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== +estree-walker@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" + integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== + esutils@^2.0.2: version "2.0.3" resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" @@ -1468,6 +1735,13 @@ is-boolean-object@^1.1.0: call-bind "^1.0.2" has-tostringtag "^1.0.0" +is-builtin-module@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169" + integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A== + dependencies: + builtin-modules "^3.3.0" + is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: version "1.2.7" resolved "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz" @@ -1525,6 +1799,11 @@ is-map@^2.0.3: resolved "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz" integrity sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw== +is-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-module/-/is-module-1.0.0.tgz#3258fb69f78c14d5b815d664336b4cffb6441591" + integrity sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g== + is-negative-zero@^2.0.3: version "2.0.3" resolved "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz" @@ -1735,21 +2014,7 @@ micromatch@^4.0.4: braces "^3.0.3" picomatch "^2.3.1" -minimatch@^3.0.5: - version "3.1.2" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^3.1.1: - version "3.1.2" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" - integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== - dependencies: - brace-expansion "^1.1.7" - -minimatch@^3.1.2: +minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -1952,12 +2217,12 @@ prettier-plugin-packagejson@^2.5: sort-package-json "2.10.0" synckit "0.9.0" -prettier@^3, "prettier@>= 1.16.0", prettier@>=2.0: +prettier@^3: version "3.3.0" resolved "https://registry.npmjs.org/prettier/-/prettier-3.3.0.tgz" integrity sha512-J9odKxERhCQ10OC2yb93583f6UnYutOeiV5i0zEDS7UGTdUt0u+y8erxl3lBKvwo/JHyyoEdXjwp4dke9oyZ/g== -prop-types@^15.8.1: +prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -1976,7 +2241,18 @@ queue-microtask@^1.2.2: resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== -react-dom@^18, react-dom@>=18: +react-datepicker@^7.1.0: + version "7.1.0" + resolved "https://registry.npmjs.org/react-datepicker/-/react-datepicker-7.1.0.tgz" + integrity sha512-Z91n5ybhmzI+YChj1ZG7ntPPOmHR2Dh4jbIl+mNgKXKoxyzUQBh7M3eQaFOwrBCVdKy5vsj370/ocQlGu1qsGA== + dependencies: + "@floating-ui/react" "^0.26.2" + clsx "^2.1.0" + date-fns "^3.3.1" + prop-types "^15.7.2" + react-onclickoutside "^6.13.0" + +react-dom@^18: version "18.3.1" resolved "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz" integrity sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw== @@ -1989,6 +2265,11 @@ react-is@^16.13.1: resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== +react-onclickoutside@^6.13.0: + version "6.13.1" + resolved "https://registry.npmjs.org/react-onclickoutside/-/react-onclickoutside-6.13.1.tgz" + integrity sha512-LdrrxK/Yh9zbBQdFbMTXPp3dTSN9B+9YJQucdDu3JNKRrbdU+H+/TVONJoWtOwy4II8Sqf1y/DTI6w/vGPYW0w== + react-refresh@^0.14.2: version "0.14.2" resolved "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz" @@ -2001,7 +2282,7 @@ react-toastify@^10.0.5: dependencies: clsx "^2.1.0" -react@^18, react@^18.3.1, react@>=18: +react@^18: version "18.3.1" resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz" integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ== @@ -2036,6 +2317,15 @@ resolve-from@^4.0.0: resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== +resolve@^1.22.1: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + resolve@^2.0.0-next.5: version "2.0.0-next.5" resolved "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz" @@ -2288,6 +2578,11 @@ synckit@0.9.0: "@pkgr/core" "^0.1.0" tslib "^2.6.2" +tabbable@^6.0.0: + version "6.2.0" + resolved "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz" + integrity sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew== + text-table@^0.2.0: version "0.2.0" resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" @@ -2371,7 +2666,7 @@ typed-array-length@^1.0.6: is-typed-array "^1.1.13" possible-typed-array-names "^1.0.0" -typescript@^5.4.5, typescript@>=2.9, typescript@>=4.2.0: +typescript@^5: version "5.4.5" resolved "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz" integrity sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ== @@ -2401,7 +2696,7 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" -"vite@^4.2.0 || ^5.0.0", vite@^5.2: +vite@^5.2: version "5.2.12" resolved "https://registry.npmjs.org/vite/-/vite-5.2.12.tgz" integrity sha512-/gC8GxzxMK5ntBwb48pR32GGhENnjtY30G4A0jemunsBkiEZFw60s8InGpN8gkhHEkjnRK1aSAxeQgwvFhUHAA== From f8d1851311b4618e1884443d699cce3e38e56b93 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Sun, 23 Jun 2024 12:24:44 +0530 Subject: [PATCH 089/130] [mob] Fix range error in getUploadedFiles --- mobile/lib/db/files_db.dart | 3 +++ .../semantic_search/semantic_search_service.dart | 3 +++ 2 files changed, 6 insertions(+) diff --git a/mobile/lib/db/files_db.dart b/mobile/lib/db/files_db.dart index e23a5c762b..7c6b7947db 100644 --- a/mobile/lib/db/files_db.dart +++ b/mobile/lib/db/files_db.dart @@ -1808,6 +1808,9 @@ class FilesDB { } Future> getUploadedFiles(List uploadedIDs) async { + if (uploadedIDs.isEmpty) { + return []; + } final db = await instance.sqliteAsyncDB; String inParam = ""; for (final id in uploadedIDs) { diff --git a/mobile/lib/services/machine_learning/semantic_search/semantic_search_service.dart b/mobile/lib/services/machine_learning/semantic_search/semantic_search_service.dart index d65c67aba3..cc34130446 100644 --- a/mobile/lib/services/machine_learning/semantic_search/semantic_search_service.dart +++ b/mobile/lib/services/machine_learning/semantic_search/semantic_search_service.dart @@ -204,6 +204,9 @@ class SemanticSearchService { await _frameworkInitialization.future; _logger.info("Attempting backfill for image embeddings"); final fileIDs = await _getFileIDsToBeIndexed(); + if (fileIDs.isEmpty) { + return; + } final files = await FilesDB.instance.getUploadedFiles(fileIDs); _logger.info(files.length.toString() + " to be embedded"); // await _cacheThumbnails(files); From f30d1fe45f435d14fe0dadfdc65fb2f8327489a4 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Sun, 23 Jun 2024 13:10:49 +0530 Subject: [PATCH 090/130] [photos] Skip sending handled error to sentry --- mobile/lib/core/error-reporting/super_logging.dart | 14 +++++++++++++- .../machine_learning/face_ml/face_ml_service.dart | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/mobile/lib/core/error-reporting/super_logging.dart b/mobile/lib/core/error-reporting/super_logging.dart index d1603ab20e..6659636368 100644 --- a/mobile/lib/core/error-reporting/super_logging.dart +++ b/mobile/lib/core/error-reporting/super_logging.dart @@ -15,7 +15,9 @@ import 'package:package_info_plus/package_info_plus.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; import 'package:photos/core/error-reporting/tunneled_transport.dart'; +import "package:photos/core/errors.dart"; import 'package:photos/models/typedefs.dart'; +import "package:photos/services/machine_learning/face_ml/face_ml_exceptions.dart"; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -234,12 +236,22 @@ class SuperLogging { if (error is DioError) { return; } + if (error is CouldNotRetrieveAnyFileData || + error is StorageLimitExceededError || + error is WiFiUnavailableError || + error is InvalidFileError || + error is NoActiveSubscriptionError) { + if (kDebugMode) { + $.info('Not sending error to sentry: $error'); + } + return; + } await Sentry.captureException( error, stackTrace: stack, ); } catch (e) { - $.info('Sending report to sentry.io failed: $e'); + $.info('Sending report to sentry failed: $e'); $.info('Original error: $error'); } } diff --git a/mobile/lib/services/machine_learning/face_ml/face_ml_service.dart b/mobile/lib/services/machine_learning/face_ml/face_ml_service.dart index b50f14ebcc..1dd9c11b50 100644 --- a/mobile/lib/services/machine_learning/face_ml/face_ml_service.dart +++ b/mobile/lib/services/machine_learning/face_ml/face_ml_service.dart @@ -925,7 +925,7 @@ class FaceMlService { await _getImagePathForML(enteFile, typeOfData: FileDataForML.fileData); if (filePath == null) { - _logger.severe( + _logger.warning( "Failed to get any data for enteFile with uploadedFileID ${enteFile.uploadedFileID} since its file path is null", ); throw CouldNotRetrieveAnyFileData(); From a3ce66b87fa203f789f0a05c5950382248f98db8 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Sun, 23 Jun 2024 13:11:16 +0530 Subject: [PATCH 091/130] [photos] Bump version --- mobile/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/pubspec.yaml b/mobile/pubspec.yaml index ab7962e182..ce76349f15 100644 --- a/mobile/pubspec.yaml +++ b/mobile/pubspec.yaml @@ -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.2+902 +version: 0.9.3+903 publish_to: none environment: From b2f13c4b3deaefce86a646b785e0201fd42380f3 Mon Sep 17 00:00:00 2001 From: Neeraj Gupta <254676+ua741@users.noreply.github.com> Date: Sun, 23 Jun 2024 13:12:13 +0530 Subject: [PATCH 092/130] [photos] Fix late init error --- .../machine_learning/face_ml/feedback/cluster_feedback.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart b/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart index 8bce9fe0e6..34ae264b92 100644 --- a/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart +++ b/mobile/lib/services/machine_learning/face_ml/feedback/cluster_feedback.dart @@ -689,7 +689,7 @@ class ClusterFeedbackService { .map((clusterID) => allClusterIdsToCountMap[clusterID] ?? 0) .reduce((value, element) => min(value, element)); final checkSizes = [100, 20, kMinimumClusterSizeSearchResult, 10, 5, 1]; - late Map clusterAvgBigClusters; + Map clusterAvgBigClusters = {}; final List<(int, double)> suggestionsMean = []; for (final minimumSize in checkSizes.toSet()) { if (smallestPersonClusterSize >= From 37b5b699557d4f209c397074ffe895bbdff286fe Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sun, 23 Jun 2024 15:43:37 +0530 Subject: [PATCH 093/130] Trailing button --- .../new/photos/components/DevSettings.tsx | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/web/packages/new/photos/components/DevSettings.tsx b/web/packages/new/photos/components/DevSettings.tsx index 5c65b2be7d..c628dcd701 100644 --- a/web/packages/new/photos/components/DevSettings.tsx +++ b/web/packages/new/photos/components/DevSettings.tsx @@ -1,9 +1,12 @@ import log from "@/next/log"; +import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; import { Dialog, DialogActions, DialogContent, DialogTitle, + IconButton, + InputAdornment, TextField, useMediaQuery, type ModalProps, @@ -73,6 +76,19 @@ export const DevSettings: React.FC = ({ open, onClose }) => { helperText={ form.touched.apiOrigin && form.errors.apiOrigin } + InputProps={{ + endAdornment: ( + + + + + + ), + }} />
    From 9579e30a68320ed0bf61db65877b177160b546cb Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sun, 23 Jun 2024 15:55:32 +0530 Subject: [PATCH 094/130] Conditional --- .../new/photos/components/DevSettings.tsx | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/web/packages/new/photos/components/DevSettings.tsx b/web/packages/new/photos/components/DevSettings.tsx index c628dcd701..c232340495 100644 --- a/web/packages/new/photos/components/DevSettings.tsx +++ b/web/packages/new/photos/components/DevSettings.tsx @@ -1,4 +1,5 @@ import log from "@/next/log"; +import CheckIcon from "@mui/icons-material/Check"; import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; import { Dialog, @@ -13,7 +14,7 @@ import { } from "@mui/material"; import { useFormik } from "formik"; import { t } from "i18next"; -import React from "react"; +import React, { useState } from "react"; import { FocusVisibleButton } from "./FocusVisibleButton"; import { SlideTransition } from "./SlideTransition"; @@ -30,10 +31,16 @@ interface DevSettingsProps { */ export const DevSettings: React.FC = ({ open, onClose }) => { const fullScreen = useMediaQuery("(max-width: 428px)"); + const [showConfirmation, setShowConfirmation] = useState(false); + + const handleClose = () => { + setShowConfirmation(false); + onClose(); + }; const handleDialogClose: ModalProps["onClose"] = (_, reason: string) => { // Don't close on backdrop clicks. - if (reason != "backdropClick") onClose(); + if (reason != "backdropClick") handleClose(); }; const form = useFormik({ @@ -44,8 +51,9 @@ export const DevSettings: React.FC = ({ open, onClose }) => { setErrors({ apiOrigin: res }); } else { setSubmitting(false); + setShowConfirmation(true); // Add a bit of delay to acknowledge the update better. - setTimeout(onClose, 200); + setTimeout(handleClose, 300); } }, }); @@ -79,13 +87,17 @@ export const DevSettings: React.FC = ({ open, onClose }) => { InputProps={{ endAdornment: ( - - - + {showConfirmation ? ( + + ) : ( + + + + )} ), }} @@ -102,7 +114,7 @@ export const DevSettings: React.FC = ({ open, onClose }) => { {"Save"} Date: Sun, 23 Jun 2024 16:07:21 +0530 Subject: [PATCH 095/130] Conditional --- .../new/photos/components/DevSettings.tsx | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/web/packages/new/photos/components/DevSettings.tsx b/web/packages/new/photos/components/DevSettings.tsx index c232340495..a5a2270e9d 100644 --- a/web/packages/new/photos/components/DevSettings.tsx +++ b/web/packages/new/photos/components/DevSettings.tsx @@ -31,10 +31,10 @@ interface DevSettingsProps { */ export const DevSettings: React.FC = ({ open, onClose }) => { const fullScreen = useMediaQuery("(max-width: 428px)"); - const [showConfirmation, setShowConfirmation] = useState(false); + const [saved, setSaved] = useState(false); const handleClose = () => { - setShowConfirmation(false); + setSaved(false); onClose(); }; @@ -45,15 +45,19 @@ export const DevSettings: React.FC = ({ open, onClose }) => { const form = useFormik({ initialValues: { apiOrigin: "" }, + validate: () => { + setSaved(false); + return {}; + }, onSubmit: async (values, { setSubmitting, setErrors }) => { const res = await updateAPIOrigin(values.apiOrigin); if (typeof res == "string") { setErrors({ apiOrigin: res }); } else { setSubmitting(false); - setShowConfirmation(true); + setSaved(true); // Add a bit of delay to acknowledge the update better. - setTimeout(handleClose, 300); + // setTimeout(handleClose, 300); } }, }); @@ -87,7 +91,7 @@ export const DevSettings: React.FC = ({ open, onClose }) => { InputProps={{ endAdornment: ( - {showConfirmation ? ( + {saved ? ( ) : ( = ({ open, onClose }) => { type="submit" color="accent" fullWidth - disabled={form.isSubmitting} + disabled={form.isSubmitting || saved} disableRipple > {"Save"} @@ -119,7 +123,7 @@ export const DevSettings: React.FC = ({ open, onClose }) => { fullWidth disableRipple > - {t("CANCEL")} + {saved ? t("DONE") : t("CANCEL")} From ec2d9a80fc77d7016ebf1bfceb06c69cc8deb3f1 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sun, 23 Jun 2024 16:41:29 +0530 Subject: [PATCH 096/130] Compare to actual state --- .../new/photos/components/DevSettings.tsx | 40 ++++++++++++++----- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/web/packages/new/photos/components/DevSettings.tsx b/web/packages/new/photos/components/DevSettings.tsx index a5a2270e9d..26ffbb2ed6 100644 --- a/web/packages/new/photos/components/DevSettings.tsx +++ b/web/packages/new/photos/components/DevSettings.tsx @@ -14,7 +14,7 @@ import { } from "@mui/material"; import { useFormik } from "formik"; import { t } from "i18next"; -import React, { useState } from "react"; +import React from "react"; import { FocusVisibleButton } from "./FocusVisibleButton"; import { SlideTransition } from "./SlideTransition"; @@ -31,10 +31,12 @@ interface DevSettingsProps { */ export const DevSettings: React.FC = ({ open, onClose }) => { const fullScreen = useMediaQuery("(max-width: 428px)"); - const [saved, setSaved] = useState(false); + // const [saved, setSaved] = useState(false); + + const savedAPIOrigin = localStorage.getItem("apiOrigin") ?? ""; const handleClose = () => { - setSaved(false); + // setSaved(false); onClose(); }; @@ -45,23 +47,34 @@ export const DevSettings: React.FC = ({ open, onClose }) => { const form = useFormik({ initialValues: { apiOrigin: "" }, - validate: () => { - setSaved(false); - return {}; - }, + // validate: () => { + // setSaved(false); + // return {}; + // }, onSubmit: async (values, { setSubmitting, setErrors }) => { + // if (saved) { + // setSubmitting(false); + // setTimeout(handleClose, 100); + // return; + // } + const res = await updateAPIOrigin(values.apiOrigin); if (typeof res == "string") { setErrors({ apiOrigin: res }); } else { setSubmitting(false); - setSaved(true); + // setSaved(true); // Add a bit of delay to acknowledge the update better. - // setTimeout(handleClose, 300); + // setTimeout(handleClose, 600); + + // handleClose(); } }, }); + const saved = + form.touched.apiOrigin && savedAPIOrigin == form.values.apiOrigin; + return ( = ({ open, onClose }) => { TransitionComponent={SlideTransition} maxWidth="xs" > + {/* + ; */}
    {"Developer settings"} @@ -112,7 +132,7 @@ export const DevSettings: React.FC = ({ open, onClose }) => { type="submit" color="accent" fullWidth - disabled={form.isSubmitting || saved} + disabled={form.isSubmitting} disableRipple > {"Save"} From 5a52d79a881c194a02b588bfb54542df00080ed6 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sun, 23 Jun 2024 17:02:59 +0530 Subject: [PATCH 097/130] Uncomplicate There are many things the form can do better, but going against the grain of the libraries we're using is resulting in a more broken interaction (e.g. keyboard nav). --- .../new/photos/components/DevSettings.tsx | 104 ++++++------------ 1 file changed, 32 insertions(+), 72 deletions(-) diff --git a/web/packages/new/photos/components/DevSettings.tsx b/web/packages/new/photos/components/DevSettings.tsx index 26ffbb2ed6..e52a824512 100644 --- a/web/packages/new/photos/components/DevSettings.tsx +++ b/web/packages/new/photos/components/DevSettings.tsx @@ -1,5 +1,3 @@ -import log from "@/next/log"; -import CheckIcon from "@mui/icons-material/Check"; import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; import { Dialog, @@ -31,50 +29,34 @@ interface DevSettingsProps { */ export const DevSettings: React.FC = ({ open, onClose }) => { const fullScreen = useMediaQuery("(max-width: 428px)"); - // const [saved, setSaved] = useState(false); - - const savedAPIOrigin = localStorage.getItem("apiOrigin") ?? ""; - - const handleClose = () => { - // setSaved(false); - onClose(); - }; const handleDialogClose: ModalProps["onClose"] = (_, reason: string) => { // Don't close on backdrop clicks. - if (reason != "backdropClick") handleClose(); + if (reason != "backdropClick") onClose(); }; const form = useFormik({ - initialValues: { apiOrigin: "" }, - // validate: () => { - // setSaved(false); - // return {}; - // }, + initialValues: { + apiOrigin: localStorage.getItem("apiOrigin") ?? "", + }, onSubmit: async (values, { setSubmitting, setErrors }) => { - // if (saved) { - // setSubmitting(false); - // setTimeout(handleClose, 100); - // return; - // } - - const res = await updateAPIOrigin(values.apiOrigin); - if (typeof res == "string") { - setErrors({ apiOrigin: res }); - } else { - setSubmitting(false); - // setSaved(true); - // Add a bit of delay to acknowledge the update better. - // setTimeout(handleClose, 600); - - // handleClose(); + try { + await updateAPIOrigin(values.apiOrigin); + } catch (e) { + // The person using this functionality is likely a developer and + // might be helped more by the original error instead of a + // friendlier but less specific message. + setErrors({ + apiOrigin: e instanceof Error ? e.message : String(e), + }); + return; } + + setSubmitting(false); + onClose(); }, }); - const saved = - form.touched.apiOrigin && savedAPIOrigin == form.values.apiOrigin; - return ( = ({ open, onClose }) => { TransitionComponent={SlideTransition} maxWidth="xs" > - {/* - ; */} {"Developer settings"} @@ -111,17 +86,13 @@ export const DevSettings: React.FC = ({ open, onClose }) => { InputProps={{ endAdornment: ( - {saved ? ( - - ) : ( - - - - )} + + + ), }} @@ -138,12 +109,12 @@ export const DevSettings: React.FC = ({ open, onClose }) => { {"Save"} - {saved ? t("DONE") : t("CANCEL")} + {t("CANCEL")} @@ -162,27 +133,16 @@ export const DevSettings: React.FC = ({ open, onClose }) => { * * @param origin The new API origin to use. Pass an empty string to clear the * previously saved API origin (if any). - * - * @returns true on success, and the user visible error message string - * otherwise. */ -const updateAPIOrigin = async (origin: string): Promise => { +const updateAPIOrigin = async (origin: string) => { if (!origin) { localStorage.removeItem("apiOrigin"); - return true; + return; } const url = `${origin}/ping`; - try { - const res = await fetch(url); - if (!res.ok) - throw new Error(`Failed to fetch ${url}: HTTP ${res.status}`); - localStorage.setItem("apiOrigin", origin); - return true; - } catch (e) { - log.error("Failed to ping the provided origin", e); - // The person using this is likely a developer, just give them the - // original error itself, they might find it helpful. - return e instanceof Error ? e.message : t("ERROR"); - } + const res = await fetch(url); + if (!res.ok) throw new Error(`Failed to fetch ${url}: HTTP ${res.status}`); + localStorage.setItem("apiOrigin", origin); + return true; }; From 815666bbe7b1e14634a8de9a4e2be25fdb4cb506 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sun, 23 Jun 2024 17:17:39 +0530 Subject: [PATCH 098/130] Assert a pong --- web/packages/new/photos/components/DevSettings.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/web/packages/new/photos/components/DevSettings.tsx b/web/packages/new/photos/components/DevSettings.tsx index e52a824512..c07290cd0e 100644 --- a/web/packages/new/photos/components/DevSettings.tsx +++ b/web/packages/new/photos/components/DevSettings.tsx @@ -13,6 +13,7 @@ import { import { useFormik } from "formik"; import { t } from "i18next"; import React from "react"; +import { z } from "zod"; import { FocusVisibleButton } from "./FocusVisibleButton"; import { SlideTransition } from "./SlideTransition"; @@ -143,6 +144,12 @@ const updateAPIOrigin = async (origin: string) => { const url = `${origin}/ping`; const res = await fetch(url); if (!res.ok) throw new Error(`Failed to fetch ${url}: HTTP ${res.status}`); + const json = PingResponse.parse(await res.json()); + if (json.message != "pong") throw new Error("Invalid response"); + localStorage.setItem("apiOrigin", origin); - return true; }; + +const PingResponse = z.object({ + message: z.string().nullish(), +}); From 86538a6d23330a2e247e94c82e7fce88424dea4a Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sun, 23 Jun 2024 17:23:07 +0530 Subject: [PATCH 099/130] Connect --- .../new/photos/components/DevSettings.tsx | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/web/packages/new/photos/components/DevSettings.tsx b/web/packages/new/photos/components/DevSettings.tsx index c07290cd0e..387e4c3073 100644 --- a/web/packages/new/photos/components/DevSettings.tsx +++ b/web/packages/new/photos/components/DevSettings.tsx @@ -6,6 +6,7 @@ import { DialogTitle, IconButton, InputAdornment, + Link, TextField, useMediaQuery, type ModalProps, @@ -87,13 +88,19 @@ export const DevSettings: React.FC = ({ open, onClose }) => { InputProps={{ endAdornment: ( - - - + + + + ), }} From 3b3c802aa4c8a1c49923a13347ea8bba2de6f78e Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sun, 23 Jun 2024 18:24:11 +0530 Subject: [PATCH 100/130] Validate URLs --- web/packages/new/photos/components/DevSettings.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/web/packages/new/photos/components/DevSettings.tsx b/web/packages/new/photos/components/DevSettings.tsx index 387e4c3073..8d93945951 100644 --- a/web/packages/new/photos/components/DevSettings.tsx +++ b/web/packages/new/photos/components/DevSettings.tsx @@ -1,3 +1,4 @@ +import { apiOrigin } from "@ente/shared/network/api"; import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; import { Dialog, @@ -41,6 +42,14 @@ export const DevSettings: React.FC = ({ open, onClose }) => { initialValues: { apiOrigin: localStorage.getItem("apiOrigin") ?? "", }, + validate: ({ apiOrigin }) => { + try { + new URL(apiOrigin); + } catch { + return { apiOrigin: "Invalid endpoint" }; + } + return {}; + }, onSubmit: async (values, { setSubmitting, setErrors }) => { try { await updateAPIOrigin(values.apiOrigin); From 669ae855f1009c057bb3f194ab7ceb2d11b905a6 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sun, 23 Jun 2024 18:54:58 +0530 Subject: [PATCH 101/130] Prevent layout shift --- web/packages/new/photos/components/DevSettings.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/web/packages/new/photos/components/DevSettings.tsx b/web/packages/new/photos/components/DevSettings.tsx index 8d93945951..1fb4837d99 100644 --- a/web/packages/new/photos/components/DevSettings.tsx +++ b/web/packages/new/photos/components/DevSettings.tsx @@ -77,7 +77,13 @@ export const DevSettings: React.FC = ({ open, onClose }) => { >
    {"Developer settings"} - + = ({ open, onClose }) => { form.touched.apiOrigin && !!form.errors.apiOrigin } helperText={ - form.touched.apiOrigin && form.errors.apiOrigin + (form.touched.apiOrigin && form.errors.apiOrigin) ?? + " " /* always show an empty string to prevent a layout shift */ } InputProps={{ endAdornment: ( From fcffd688d69329c8e0fcd492aa174a4e2be9a13e Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sun, 23 Jun 2024 19:14:13 +0530 Subject: [PATCH 102/130] Deal with worker urls --- .../src/services/upload/uploadHttpClient.ts | 9 ++-- web/apps/photos/src/services/userService.ts | 17 +++++-- .../new/photos/components/DevSettings.tsx | 1 - web/packages/shared/network/api.ts | 46 +++++++++++++------ 4 files changed, 48 insertions(+), 25 deletions(-) diff --git a/web/apps/photos/src/services/upload/uploadHttpClient.ts b/web/apps/photos/src/services/upload/uploadHttpClient.ts index c23a58b520..3323edc281 100644 --- a/web/apps/photos/src/services/upload/uploadHttpClient.ts +++ b/web/apps/photos/src/services/upload/uploadHttpClient.ts @@ -2,13 +2,12 @@ import log from "@/next/log"; import { wait } from "@/utils/promise"; import { CustomError, handleUploadError } from "@ente/shared/error"; import HTTPService from "@ente/shared/network/HTTPService"; -import { getEndpoint, getUploadEndpoint } from "@ente/shared/network/api"; +import { getEndpoint, uploaderOrigin } from "@ente/shared/network/api"; import { getToken } from "@ente/shared/storage/localStorage/helpers"; import { EnteFile } from "types/file"; import { MultipartUploadURLs, UploadFile, UploadURL } from "./uploadService"; const ENDPOINT = getEndpoint(); -const UPLOAD_ENDPOINT = getUploadEndpoint(); const MAX_URL_REQUESTS = 50; @@ -122,7 +121,7 @@ class UploadHttpClient { try { await retryHTTPCall(() => HTTPService.put( - `${UPLOAD_ENDPOINT}/file-upload`, + `${uploaderOrigin()}/file-upload`, file, null, { @@ -178,7 +177,7 @@ class UploadHttpClient { try { const response = await retryHTTPCall(async () => { const resp = await HTTPService.put( - `${UPLOAD_ENDPOINT}/multipart-upload`, + `${uploaderOrigin()}/multipart-upload`, filePart, null, { @@ -219,7 +218,7 @@ class UploadHttpClient { try { await retryHTTPCall(() => HTTPService.post( - `${UPLOAD_ENDPOINT}/multipart-complete`, + `${uploaderOrigin()}/multipart-complete`, reqBody, null, { diff --git a/web/apps/photos/src/services/userService.ts b/web/apps/photos/src/services/userService.ts index 6ec018a368..8be4d55d57 100644 --- a/web/apps/photos/src/services/userService.ts +++ b/web/apps/photos/src/services/userService.ts @@ -2,7 +2,11 @@ import log from "@/next/log"; import { putAttributes } from "@ente/accounts/api/user"; import { ApiError } from "@ente/shared/error"; import HTTPService from "@ente/shared/network/HTTPService"; -import { getEndpoint, getFamilyPortalURL } from "@ente/shared/network/api"; +import { + customAPIOrigin, + getEndpoint, + getFamilyPortalURL, +} from "@ente/shared/network/api"; import { LS_KEYS, getData } from "@ente/shared/storage/localStorage"; import { getToken, @@ -314,10 +318,13 @@ export const updateMapEnabledStatus = async (newStatus: boolean) => { * rename this to say getUseDirectUpload). */ export async function getDisableCFUploadProxyFlag(): Promise { - // If NEXT_PUBLIC_ENTE_ENDPOINT is set, that means we're not running a - // production deployment. Disable the Cloudflare upload proxy, and instead - // just directly use the upload URLs that museum gives us. - if (process.env.NEXT_PUBLIC_ENTE_ENDPOINT) return true; + // If a custom origin is set, that means we're not running a production + // deployment (maybe we're running locally, or being self-hosted). + // + // In such cases, disable the Cloudflare upload proxy (which won't work for + // self-hosters), and instead just directly use the upload URLs that museum + // gives us. + if (customAPIOrigin()) return true; try { const featureFlags = ( diff --git a/web/packages/new/photos/components/DevSettings.tsx b/web/packages/new/photos/components/DevSettings.tsx index 1fb4837d99..a5f0ab6d85 100644 --- a/web/packages/new/photos/components/DevSettings.tsx +++ b/web/packages/new/photos/components/DevSettings.tsx @@ -1,4 +1,3 @@ -import { apiOrigin } from "@ente/shared/network/api"; import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; import { Dialog, diff --git a/web/packages/shared/network/api.ts b/web/packages/shared/network/api.ts index f708e29e04..934256c094 100644 --- a/web/packages/shared/network/api.ts +++ b/web/packages/shared/network/api.ts @@ -1,32 +1,50 @@ +import { nullToUndefined } from "@/utils/transform"; + /** * Return the origin (scheme, host, port triple) that should be used for making * API requests to museum. * - * This defaults to "https://api.ente.io", Ente's own servers, but can be - * overridden when self hosting or developing by setting the - * `NEXT_PUBLIC_ENTE_ENDPOINT` environment variable. + * This defaults to {@link defaultAPIOrigin}, but can be overridden when self + * hosting or developing by setting the `NEXT_PUBLIC_ENTE_ENDPOINT` environment + * variable. */ -export const apiOrigin = () => customAPIOrigin() ?? "https://api.ente.io"; +export const apiOrigin = () => customAPIOrigin() ?? defaultAPIOrigin; /** - * Return the overridden API origin, if one is defined by setting the - * `NEXT_PUBLIC_ENTE_ENDPOINT` environment variable. + * Return the overridden API origin, if one is defined by either (in priority + * order): + * + * - Setting the custom server on the landing page (See: [Note: Configuring + * custom server]); or by + * + * - Setting the `NEXT_PUBLIC_ENTE_ENDPOINT` environment variable. * * Otherwise return undefined. */ export const customAPIOrigin = () => - process.env.NEXT_PUBLIC_ENTE_ENDPOINT ?? undefined; + nullToUndefined(localStorage.getItem("apiOrigin")) ?? + process.env.NEXT_PUBLIC_ENTE_ENDPOINT ?? + undefined; + +/** + * Default value of {@link apiOrigin}: "https://api.ente.io", Ente's production + * API servers. + */ +export const defaultAPIOrigin = "https://api.ente.io"; /** Deprecated, use {@link apiOrigin} instead. */ export const getEndpoint = apiOrigin; -export const getUploadEndpoint = () => { - const endpoint = process.env.NEXT_PUBLIC_ENTE_ENDPOINT; - if (endpoint) { - return endpoint; - } - return `https://uploader.ente.io`; -}; +/** + * Return the origin that should be used for uploading files. + * + * This defaults to `https://uploader.ente.io`, serviced by a Cloudflare worker + * (see infra/workers/uploader). But if a {@link customAPIOrigin} is set then + * this value is set to the {@link customAPIOrigin} itself, effectively + * bypassing the Cloudflare worker for non-Ente deployments. + */ +export const uploaderOrigin = () => + customAPIOrigin() ?? "https://uploader.ente.io"; /** * Return the URL of the Ente Accounts app. From 6cf942ec9e65b81f3a1735ac4f35202b14a3e92d Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sun, 23 Jun 2024 19:17:04 +0530 Subject: [PATCH 103/130] Also handle arbitrary validation errors --- web/packages/new/photos/components/DevSettings.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/web/packages/new/photos/components/DevSettings.tsx b/web/packages/new/photos/components/DevSettings.tsx index a5f0ab6d85..878cc084af 100644 --- a/web/packages/new/photos/components/DevSettings.tsx +++ b/web/packages/new/photos/components/DevSettings.tsx @@ -1,3 +1,4 @@ +import log from "@/next/log"; import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; import { Dialog, @@ -166,12 +167,16 @@ const updateAPIOrigin = async (origin: string) => { const url = `${origin}/ping`; const res = await fetch(url); if (!res.ok) throw new Error(`Failed to fetch ${url}: HTTP ${res.status}`); - const json = PingResponse.parse(await res.json()); - if (json.message != "pong") throw new Error("Invalid response"); + try { + PingResponse.parse(await res.json()); + } catch (e) { + log.error("Invalid response", e); + throw new Error("Invalid response"); + } localStorage.setItem("apiOrigin", origin); }; const PingResponse = z.object({ - message: z.string().nullish(), + message: z.enum(["pong"]), }); From 2436d2fcaa7ec21d513f8cb455f0d27c7aad3fd2 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sun, 23 Jun 2024 19:32:59 +0530 Subject: [PATCH 104/130] Move --- web/packages/next/origins.ts | 83 ++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 web/packages/next/origins.ts diff --git a/web/packages/next/origins.ts b/web/packages/next/origins.ts new file mode 100644 index 0000000000..db5b55aef2 --- /dev/null +++ b/web/packages/next/origins.ts @@ -0,0 +1,83 @@ +import { nullToUndefined } from "@/utils/transform"; + +/** + * Return the origin (scheme, host, port triple) that should be used for making + * API requests to museum. + * + * This defaults to {@link defaultAPIOrigin}, but can be overridden when self + * hosting or developing by setting the `NEXT_PUBLIC_ENTE_ENDPOINT` environment + * variable. + */ +export const apiOrigin = () => customAPIOrigin() ?? defaultAPIOrigin; + +/** + * Return the overridden API origin, if one is defined by either (in priority + * order): + * + * - Setting the custom server on the landing page (See: [Note: Configuring + * custom server]); or by + * + * - Setting the `NEXT_PUBLIC_ENTE_ENDPOINT` environment variable. + * + * Otherwise return undefined. + */ +export const customAPIOrigin = () => + nullToUndefined(localStorage.getItem("apiOrigin")) ?? + process.env.NEXT_PUBLIC_ENTE_ENDPOINT ?? + undefined; + +/** + * Default value of {@link apiOrigin}: "https://api.ente.io", Ente's production + * API servers. + */ +export const defaultAPIOrigin = "https://api.ente.io"; + +/** + * Return the origin that should be used for uploading files. + * + * This defaults to `https://uploader.ente.io`, serviced by a Cloudflare worker + * (see infra/workers/uploader). But if a {@link customAPIOrigin} is set then + * this value is set to the {@link customAPIOrigin} itself, effectively + * bypassing the Cloudflare worker for non-Ente deployments. + */ +export const uploaderOrigin = () => + customAPIOrigin() ?? "https://uploader.ente.io"; + +/** + * Return the origin that serves the accounts app. + * + * Defaults to our production instance, "https://accounts.ente.io", but can be + * overridden by setting the `NEXT_PUBLIC_ENTE_ACCOUNTS_URL` environment + * variable. + */ +export const accountsAppOrigin = () => + process.env.NEXT_PUBLIC_ENTE_ACCOUNTS_URL ?? `https://accounts.ente.io`; + +/** + * Return the origin that serves public albums. + * + * Defaults to our production instance, "https://albums.ente.io", but can be + * overridden by setting the `NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT` environment + * variable. + */ +export const albumsAppOrigin = () => + process.env.NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT ?? "https://albums.ente.io"; + +/** + * Return the origin that serves the family dashboard which can be used to + * create or manage family plans.. + * + * Defaults to our production instance, "https://family.ente.io", but can be + * overridden by setting the `NEXT_PUBLIC_ENTE_FAMILY_URL` environment variable. + */ +export const familyAppOrigin = () => + process.env.NEXT_PUBLIC_ENTE_FAMILY_URL ?? "https://family.ente.io"; + +/** + * Return the origin that serves the payments app. + * + * Defaults to our production instance, "https://payments.ente.io", but can be + * overridden by setting the `NEXT_PUBLIC_ENTE_PAYMENTS_URL` environment variable. + */ +export const paymentsAppOrigin = () => + process.env.NEXT_PUBLIC_ENTE_PAYMENTS_URL ?? "https://payments.ente.io"; From bcbf03fa517dd8bb3a6763c05381b05656998cd8 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sun, 23 Jun 2024 19:52:01 +0530 Subject: [PATCH 105/130] Move --- web/apps/auth/src/services/remote.ts | 8 +- .../photos/src/services/billingService.ts | 31 ++++--- .../photos/src/services/collectionService.ts | 44 +++++----- .../src/services/deduplicationService.ts | 6 +- .../photos/src/services/embeddingService.ts | 6 +- web/apps/photos/src/services/entityService.ts | 8 +- web/apps/photos/src/services/fileService.ts | 13 ++- .../src/services/publicCollectionService.ts | 9 +- web/apps/photos/src/services/trashService.ts | 8 +- .../services/upload/publicUploadHttpClient.ts | 10 +-- .../src/services/upload/uploadHttpClient.ts | 10 +-- web/apps/photos/src/services/userService.ts | 36 ++++---- web/packages/accounts/api/srp.ts | 26 +++--- web/packages/accounts/api/user.ts | 68 ++++++++------ web/packages/shared/network/api.ts | 88 ------------------- web/packages/shared/network/cast.ts | 2 +- 16 files changed, 139 insertions(+), 234 deletions(-) delete mode 100644 web/packages/shared/network/api.ts diff --git a/web/apps/auth/src/services/remote.ts b/web/apps/auth/src/services/remote.ts index 9885e0b75b..f6f24af022 100644 --- a/web/apps/auth/src/services/remote.ts +++ b/web/apps/auth/src/services/remote.ts @@ -2,14 +2,12 @@ import log from "@/next/log"; import ComlinkCryptoWorker from "@ente/shared/crypto"; import { ApiError, CustomError } from "@ente/shared/error"; import HTTPService from "@ente/shared/network/HTTPService"; -import { getEndpoint } from "@ente/shared/network/api"; +import { apiOrigin } from "@/next/origins"; import { getToken } from "@ente/shared/storage/localStorage/helpers"; import { getActualKey } from "@ente/shared/user"; import { HttpStatusCode } from "axios"; import { codeFromURIString, type Code } from "services/code"; -const ENDPOINT = getEndpoint(); - export const getAuthCodes = async (): Promise => { const masterKey = await getActualKey(); try { @@ -83,7 +81,7 @@ interface AuthKey { export const getAuthKey = async (): Promise => { try { const resp = await HTTPService.get( - `${ENDPOINT}/authenticator/key`, + `${apiOrigin()}/authenticator/key`, {}, { "X-Auth-Token": getToken(), @@ -110,7 +108,7 @@ export const getDiff = async ( ): Promise => { try { const resp = await HTTPService.get( - `${ENDPOINT}/authenticator/entity/diff`, + `${apiOrigin()}/authenticator/entity/diff`, { sinceTime, limit, diff --git a/web/apps/photos/src/services/billingService.ts b/web/apps/photos/src/services/billingService.ts index d68938eb5e..209d2ca59a 100644 --- a/web/apps/photos/src/services/billingService.ts +++ b/web/apps/photos/src/services/billingService.ts @@ -1,6 +1,6 @@ import log from "@/next/log"; +import { apiOrigin, paymentsAppOrigin } from "@/next/origins"; import HTTPService from "@ente/shared/network/HTTPService"; -import { getEndpoint, getPaymentsURL } from "@ente/shared/network/api"; import { LS_KEYS, removeData, @@ -12,8 +12,6 @@ import isElectron from "is-electron"; import { Plan, Subscription } from "types/billing"; import { getPaymentToken } from "./userService"; -const ENDPOINT = getEndpoint(); - enum PaymentActionType { Buy = "buy", Update = "update", @@ -36,11 +34,11 @@ class billingService { let response; if (!token) { response = await HTTPService.get( - `${ENDPOINT}/billing/plans/v2`, + `${apiOrigin()}/billing/plans/v2`, ); } else { response = await HTTPService.get( - `${ENDPOINT}/billing/user-plans`, + `${apiOrigin()}/billing/user-plans`, null, { "X-Auth-Token": getToken(), @@ -56,7 +54,7 @@ class billingService { public async syncSubscription() { try { const response = await HTTPService.get( - `${ENDPOINT}/billing/subscription`, + `${apiOrigin()}/billing/subscription`, null, { "X-Auth-Token": getToken(), @@ -100,7 +98,7 @@ class billingService { public async cancelSubscription() { try { const response = await HTTPService.post( - `${ENDPOINT}/billing/stripe/cancel-subscription`, + `${apiOrigin()}/billing/stripe/cancel-subscription`, null, null, { @@ -118,7 +116,7 @@ class billingService { public async activateSubscription() { try { const response = await HTTPService.post( - `${ENDPOINT}/billing/stripe/activate-subscription`, + `${apiOrigin()}/billing/stripe/activate-subscription`, null, null, { @@ -142,7 +140,7 @@ class billingService { return; } const response = await HTTPService.post( - `${ENDPOINT}/billing/verify-subscription`, + `${apiOrigin()}/billing/verify-subscription`, { paymentProvider: "stripe", productID: null, @@ -167,9 +165,14 @@ class billingService { return; } try { - await HTTPService.delete(`${ENDPOINT}/family/leave`, null, null, { - "X-Auth-Token": getToken(), - }); + await HTTPService.delete( + `${apiOrigin()}/family/leave`, + null, + null, + { + "X-Auth-Token": getToken(), + }, + ); removeData(LS_KEYS.FAMILY_DATA); } catch (e) { log.error("/family/leave failed", e); @@ -184,7 +187,7 @@ class billingService { ) { try { const redirectURL = this.getRedirectURL(); - window.location.href = `${getPaymentsURL()}?productID=${productID}&paymentToken=${paymentToken}&action=${action}&redirectURL=${redirectURL}`; + window.location.href = `${paymentsAppOrigin()}?productID=${productID}&paymentToken=${paymentToken}&action=${action}&redirectURL=${redirectURL}`; } catch (e) { log.error("unable to get payments url", e); throw e; @@ -195,7 +198,7 @@ class billingService { try { const redirectURL = this.getRedirectURL(); const response = await HTTPService.get( - `${ENDPOINT}/billing/stripe/customer-portal`, + `${apiOrigin()}/billing/stripe/customer-portal`, { redirectURL }, { "X-Auth-Token": getToken(), diff --git a/web/apps/photos/src/services/collectionService.ts b/web/apps/photos/src/services/collectionService.ts index 18d43d7447..485ea7cb76 100644 --- a/web/apps/photos/src/services/collectionService.ts +++ b/web/apps/photos/src/services/collectionService.ts @@ -1,11 +1,10 @@ -import { getEndpoint } from "@ente/shared/network/api"; -import localForage from "@ente/shared/storage/localForage"; -import { getData, LS_KEYS } from "@ente/shared/storage/localStorage"; - import log from "@/next/log"; +import { apiOrigin } from "@/next/origins"; import ComlinkCryptoWorker from "@ente/shared/crypto"; import { CustomError } from "@ente/shared/error"; import HTTPService from "@ente/shared/network/HTTPService"; +import localForage from "@ente/shared/storage/localForage"; +import { getData, LS_KEYS } from "@ente/shared/storage/localStorage"; import { getToken } from "@ente/shared/storage/localStorage/helpers"; import { getActualKey } from "@ente/shared/user"; import type { User } from "@ente/shared/user/types"; @@ -77,7 +76,6 @@ import { import { getLocalFiles } from "./fileService"; import { getPublicKey } from "./userService"; -const ENDPOINT = getEndpoint(); const COLLECTION_TABLE = "collections"; const COLLECTION_UPDATION_TIME = "collection-updation-time"; const HIDDEN_COLLECTION_IDS = "hidden-collection-ids"; @@ -183,7 +181,7 @@ const getCollections = async ( ): Promise => { try { const resp = await HTTPService.get( - `${ENDPOINT}/collections/v2`, + `${apiOrigin()}/collections/v2`, { sinceTime, }, @@ -330,7 +328,7 @@ export const getCollection = async ( return; } const resp = await HTTPService.get( - `${ENDPOINT}/collections/${collectionID}`, + `${apiOrigin()}/collections/${collectionID}`, null, { "X-Auth-Token": token }, ); @@ -474,7 +472,7 @@ const postCollection = async ( ): Promise => { try { const response = await HTTPService.post( - `${ENDPOINT}/collections`, + `${apiOrigin()}/collections`, collectionData, null, { "X-Auth-Token": token }, @@ -529,7 +527,7 @@ export const addToCollection = async ( files: fileKeysEncryptedWithNewCollection, }; await HTTPService.post( - `${ENDPOINT}/collections/add-files`, + `${apiOrigin()}/collections/add-files`, requestBody, null, { @@ -559,7 +557,7 @@ export const restoreToCollection = async ( files: fileKeysEncryptedWithNewCollection, }; await HTTPService.post( - `${ENDPOINT}/collections/restore-files`, + `${apiOrigin()}/collections/restore-files`, requestBody, null, { @@ -590,7 +588,7 @@ export const moveToCollection = async ( files: fileKeysEncryptedWithNewCollection, }; await HTTPService.post( - `${ENDPOINT}/collections/move-files`, + `${apiOrigin()}/collections/move-files`, requestBody, null, { @@ -736,7 +734,7 @@ export const removeNonUserFiles = async ( }; await HTTPService.post( - `${ENDPOINT}/collections/v3/remove-files`, + `${apiOrigin()}/collections/v3/remove-files`, request, null, { "X-Auth-Token": token }, @@ -763,7 +761,7 @@ export const deleteCollection = async ( const token = getToken(); await HTTPService.delete( - `${ENDPOINT}/collections/v3/${collectionID}`, + `${apiOrigin()}/collections/v3/${collectionID}`, null, { collectionID, keepFiles }, { "X-Auth-Token": token }, @@ -779,7 +777,7 @@ export const leaveSharedAlbum = async (collectionID: number) => { const token = getToken(); await HTTPService.post( - `${ENDPOINT}/collections/leave/${collectionID}`, + `${apiOrigin()}/collections/leave/${collectionID}`, null, null, { "X-Auth-Token": token }, @@ -817,7 +815,7 @@ export const updateCollectionMagicMetadata = async ( }; await HTTPService.put( - `${ENDPOINT}/collections/magic-metadata`, + `${apiOrigin()}/collections/magic-metadata`, reqBody, null, { @@ -861,7 +859,7 @@ export const updateSharedCollectionMagicMetadata = async ( }; await HTTPService.put( - `${ENDPOINT}/collections/sharee-magic-metadata`, + `${apiOrigin()}/collections/sharee-magic-metadata`, reqBody, null, { @@ -905,7 +903,7 @@ export const updatePublicCollectionMagicMetadata = async ( }; await HTTPService.put( - `${ENDPOINT}/collections/public-magic-metadata`, + `${apiOrigin()}/collections/public-magic-metadata`, reqBody, null, { @@ -940,7 +938,7 @@ export const renameCollection = async ( nameDecryptionNonce, }; await HTTPService.post( - `${ENDPOINT}/collections/rename`, + `${apiOrigin()}/collections/rename`, collectionRenameRequest, null, { @@ -969,7 +967,7 @@ export const shareCollection = async ( encryptedKey, }; await HTTPService.post( - `${ENDPOINT}/collections/share`, + `${apiOrigin()}/collections/share`, shareCollectionRequest, null, { @@ -993,7 +991,7 @@ export const unshareCollection = async ( email: withUserEmail, }; await HTTPService.post( - `${ENDPOINT}/collections/unshare`, + `${apiOrigin()}/collections/unshare`, shareCollectionRequest, null, { @@ -1015,7 +1013,7 @@ export const createShareableURL = async (collection: Collection) => { collectionID: collection.id, }; const resp = await HTTPService.post( - `${ENDPOINT}/collections/share-url`, + `${apiOrigin()}/collections/share-url`, createPublicAccessTokenRequest, null, { @@ -1036,7 +1034,7 @@ export const deleteShareableURL = async (collection: Collection) => { return null; } await HTTPService.delete( - `${ENDPOINT}/collections/share-url/${collection.id}`, + `${apiOrigin()}/collections/share-url/${collection.id}`, null, null, { @@ -1058,7 +1056,7 @@ export const updateShareableURL = async ( return null; } const res = await HTTPService.put( - `${ENDPOINT}/collections/share-url`, + `${apiOrigin()}/collections/share-url`, request, null, { diff --git a/web/apps/photos/src/services/deduplicationService.ts b/web/apps/photos/src/services/deduplicationService.ts index 1683e554c4..c50794b9a0 100644 --- a/web/apps/photos/src/services/deduplicationService.ts +++ b/web/apps/photos/src/services/deduplicationService.ts @@ -3,12 +3,10 @@ import { FILE_TYPE } from "@/media/file-type"; import type { Metadata } from "@/media/types/file"; import log from "@/next/log"; import HTTPService from "@ente/shared/network/HTTPService"; -import { getEndpoint } from "@ente/shared/network/api"; +import { apiOrigin } from "@/next/origins"; import { getToken } from "@ente/shared/storage/localStorage/helpers"; import { EnteFile } from "types/file"; -const ENDPOINT = getEndpoint(); - interface DuplicatesResponse { duplicates: Array<{ fileIDs: number[]; @@ -148,7 +146,7 @@ function groupDupesByFileHashes(dupe: Duplicate) { async function fetchDuplicateFileIDs() { try { const response = await HTTPService.get( - `${ENDPOINT}/files/duplicates`, + `${apiOrigin()}/files/duplicates`, null, { "X-Auth-Token": getToken(), diff --git a/web/apps/photos/src/services/embeddingService.ts b/web/apps/photos/src/services/embeddingService.ts index fb77609258..04c5089f9e 100644 --- a/web/apps/photos/src/services/embeddingService.ts +++ b/web/apps/photos/src/services/embeddingService.ts @@ -1,10 +1,10 @@ import { inWorker } from "@/next/env"; import log from "@/next/log"; +import { apiOrigin } from "@/next/origins"; import { workerBridge } from "@/next/worker/worker-bridge"; import ComlinkCryptoWorker from "@ente/shared/crypto"; import { CustomError } from "@ente/shared/error"; import HTTPService from "@ente/shared/network/HTTPService"; -import { getEndpoint } from "@ente/shared/network/api"; import localForage from "@ente/shared/storage/localForage"; import { getToken } from "@ente/shared/storage/localStorage/helpers"; import type { @@ -285,7 +285,7 @@ export const getEmbeddingsDiff = async ( return; } const response = await HTTPService.get( - `${getEndpoint()}/embeddings/diff`, + `${apiOrigin()}/embeddings/diff`, { sinceTime, limit: DIFF_LIMIT, @@ -314,7 +314,7 @@ export const putEmbedding = async ( throw Error(CustomError.TOKEN_MISSING); } const resp = await HTTPService.put( - `${getEndpoint()}/embeddings`, + `${apiOrigin()}/embeddings`, putEmbeddingReq, null, { diff --git a/web/apps/photos/src/services/entityService.ts b/web/apps/photos/src/services/entityService.ts index 31607110ed..90162a3bdc 100644 --- a/web/apps/photos/src/services/entityService.ts +++ b/web/apps/photos/src/services/entityService.ts @@ -1,7 +1,7 @@ import log from "@/next/log"; import ComlinkCryptoWorker from "@ente/shared/crypto"; import HTTPService from "@ente/shared/network/HTTPService"; -import { getEndpoint } from "@ente/shared/network/api"; +import { apiOrigin } from "@/next/origins"; import localForage from "@ente/shared/storage/localForage"; import { getToken } from "@ente/shared/storage/localStorage/helpers"; import { getActualKey } from "@ente/shared/user"; @@ -15,8 +15,6 @@ import { } from "types/entity"; import { getLatestVersionEntities } from "utils/entity"; -const ENDPOINT = getEndpoint(); - const DIFF_LIMIT = 500; const ENTITY_TABLES: Record = { @@ -60,7 +58,7 @@ const getEntityKey = async (type: EntityType) => { return; } const resp = await HTTPService.get( - `${ENDPOINT}/user-entity/key`, + `${apiOrigin()}/user-entity/key`, { type, }, @@ -175,7 +173,7 @@ const getEntityDiff = async ( return; } const resp = await HTTPService.get( - `${ENDPOINT}/user-entity/entity/diff`, + `${apiOrigin()}/user-entity/entity/diff`, { sinceTime: time, type, diff --git a/web/apps/photos/src/services/fileService.ts b/web/apps/photos/src/services/fileService.ts index a3aa90ab04..a9ac0c7848 100644 --- a/web/apps/photos/src/services/fileService.ts +++ b/web/apps/photos/src/services/fileService.ts @@ -2,7 +2,7 @@ import log from "@/next/log"; import ComlinkCryptoWorker from "@ente/shared/crypto"; import { Events, eventBus } from "@ente/shared/events"; import HTTPService from "@ente/shared/network/HTTPService"; -import { getEndpoint } from "@ente/shared/network/api"; +import { apiOrigin } from "@/next/origins"; import localForage from "@ente/shared/storage/localForage"; import { getToken } from "@ente/shared/storage/localStorage/helpers"; import { REQUEST_BATCH_SIZE } from "constants/api"; @@ -28,7 +28,6 @@ import { setCollectionLastSyncTime, } from "./collectionService"; -const ENDPOINT = getEndpoint(); const FILES_TABLE = "files"; const HIDDEN_FILES_TABLE = "hidden-files"; @@ -118,7 +117,7 @@ export const getFiles = async ( break; } resp = await HTTPService.get( - `${ENDPOINT}/collections/v2/diff`, + `${apiOrigin()}/collections/v2/diff`, { collectionID: collection.id, sinceTime: time, @@ -187,7 +186,7 @@ export const trashFiles = async (filesToTrash: EnteFile[]) => { })), }; await HTTPService.post( - `${ENDPOINT}/files/trash`, + `${apiOrigin()}/files/trash`, trashRequest, null, { @@ -211,7 +210,7 @@ export const deleteFromTrash = async (filesToDelete: number[]) => { for (const batch of batchedFilesToDelete) { await HTTPService.post( - `${ENDPOINT}/trash/delete`, + `${apiOrigin()}/trash/delete`, { fileIDs: batch }, null, { @@ -253,7 +252,7 @@ export const updateFileMagicMetadata = async ( }, }); } - await HTTPService.put(`${ENDPOINT}/files/magic-metadata`, reqBody, null, { + await HTTPService.put(`${apiOrigin()}/files/magic-metadata`, reqBody, null, { "X-Auth-Token": token, }); return fileWithUpdatedMagicMetadataList.map( @@ -296,7 +295,7 @@ export const updateFilePublicMagicMetadata = async ( }); } await HTTPService.put( - `${ENDPOINT}/files/public-magic-metadata`, + `${apiOrigin()}/files/public-magic-metadata`, reqBody, null, { diff --git a/web/apps/photos/src/services/publicCollectionService.ts b/web/apps/photos/src/services/publicCollectionService.ts index 2cf42254f5..db03ab4bc3 100644 --- a/web/apps/photos/src/services/publicCollectionService.ts +++ b/web/apps/photos/src/services/publicCollectionService.ts @@ -2,14 +2,13 @@ import log from "@/next/log"; import ComlinkCryptoWorker from "@ente/shared/crypto"; import { CustomError, parseSharingErrorCodes } from "@ente/shared/error"; import HTTPService from "@ente/shared/network/HTTPService"; -import { getEndpoint } from "@ente/shared/network/api"; +import { apiOrigin } from "@/next/origins"; import localForage from "@ente/shared/storage/localForage"; import { Collection, CollectionPublicMagicMetadata } from "types/collection"; import { EncryptedEnteFile, EnteFile } from "types/file"; import { LocalSavedPublicCollectionFiles } from "types/publicCollection"; import { decryptFile, mergeMetadata, sortFiles } from "utils/file"; -const ENDPOINT = getEndpoint(); const PUBLIC_COLLECTION_FILES_TABLE = "public-collection-files"; const PUBLIC_COLLECTIONS_TABLE = "public-collections"; const PUBLIC_REFERRAL_CODE = "public-referral-code"; @@ -253,7 +252,7 @@ const getPublicFiles = async ( break; } resp = await HTTPService.get( - `${ENDPOINT}/public-collection/diff`, + `${apiOrigin()}/public-collection/diff`, { sinceTime: time, }, @@ -308,7 +307,7 @@ export const getPublicCollection = async ( return; } const resp = await HTTPService.get( - `${ENDPOINT}/public-collection/info`, + `${apiOrigin()}/public-collection/info`, null, { "Cache-Control": "no-cache", "X-Auth-Access-Token": token }, ); @@ -358,7 +357,7 @@ export const verifyPublicCollectionPassword = async ( ): Promise => { try { const resp = await HTTPService.post( - `${ENDPOINT}/public-collection/verify-password`, + `${apiOrigin()}/public-collection/verify-password`, { passHash: passwordHash }, null, { "Cache-Control": "no-cache", "X-Auth-Access-Token": token }, diff --git a/web/apps/photos/src/services/trashService.ts b/web/apps/photos/src/services/trashService.ts index 7088bc0860..b281a9a13f 100644 --- a/web/apps/photos/src/services/trashService.ts +++ b/web/apps/photos/src/services/trashService.ts @@ -1,6 +1,6 @@ import log from "@/next/log"; import HTTPService from "@ente/shared/network/HTTPService"; -import { getEndpoint } from "@ente/shared/network/api"; +import { apiOrigin } from "@/next/origins"; import localForage from "@ente/shared/storage/localForage"; import { getToken } from "@ente/shared/storage/localStorage/helpers"; import { Collection } from "types/collection"; @@ -14,8 +14,6 @@ const TRASH = "file-trash"; const TRASH_TIME = "trash-time"; const DELETED_COLLECTION = "deleted-collection"; -const ENDPOINT = getEndpoint(); - async function getLocalTrash() { const trash = (await localForage.getItem(TRASH)) || []; return trash; @@ -91,7 +89,7 @@ export const updateTrash = async ( break; } resp = await HTTPService.get( - `${ENDPOINT}/trash/v2/diff`, + `${apiOrigin()}/trash/v2/diff`, { sinceTime: time, }, @@ -160,7 +158,7 @@ export const emptyTrash = async () => { const lastUpdatedAt = await getLastSyncTime(); await HTTPService.post( - `${ENDPOINT}/trash/empty`, + `${apiOrigin()}/trash/empty`, { lastUpdatedAt }, null, { diff --git a/web/apps/photos/src/services/upload/publicUploadHttpClient.ts b/web/apps/photos/src/services/upload/publicUploadHttpClient.ts index 8f18a1638b..0750211458 100644 --- a/web/apps/photos/src/services/upload/publicUploadHttpClient.ts +++ b/web/apps/photos/src/services/upload/publicUploadHttpClient.ts @@ -1,13 +1,11 @@ import log from "@/next/log"; import { CustomError, handleUploadError } from "@ente/shared/error"; import HTTPService from "@ente/shared/network/HTTPService"; -import { getEndpoint } from "@ente/shared/network/api"; +import { apiOrigin } from "@/next/origins"; import { EnteFile } from "types/file"; import { retryHTTPCall } from "./uploadHttpClient"; import { MultipartUploadURLs, UploadFile, UploadURL } from "./uploadService"; -const ENDPOINT = getEndpoint(); - const MAX_URL_REQUESTS = 50; class PublicUploadHttpClient { @@ -25,7 +23,7 @@ class PublicUploadHttpClient { const response = await retryHTTPCall( () => HTTPService.post( - `${ENDPOINT}/public-collection/file`, + `${apiOrigin()}/public-collection/file`, uploadFile, null, { @@ -57,7 +55,7 @@ class PublicUploadHttpClient { throw Error(CustomError.TOKEN_MISSING); } this.uploadURLFetchInProgress = HTTPService.get( - `${ENDPOINT}/public-collection/upload-urls`, + `${apiOrigin()}/public-collection/upload-urls`, { count: Math.min(MAX_URL_REQUESTS, count * 2), }, @@ -93,7 +91,7 @@ class PublicUploadHttpClient { throw Error(CustomError.TOKEN_MISSING); } const response = await HTTPService.get( - `${ENDPOINT}/public-collection/multipart-upload-urls`, + `${apiOrigin()}/public-collection/multipart-upload-urls`, { count, }, diff --git a/web/apps/photos/src/services/upload/uploadHttpClient.ts b/web/apps/photos/src/services/upload/uploadHttpClient.ts index 3323edc281..6841c0c1e8 100644 --- a/web/apps/photos/src/services/upload/uploadHttpClient.ts +++ b/web/apps/photos/src/services/upload/uploadHttpClient.ts @@ -1,14 +1,12 @@ import log from "@/next/log"; +import { apiOrigin, uploaderOrigin } from "@/next/origins"; import { wait } from "@/utils/promise"; import { CustomError, handleUploadError } from "@ente/shared/error"; import HTTPService from "@ente/shared/network/HTTPService"; -import { getEndpoint, uploaderOrigin } from "@ente/shared/network/api"; import { getToken } from "@ente/shared/storage/localStorage/helpers"; import { EnteFile } from "types/file"; import { MultipartUploadURLs, UploadFile, UploadURL } from "./uploadService"; -const ENDPOINT = getEndpoint(); - const MAX_URL_REQUESTS = 50; class UploadHttpClient { @@ -22,7 +20,7 @@ class UploadHttpClient { } const response = await retryHTTPCall( () => - HTTPService.post(`${ENDPOINT}/files`, uploadFile, null, { + HTTPService.post(`${apiOrigin()}/files`, uploadFile, null, { "X-Auth-Token": token, }), handleUploadError, @@ -43,7 +41,7 @@ class UploadHttpClient { return; } this.uploadURLFetchInProgress = HTTPService.get( - `${ENDPOINT}/files/upload-urls`, + `${apiOrigin()}/files/upload-urls`, { count: Math.min(MAX_URL_REQUESTS, count * 2), }, @@ -73,7 +71,7 @@ class UploadHttpClient { return; } const response = await HTTPService.get( - `${ENDPOINT}/files/multipart-upload-urls`, + `${apiOrigin()}/files/multipart-upload-urls`, { count, }, diff --git a/web/apps/photos/src/services/userService.ts b/web/apps/photos/src/services/userService.ts index 8be4d55d57..6ae112635a 100644 --- a/web/apps/photos/src/services/userService.ts +++ b/web/apps/photos/src/services/userService.ts @@ -1,12 +1,8 @@ import log from "@/next/log"; +import { apiOrigin, customAPIOrigin, familyAppOrigin } from "@/next/origins"; import { putAttributes } from "@ente/accounts/api/user"; import { ApiError } from "@ente/shared/error"; import HTTPService from "@ente/shared/network/HTTPService"; -import { - customAPIOrigin, - getEndpoint, - getFamilyPortalURL, -} from "@ente/shared/network/api"; import { LS_KEYS, getData } from "@ente/shared/storage/localStorage"; import { getToken, @@ -21,15 +17,13 @@ import { } from "types/user"; import { getLocalFamilyData, isPartOfFamily } from "utils/user/family"; -const ENDPOINT = getEndpoint(); - const HAS_SET_KEYS = "hasSetKeys"; export const getPublicKey = async (email: string) => { const token = getToken(); const resp = await HTTPService.get( - `${ENDPOINT}/users/public-key`, + `${apiOrigin()}/users/public-key`, { email }, { "X-Auth-Token": token, @@ -42,7 +36,7 @@ export const getPaymentToken = async () => { const token = getToken(); const resp = await HTTPService.get( - `${ENDPOINT}/users/payment-token`, + `${apiOrigin()}/users/payment-token`, null, { "X-Auth-Token": token, @@ -56,7 +50,7 @@ export const getFamiliesToken = async () => { const token = getToken(); const resp = await HTTPService.get( - `${ENDPOINT}/users/families-token`, + `${apiOrigin()}/users/families-token`, null, { "X-Auth-Token": token, @@ -74,7 +68,7 @@ export const getRoadmapRedirectURL = async () => { const token = getToken(); const resp = await HTTPService.get( - `${ENDPOINT}/users/roadmap/v2`, + `${apiOrigin()}/users/roadmap/v2`, null, { "X-Auth-Token": token, @@ -90,7 +84,7 @@ export const getRoadmapRedirectURL = async () => { export const isTokenValid = async (token: string) => { try { const resp = await HTTPService.get( - `${ENDPOINT}/users/session-validity/v2`, + `${apiOrigin()}/users/session-validity/v2`, null, { "X-Auth-Token": token, @@ -129,7 +123,7 @@ export const isTokenValid = async (token: string) => { export const getTwoFactorStatus = async () => { const resp = await HTTPService.get( - `${ENDPOINT}/users/two-factor/status`, + `${apiOrigin()}/users/two-factor/status`, null, { "X-Auth-Token": getToken(), @@ -143,7 +137,7 @@ export const getUserDetailsV2 = async (): Promise => { const token = getToken(); const resp = await HTTPService.get( - `${ENDPOINT}/users/details/v2`, + `${apiOrigin()}/users/details/v2`, null, { "X-Auth-Token": token, @@ -160,7 +154,7 @@ export const getFamilyPortalRedirectURL = async () => { try { const jwtToken = await getFamiliesToken(); const isFamilyCreated = isPartOfFamily(getLocalFamilyData()); - return `${getFamilyPortalURL()}?token=${jwtToken}&isFamilyCreated=${isFamilyCreated}&redirectURL=${ + return `${familyAppOrigin()}?token=${jwtToken}&isFamilyCreated=${isFamilyCreated}&redirectURL=${ window.location.origin }/gallery`; } catch (e) { @@ -174,7 +168,7 @@ export const getAccountDeleteChallenge = async () => { const token = getToken(); const resp = await HTTPService.get( - `${ENDPOINT}/users/delete-challenge`, + `${apiOrigin()}/users/delete-challenge`, null, { "X-Auth-Token": token, @@ -199,7 +193,7 @@ export const deleteAccount = async ( } await HTTPService.delete( - `${ENDPOINT}/users/delete`, + `${apiOrigin()}/users/delete`, { challenge, reason, feedback }, null, { @@ -217,7 +211,7 @@ export const getFaceSearchEnabledStatus = async () => { const token = getToken(); const resp: AxiosResponse = await HTTPService.get( - `${ENDPOINT}/remote-store`, + `${apiOrigin()}/remote-store`, { key: "faceSearchEnabled", defaultValue: false, @@ -237,7 +231,7 @@ export const updateFaceSearchEnabledStatus = async (newStatus: boolean) => { try { const token = getToken(); await HTTPService.post( - `${ENDPOINT}/remote-store/update`, + `${apiOrigin()}/remote-store/update`, { key: "faceSearchEnabled", value: newStatus.toString(), @@ -268,7 +262,7 @@ export const getMapEnabledStatus = async () => { const token = getToken(); const resp: AxiosResponse = await HTTPService.get( - `${ENDPOINT}/remote-store`, + `${apiOrigin()}/remote-store`, { key: "mapEnabled", defaultValue: false, @@ -288,7 +282,7 @@ export const updateMapEnabledStatus = async (newStatus: boolean) => { try { const token = getToken(); await HTTPService.post( - `${ENDPOINT}/remote-store/update`, + `${apiOrigin()}/remote-store/update`, { key: "mapEnabled", value: newStatus.toString(), diff --git a/web/packages/accounts/api/srp.ts b/web/packages/accounts/api/srp.ts index a9af2c15e1..d221309f05 100644 --- a/web/packages/accounts/api/srp.ts +++ b/web/packages/accounts/api/srp.ts @@ -1,7 +1,5 @@ import log from "@/next/log"; -import HTTPService from "@ente/shared/network/HTTPService"; -import { getEndpoint } from "@ente/shared/network/api"; - +import { apiOrigin } from "@/next/origins"; import type { CompleteSRPSetupRequest, CompleteSRPSetupResponse, @@ -15,17 +13,19 @@ import type { UpdateSRPAndKeysResponse, } from "@ente/accounts/types/srp"; import { ApiError, CustomError } from "@ente/shared/error"; +import HTTPService from "@ente/shared/network/HTTPService"; import { HttpStatusCode } from "axios"; -const ENDPOINT = getEndpoint(); - export const getSRPAttributes = async ( email: string, ): Promise => { try { - const resp = await HTTPService.get(`${ENDPOINT}/users/srp/attributes`, { - email, - }); + const resp = await HTTPService.get( + `${apiOrigin()}/users/srp/attributes`, + { + email, + }, + ); return (resp.data as GetSRPAttributesResponse).attributes; } catch (e) { log.error("failed to get SRP attributes", e); @@ -39,7 +39,7 @@ export const startSRPSetup = async ( ): Promise => { try { const resp = await HTTPService.post( - `${ENDPOINT}/users/srp/setup`, + `${apiOrigin()}/users/srp/setup`, setupSRPRequest, undefined, { @@ -60,7 +60,7 @@ export const completeSRPSetup = async ( ) => { try { const resp = await HTTPService.post( - `${ENDPOINT}/users/srp/complete`, + `${apiOrigin()}/users/srp/complete`, completeSRPSetupRequest, undefined, { @@ -77,7 +77,7 @@ export const completeSRPSetup = async ( export const createSRPSession = async (srpUserID: string, srpA: string) => { try { const resp = await HTTPService.post( - `${ENDPOINT}/users/srp/create-session`, + `${apiOrigin()}/users/srp/create-session`, { srpUserID, srpA, @@ -97,7 +97,7 @@ export const verifySRPSession = async ( ) => { try { const resp = await HTTPService.post( - `${ENDPOINT}/users/srp/verify-session`, + `${apiOrigin()}/users/srp/verify-session`, { sessionID, srpUserID, @@ -125,7 +125,7 @@ export const updateSRPAndKeys = async ( ): Promise => { try { const resp = await HTTPService.post( - `${ENDPOINT}/users/srp/update`, + `${apiOrigin()}/users/srp/update`, updateSRPAndKeyRequest, undefined, { diff --git a/web/packages/accounts/api/user.ts b/web/packages/accounts/api/user.ts index 1060bb6ebb..3303778730 100644 --- a/web/packages/accounts/api/user.ts +++ b/web/packages/accounts/api/user.ts @@ -1,3 +1,4 @@ +import { apiOrigin } from "@/next/origins"; import type { AppName } from "@/next/types/app"; import type { RecoveryKey, @@ -9,15 +10,12 @@ import type { import type { B64EncryptionResult } from "@ente/shared/crypto/types"; import { ApiError, CustomError } from "@ente/shared/error"; import HTTPService from "@ente/shared/network/HTTPService"; -import { getEndpoint } from "@ente/shared/network/api"; import { getToken } from "@ente/shared/storage/localStorage/helpers"; import type { KeyAttributes } from "@ente/shared/user/types"; import { HttpStatusCode } from "axios"; -const ENDPOINT = getEndpoint(); - export const sendOtt = (appName: AppName, email: string) => { - return HTTPService.post(`${ENDPOINT}/users/ott`, { + return HTTPService.post(`${apiOrigin()}/users/ott`, { email, client: appName == "auth" ? "totp" : "web", }); @@ -25,7 +23,7 @@ export const sendOtt = (appName: AppName, email: string) => { export const verifyOtt = (email: string, ott: string, referral: string) => { const cleanedReferral = `web:${referral?.trim() || ""}`; - return HTTPService.post(`${ENDPOINT}/users/verify-email`, { + return HTTPService.post(`${apiOrigin()}/users/verify-email`, { email, ott, source: cleanedReferral, @@ -34,7 +32,7 @@ export const verifyOtt = (email: string, ott: string, referral: string) => { export const putAttributes = (token: string, keyAttributes: KeyAttributes) => HTTPService.put( - `${ENDPOINT}/users/attributes`, + `${apiOrigin()}/users/attributes`, { keyAttributes }, undefined, { @@ -45,7 +43,7 @@ export const putAttributes = (token: string, keyAttributes: KeyAttributes) => export const logout = async () => { try { const token = getToken(); - await HTTPService.post(`${ENDPOINT}/users/logout`, null, undefined, { + await HTTPService.post(`${apiOrigin()}/users/logout`, null, undefined, { "X-Auth-Token": token, }); } catch (e) { @@ -65,10 +63,13 @@ export const logout = async () => { }; export const verifyTwoFactor = async (code: string, sessionID: string) => { - const resp = await HTTPService.post(`${ENDPOINT}/users/two-factor/verify`, { - code, - sessionID, - }); + const resp = await HTTPService.post( + `${apiOrigin()}/users/two-factor/verify`, + { + code, + sessionID, + }, + ); return resp.data as UserVerificationResponse; }; @@ -79,10 +80,13 @@ export const recoverTwoFactor = async ( sessionID: string, twoFactorType: TwoFactorType, ) => { - const resp = await HTTPService.get(`${ENDPOINT}/users/two-factor/recover`, { - sessionID, - twoFactorType, - }); + const resp = await HTTPService.get( + `${apiOrigin()}/users/two-factor/recover`, + { + sessionID, + twoFactorType, + }, + ); return resp.data as TwoFactorRecoveryResponse; }; @@ -91,17 +95,20 @@ export const removeTwoFactor = async ( secret: string, twoFactorType: TwoFactorType, ) => { - const resp = await HTTPService.post(`${ENDPOINT}/users/two-factor/remove`, { - sessionID, - secret, - twoFactorType, - }); + const resp = await HTTPService.post( + `${apiOrigin()}/users/two-factor/remove`, + { + sessionID, + secret, + twoFactorType, + }, + ); return resp.data as TwoFactorVerificationResponse; }; export const changeEmail = async (email: string, ott: string) => { await HTTPService.post( - `${ENDPOINT}/users/change-email`, + `${apiOrigin()}/users/change-email`, { email, ott, @@ -114,7 +121,7 @@ export const changeEmail = async (email: string, ott: string) => { }; export const sendOTTForEmailChange = async (email: string) => { - await HTTPService.post(`${ENDPOINT}/users/ott`, { + await HTTPService.post(`${apiOrigin()}/users/ott`, { email, client: "web", purpose: "change", @@ -123,7 +130,7 @@ export const sendOTTForEmailChange = async (email: string) => { export const setupTwoFactor = async () => { const resp = await HTTPService.post( - `${ENDPOINT}/users/two-factor/setup`, + `${apiOrigin()}/users/two-factor/setup`, null, undefined, { @@ -138,7 +145,7 @@ export const enableTwoFactor = async ( recoveryEncryptedTwoFactorSecret: B64EncryptionResult, ) => { await HTTPService.post( - `${ENDPOINT}/users/two-factor/enable`, + `${apiOrigin()}/users/two-factor/enable`, { code, encryptedTwoFactorSecret: @@ -154,13 +161,18 @@ export const enableTwoFactor = async ( }; export const setRecoveryKey = (token: string, recoveryKey: RecoveryKey) => - HTTPService.put(`${ENDPOINT}/users/recovery-key`, recoveryKey, undefined, { - "X-Auth-Token": token, - }); + HTTPService.put( + `${apiOrigin()}/users/recovery-key`, + recoveryKey, + undefined, + { + "X-Auth-Token": token, + }, + ); export const disableTwoFactor = async () => { await HTTPService.post( - `${ENDPOINT}/users/two-factor/disable`, + `${apiOrigin()}/users/two-factor/disable`, null, undefined, { diff --git a/web/packages/shared/network/api.ts b/web/packages/shared/network/api.ts deleted file mode 100644 index 934256c094..0000000000 --- a/web/packages/shared/network/api.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { nullToUndefined } from "@/utils/transform"; - -/** - * Return the origin (scheme, host, port triple) that should be used for making - * API requests to museum. - * - * This defaults to {@link defaultAPIOrigin}, but can be overridden when self - * hosting or developing by setting the `NEXT_PUBLIC_ENTE_ENDPOINT` environment - * variable. - */ -export const apiOrigin = () => customAPIOrigin() ?? defaultAPIOrigin; - -/** - * Return the overridden API origin, if one is defined by either (in priority - * order): - * - * - Setting the custom server on the landing page (See: [Note: Configuring - * custom server]); or by - * - * - Setting the `NEXT_PUBLIC_ENTE_ENDPOINT` environment variable. - * - * Otherwise return undefined. - */ -export const customAPIOrigin = () => - nullToUndefined(localStorage.getItem("apiOrigin")) ?? - process.env.NEXT_PUBLIC_ENTE_ENDPOINT ?? - undefined; - -/** - * Default value of {@link apiOrigin}: "https://api.ente.io", Ente's production - * API servers. - */ -export const defaultAPIOrigin = "https://api.ente.io"; - -/** Deprecated, use {@link apiOrigin} instead. */ -export const getEndpoint = apiOrigin; - -/** - * Return the origin that should be used for uploading files. - * - * This defaults to `https://uploader.ente.io`, serviced by a Cloudflare worker - * (see infra/workers/uploader). But if a {@link customAPIOrigin} is set then - * this value is set to the {@link customAPIOrigin} itself, effectively - * bypassing the Cloudflare worker for non-Ente deployments. - */ -export const uploaderOrigin = () => - customAPIOrigin() ?? "https://uploader.ente.io"; - -/** - * Return the URL of the Ente Accounts app. - * - * Defaults to our production instance, "https://accounts.ente.io", but can be - * overridden by setting the `NEXT_PUBLIC_ENTE_ACCOUNTS_URL` environment - * variable. - */ -export const accountsAppURL = () => - process.env.NEXT_PUBLIC_ENTE_ACCOUNTS_URL ?? `https://accounts.ente.io`; - -export const getAlbumsURL = () => { - const albumsURL = process.env.NEXT_PUBLIC_ENTE_ALBUMS_ENDPOINT; - if (albumsURL) { - return albumsURL; - } - return `https://albums.ente.io`; -}; - -/** - * Return the URL for the family dashboard which can be used to create or manage - * family plans. - */ -export const getFamilyPortalURL = () => { - const familyURL = process.env.NEXT_PUBLIC_ENTE_FAMILY_URL; - if (familyURL) { - return familyURL; - } - return `https://family.ente.io`; -}; - -/** - * Return the URL for the host that handles payment related functionality. - */ -export const getPaymentsURL = () => { - const paymentsURL = process.env.NEXT_PUBLIC_ENTE_PAYMENTS_URL; - if (paymentsURL) { - return paymentsURL; - } - return `https://payments.ente.io`; -}; diff --git a/web/packages/shared/network/cast.ts b/web/packages/shared/network/cast.ts index a18767baa2..0425d0dc4f 100644 --- a/web/packages/shared/network/cast.ts +++ b/web/packages/shared/network/cast.ts @@ -2,7 +2,7 @@ import log from "@/next/log"; import { ApiError } from "../error"; import { getToken } from "../storage/localStorage/helpers"; import HTTPService from "./HTTPService"; -import { getEndpoint } from "./api"; +import { apiOrigin } from "@/next/origins"; class CastGateway { constructor() {} From 4c1462e18a90b8fac1568d91a312f305778ab36a Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sun, 23 Jun 2024 19:52:55 +0530 Subject: [PATCH 106/130] Remove unused --- web/packages/shared/storage/localStorage/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/web/packages/shared/storage/localStorage/index.ts b/web/packages/shared/storage/localStorage/index.ts index beefbf37fe..3df5caf973 100644 --- a/web/packages/shared/storage/localStorage/index.ts +++ b/web/packages/shared/storage/localStorage/index.ts @@ -18,7 +18,6 @@ export enum LS_KEYS { COLLECTION_SORT_BY = "collectionSortBy", THEME = "theme", WAIT_TIME = "waitTime", - API_ENDPOINT = "apiEndpoint", // Moved to the new wrapper @/next/local-storage // LOCALE = 'locale', MAP_ENABLED = "mapEnabled", From f34ecc101602d3b7f1652b823b2062dd73d6f42f Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sun, 23 Jun 2024 19:54:44 +0530 Subject: [PATCH 107/130] Fix imports - This + preceding commits fix SSR --- web/apps/accounts/src/services/passkey.ts | 2 +- web/apps/auth/src/services/remote.ts | 2 +- web/apps/cast/src/services/render.ts | 2 +- web/apps/photos/src/constants/billing.ts | 4 ---- web/apps/photos/src/pages/index.tsx | 4 ++-- web/apps/photos/src/services/billingService.ts | 3 +-- .../photos/src/services/deduplicationService.ts | 2 +- .../photos/src/services/download/clients/photos.ts | 2 +- .../src/services/download/clients/publicAlbums.ts | 2 +- web/apps/photos/src/services/entityService.ts | 2 +- web/apps/photos/src/services/fileService.ts | 13 +++++++++---- .../photos/src/services/publicCollectionService.ts | 2 +- web/apps/photos/src/services/trashService.ts | 2 +- .../src/services/upload/publicUploadHttpClient.ts | 2 +- web/packages/accounts/services/passkey.ts | 6 +++--- web/packages/new/photos/services/feature-flags.ts | 2 +- web/packages/shared/components/LoginComponents.tsx | 2 +- web/packages/shared/network/cast.ts | 12 ++++++------ 18 files changed, 33 insertions(+), 33 deletions(-) delete mode 100644 web/apps/photos/src/constants/billing.ts diff --git a/web/apps/accounts/src/services/passkey.ts b/web/apps/accounts/src/services/passkey.ts index 5d056901fa..da026c42e6 100644 --- a/web/apps/accounts/src/services/passkey.ts +++ b/web/apps/accounts/src/services/passkey.ts @@ -1,4 +1,5 @@ import { isDevBuild } from "@/next/env"; +import { apiOrigin } from "@/next/origins"; import { clientPackageName } from "@/next/types/app"; import { TwoFactorAuthorizationResponse } from "@/next/types/credentials"; import { ensure } from "@/utils/ensure"; @@ -8,7 +9,6 @@ import { toB64URLSafeNoPadding, toB64URLSafeNoPaddingString, } from "@ente/shared/crypto/internal/libsodium"; -import { apiOrigin } from "@ente/shared/network/api"; import { z } from "zod"; /** Return true if the user's browser supports WebAuthn (Passkeys). */ diff --git a/web/apps/auth/src/services/remote.ts b/web/apps/auth/src/services/remote.ts index f6f24af022..0202d997e3 100644 --- a/web/apps/auth/src/services/remote.ts +++ b/web/apps/auth/src/services/remote.ts @@ -1,8 +1,8 @@ import log from "@/next/log"; +import { apiOrigin } from "@/next/origins"; import ComlinkCryptoWorker from "@ente/shared/crypto"; import { ApiError, CustomError } from "@ente/shared/error"; import HTTPService from "@ente/shared/network/HTTPService"; -import { apiOrigin } from "@/next/origins"; import { getToken } from "@ente/shared/storage/localStorage/helpers"; import { getActualKey } from "@ente/shared/user"; import { HttpStatusCode } from "axios"; diff --git a/web/apps/cast/src/services/render.ts b/web/apps/cast/src/services/render.ts index d70bc8f343..18c4fa928f 100644 --- a/web/apps/cast/src/services/render.ts +++ b/web/apps/cast/src/services/render.ts @@ -11,13 +11,13 @@ import { heicToJPEG } from "@/media/heic-convert"; import { decodeLivePhoto } from "@/media/live-photo"; import { nameAndExtension } from "@/next/file"; import log from "@/next/log"; +import { apiOrigin, customAPIOrigin } from "@/next/origins"; import { shuffled } from "@/utils/array"; import { ensure } from "@/utils/ensure"; import { wait } from "@/utils/promise"; import ComlinkCryptoWorker from "@ente/shared/crypto"; import { ApiError } from "@ente/shared/error"; import HTTPService from "@ente/shared/network/HTTPService"; -import { apiOrigin, customAPIOrigin } from "@ente/shared/network/api"; import type { AxiosResponse } from "axios"; import type { CastData } from "services/cast-data"; import { detectMediaMIMEType } from "services/detect-type"; diff --git a/web/apps/photos/src/constants/billing.ts b/web/apps/photos/src/constants/billing.ts deleted file mode 100644 index f66263eda4..0000000000 --- a/web/apps/photos/src/constants/billing.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { getPaymentsURL } from "@ente/shared/network/api"; - -export const getDesktopRedirectURL = () => - `${getPaymentsURL()}/desktop-redirect`; diff --git a/web/apps/photos/src/pages/index.tsx b/web/apps/photos/src/pages/index.tsx index 2e4c07e577..c4af5b4136 100644 --- a/web/apps/photos/src/pages/index.tsx +++ b/web/apps/photos/src/pages/index.tsx @@ -1,12 +1,12 @@ import { DevSettings } from "@/new/photos/components/DevSettings"; import log from "@/next/log"; +import { albumsAppOrigin } from "@/next/origins"; import { Login } from "@ente/accounts/components/Login"; import { SignUp } from "@ente/accounts/components/SignUp"; import { EnteLogo } from "@ente/shared/components/EnteLogo"; import EnteSpinner from "@ente/shared/components/EnteSpinner"; import { PHOTOS_PAGES as PAGES } from "@ente/shared/constants/pages"; import { saveKeyInSessionStore } from "@ente/shared/crypto/helpers"; -import { getAlbumsURL } from "@ente/shared/network/api"; import localForage from "@ente/shared/storage/localForage"; import { getData, LS_KEYS } from "@ente/shared/storage/localStorage"; import { getToken } from "@ente/shared/storage/localStorage/helpers"; @@ -36,7 +36,7 @@ export default function LandingPage() { useEffect(() => { showNavBar(false); const currentURL = new URL(window.location.href); - const albumsURL = new URL(getAlbumsURL()); + const albumsURL = new URL(albumsAppOrigin()); currentURL.pathname = router.pathname; if ( currentURL.host === albumsURL.host && diff --git a/web/apps/photos/src/services/billingService.ts b/web/apps/photos/src/services/billingService.ts index 209d2ca59a..67004874a0 100644 --- a/web/apps/photos/src/services/billingService.ts +++ b/web/apps/photos/src/services/billingService.ts @@ -7,7 +7,6 @@ import { setData, } from "@ente/shared/storage/localStorage"; import { getToken } from "@ente/shared/storage/localStorage/helpers"; -import { getDesktopRedirectURL } from "constants/billing"; import isElectron from "is-electron"; import { Plan, Subscription } from "types/billing"; import { getPaymentToken } from "./userService"; @@ -213,7 +212,7 @@ class billingService { public getRedirectURL() { if (isElectron()) { - return getDesktopRedirectURL(); + return `${paymentsAppOrigin()}/desktop-redirect`; } else { return `${window.location.origin}/gallery`; } diff --git a/web/apps/photos/src/services/deduplicationService.ts b/web/apps/photos/src/services/deduplicationService.ts index c50794b9a0..b17d9f4f0b 100644 --- a/web/apps/photos/src/services/deduplicationService.ts +++ b/web/apps/photos/src/services/deduplicationService.ts @@ -2,8 +2,8 @@ import { hasFileHash } from "@/media/file"; import { FILE_TYPE } from "@/media/file-type"; import type { Metadata } from "@/media/types/file"; import log from "@/next/log"; -import HTTPService from "@ente/shared/network/HTTPService"; import { apiOrigin } from "@/next/origins"; +import HTTPService from "@ente/shared/network/HTTPService"; import { getToken } from "@ente/shared/storage/localStorage/helpers"; import { EnteFile } from "types/file"; diff --git a/web/apps/photos/src/services/download/clients/photos.ts b/web/apps/photos/src/services/download/clients/photos.ts index 9f4ecc6079..ac88fa0324 100644 --- a/web/apps/photos/src/services/download/clients/photos.ts +++ b/web/apps/photos/src/services/download/clients/photos.ts @@ -1,6 +1,6 @@ +import { customAPIOrigin } from "@/next/origins"; import { CustomError } from "@ente/shared/error"; import HTTPService from "@ente/shared/network/HTTPService"; -import { customAPIOrigin } from "@ente/shared/network/api"; import { retryAsyncFunction } from "@ente/shared/utils"; import { DownloadClient } from "services/download"; import { EnteFile } from "types/file"; diff --git a/web/apps/photos/src/services/download/clients/publicAlbums.ts b/web/apps/photos/src/services/download/clients/publicAlbums.ts index 4db8d2cf45..4a15407e59 100644 --- a/web/apps/photos/src/services/download/clients/publicAlbums.ts +++ b/web/apps/photos/src/services/download/clients/publicAlbums.ts @@ -1,6 +1,6 @@ +import { customAPIOrigin } from "@/next/origins"; import { CustomError } from "@ente/shared/error"; import HTTPService from "@ente/shared/network/HTTPService"; -import { customAPIOrigin } from "@ente/shared/network/api"; import { retryAsyncFunction } from "@ente/shared/utils"; import { DownloadClient } from "services/download"; import { EnteFile } from "types/file"; diff --git a/web/apps/photos/src/services/entityService.ts b/web/apps/photos/src/services/entityService.ts index 90162a3bdc..67f6275a03 100644 --- a/web/apps/photos/src/services/entityService.ts +++ b/web/apps/photos/src/services/entityService.ts @@ -1,7 +1,7 @@ import log from "@/next/log"; +import { apiOrigin } from "@/next/origins"; import ComlinkCryptoWorker from "@ente/shared/crypto"; import HTTPService from "@ente/shared/network/HTTPService"; -import { apiOrigin } from "@/next/origins"; import localForage from "@ente/shared/storage/localForage"; import { getToken } from "@ente/shared/storage/localStorage/helpers"; import { getActualKey } from "@ente/shared/user"; diff --git a/web/apps/photos/src/services/fileService.ts b/web/apps/photos/src/services/fileService.ts index a9ac0c7848..ebabc9dbfc 100644 --- a/web/apps/photos/src/services/fileService.ts +++ b/web/apps/photos/src/services/fileService.ts @@ -1,8 +1,8 @@ import log from "@/next/log"; +import { apiOrigin } from "@/next/origins"; import ComlinkCryptoWorker from "@ente/shared/crypto"; import { Events, eventBus } from "@ente/shared/events"; import HTTPService from "@ente/shared/network/HTTPService"; -import { apiOrigin } from "@/next/origins"; import localForage from "@ente/shared/storage/localForage"; import { getToken } from "@ente/shared/storage/localStorage/helpers"; import { REQUEST_BATCH_SIZE } from "constants/api"; @@ -252,9 +252,14 @@ export const updateFileMagicMetadata = async ( }, }); } - await HTTPService.put(`${apiOrigin()}/files/magic-metadata`, reqBody, null, { - "X-Auth-Token": token, - }); + await HTTPService.put( + `${apiOrigin()}/files/magic-metadata`, + reqBody, + null, + { + "X-Auth-Token": token, + }, + ); return fileWithUpdatedMagicMetadataList.map( ({ file, updatedMagicMetadata }): EnteFile => ({ ...file, diff --git a/web/apps/photos/src/services/publicCollectionService.ts b/web/apps/photos/src/services/publicCollectionService.ts index db03ab4bc3..9dced45610 100644 --- a/web/apps/photos/src/services/publicCollectionService.ts +++ b/web/apps/photos/src/services/publicCollectionService.ts @@ -1,8 +1,8 @@ import log from "@/next/log"; +import { apiOrigin } from "@/next/origins"; import ComlinkCryptoWorker from "@ente/shared/crypto"; import { CustomError, parseSharingErrorCodes } from "@ente/shared/error"; import HTTPService from "@ente/shared/network/HTTPService"; -import { apiOrigin } from "@/next/origins"; import localForage from "@ente/shared/storage/localForage"; import { Collection, CollectionPublicMagicMetadata } from "types/collection"; import { EncryptedEnteFile, EnteFile } from "types/file"; diff --git a/web/apps/photos/src/services/trashService.ts b/web/apps/photos/src/services/trashService.ts index b281a9a13f..f367304550 100644 --- a/web/apps/photos/src/services/trashService.ts +++ b/web/apps/photos/src/services/trashService.ts @@ -1,6 +1,6 @@ import log from "@/next/log"; -import HTTPService from "@ente/shared/network/HTTPService"; import { apiOrigin } from "@/next/origins"; +import HTTPService from "@ente/shared/network/HTTPService"; import localForage from "@ente/shared/storage/localForage"; import { getToken } from "@ente/shared/storage/localStorage/helpers"; import { Collection } from "types/collection"; diff --git a/web/apps/photos/src/services/upload/publicUploadHttpClient.ts b/web/apps/photos/src/services/upload/publicUploadHttpClient.ts index 0750211458..22ed45e2f3 100644 --- a/web/apps/photos/src/services/upload/publicUploadHttpClient.ts +++ b/web/apps/photos/src/services/upload/publicUploadHttpClient.ts @@ -1,7 +1,7 @@ import log from "@/next/log"; +import { apiOrigin } from "@/next/origins"; import { CustomError, handleUploadError } from "@ente/shared/error"; import HTTPService from "@ente/shared/network/HTTPService"; -import { apiOrigin } from "@/next/origins"; import { EnteFile } from "types/file"; import { retryHTTPCall } from "./uploadHttpClient"; import { MultipartUploadURLs, UploadFile, UploadURL } from "./uploadService"; diff --git a/web/packages/accounts/services/passkey.ts b/web/packages/accounts/services/passkey.ts index f16ceeaae9..56b0e78ba4 100644 --- a/web/packages/accounts/services/passkey.ts +++ b/web/packages/accounts/services/passkey.ts @@ -1,5 +1,6 @@ import { clientPackageHeaderIfPresent } from "@/next/http"; import log from "@/next/log"; +import { accountsAppOrigin, apiOrigin } from "@/next/origins"; import type { AppName } from "@/next/types/app"; import { clientPackageName } from "@/next/types/app"; import { TwoFactorAuthorizationResponse } from "@/next/types/credentials"; @@ -12,7 +13,6 @@ import { } from "@ente/shared/crypto/internal/libsodium"; import { CustomError } from "@ente/shared/error"; import HTTPService from "@ente/shared/network/HTTPService"; -import { accountsAppURL, apiOrigin } from "@ente/shared/network/api"; import InMemoryStore, { MS_KEYS } from "@ente/shared/storage/InMemoryStore"; import { LS_KEYS, getData, setData } from "@ente/shared/storage/localStorage"; import { getToken } from "@ente/shared/storage/localStorage/helpers"; @@ -48,7 +48,7 @@ export const passkeyVerificationRedirectURL = ( redirect, ...recoverOption, }); - return `${accountsAppURL()}/passkeys/verify?${params.toString()}`; + return `${accountsAppOrigin()}/passkeys/verify?${params.toString()}`; }; interface OpenPasskeyVerificationURLOptions { @@ -131,7 +131,7 @@ export const openAccountsManagePasskeysPage = async () => { const token = await getAccountsToken(); const params = new URLSearchParams({ token }); - window.open(`${accountsAppURL()}/passkeys?${params.toString()}`); + window.open(`${accountsAppOrigin()}/passkeys?${params.toString()}`); }; export const isPasskeyRecoveryEnabled = async () => { diff --git a/web/packages/new/photos/services/feature-flags.ts b/web/packages/new/photos/services/feature-flags.ts index ab7787b75e..8bef610f88 100644 --- a/web/packages/new/photos/services/feature-flags.ts +++ b/web/packages/new/photos/services/feature-flags.ts @@ -2,8 +2,8 @@ import { isDevBuild } from "@/next/env"; import { authenticatedRequestHeaders } from "@/next/http"; import { localUser } from "@/next/local-user"; import log from "@/next/log"; +import { apiOrigin } from "@/next/origins"; import { nullToUndefined } from "@/utils/transform"; -import { apiOrigin } from "@ente/shared/network/api"; import { z } from "zod"; let _fetchTimeout: ReturnType | undefined; diff --git a/web/packages/shared/components/LoginComponents.tsx b/web/packages/shared/components/LoginComponents.tsx index afe12a690d..b3e5880593 100644 --- a/web/packages/shared/components/LoginComponents.tsx +++ b/web/packages/shared/components/LoginComponents.tsx @@ -1,5 +1,6 @@ import { isDevBuild } from "@/next/env"; import log from "@/next/log"; +import { apiOrigin } from "@/next/origins"; import type { BaseAppContextT } from "@/next/types/app"; import { checkPasskeyVerificationStatus, @@ -7,7 +8,6 @@ import { saveCredentialsAndNavigateTo, } from "@ente/accounts/services/passkey"; import EnteButton from "@ente/shared/components/EnteButton"; -import { apiOrigin } from "@ente/shared/network/api"; import { CircularProgress, Typography, styled } from "@mui/material"; import { t } from "i18next"; import { useRouter } from "next/router"; diff --git a/web/packages/shared/network/cast.ts b/web/packages/shared/network/cast.ts index 0425d0dc4f..7b2af75907 100644 --- a/web/packages/shared/network/cast.ts +++ b/web/packages/shared/network/cast.ts @@ -1,8 +1,8 @@ import log from "@/next/log"; +import { apiOrigin } from "@/next/origins"; import { ApiError } from "../error"; import { getToken } from "../storage/localStorage/helpers"; import HTTPService from "./HTTPService"; -import { apiOrigin } from "@/next/origins"; class CastGateway { constructor() {} @@ -11,7 +11,7 @@ class CastGateway { let resp; try { resp = await HTTPService.get( - `${getEndpoint()}/cast/cast-data/${code}`, + `${apiOrigin()}/cast/cast-data/${code}`, ); } catch (e) { log.error("failed to getCastData", e); @@ -24,7 +24,7 @@ class CastGateway { try { const token = getToken(); await HTTPService.delete( - getEndpoint() + "/cast/revoke-all-tokens/", + apiOrigin() + "/cast/revoke-all-tokens/", undefined, undefined, { @@ -42,7 +42,7 @@ class CastGateway { try { const token = getToken(); resp = await HTTPService.get( - `${getEndpoint()}/cast/device-info/${code}`, + `${apiOrigin()}/cast/device-info/${code}`, undefined, { "X-Auth-Token": token, @@ -60,7 +60,7 @@ class CastGateway { public async registerDevice(publicKey: string): Promise { const resp = await HTTPService.post( - getEndpoint() + "/cast/device-info/", + apiOrigin() + "/cast/device-info/", { publicKey: publicKey, }, @@ -76,7 +76,7 @@ class CastGateway { ) { const token = getToken(); await HTTPService.post( - getEndpoint() + "/cast/cast-data/", + apiOrigin() + "/cast/cast-data/", { deviceCode: `${code}`, encPayload: castPayload, From 704e89c903bcd978d17a2fca28bf5fbfdf299f82 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sun, 23 Jun 2024 20:01:54 +0530 Subject: [PATCH 108/130] allow empty --- web/packages/new/photos/components/DevSettings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/packages/new/photos/components/DevSettings.tsx b/web/packages/new/photos/components/DevSettings.tsx index 878cc084af..5b2be663b7 100644 --- a/web/packages/new/photos/components/DevSettings.tsx +++ b/web/packages/new/photos/components/DevSettings.tsx @@ -44,7 +44,7 @@ export const DevSettings: React.FC = ({ open, onClose }) => { }, validate: ({ apiOrigin }) => { try { - new URL(apiOrigin); + apiOrigin && new URL(apiOrigin); } catch { return { apiOrigin: "Invalid endpoint" }; } From 6b4416c3a9ec1e781be338fa1d96563825e293dc Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sun, 23 Jun 2024 20:07:02 +0530 Subject: [PATCH 109/130] l10n keys --- web/packages/new/photos/components/DevSettings.tsx | 8 ++++---- web/packages/next/locales/en-US/translation.json | 6 +++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/web/packages/new/photos/components/DevSettings.tsx b/web/packages/new/photos/components/DevSettings.tsx index 5b2be663b7..0f37e60c8c 100644 --- a/web/packages/new/photos/components/DevSettings.tsx +++ b/web/packages/new/photos/components/DevSettings.tsx @@ -76,7 +76,7 @@ export const DevSettings: React.FC = ({ open, onClose }) => { maxWidth="xs" > - {"Developer settings"} + {t("developer_settings")} = ({ open, onClose }) => { autoFocus id="apiOrigin" name="apiOrigin" - label="Server endpoint" + label={t("server_endpoint")} placeholder="http://localhost:8080" value={form.values.apiOrigin} onChange={form.handleChange} @@ -110,7 +110,7 @@ export const DevSettings: React.FC = ({ open, onClose }) => { rel="noopener" > @@ -130,7 +130,7 @@ export const DevSettings: React.FC = ({ open, onClose }) => { disabled={form.isSubmitting} disableRipple > - {"Save"} + {t("save")} Date: Sun, 23 Jun 2024 20:10:06 +0530 Subject: [PATCH 110/130] Rename --- web/apps/photos/src/pages/index.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/web/apps/photos/src/pages/index.tsx b/web/apps/photos/src/pages/index.tsx index c4af5b4136..e359e572de 100644 --- a/web/apps/photos/src/pages/index.tsx +++ b/web/apps/photos/src/pages/index.tsx @@ -110,7 +110,7 @@ export default function LandingPage() { const redirectToLoginPage = () => router.push(PAGES.LOGIN); return ( - + {loading ? ( ) : ( @@ -144,11 +144,11 @@ export default function LandingPage() { )} - + ); } -const Container: React.FC = ({ children }) => { +const TappableContainer: React.FC = ({ children }) => { // [Note: Configuring custom server] // // Allow the user to tap 7 times anywhere on the onboarding screen to bring @@ -178,7 +178,7 @@ const Container: React.FC = ({ children }) => { }; return ( - + <> = ({ children }) => { /> {children} - + ); }; -const Container_ = styled("div")` +const TappableContainer_ = styled("div")` display: flex; flex: 1; align-items: center; From 66f9dc98eefee02d8545af31dbdf3cc245a463e9 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sun, 23 Jun 2024 20:12:05 +0530 Subject: [PATCH 111/130] Rearrange --- web/apps/accounts/src/services/passkey.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/web/apps/accounts/src/services/passkey.ts b/web/apps/accounts/src/services/passkey.ts index da026c42e6..b30155e483 100644 --- a/web/apps/accounts/src/services/passkey.ts +++ b/web/apps/accounts/src/services/passkey.ts @@ -354,14 +354,6 @@ export const isWhitelistedRedirect = (redirectURL: URL) => ? _isWhitelistedRedirect(redirectURL) : true; -const _isWhitelistedRedirect = (redirectURL: URL) => - (isDevBuild && redirectURL.hostname.endsWith("localhost")) || - redirectURL.host.endsWith(".ente.io") || - redirectURL.host.endsWith(".ente.sh") || - redirectURL.protocol == "ente:" || - redirectURL.protocol == "enteauth:" || - redirectURL.protocol == "ente-cli:"; - export const shouldRestrictToWhitelistedRedirect = () => { // host includes port, hostname is sans port const hostname = new URL(window.location.origin).hostname; @@ -372,6 +364,14 @@ export const shouldRestrictToWhitelistedRedirect = () => { ); }; +const _isWhitelistedRedirect = (redirectURL: URL) => + (isDevBuild && redirectURL.hostname.endsWith("localhost")) || + redirectURL.host.endsWith(".ente.io") || + redirectURL.host.endsWith(".ente.sh") || + redirectURL.protocol == "ente:" || + redirectURL.protocol == "enteauth:" || + redirectURL.protocol == "ente-cli:"; + export interface BeginPasskeyAuthenticationResponse { /** * An identifier for this authentication ceremony / session. From 57a674fd26937be483f1b8feb67a3cf54b71c7ab Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sun, 23 Jun 2024 20:17:19 +0530 Subject: [PATCH 112/130] Skip on our own prod --- web/apps/photos/src/pages/index.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/web/apps/photos/src/pages/index.tsx b/web/apps/photos/src/pages/index.tsx index e359e572de..5c50e1de48 100644 --- a/web/apps/photos/src/pages/index.tsx +++ b/web/apps/photos/src/pages/index.tsx @@ -162,6 +162,9 @@ const TappableContainer: React.FC = ({ children }) => { const handleClick: React.MouseEventHandler = (event) => { console.log("click", tapCount, event, event.target); + // Don't allow this when runinng on (e.g.) web.ente.io. + if (!shouldAllowChangingAPIOrigin()) return; + // Ignore clicks on buttons when counting up towards 7. if (event.target instanceof HTMLButtonElement) return; @@ -202,6 +205,15 @@ const TappableContainer_ = styled("div")` } `; +/** + * Disable the ability to set the custom server when we're running on our own + * production deployment. + */ +const shouldAllowChangingAPIOrigin = () => { + const hostname = new URL(window.location.origin).hostname; + return !(hostname.endsWith(".ente.io") || hostname.endsWith(".ente.sh")); +}; + const SlideContainer = styled("div")` flex: 1; display: flex; From 72de042b53a7ac3e6331152e4cbf6aed9fce083b Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sun, 23 Jun 2024 20:22:28 +0530 Subject: [PATCH 113/130] Inline --- web/packages/next/origins.ts | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/web/packages/next/origins.ts b/web/packages/next/origins.ts index db5b55aef2..13b44fc5ed 100644 --- a/web/packages/next/origins.ts +++ b/web/packages/next/origins.ts @@ -4,11 +4,10 @@ import { nullToUndefined } from "@/utils/transform"; * Return the origin (scheme, host, port triple) that should be used for making * API requests to museum. * - * This defaults to {@link defaultAPIOrigin}, but can be overridden when self - * hosting or developing by setting the `NEXT_PUBLIC_ENTE_ENDPOINT` environment - * variable. + * This defaults "https://api.ente.io", Ente's production API servers. but can + * be overridden when self hosting or developing (see {@link customAPIOrigin}). */ -export const apiOrigin = () => customAPIOrigin() ?? defaultAPIOrigin; +export const apiOrigin = () => customAPIOrigin() ?? "https://api.ente.io"; /** * Return the overridden API origin, if one is defined by either (in priority @@ -26,12 +25,6 @@ export const customAPIOrigin = () => process.env.NEXT_PUBLIC_ENTE_ENDPOINT ?? undefined; -/** - * Default value of {@link apiOrigin}: "https://api.ente.io", Ente's production - * API servers. - */ -export const defaultAPIOrigin = "https://api.ente.io"; - /** * Return the origin that should be used for uploading files. * From ea4da2c2a45fe6aefc103ae759959d7fe88e8576 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sun, 23 Jun 2024 20:23:19 +0530 Subject: [PATCH 114/130] Fin --- web/apps/photos/src/pages/index.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/web/apps/photos/src/pages/index.tsx b/web/apps/photos/src/pages/index.tsx index 5c50e1de48..44d89d5089 100644 --- a/web/apps/photos/src/pages/index.tsx +++ b/web/apps/photos/src/pages/index.tsx @@ -160,8 +160,6 @@ const TappableContainer: React.FC = ({ children }) => { const [showDevSettings, setShowDevSettings] = useState(false); const handleClick: React.MouseEventHandler = (event) => { - console.log("click", tapCount, event, event.target); - // Don't allow this when runinng on (e.g.) web.ente.io. if (!shouldAllowChangingAPIOrigin()) return; From ef0396983b709373fb7dc1c5ac7e58c18fcb0f1d Mon Sep 17 00:00:00 2001 From: laurenspriem Date: Mon, 24 Jun 2024 10:59:57 +0530 Subject: [PATCH 115/130] [mob][photos] Remove unused method --- .../semantic_search/semantic_search_service.dart | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/mobile/lib/services/machine_learning/semantic_search/semantic_search_service.dart b/mobile/lib/services/machine_learning/semantic_search/semantic_search_service.dart index cc34130446..8c4418f2a8 100644 --- a/mobile/lib/services/machine_learning/semantic_search/semantic_search_service.dart +++ b/mobile/lib/services/machine_learning/semantic_search/semantic_search_service.dart @@ -214,20 +214,6 @@ class SemanticSearchService { unawaited(_pollQueue()); } - Future _cacheThumbnails(List files) async { - int counter = 0; - const batchSize = 100; - for (var i = 0; i < files.length;) { - final futures = []; - for (var j = 0; j < batchSize && i < files.length; j++, i++) { - futures.add(getThumbnail(files[i])); - } - await Future.wait(futures); - counter += futures.length; - _logger.info("$counter/${files.length} thumbnails cached"); - } - } - Future> _getFileIDsToBeIndexed() async { final uploadedFileIDs = await getIndexableFileIDs(); final embeddedFileIDs = From f40c277aa898b578431e43d91bf764371701dd8f Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 24 Jun 2024 11:00:45 +0530 Subject: [PATCH 116/130] Show in sidebar --- .../photos/src/components/Sidebar/index.tsx | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/web/apps/photos/src/components/Sidebar/index.tsx b/web/apps/photos/src/components/Sidebar/index.tsx index 32665b7120..5f49690335 100644 --- a/web/apps/photos/src/components/Sidebar/index.tsx +++ b/web/apps/photos/src/components/Sidebar/index.tsx @@ -1,5 +1,6 @@ import log from "@/next/log"; import { savedLogs } from "@/next/log-web"; +import { customAPIOrigin } from "@/next/origins"; import { openAccountsManagePasskeysPage } from "@ente/accounts/services/passkey"; import { SpaceBetweenFlex } from "@ente/shared/components/Container"; import { EnteLogo } from "@ente/shared/components/EnteLogo"; @@ -685,6 +686,8 @@ const DebugSection: React.FC = () => { electron?.appVersion().then((v) => setAppVersion(v)); }); + const origin = customAPIOrigin(); + const confirmLogDownload = () => appContext.setDialogMessage({ title: t("DOWNLOAD_LOGS"), @@ -707,21 +710,6 @@ const DebugSection: React.FC = () => { return ( <> - - {appVersion && ( - - {appVersion} - - )} {isInternalUserViaEmailCheck() && ( { label={"Test Upload"} /> )} + + + {appVersion && ( + {appVersion} + )} + {origin && {origin}} + ); }; From 9e3a3d852e19e0288f43a68a74a1add15a99e4ca Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 24 Jun 2024 11:21:00 +0530 Subject: [PATCH 117/130] Show on login --- web/apps/photos/src/pages/index.tsx | 2 +- web/packages/accounts/components/Login.tsx | 15 +++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/web/apps/photos/src/pages/index.tsx b/web/apps/photos/src/pages/index.tsx index 44d89d5089..18d45fcb82 100644 --- a/web/apps/photos/src/pages/index.tsx +++ b/web/apps/photos/src/pages/index.tsx @@ -160,7 +160,7 @@ const TappableContainer: React.FC = ({ children }) => { const [showDevSettings, setShowDevSettings] = useState(false); const handleClick: React.MouseEventHandler = (event) => { - // Don't allow this when runinng on (e.g.) web.ente.io. + // Don't allow this when running on (e.g.) web.ente.io. if (!shouldAllowChangingAPIOrigin()) return; // Ignore clicks on buttons when counting up towards 7. diff --git a/web/packages/accounts/components/Login.tsx b/web/packages/accounts/components/Login.tsx index 50cebd56ee..7e79d4e377 100644 --- a/web/packages/accounts/components/Login.tsx +++ b/web/packages/accounts/components/Login.tsx @@ -1,4 +1,5 @@ import log from "@/next/log"; +import { customAPIOrigin } from "@/next/origins"; import type { AppName } from "@/next/types/app"; import FormPaperFooter from "@ente/shared/components/Form/FormPaper/Footer"; import FormPaperTitle from "@ente/shared/components/Form/FormPaper/Title"; @@ -7,7 +8,7 @@ import SingleInputForm, { type SingleInputFormProps, } from "@ente/shared/components/SingleInputForm"; import { LS_KEYS, setData } from "@ente/shared/storage/localStorage"; -import { Input } from "@mui/material"; +import { Input, Typography, Stack } from "@mui/material"; import { t } from "i18next"; import { useRouter } from "next/router"; import { getSRPAttributes } from "../api/srp"; @@ -22,6 +23,9 @@ interface LoginProps { export function Login({ appName, signUp }: LoginProps) { const router = useRouter(); + const origin = customAPIOrigin(); + const host = origin ? new URL(origin).host : undefined; + const loginUser: SingleInputFormProps["callback"] = async ( email, setFieldError, @@ -63,7 +67,14 @@ export function Login({ appName, signUp }: LoginProps) { /> - {t("NO_ACCOUNT")} + + {t("NO_ACCOUNT")} + {host && ( + + {host} + + )} + ); From e81e088b02b50a5dabe68f7103790e9ac423d549 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 24 Jun 2024 11:36:33 +0530 Subject: [PATCH 118/130] Also show in signup section --- web/packages/accounts/components/SignUp.tsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/web/packages/accounts/components/SignUp.tsx b/web/packages/accounts/components/SignUp.tsx index 9e0086075b..0ab61ddc00 100644 --- a/web/packages/accounts/components/SignUp.tsx +++ b/web/packages/accounts/components/SignUp.tsx @@ -1,4 +1,5 @@ import log from "@/next/log"; +import { customAPIOrigin } from "@/next/origins"; import type { AppName } from "@/next/types/app"; import { sendOtt } from "@ente/accounts/api/user"; import { PasswordStrengthHint } from "@ente/accounts/components/PasswordStrength"; @@ -31,6 +32,7 @@ import { IconButton, InputAdornment, Link, + Stack, TextField, Tooltip, Typography, @@ -60,6 +62,9 @@ export function SignUp({ router, appName, login }: SignUpProps) { const [loading, setLoading] = useState(false); const [showPassword, setShowPassword] = useState(false); + const origin = customAPIOrigin(); + const host = origin ? new URL(origin).host : undefined; + const handleClickShowPassword = () => { setShowPassword(!showPassword); }; @@ -310,7 +315,16 @@ export function SignUp({ router, appName, login }: SignUpProps) { - {t("ACCOUNT_EXISTS")} + + + {t("ACCOUNT_EXISTS")} + + {host && ( + + {host} + + )} + ); From 2db166bcf7647646a84571ab71397cf26a12cc64 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 24 Jun 2024 11:42:49 +0530 Subject: [PATCH 119/130] Add a convenience function to avoid code duplication --- web/packages/accounts/components/Login.tsx | 7 +++---- web/packages/accounts/components/SignUp.tsx | 5 ++--- web/packages/next/origins.ts | 11 +++++++++++ 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/web/packages/accounts/components/Login.tsx b/web/packages/accounts/components/Login.tsx index 7e79d4e377..69775957b8 100644 --- a/web/packages/accounts/components/Login.tsx +++ b/web/packages/accounts/components/Login.tsx @@ -1,5 +1,5 @@ import log from "@/next/log"; -import { customAPIOrigin } from "@/next/origins"; +import { customAPIHost } from "@/next/origins"; import type { AppName } from "@/next/types/app"; import FormPaperFooter from "@ente/shared/components/Form/FormPaper/Footer"; import FormPaperTitle from "@ente/shared/components/Form/FormPaper/Title"; @@ -8,7 +8,7 @@ import SingleInputForm, { type SingleInputFormProps, } from "@ente/shared/components/SingleInputForm"; import { LS_KEYS, setData } from "@ente/shared/storage/localStorage"; -import { Input, Typography, Stack } from "@mui/material"; +import { Input, Stack, Typography } from "@mui/material"; import { t } from "i18next"; import { useRouter } from "next/router"; import { getSRPAttributes } from "../api/srp"; @@ -23,8 +23,7 @@ interface LoginProps { export function Login({ appName, signUp }: LoginProps) { const router = useRouter(); - const origin = customAPIOrigin(); - const host = origin ? new URL(origin).host : undefined; + const host = customAPIHost(); const loginUser: SingleInputFormProps["callback"] = async ( email, diff --git a/web/packages/accounts/components/SignUp.tsx b/web/packages/accounts/components/SignUp.tsx index 0ab61ddc00..24b0fef334 100644 --- a/web/packages/accounts/components/SignUp.tsx +++ b/web/packages/accounts/components/SignUp.tsx @@ -1,5 +1,5 @@ import log from "@/next/log"; -import { customAPIOrigin } from "@/next/origins"; +import { customAPIHost } from "@/next/origins"; import type { AppName } from "@/next/types/app"; import { sendOtt } from "@ente/accounts/api/user"; import { PasswordStrengthHint } from "@ente/accounts/components/PasswordStrength"; @@ -62,8 +62,7 @@ export function SignUp({ router, appName, login }: SignUpProps) { const [loading, setLoading] = useState(false); const [showPassword, setShowPassword] = useState(false); - const origin = customAPIOrigin(); - const host = origin ? new URL(origin).host : undefined; + const host = customAPIHost(); const handleClickShowPassword = () => { setShowPassword(!showPassword); diff --git a/web/packages/next/origins.ts b/web/packages/next/origins.ts index 13b44fc5ed..66e6a9aefa 100644 --- a/web/packages/next/origins.ts +++ b/web/packages/next/origins.ts @@ -25,6 +25,17 @@ export const customAPIOrigin = () => process.env.NEXT_PUBLIC_ENTE_ENDPOINT ?? undefined; +/** + * A convenience wrapper over {@link customAPIOrigin} that returns the only the + * host part of the custom origin (if any). + * + * This is useful in places where we indicate the custom origin in the UI. + */ +export const customAPIHost = () => { + const origin = customAPIOrigin(); + return origin ? new URL(origin).host : undefined; +}; + /** * Return the origin that should be used for uploading files. * From eccde54afe82884f4fa85c0525885986637e62d0 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 24 Jun 2024 11:44:01 +0530 Subject: [PATCH 120/130] Use same format in sidebar --- web/apps/photos/src/components/Sidebar/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/web/apps/photos/src/components/Sidebar/index.tsx b/web/apps/photos/src/components/Sidebar/index.tsx index 5f49690335..7ec6f70559 100644 --- a/web/apps/photos/src/components/Sidebar/index.tsx +++ b/web/apps/photos/src/components/Sidebar/index.tsx @@ -1,6 +1,6 @@ import log from "@/next/log"; import { savedLogs } from "@/next/log-web"; -import { customAPIOrigin } from "@/next/origins"; +import { customAPIHost } from "@/next/origins"; import { openAccountsManagePasskeysPage } from "@ente/accounts/services/passkey"; import { SpaceBetweenFlex } from "@ente/shared/components/Container"; import { EnteLogo } from "@ente/shared/components/EnteLogo"; @@ -686,7 +686,7 @@ const DebugSection: React.FC = () => { electron?.appVersion().then((v) => setAppVersion(v)); }); - const origin = customAPIOrigin(); + const host = customAPIHost(); const confirmLogDownload = () => appContext.setDialogMessage({ @@ -726,7 +726,7 @@ const DebugSection: React.FC = () => { {appVersion && ( {appVersion} )} - {origin && {origin}} + {host && {host}} ); From 04f4103314332e07aa5958b4db944ea9420301d3 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 24 Jun 2024 13:46:56 +0530 Subject: [PATCH 121/130] Landing page --- web/apps/photos/src/pages/index.tsx | 50 +++++++++++++++++++---------- 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/web/apps/photos/src/pages/index.tsx b/web/apps/photos/src/pages/index.tsx index 18d45fcb82..6ec6fa2d8c 100644 --- a/web/apps/photos/src/pages/index.tsx +++ b/web/apps/photos/src/pages/index.tsx @@ -1,6 +1,6 @@ import { DevSettings } from "@/new/photos/components/DevSettings"; import log from "@/next/log"; -import { albumsAppOrigin } from "@/next/origins"; +import { albumsAppOrigin, customAPIHost } from "@/next/origins"; import { Login } from "@ente/accounts/components/Login"; import { SignUp } from "@ente/accounts/components/SignUp"; import { EnteLogo } from "@ente/shared/components/EnteLogo"; @@ -8,13 +8,14 @@ import EnteSpinner from "@ente/shared/components/EnteSpinner"; import { PHOTOS_PAGES as PAGES } from "@ente/shared/constants/pages"; import { saveKeyInSessionStore } from "@ente/shared/crypto/helpers"; import localForage from "@ente/shared/storage/localForage"; -import { getData, LS_KEYS } from "@ente/shared/storage/localStorage"; +import { LS_KEYS, getData } from "@ente/shared/storage/localStorage"; import { getToken } from "@ente/shared/storage/localStorage/helpers"; -import { getKey, SESSION_KEYS } from "@ente/shared/storage/sessionStorage"; +import { SESSION_KEYS, getKey } from "@ente/shared/storage/sessionStorage"; import { Button, - styled, + Box, Typography, + styled, type TypographyProps, } from "@mui/material"; import { t } from "i18next"; @@ -132,6 +133,7 @@ export default function LandingPage() { + @@ -229,6 +231,33 @@ const Logo_ = styled("div")` margin-block-end: 64px; `; +const MobileBox = styled("div")` + display: none; + + @media (max-width: 1024px) { + max-width: 375px; + width: 100%; + padding: 12px; + display: flex; + flex-direction: column; + gap: 8px; + } +`; + +const MobileBoxFooter: React.FC = () => { + const host = customAPIHost(); + + return ( + + {host && ( + + {host} + + )} + + ); +}; + const DesktopBox = styled("div")` flex: 1; height: 100%; @@ -243,19 +272,6 @@ const DesktopBox = styled("div")` } `; -const MobileBox = styled("div")` - display: none; - - @media (max-width: 1024px) { - max-width: 375px; - width: 100%; - padding: 12px; - display: flex; - flex-direction: column; - gap: 8px; - } -`; - const SideBox = styled("div")` display: flex; flex-direction: column; From 21fa0ee7a70654efd5469612970ca586c22fcfe8 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 24 Jun 2024 14:47:29 +0530 Subject: [PATCH 122/130] On email verification page --- web/packages/accounts/pages/verify.tsx | 37 +++++++++++++++++--------- 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/web/packages/accounts/pages/verify.tsx b/web/packages/accounts/pages/verify.tsx index c6e6954d12..2217f30298 100644 --- a/web/packages/accounts/pages/verify.tsx +++ b/web/packages/accounts/pages/verify.tsx @@ -1,3 +1,4 @@ +import { customAPIHost } from "@/next/origins"; import { ensure } from "@/utils/ensure"; import type { UserVerificationResponse } from "@ente/accounts/types/user"; import { VerticallyCentered } from "@ente/shared/components/Container"; @@ -20,7 +21,7 @@ import { } from "@ente/shared/storage/localStorage/helpers"; import { clearKeys } from "@ente/shared/storage/sessionStorage"; import type { KeyAttributes, User } from "@ente/shared/user/types"; -import { Box, Typography } from "@mui/material"; +import { Box, Stack, Typography } from "@mui/material"; import { HttpStatusCode } from "axios"; import { t } from "i18next"; import { useRouter } from "next/router"; @@ -164,6 +165,8 @@ const Page: React.FC = ({ appContext }) => { setTimeout(() => setResend(0), 3000); }; + const host = customAPIHost(); + if (!email) { return ( @@ -225,17 +228,27 @@ const Page: React.FC = ({ appContext }) => { callback={onSubmit} /> - - {resend === 0 && ( - - {t("RESEND_MAIL")} - - )} - {resend === 1 && {t("SENDING")}} - {resend === 2 && {t("SENT")}} - - {t("CHANGE_EMAIL")} - + + + + {resend === 0 && ( + + {t("RESEND_MAIL")} + + )} + {resend === 1 && {t("SENDING")}} + {resend === 2 && {t("SENT")}} + + {t("CHANGE_EMAIL")} + + + + {host && ( + + {host} + + )} + From 47128ab52daa1e593bcc7e5922f1bbca51c48773 Mon Sep 17 00:00:00 2001 From: laurenspriem Date: Mon, 24 Jun 2024 15:04:01 +0530 Subject: [PATCH 123/130] [mob][photos] Store local empty clip result on errors --- mobile/lib/db/embeddings_db.dart | 4 +++- mobile/lib/models/embedding.dart | 10 ++++++++++ .../semantic_search/semantic_search_service.dart | 16 ++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/mobile/lib/db/embeddings_db.dart b/mobile/lib/db/embeddings_db.dart index b5a6111f45..4da5c056a5 100644 --- a/mobile/lib/db/embeddings_db.dart +++ b/mobile/lib/db/embeddings_db.dart @@ -123,7 +123,9 @@ class EmbeddingsDB { List _convertToEmbeddings(List> results) { final List embeddings = []; for (final result in results) { - embeddings.add(_getEmbeddingFromRow(result)); + final embedding = _getEmbeddingFromRow(result); + if (embedding.isEmpty) continue; + embeddings.add(embedding); } return embeddings; } diff --git a/mobile/lib/models/embedding.dart b/mobile/lib/models/embedding.dart index c8f742caa9..91ac9a0213 100644 --- a/mobile/lib/models/embedding.dart +++ b/mobile/lib/models/embedding.dart @@ -6,6 +6,8 @@ class Embedding { final List embedding; int? updationTime; + bool get isEmpty => embedding.isEmpty; + Embedding({ required this.fileID, required this.model, @@ -13,6 +15,14 @@ class Embedding { this.updationTime, }); + factory Embedding.empty(int fileID, Model model) { + return Embedding( + fileID: fileID, + model: model, + embedding: [], + ); + } + static List decodeEmbedding(String embedding) { return List.from(jsonDecode(embedding) as List); } diff --git a/mobile/lib/services/machine_learning/semantic_search/semantic_search_service.dart b/mobile/lib/services/machine_learning/semantic_search/semantic_search_service.dart index 8c4418f2a8..bfe8d9cec7 100644 --- a/mobile/lib/services/machine_learning/semantic_search/semantic_search_service.dart +++ b/mobile/lib/services/machine_learning/semantic_search/semantic_search_service.dart @@ -3,6 +3,7 @@ import "dart:collection"; import "dart:math" show min; import "package:computer/computer.dart"; +import "package:flutter/services.dart"; import "package:logging/logging.dart"; import "package:photos/core/cache/lru_map.dart"; import "package:photos/core/configuration.dart"; @@ -333,6 +334,21 @@ class SemanticSearchService { file, embedding, ); + } on FormatException catch (e, _) { + _logger.severe( + "Could not get embedding for $file because FormatException occured, storing empty result locally", + e, + ); + final embedding = Embedding.empty(file.uploadedFileID!, _currentModel); + await EmbeddingsDB.instance.put(embedding); + } on PlatformException catch (e, s) { + _logger.severe( + "Could not get thumbnail for $file due to PlatformException related to thumbnails, storing empty result locally", + e, + s, + ); + final embedding = Embedding.empty(file.uploadedFileID!, _currentModel); + await EmbeddingsDB.instance.put(embedding); } catch (e, s) { _logger.severe(e, s); } From 899d1ff6a4c4d09bd1cb315832d73149a9540002 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 24 Jun 2024 15:08:18 +0530 Subject: [PATCH 124/130] Custom component --- web/packages/accounts/pages/credentials.tsx | 25 +++++---- web/packages/accounts/pages/verify.tsx | 2 +- .../shared/components/LoginComponents.tsx | 51 ++++++++++--------- 3 files changed, 40 insertions(+), 38 deletions(-) diff --git a/web/packages/accounts/pages/credentials.tsx b/web/packages/accounts/pages/credentials.tsx index b44bc4e103..312eba6b4b 100644 --- a/web/packages/accounts/pages/credentials.tsx +++ b/web/packages/accounts/pages/credentials.tsx @@ -1,13 +1,11 @@ -import { isDevBuild } from "@/next/env"; import log from "@/next/log"; import { ensure } from "@/utils/ensure"; import { VerticallyCentered } from "@ente/shared/components/Container"; import EnteSpinner from "@ente/shared/components/EnteSpinner"; import FormPaper from "@ente/shared/components/Form/FormPaper"; -import FormPaperFooter from "@ente/shared/components/Form/FormPaper/Footer"; import LinkButton from "@ente/shared/components/LinkButton"; import { - ConnectionDetails, + LoginFlowFormFooter, PasswordHeader, VerifyingPasskey, } from "@ente/shared/components/LoginComponents"; @@ -42,6 +40,7 @@ import { setKey, } from "@ente/shared/storage/sessionStorage"; import type { KeyAttributes, User } from "@ente/shared/user/types"; +import { Stack } from "@mui/material"; import { t } from "i18next"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; @@ -321,16 +320,16 @@ const Page: React.FC = ({ appContext }) => { srpAttributes={srpAttributes} /> - - router.push(PAGES.RECOVER)}> - {t("FORGOT_PASSWORD")} - - - {t("CHANGE_EMAIL")} - - - - {isDevBuild && } + + + router.push(PAGES.RECOVER)}> + {t("FORGOT_PASSWORD")} + + + {t("CHANGE_EMAIL")} + + + ); diff --git a/web/packages/accounts/pages/verify.tsx b/web/packages/accounts/pages/verify.tsx index 2217f30298..20ecd52069 100644 --- a/web/packages/accounts/pages/verify.tsx +++ b/web/packages/accounts/pages/verify.tsx @@ -229,7 +229,7 @@ const Page: React.FC = ({ appContext }) => { /> - + {resend === 0 && ( diff --git a/web/packages/shared/components/LoginComponents.tsx b/web/packages/shared/components/LoginComponents.tsx index b3e5880593..024ac894b4 100644 --- a/web/packages/shared/components/LoginComponents.tsx +++ b/web/packages/shared/components/LoginComponents.tsx @@ -1,6 +1,5 @@ -import { isDevBuild } from "@/next/env"; import log from "@/next/log"; -import { apiOrigin } from "@/next/origins"; +import { customAPIHost } from "@/next/origins"; import type { BaseAppContextT } from "@/next/types/app"; import { checkPasskeyVerificationStatus, @@ -8,7 +7,7 @@ import { saveCredentialsAndNavigateTo, } from "@ente/accounts/services/passkey"; import EnteButton from "@ente/shared/components/EnteButton"; -import { CircularProgress, Typography, styled } from "@mui/material"; +import { CircularProgress, Stack, Typography, styled } from "@mui/material"; import { t } from "i18next"; import { useRouter } from "next/router"; import React, { useState } from "react"; @@ -46,22 +45,26 @@ const Header_ = styled("div")` gap: 8px; `; -export const ConnectionDetails: React.FC = () => { - const host = new URL(apiOrigin()).host; +export const LoginFlowFormFooter: React.FC = ({ + children, +}) => { + const host = customAPIHost(); return ( - - - {host} - - + + + {children} + + {host && ( + + {host} + + )} + + ); }; -const ConnectionDetails_ = styled("div")` - margin-block-start: 1rem; -`; - interface VerifyingPasskeyProps { /** ID of the current passkey verification session. */ passkeySessionID: string; @@ -161,16 +164,16 @@ export const VerifyingPasskey: React.FC = ({ - - - {t("RECOVER_ACCOUNT")} - - - {t("CHANGE_EMAIL")} - - - - {isDevBuild && } + + + + {t("RECOVER_ACCOUNT")} + + + {t("CHANGE_EMAIL")} + + + ); From 6926167f3dee1e4d2ecf59641c3565152cb8f3c8 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 24 Jun 2024 15:09:19 +0530 Subject: [PATCH 125/130] System props --- web/packages/shared/components/LoginComponents.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/packages/shared/components/LoginComponents.tsx b/web/packages/shared/components/LoginComponents.tsx index 024ac894b4..8201ccacc9 100644 --- a/web/packages/shared/components/LoginComponents.tsx +++ b/web/packages/shared/components/LoginComponents.tsx @@ -52,7 +52,7 @@ export const LoginFlowFormFooter: React.FC = ({ return ( - + {children} {host && ( From 073c22ae47d6b94c4f37168bc2757bd063225708 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 24 Jun 2024 15:10:55 +0530 Subject: [PATCH 126/130] Reuse --- web/packages/accounts/pages/verify.tsx | 39 ++++++++++---------------- 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/web/packages/accounts/pages/verify.tsx b/web/packages/accounts/pages/verify.tsx index 20ecd52069..9f84964904 100644 --- a/web/packages/accounts/pages/verify.tsx +++ b/web/packages/accounts/pages/verify.tsx @@ -1,13 +1,14 @@ -import { customAPIHost } from "@/next/origins"; import { ensure } from "@/utils/ensure"; import type { UserVerificationResponse } from "@ente/accounts/types/user"; import { VerticallyCentered } from "@ente/shared/components/Container"; import EnteSpinner from "@ente/shared/components/EnteSpinner"; import FormPaper from "@ente/shared/components/Form/FormPaper"; -import FormPaperFooter from "@ente/shared/components/Form/FormPaper/Footer"; import FormPaperTitle from "@ente/shared/components/Form/FormPaper/Title"; import LinkButton from "@ente/shared/components/LinkButton"; -import { VerifyingPasskey } from "@ente/shared/components/LoginComponents"; +import { + LoginFlowFormFooter, + VerifyingPasskey, +} from "@ente/shared/components/LoginComponents"; import SingleInputForm, { type SingleInputFormProps, } from "@ente/shared/components/SingleInputForm"; @@ -165,8 +166,6 @@ const Page: React.FC = ({ appContext }) => { setTimeout(() => setResend(0), 3000); }; - const host = customAPIHost(); - if (!email) { return ( @@ -228,28 +227,20 @@ const Page: React.FC = ({ appContext }) => { callback={onSubmit} /> - - - - {resend === 0 && ( - - {t("RESEND_MAIL")} - - )} - {resend === 1 && {t("SENDING")}} - {resend === 2 && {t("SENT")}} - - {t("CHANGE_EMAIL")} + + + {resend === 0 && ( + + {t("RESEND_MAIL")} - - - {host && ( - - {host} - )} + {resend === 1 && {t("SENDING")}} + {resend === 2 && {t("SENT")}} + + {t("CHANGE_EMAIL")} + - + ); From 4651c6f6d1779cd872104078736d37a51e178587 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 24 Jun 2024 15:27:39 +0530 Subject: [PATCH 127/130] Reactive --- web/apps/photos/src/pages/index.tsx | 46 +++++++++++++++------ web/packages/accounts/components/Login.tsx | 6 +-- web/packages/accounts/components/SignUp.tsx | 6 +-- 3 files changed, 40 insertions(+), 18 deletions(-) diff --git a/web/apps/photos/src/pages/index.tsx b/web/apps/photos/src/pages/index.tsx index 6ec6fa2d8c..ec37ca3bf8 100644 --- a/web/apps/photos/src/pages/index.tsx +++ b/web/apps/photos/src/pages/index.tsx @@ -12,8 +12,8 @@ import { LS_KEYS, getData } from "@ente/shared/storage/localStorage"; import { getToken } from "@ente/shared/storage/localStorage/helpers"; import { SESSION_KEYS, getKey } from "@ente/shared/storage/sessionStorage"; import { - Button, Box, + Button, Typography, styled, type TypographyProps, @@ -31,6 +31,10 @@ export default function LandingPage() { const [loading, setLoading] = useState(true); const [showLogin, setShowLogin] = useState(true); + // This is kept as state because it can change as a result of user action + // while we're on this page (there currently isn't an event listener we can + // attach to for observing changes to local storage by the same window). + const [host, setHost] = useState(customAPIHost()); const router = useRouter(); @@ -49,6 +53,8 @@ export default function LandingPage() { } }, []); + const handleMaybeChangeHost = () => setHost(customAPIHost()); + const handleAlbumsRedirect = async (currentURL: URL) => { const end = currentURL.hash.lastIndexOf("&"); const hash = currentURL.hash.slice(1, end !== -1 ? end : undefined); @@ -111,7 +117,7 @@ export default function LandingPage() { const redirectToLoginPage = () => router.push(PAGES.LOGIN); return ( - + {loading ? ( ) : ( @@ -133,14 +139,14 @@ export default function LandingPage() { - + {showLogin ? ( - + ) : ( - + )} @@ -150,7 +156,19 @@ export default function LandingPage() { ); } -const TappableContainer: React.FC = ({ children }) => { +interface TappableContainerProps { + /** + * Called when the user closes the dialog to set a custom server. + * + * This is our chance to re-read the value of the custom API origin from + * local storage since the user might've changed it. + */ + onMaybeChangeHost: () => void; +} + +const TappableContainer: React.FC< + React.PropsWithChildren +> = ({ onMaybeChangeHost, children }) => { // [Note: Configuring custom server] // // Allow the user to tap 7 times anywhere on the onboarding screen to bring @@ -180,13 +198,15 @@ const TappableContainer: React.FC = ({ children }) => { } }; + const handleClose = () => { + setShowDevSettings(false); + onMaybeChangeHost(); + }; + return ( <> - setShowDevSettings(false)} - /> + {children} @@ -244,9 +264,11 @@ const MobileBox = styled("div")` } `; -const MobileBoxFooter: React.FC = () => { - const host = customAPIHost(); +interface MobileBoxFooterProps { + host: string | undefined; +} +const MobileBoxFooter: React.FC = ({ host }) => { return ( {host && ( diff --git a/web/packages/accounts/components/Login.tsx b/web/packages/accounts/components/Login.tsx index 69775957b8..6e9d523aa4 100644 --- a/web/packages/accounts/components/Login.tsx +++ b/web/packages/accounts/components/Login.tsx @@ -18,13 +18,13 @@ import { PAGES } from "../constants/pages"; interface LoginProps { signUp: () => void; appName: AppName; + /** Reactive value of {@link customAPIHost}. */ + host: string | undefined; } -export function Login({ appName, signUp }: LoginProps) { +export function Login({ appName, signUp, host }: LoginProps) { const router = useRouter(); - const host = customAPIHost(); - const loginUser: SingleInputFormProps["callback"] = async ( email, setFieldError, diff --git a/web/packages/accounts/components/SignUp.tsx b/web/packages/accounts/components/SignUp.tsx index 24b0fef334..746d92fe12 100644 --- a/web/packages/accounts/components/SignUp.tsx +++ b/web/packages/accounts/components/SignUp.tsx @@ -55,15 +55,15 @@ interface SignUpProps { router: NextRouter; login: () => void; appName: AppName; + /** Reactive value of {@link customAPIHost}. */ + host: string | undefined; } -export function SignUp({ router, appName, login }: SignUpProps) { +export function SignUp({ router, appName, login, host }: SignUpProps) { const [acceptTerms, setAcceptTerms] = useState(false); const [loading, setLoading] = useState(false); const [showPassword, setShowPassword] = useState(false); - const host = customAPIHost(); - const handleClickShowPassword = () => { setShowPassword(!showPassword); }; From 51c9094da4fbe3b5c9264e156d6add8b23132cb9 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 24 Jun 2024 15:38:38 +0530 Subject: [PATCH 128/130] Prevent layout shift --- web/packages/accounts/components/Login.tsx | 14 ++++++++------ web/packages/accounts/components/SignUp.tsx | 14 ++++++++------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/web/packages/accounts/components/Login.tsx b/web/packages/accounts/components/Login.tsx index 6e9d523aa4..8d31b2bc4b 100644 --- a/web/packages/accounts/components/Login.tsx +++ b/web/packages/accounts/components/Login.tsx @@ -1,5 +1,4 @@ import log from "@/next/log"; -import { customAPIHost } from "@/next/origins"; import type { AppName } from "@/next/types/app"; import FormPaperFooter from "@ente/shared/components/Form/FormPaper/Footer"; import FormPaperTitle from "@ente/shared/components/Form/FormPaper/Title"; @@ -68,11 +67,14 @@ export function Login({ appName, signUp, host }: LoginProps) { {t("NO_ACCOUNT")} - {host && ( - - {host} - - )} + + + {host ?? "" /* prevent layout shift with a minHeight */} + diff --git a/web/packages/accounts/components/SignUp.tsx b/web/packages/accounts/components/SignUp.tsx index 746d92fe12..7d021812d9 100644 --- a/web/packages/accounts/components/SignUp.tsx +++ b/web/packages/accounts/components/SignUp.tsx @@ -1,5 +1,4 @@ import log from "@/next/log"; -import { customAPIHost } from "@/next/origins"; import type { AppName } from "@/next/types/app"; import { sendOtt } from "@ente/accounts/api/user"; import { PasswordStrengthHint } from "@ente/accounts/components/PasswordStrength"; @@ -318,11 +317,14 @@ export function SignUp({ router, appName, login, host }: SignUpProps) { {t("ACCOUNT_EXISTS")} - {host && ( - - {host} - - )} + + + {host ?? "" /* prevent layout shift with a minHeight */} + From ca0af1f53ab9e312b4601b3b277292329a71ad26 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 24 Jun 2024 16:04:25 +0530 Subject: [PATCH 129/130] Also on standalone pages --- web/packages/accounts/pages/login.tsx | 7 +++++-- web/packages/accounts/pages/signup.tsx | 5 ++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/web/packages/accounts/pages/login.tsx b/web/packages/accounts/pages/login.tsx index ce9c8915f7..b6ea5ed207 100644 --- a/web/packages/accounts/pages/login.tsx +++ b/web/packages/accounts/pages/login.tsx @@ -1,3 +1,4 @@ +import { customAPIHost } from "@/next/origins"; import { VerticallyCentered } from "@ente/shared/components/Container"; import EnteSpinner from "@ente/shared/components/EnteSpinner"; import FormPaper from "@ente/shared/components/Form/FormPaper"; @@ -15,6 +16,8 @@ const Page: React.FC = ({ appContext }) => { const router = useRouter(); + const host = customAPIHost(); + useEffect(() => { const user = getData(LS_KEYS.USER); if (user?.email) { @@ -24,7 +27,7 @@ const Page: React.FC = ({ appContext }) => { showNavBar(true); }, []); - const register = () => { + const signUp = () => { router.push(PAGES.SIGNUP); }; @@ -35,7 +38,7 @@ const Page: React.FC = ({ appContext }) => { ) : ( - + ); diff --git a/web/packages/accounts/pages/signup.tsx b/web/packages/accounts/pages/signup.tsx index ce7e14e568..c55a2a13e0 100644 --- a/web/packages/accounts/pages/signup.tsx +++ b/web/packages/accounts/pages/signup.tsx @@ -1,3 +1,4 @@ +import { customAPIHost } from "@/next/origins"; import { PAGES } from "@ente/accounts/constants/pages"; import { LS_KEYS, getData } from "@ente/shared//storage/localStorage"; import { VerticallyCentered } from "@ente/shared/components/Container"; @@ -15,6 +16,8 @@ const Page: React.FC = ({ appContext }) => { const router = useRouter(); + const host = customAPIHost(); + useEffect(() => { const user = getData(LS_KEYS.USER); if (user?.email) { @@ -34,7 +37,7 @@ const Page: React.FC = ({ appContext }) => { ) : ( - + )} From 8ce3126c921790aecb1375fa893bc04af7d9c99c Mon Sep 17 00:00:00 2001 From: Crowdin Bot Date: Mon, 24 Jun 2024 10:38:53 +0000 Subject: [PATCH 130/130] New Crowdin translations by GitHub Action --- .../next/locales/ar-SA/translation.json | 6 +- .../next/locales/bg-BG/translation.json | 6 +- .../next/locales/de-DE/translation.json | 6 +- .../next/locales/es-ES/translation.json | 6 +- .../next/locales/fa-IR/translation.json | 6 +- .../next/locales/fi-FI/translation.json | 6 +- .../next/locales/fr-FR/translation.json | 6 +- .../next/locales/gu-IN/translation.json | 647 ++++++++++++++++++ .../next/locales/hi-IN/translation.json | 647 ++++++++++++++++++ .../next/locales/id-ID/translation.json | 10 +- .../next/locales/is-IS/translation.json | 6 +- .../next/locales/it-IT/translation.json | 6 +- .../next/locales/ko-KR/translation.json | 6 +- .../next/locales/nl-NL/translation.json | 6 +- .../next/locales/pt-BR/translation.json | 8 +- .../next/locales/pt-PT/translation.json | 6 +- .../next/locales/ru-RU/translation.json | 6 +- .../next/locales/sv-SE/translation.json | 6 +- .../next/locales/te-IN/translation.json | 6 +- .../next/locales/th-TH/translation.json | 6 +- .../next/locales/ti-ER/translation.json | 647 ++++++++++++++++++ .../next/locales/tr-TR/translation.json | 6 +- .../next/locales/zh-CN/translation.json | 6 +- 23 files changed, 2044 insertions(+), 23 deletions(-) create mode 100644 web/packages/next/locales/gu-IN/translation.json create mode 100644 web/packages/next/locales/hi-IN/translation.json create mode 100644 web/packages/next/locales/ti-ER/translation.json diff --git a/web/packages/next/locales/ar-SA/translation.json b/web/packages/next/locales/ar-SA/translation.json index 0e2bc528c4..a644dd4a17 100644 --- a/web/packages/next/locales/ar-SA/translation.json +++ b/web/packages/next/locales/ar-SA/translation.json @@ -639,5 +639,9 @@ "redirect_close_instructions": "", "redirect_again": "", "autogenerated_first_album_name": "", - "autogenerated_default_album_name": "" + "autogenerated_default_album_name": "", + "developer_settings": "", + "server_endpoint": "", + "more_information": "", + "save": "" } diff --git a/web/packages/next/locales/bg-BG/translation.json b/web/packages/next/locales/bg-BG/translation.json index 006c6adb7f..2c2c947fde 100644 --- a/web/packages/next/locales/bg-BG/translation.json +++ b/web/packages/next/locales/bg-BG/translation.json @@ -639,5 +639,9 @@ "redirect_close_instructions": "", "redirect_again": "", "autogenerated_first_album_name": "", - "autogenerated_default_album_name": "" + "autogenerated_default_album_name": "", + "developer_settings": "", + "server_endpoint": "", + "more_information": "", + "save": "" } diff --git a/web/packages/next/locales/de-DE/translation.json b/web/packages/next/locales/de-DE/translation.json index dcbb5e5239..def17d0466 100644 --- a/web/packages/next/locales/de-DE/translation.json +++ b/web/packages/next/locales/de-DE/translation.json @@ -639,5 +639,9 @@ "redirect_close_instructions": "Sie werden zurück zur App weitergeleitet.", "redirect_again": "", "autogenerated_first_album_name": "Mein erstes Album", - "autogenerated_default_album_name": "Neues Album" + "autogenerated_default_album_name": "Neues Album", + "developer_settings": "", + "server_endpoint": "", + "more_information": "", + "save": "" } diff --git a/web/packages/next/locales/es-ES/translation.json b/web/packages/next/locales/es-ES/translation.json index d0a3657fa7..c4bad10b3d 100644 --- a/web/packages/next/locales/es-ES/translation.json +++ b/web/packages/next/locales/es-ES/translation.json @@ -639,5 +639,9 @@ "redirect_close_instructions": "", "redirect_again": "", "autogenerated_first_album_name": "", - "autogenerated_default_album_name": "" + "autogenerated_default_album_name": "", + "developer_settings": "", + "server_endpoint": "", + "more_information": "", + "save": "" } diff --git a/web/packages/next/locales/fa-IR/translation.json b/web/packages/next/locales/fa-IR/translation.json index f43f14fa12..a06d4e9acc 100644 --- a/web/packages/next/locales/fa-IR/translation.json +++ b/web/packages/next/locales/fa-IR/translation.json @@ -639,5 +639,9 @@ "redirect_close_instructions": "", "redirect_again": "", "autogenerated_first_album_name": "", - "autogenerated_default_album_name": "" + "autogenerated_default_album_name": "", + "developer_settings": "", + "server_endpoint": "", + "more_information": "", + "save": "" } diff --git a/web/packages/next/locales/fi-FI/translation.json b/web/packages/next/locales/fi-FI/translation.json index 0e2bc528c4..a644dd4a17 100644 --- a/web/packages/next/locales/fi-FI/translation.json +++ b/web/packages/next/locales/fi-FI/translation.json @@ -639,5 +639,9 @@ "redirect_close_instructions": "", "redirect_again": "", "autogenerated_first_album_name": "", - "autogenerated_default_album_name": "" + "autogenerated_default_album_name": "", + "developer_settings": "", + "server_endpoint": "", + "more_information": "", + "save": "" } diff --git a/web/packages/next/locales/fr-FR/translation.json b/web/packages/next/locales/fr-FR/translation.json index f4d2880a34..d56afd6168 100644 --- a/web/packages/next/locales/fr-FR/translation.json +++ b/web/packages/next/locales/fr-FR/translation.json @@ -639,5 +639,9 @@ "redirect_close_instructions": "Vous pouvez fermer cette fenêtre après l'ouverture de l'application.", "redirect_again": "Rediriger à nouveau", "autogenerated_first_album_name": "Mon premier album", - "autogenerated_default_album_name": "Nouvel album" + "autogenerated_default_album_name": "Nouvel album", + "developer_settings": "", + "server_endpoint": "", + "more_information": "", + "save": "" } diff --git a/web/packages/next/locales/gu-IN/translation.json b/web/packages/next/locales/gu-IN/translation.json new file mode 100644 index 0000000000..a644dd4a17 --- /dev/null +++ b/web/packages/next/locales/gu-IN/translation.json @@ -0,0 +1,647 @@ +{ + "HERO_SLIDE_1_TITLE": "", + "HERO_SLIDE_1": "", + "HERO_SLIDE_2_TITLE": "", + "HERO_SLIDE_2": "", + "HERO_SLIDE_3_TITLE": "", + "HERO_SLIDE_3": "", + "LOGIN": "", + "SIGN_UP": "", + "NEW_USER": "", + "EXISTING_USER": "", + "ENTER_NAME": "", + "PUBLIC_UPLOADER_NAME_MESSAGE": "", + "ENTER_EMAIL": "", + "EMAIL_ERROR": "", + "REQUIRED": "", + "EMAIL_SENT": "", + "CHECK_INBOX": "", + "ENTER_OTT": "", + "RESEND_MAIL": "", + "VERIFY": "", + "UNKNOWN_ERROR": "", + "INVALID_CODE": "", + "EXPIRED_CODE": "", + "SENDING": "", + "SENT": "", + "password": "", + "link_password_description": "", + "unlock": "", + "SET_PASSPHRASE": "", + "VERIFY_PASSPHRASE": "", + "INCORRECT_PASSPHRASE": "", + "ENTER_ENC_PASSPHRASE": "", + "PASSPHRASE_DISCLAIMER": "", + "WELCOME_TO_ENTE_HEADING": "", + "WELCOME_TO_ENTE_SUBHEADING": "", + "WHERE_YOUR_BEST_PHOTOS_LIVE": "", + "KEY_GENERATION_IN_PROGRESS_MESSAGE": "", + "PASSPHRASE_HINT": "", + "CONFIRM_PASSPHRASE": "", + "REFERRAL_CODE_HINT": "", + "REFERRAL_INFO": "", + "PASSPHRASE_MATCH_ERROR": "", + "CREATE_COLLECTION": "", + "ENTER_ALBUM_NAME": "", + "CLOSE_OPTION": "", + "ENTER_FILE_NAME": "", + "CLOSE": "", + "NO": "", + "NOTHING_HERE": "", + "UPLOAD": "", + "IMPORT": "", + "ADD_PHOTOS": "", + "ADD_MORE_PHOTOS": "", + "add_photos_one": "", + "add_photos_other": "", + "SELECT_PHOTOS": "", + "FILE_UPLOAD": "", + "UPLOAD_STAGE_MESSAGE": { + "0": "", + "1": "", + "2": "", + "3": "", + "4": "", + "5": "" + }, + "FILE_NOT_UPLOADED_LIST": "", + "SUBSCRIPTION_EXPIRED": "", + "SUBSCRIPTION_EXPIRED_MESSAGE": "", + "STORAGE_QUOTA_EXCEEDED": "", + "INITIAL_LOAD_DELAY_WARNING": "", + "USER_DOES_NOT_EXIST": "", + "NO_ACCOUNT": "", + "ACCOUNT_EXISTS": "", + "CREATE": "", + "DOWNLOAD": "", + "DOWNLOAD_OPTION": "", + "DOWNLOAD_FAVORITES": "", + "DOWNLOAD_UNCATEGORIZED": "", + "DOWNLOAD_HIDDEN_ITEMS": "", + "COPY_OPTION": "", + "TOGGLE_FULLSCREEN": "", + "ZOOM_IN_OUT": "", + "PREVIOUS": "", + "NEXT": "", + "title_photos": "", + "title_auth": "", + "title_accounts": "", + "UPLOAD_FIRST_PHOTO": "", + "IMPORT_YOUR_FOLDERS": "", + "UPLOAD_DROPZONE_MESSAGE": "", + "WATCH_FOLDER_DROPZONE_MESSAGE": "", + "TRASH_FILES_TITLE": "", + "TRASH_FILE_TITLE": "", + "DELETE_FILES_TITLE": "", + "DELETE_FILES_MESSAGE": "", + "DELETE": "", + "DELETE_OPTION": "", + "FAVORITE_OPTION": "", + "UNFAVORITE_OPTION": "", + "MULTI_FOLDER_UPLOAD": "", + "UPLOAD_STRATEGY_CHOICE": "", + "UPLOAD_STRATEGY_SINGLE_COLLECTION": "", + "OR": "", + "UPLOAD_STRATEGY_COLLECTION_PER_FOLDER": "", + "SESSION_EXPIRED_MESSAGE": "", + "SESSION_EXPIRED": "", + "PASSWORD_GENERATION_FAILED": "", + "CHANGE_PASSWORD": "", + "GO_BACK": "", + "RECOVERY_KEY": "", + "SAVE_LATER": "", + "SAVE": "", + "RECOVERY_KEY_DESCRIPTION": "", + "RECOVER_KEY_GENERATION_FAILED": "", + "KEY_NOT_STORED_DISCLAIMER": "", + "FORGOT_PASSWORD": "", + "RECOVER_ACCOUNT": "", + "RECOVERY_KEY_HINT": "", + "RECOVER": "", + "NO_RECOVERY_KEY": "", + "INCORRECT_RECOVERY_KEY": "", + "SORRY": "", + "NO_RECOVERY_KEY_MESSAGE": "", + "NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "", + "CONTACT_SUPPORT": "", + "REQUEST_FEATURE": "", + "SUPPORT": "", + "CONFIRM": "", + "CANCEL": "", + "LOGOUT": "", + "delete_account": "", + "delete_account_manually_message": "", + "LOGOUT_MESSAGE": "", + "CHANGE_EMAIL": "", + "OK": "", + "SUCCESS": "", + "ERROR": "", + "MESSAGE": "", + "INSTALL_MOBILE_APP": "", + "DOWNLOAD_APP_MESSAGE": "", + "DOWNLOAD_APP": "", + "EXPORT": "", + "SUBSCRIPTION": "", + "SUBSCRIBE": "", + "MANAGEMENT_PORTAL": "", + "MANAGE_FAMILY_PORTAL": "", + "LEAVE_FAMILY_PLAN": "", + "LEAVE": "", + "LEAVE_FAMILY_CONFIRM": "", + "CHOOSE_PLAN": "", + "MANAGE_PLAN": "", + "ACTIVE": "", + "OFFLINE_MSG": "", + "FREE_SUBSCRIPTION_INFO": "", + "FAMILY_SUBSCRIPTION_INFO": "", + "RENEWAL_ACTIVE_SUBSCRIPTION_STATUS": "", + "RENEWAL_CANCELLED_SUBSCRIPTION_STATUS": "", + "RENEWAL_CANCELLED_SUBSCRIPTION_INFO": "", + "ADD_ON_AVAILABLE_TILL": "", + "STORAGE_QUOTA_EXCEEDED_SUBSCRIPTION_INFO": "", + "SUBSCRIPTION_PURCHASE_SUCCESS": "", + "SUBSCRIPTION_PURCHASE_CANCELLED": "", + "SUBSCRIPTION_PURCHASE_FAILED": "", + "SUBSCRIPTION_UPDATE_FAILED": "", + "UPDATE_PAYMENT_METHOD_MESSAGE": "", + "STRIPE_AUTHENTICATION_FAILED": "", + "UPDATE_PAYMENT_METHOD": "", + "MONTHLY": "", + "YEARLY": "", + "update_subscription_title": "", + "UPDATE_SUBSCRIPTION_MESSAGE": "", + "UPDATE_SUBSCRIPTION": "", + "CANCEL_SUBSCRIPTION": "", + "CANCEL_SUBSCRIPTION_MESSAGE": "", + "CANCEL_SUBSCRIPTION_WITH_ADDON_MESSAGE": "", + "SUBSCRIPTION_CANCEL_FAILED": "", + "SUBSCRIPTION_CANCEL_SUCCESS": "", + "REACTIVATE_SUBSCRIPTION": "", + "REACTIVATE_SUBSCRIPTION_MESSAGE": "", + "SUBSCRIPTION_ACTIVATE_SUCCESS": "", + "SUBSCRIPTION_ACTIVATE_FAILED": "", + "SUBSCRIPTION_PURCHASE_SUCCESS_TITLE": "", + "CANCEL_SUBSCRIPTION_ON_MOBILE": "", + "CANCEL_SUBSCRIPTION_ON_MOBILE_MESSAGE": "", + "MAIL_TO_MANAGE_SUBSCRIPTION": "", + "RENAME": "", + "RENAME_FILE": "", + "RENAME_COLLECTION": "", + "DELETE_COLLECTION_TITLE": "", + "DELETE_COLLECTION": "", + "DELETE_COLLECTION_MESSAGE": "", + "DELETE_PHOTOS": "", + "KEEP_PHOTOS": "", + "SHARE_COLLECTION": "", + "SHARE_WITH_SELF": "", + "ALREADY_SHARED": "", + "SHARING_BAD_REQUEST_ERROR": "", + "SHARING_DISABLED_FOR_FREE_ACCOUNTS": "", + "DOWNLOAD_COLLECTION": "", + "CREATE_ALBUM_FAILED": "", + "SEARCH": "", + "SEARCH_RESULTS": "", + "NO_RESULTS": "", + "SEARCH_HINT": "", + "SEARCH_TYPE": { + "COLLECTION": "", + "LOCATION": "", + "CITY": "", + "DATE": "", + "FILE_NAME": "", + "THING": "", + "FILE_CAPTION": "", + "FILE_TYPE": "", + "CLIP": "" + }, + "photos_count_zero": "", + "photos_count_one": "", + "photos_count_other": "", + "TERMS_AND_CONDITIONS": "", + "ADD_TO_COLLECTION": "", + "SELECTED": "", + "PEOPLE": "", + "INDEXING_SCHEDULED": "", + "ANALYZING_PHOTOS": "", + "INDEXING_PEOPLE": "", + "INDEXING_DONE": "", + "UNIDENTIFIED_FACES": "", + "OBJECTS": "", + "TEXT": "", + "INFO": "", + "INFO_OPTION": "", + "FILE_NAME": "", + "CAPTION_PLACEHOLDER": "", + "LOCATION": "", + "SHOW_ON_MAP": "", + "MAP": "", + "MAP_SETTINGS": "", + "ENABLE_MAPS": "", + "ENABLE_MAP": "", + "DISABLE_MAPS": "", + "ENABLE_MAP_DESCRIPTION": "", + "DISABLE_MAP_DESCRIPTION": "", + "DISABLE_MAP": "", + "DETAILS": "", + "VIEW_EXIF": "", + "NO_EXIF": "", + "EXIF": "", + "ISO": "", + "TWO_FACTOR": "", + "TWO_FACTOR_AUTHENTICATION": "", + "TWO_FACTOR_QR_INSTRUCTION": "", + "ENTER_CODE_MANUALLY": "", + "TWO_FACTOR_MANUAL_CODE_INSTRUCTION": "", + "SCAN_QR_CODE": "", + "ENABLE_TWO_FACTOR": "", + "ENABLE": "", + "LOST_DEVICE": "", + "INCORRECT_CODE": "", + "TWO_FACTOR_INFO": "", + "DISABLE_TWO_FACTOR_LABEL": "", + "UPDATE_TWO_FACTOR_LABEL": "", + "DISABLE": "", + "RECONFIGURE": "", + "UPDATE_TWO_FACTOR": "", + "UPDATE_TWO_FACTOR_MESSAGE": "", + "UPDATE": "", + "DISABLE_TWO_FACTOR": "", + "DISABLE_TWO_FACTOR_MESSAGE": "", + "TWO_FACTOR_DISABLE_FAILED": "", + "EXPORT_DATA": "", + "SELECT_FOLDER": "", + "DESTINATION": "", + "START": "", + "LAST_EXPORT_TIME": "", + "EXPORT_AGAIN": "", + "LOCAL_STORAGE_NOT_ACCESSIBLE": "", + "LOCAL_STORAGE_NOT_ACCESSIBLE_MESSAGE": "", + "SEND_OTT": "", + "EMAIl_ALREADY_OWNED": "", + "ETAGS_BLOCKED": "", + "LIVE_PHOTOS_DETECTED": "", + "RETRY_FAILED": "", + "FAILED_UPLOADS": "", + "SKIPPED_FILES": "", + "THUMBNAIL_GENERATION_FAILED_UPLOADS": "", + "UNSUPPORTED_FILES": "", + "SUCCESSFUL_UPLOADS": "", + "SKIPPED_INFO": "", + "UNSUPPORTED_INFO": "", + "BLOCKED_UPLOADS": "", + "INPROGRESS_METADATA_EXTRACTION": "", + "INPROGRESS_UPLOADS": "", + "TOO_LARGE_UPLOADS": "", + "LARGER_THAN_AVAILABLE_STORAGE_UPLOADS": "", + "LARGER_THAN_AVAILABLE_STORAGE_INFO": "", + "TOO_LARGE_INFO": "", + "THUMBNAIL_GENERATION_FAILED_INFO": "", + "UPLOAD_TO_COLLECTION": "", + "UNCATEGORIZED": "", + "ARCHIVE": "", + "FAVORITES": "", + "ARCHIVE_COLLECTION": "", + "ARCHIVE_SECTION_NAME": "", + "ALL_SECTION_NAME": "", + "MOVE_TO_COLLECTION": "", + "UNARCHIVE": "", + "UNARCHIVE_COLLECTION": "", + "HIDE_COLLECTION": "", + "UNHIDE_COLLECTION": "", + "MOVE": "", + "ADD": "", + "REMOVE": "", + "YES_REMOVE": "", + "REMOVE_FROM_COLLECTION": "", + "TRASH": "", + "MOVE_TO_TRASH": "", + "TRASH_FILES_MESSAGE": "", + "TRASH_FILE_MESSAGE": "", + "DELETE_PERMANENTLY": "", + "RESTORE": "", + "RESTORE_TO_COLLECTION": "", + "EMPTY_TRASH": "", + "EMPTY_TRASH_TITLE": "", + "EMPTY_TRASH_MESSAGE": "", + "LEAVE_SHARED_ALBUM": "", + "LEAVE_ALBUM": "", + "LEAVE_SHARED_ALBUM_TITLE": "", + "LEAVE_SHARED_ALBUM_MESSAGE": "", + "NOT_FILE_OWNER": "", + "CONFIRM_SELF_REMOVE_MESSAGE": "", + "CONFIRM_SELF_AND_OTHER_REMOVE_MESSAGE": "", + "SORT_BY_CREATION_TIME_ASCENDING": "", + "SORT_BY_UPDATION_TIME_DESCENDING": "", + "SORT_BY_NAME": "", + "FIX_CREATION_TIME": "", + "FIX_CREATION_TIME_IN_PROGRESS": "", + "CREATION_TIME_UPDATED": "", + "UPDATE_CREATION_TIME_NOT_STARTED": "", + "UPDATE_CREATION_TIME_COMPLETED": "", + "UPDATE_CREATION_TIME_COMPLETED_WITH_ERROR": "", + "CAPTION_CHARACTER_LIMIT": "", + "DATE_TIME_ORIGINAL": "", + "DATE_TIME_DIGITIZED": "", + "METADATA_DATE": "", + "CUSTOM_TIME": "", + "REOPEN_PLAN_SELECTOR_MODAL": "", + "OPEN_PLAN_SELECTOR_MODAL_FAILED": "", + "INSTALL": "", + "SHARING_DETAILS": "", + "MODIFY_SHARING": "", + "ADD_COLLABORATORS": "", + "ADD_NEW_EMAIL": "", + "shared_with_people_zero": "", + "shared_with_people_one": "", + "shared_with_people_other": "", + "participants_zero": "", + "participants_one": "", + "participants_other": "", + "ADD_VIEWERS": "", + "CHANGE_PERMISSIONS_TO_VIEWER": "", + "CHANGE_PERMISSIONS_TO_COLLABORATOR": "", + "CONVERT_TO_VIEWER": "", + "CONVERT_TO_COLLABORATOR": "", + "CHANGE_PERMISSION": "", + "REMOVE_PARTICIPANT": "", + "CONFIRM_REMOVE": "", + "MANAGE": "", + "ADDED_AS": "", + "COLLABORATOR_RIGHTS": "", + "REMOVE_PARTICIPANT_HEAD": "", + "OWNER": "", + "COLLABORATORS": "", + "ADD_MORE": "", + "VIEWERS": "", + "OR_ADD_EXISTING": "", + "REMOVE_PARTICIPANT_MESSAGE": "", + "NOT_FOUND": "", + "LINK_EXPIRED": "", + "LINK_EXPIRED_MESSAGE": "", + "MANAGE_LINK": "", + "LINK_TOO_MANY_REQUESTS": "", + "FILE_DOWNLOAD": "", + "link_password_lock": "", + "PUBLIC_COLLECT": "", + "LINK_DEVICE_LIMIT": "", + "NO_DEVICE_LIMIT": "", + "LINK_EXPIRY": "", + "NEVER": "", + "DISABLE_FILE_DOWNLOAD": "", + "DISABLE_FILE_DOWNLOAD_MESSAGE": "", + "SHARED_USING": "", + "SHARING_REFERRAL_CODE": "", + "LIVE": "", + "DISABLE_PASSWORD": "", + "DISABLE_PASSWORD_MESSAGE": "", + "PASSWORD_LOCK": "", + "LOCK": "", + "DOWNLOAD_UPLOAD_LOGS": "", + "UPLOAD_FILES": "", + "UPLOAD_DIRS": "", + "UPLOAD_GOOGLE_TAKEOUT": "", + "DEDUPLICATE_FILES": "", + "NO_DUPLICATES_FOUND": "", + "FILES": "", + "EACH": "", + "DEDUPLICATE_BASED_ON_SIZE": "", + "STOP_ALL_UPLOADS_MESSAGE": "", + "STOP_UPLOADS_HEADER": "", + "YES_STOP_UPLOADS": "", + "STOP_DOWNLOADS_HEADER": "", + "YES_STOP_DOWNLOADS": "", + "STOP_ALL_DOWNLOADS_MESSAGE": "", + "albums_one": "", + "albums_other": "", + "ALL_ALBUMS": "", + "ALBUMS": "", + "ALL_HIDDEN_ALBUMS": "", + "HIDDEN_ALBUMS": "", + "HIDDEN_ITEMS": "", + "ENTER_TWO_FACTOR_OTP": "", + "CREATE_ACCOUNT": "", + "COPIED": "", + "WATCH_FOLDERS": "", + "UPGRADE_NOW": "", + "RENEW_NOW": "", + "STORAGE": "", + "USED": "", + "YOU": "", + "FAMILY": "", + "FREE": "", + "OF": "", + "WATCHED_FOLDERS": "", + "NO_FOLDERS_ADDED": "", + "FOLDERS_AUTOMATICALLY_MONITORED": "", + "UPLOAD_NEW_FILES_TO_ENTE": "", + "REMOVE_DELETED_FILES_FROM_ENTE": "", + "ADD_FOLDER": "", + "STOP_WATCHING": "", + "STOP_WATCHING_FOLDER": "", + "STOP_WATCHING_DIALOG_MESSAGE": "", + "YES_STOP": "", + "MONTH_SHORT": "", + "YEAR": "", + "FAMILY_PLAN": "", + "DOWNLOAD_LOGS": "", + "DOWNLOAD_LOGS_MESSAGE": "", + "CHANGE_FOLDER": "", + "TWO_MONTHS_FREE": "", + "POPULAR": "", + "FREE_PLAN_OPTION_LABEL": "", + "free_plan_description": "", + "CURRENT_USAGE": "", + "WEAK_DEVICE": "", + "DRAG_AND_DROP_HINT": "", + "AUTHENTICATE": "", + "UPLOADED_TO_SINGLE_COLLECTION": "", + "UPLOADED_TO_SEPARATE_COLLECTIONS": "", + "NEVERMIND": "", + "UPDATE_AVAILABLE": "", + "UPDATE_INSTALLABLE_MESSAGE": "", + "INSTALL_NOW": "", + "INSTALL_ON_NEXT_LAUNCH": "", + "UPDATE_AVAILABLE_MESSAGE": "", + "DOWNLOAD_AND_INSTALL": "", + "IGNORE_THIS_VERSION": "", + "TODAY": "", + "YESTERDAY": "", + "NAME_PLACEHOLDER": "", + "ROOT_LEVEL_FILE_WITH_FOLDER_NOT_ALLOWED": "", + "ROOT_LEVEL_FILE_WITH_FOLDER_NOT_ALLOWED_MESSAGE": "", + "CHOSE_THEME": "", + "ML_SEARCH": "", + "ENABLE_ML_SEARCH_DESCRIPTION": "", + "ML_MORE_DETAILS": "", + "ENABLE_FACE_SEARCH": "", + "ENABLE_FACE_SEARCH_TITLE": "", + "ENABLE_FACE_SEARCH_DESCRIPTION": "", + "DISABLE_BETA": "", + "DISABLE_FACE_SEARCH": "", + "DISABLE_FACE_SEARCH_TITLE": "", + "DISABLE_FACE_SEARCH_DESCRIPTION": "", + "ADVANCED": "", + "FACE_SEARCH_CONFIRMATION": "", + "LABS": "", + "YOURS": "", + "PASSPHRASE_STRENGTH_WEAK": "", + "PASSPHRASE_STRENGTH_MODERATE": "", + "PASSPHRASE_STRENGTH_STRONG": "", + "PREFERENCES": "", + "LANGUAGE": "", + "EXPORT_DIRECTORY_DOES_NOT_EXIST": "", + "EXPORT_DIRECTORY_DOES_NOT_EXIST_MESSAGE": "", + "SUBSCRIPTION_VERIFICATION_ERROR": "", + "storage_unit": { + "b": "", + "kb": "", + "mb": "", + "gb": "", + "tb": "" + }, + "AFTER_TIME": { + "HOUR": "", + "DAY": "", + "WEEK": "", + "MONTH": "", + "YEAR": "" + }, + "COPY_LINK": "", + "DONE": "", + "LINK_SHARE_TITLE": "", + "REMOVE_LINK": "", + "CREATE_PUBLIC_SHARING": "", + "PUBLIC_LINK_CREATED": "", + "PUBLIC_LINK_ENABLED": "", + "COLLECT_PHOTOS": "", + "PUBLIC_COLLECT_SUBTEXT": "", + "STOP_EXPORT": "", + "EXPORT_PROGRESS": "", + "MIGRATING_EXPORT": "", + "RENAMING_COLLECTION_FOLDERS": "", + "TRASHING_DELETED_FILES": "", + "TRASHING_DELETED_COLLECTIONS": "", + "CONTINUOUS_EXPORT": "", + "PENDING_ITEMS": "", + "EXPORT_STARTING": "", + "delete_account_reason_label": "", + "delete_account_reason_placeholder": "", + "delete_reason": { + "missing_feature": "", + "behaviour": "", + "found_another_service": "", + "not_listed": "" + }, + "delete_account_feedback_label": "", + "delete_account_feedback_placeholder": "", + "delete_account_confirm_checkbox_label": "", + "delete_account_confirm": "", + "delete_account_confirm_message": "", + "feedback_required": "", + "feedback_required_found_another_service": "", + "RECOVER_TWO_FACTOR": "", + "at": "", + "AUTH_NEXT": "", + "AUTH_DOWNLOAD_MOBILE_APP": "", + "HIDDEN": "", + "HIDE": "", + "UNHIDE": "", + "UNHIDE_TO_COLLECTION": "", + "SORT_BY": "", + "NEWEST_FIRST": "", + "OLDEST_FIRST": "", + "CONVERSION_FAILED_NOTIFICATION_MESSAGE": "", + "SELECT_COLLECTION": "", + "PIN_ALBUM": "", + "UNPIN_ALBUM": "", + "DOWNLOAD_COMPLETE": "", + "DOWNLOADING_COLLECTION": "", + "DOWNLOAD_FAILED": "", + "DOWNLOAD_PROGRESS": "", + "CHRISTMAS": "", + "CHRISTMAS_EVE": "", + "NEW_YEAR": "", + "NEW_YEAR_EVE": "", + "IMAGE": "", + "VIDEO": "", + "LIVE_PHOTO": "", + "editor": { + "crop": "" + }, + "CONVERT": "", + "CONFIRM_EDITOR_CLOSE_MESSAGE": "", + "CONFIRM_EDITOR_CLOSE_DESCRIPTION": "", + "BRIGHTNESS": "", + "CONTRAST": "", + "SATURATION": "", + "BLUR": "", + "INVERT_COLORS": "", + "ASPECT_RATIO": "", + "SQUARE": "", + "ROTATE_LEFT": "", + "ROTATE_RIGHT": "", + "FLIP_VERTICALLY": "", + "FLIP_HORIZONTALLY": "", + "DOWNLOAD_EDITED": "", + "SAVE_A_COPY_TO_ENTE": "", + "RESTORE_ORIGINAL": "", + "TRANSFORM": "", + "COLORS": "", + "FLIP": "", + "ROTATION": "", + "RESET": "", + "PHOTO_EDITOR": "", + "FASTER_UPLOAD": "", + "FASTER_UPLOAD_DESCRIPTION": "", + "MAGIC_SEARCH_STATUS": "", + "INDEXED_ITEMS": "", + "CAST_ALBUM_TO_TV": "", + "ENTER_CAST_PIN_CODE": "", + "PAIR_DEVICE_TO_TV": "", + "TV_NOT_FOUND": "", + "AUTO_CAST_PAIR": "", + "AUTO_CAST_PAIR_DESC": "", + "PAIR_WITH_PIN": "", + "CHOOSE_DEVICE_FROM_BROWSER": "", + "PAIR_WITH_PIN_DESC": "", + "VISIT_CAST_ENTE_IO": "", + "CAST_AUTO_PAIR_FAILED": "", + "FREEHAND": "", + "APPLY_CROP": "", + "PHOTO_EDIT_REQUIRED_TO_SAVE": "", + "passkeys": "", + "passkey_fetch_failed": "", + "manage_passkey": "", + "delete_passkey": "", + "delete_passkey_confirmation": "", + "rename_passkey": "", + "add_passkey": "", + "enter_passkey_name": "", + "passkeys_description": "", + "CREATED_AT": "", + "passkey_add_failed": "", + "passkey_login_failed": "", + "passkey_login_invalid_url": "", + "passkey_login_already_claimed_session": "", + "passkey_login_generic_error": "", + "passkey_login_credential_hint": "", + "passkeys_not_supported": "", + "try_again": "", + "check_status": "", + "passkey_login_instructions": "", + "passkey_login": "", + "passkey": "", + "passkey_verify_description": "", + "waiting_for_verification": "", + "verification_still_pending": "", + "passkey_verified": "", + "redirecting_back_to_app": "", + "redirect_close_instructions": "", + "redirect_again": "", + "autogenerated_first_album_name": "", + "autogenerated_default_album_name": "", + "developer_settings": "", + "server_endpoint": "", + "more_information": "", + "save": "" +} diff --git a/web/packages/next/locales/hi-IN/translation.json b/web/packages/next/locales/hi-IN/translation.json new file mode 100644 index 0000000000..a644dd4a17 --- /dev/null +++ b/web/packages/next/locales/hi-IN/translation.json @@ -0,0 +1,647 @@ +{ + "HERO_SLIDE_1_TITLE": "", + "HERO_SLIDE_1": "", + "HERO_SLIDE_2_TITLE": "", + "HERO_SLIDE_2": "", + "HERO_SLIDE_3_TITLE": "", + "HERO_SLIDE_3": "", + "LOGIN": "", + "SIGN_UP": "", + "NEW_USER": "", + "EXISTING_USER": "", + "ENTER_NAME": "", + "PUBLIC_UPLOADER_NAME_MESSAGE": "", + "ENTER_EMAIL": "", + "EMAIL_ERROR": "", + "REQUIRED": "", + "EMAIL_SENT": "", + "CHECK_INBOX": "", + "ENTER_OTT": "", + "RESEND_MAIL": "", + "VERIFY": "", + "UNKNOWN_ERROR": "", + "INVALID_CODE": "", + "EXPIRED_CODE": "", + "SENDING": "", + "SENT": "", + "password": "", + "link_password_description": "", + "unlock": "", + "SET_PASSPHRASE": "", + "VERIFY_PASSPHRASE": "", + "INCORRECT_PASSPHRASE": "", + "ENTER_ENC_PASSPHRASE": "", + "PASSPHRASE_DISCLAIMER": "", + "WELCOME_TO_ENTE_HEADING": "", + "WELCOME_TO_ENTE_SUBHEADING": "", + "WHERE_YOUR_BEST_PHOTOS_LIVE": "", + "KEY_GENERATION_IN_PROGRESS_MESSAGE": "", + "PASSPHRASE_HINT": "", + "CONFIRM_PASSPHRASE": "", + "REFERRAL_CODE_HINT": "", + "REFERRAL_INFO": "", + "PASSPHRASE_MATCH_ERROR": "", + "CREATE_COLLECTION": "", + "ENTER_ALBUM_NAME": "", + "CLOSE_OPTION": "", + "ENTER_FILE_NAME": "", + "CLOSE": "", + "NO": "", + "NOTHING_HERE": "", + "UPLOAD": "", + "IMPORT": "", + "ADD_PHOTOS": "", + "ADD_MORE_PHOTOS": "", + "add_photos_one": "", + "add_photos_other": "", + "SELECT_PHOTOS": "", + "FILE_UPLOAD": "", + "UPLOAD_STAGE_MESSAGE": { + "0": "", + "1": "", + "2": "", + "3": "", + "4": "", + "5": "" + }, + "FILE_NOT_UPLOADED_LIST": "", + "SUBSCRIPTION_EXPIRED": "", + "SUBSCRIPTION_EXPIRED_MESSAGE": "", + "STORAGE_QUOTA_EXCEEDED": "", + "INITIAL_LOAD_DELAY_WARNING": "", + "USER_DOES_NOT_EXIST": "", + "NO_ACCOUNT": "", + "ACCOUNT_EXISTS": "", + "CREATE": "", + "DOWNLOAD": "", + "DOWNLOAD_OPTION": "", + "DOWNLOAD_FAVORITES": "", + "DOWNLOAD_UNCATEGORIZED": "", + "DOWNLOAD_HIDDEN_ITEMS": "", + "COPY_OPTION": "", + "TOGGLE_FULLSCREEN": "", + "ZOOM_IN_OUT": "", + "PREVIOUS": "", + "NEXT": "", + "title_photos": "", + "title_auth": "", + "title_accounts": "", + "UPLOAD_FIRST_PHOTO": "", + "IMPORT_YOUR_FOLDERS": "", + "UPLOAD_DROPZONE_MESSAGE": "", + "WATCH_FOLDER_DROPZONE_MESSAGE": "", + "TRASH_FILES_TITLE": "", + "TRASH_FILE_TITLE": "", + "DELETE_FILES_TITLE": "", + "DELETE_FILES_MESSAGE": "", + "DELETE": "", + "DELETE_OPTION": "", + "FAVORITE_OPTION": "", + "UNFAVORITE_OPTION": "", + "MULTI_FOLDER_UPLOAD": "", + "UPLOAD_STRATEGY_CHOICE": "", + "UPLOAD_STRATEGY_SINGLE_COLLECTION": "", + "OR": "", + "UPLOAD_STRATEGY_COLLECTION_PER_FOLDER": "", + "SESSION_EXPIRED_MESSAGE": "", + "SESSION_EXPIRED": "", + "PASSWORD_GENERATION_FAILED": "", + "CHANGE_PASSWORD": "", + "GO_BACK": "", + "RECOVERY_KEY": "", + "SAVE_LATER": "", + "SAVE": "", + "RECOVERY_KEY_DESCRIPTION": "", + "RECOVER_KEY_GENERATION_FAILED": "", + "KEY_NOT_STORED_DISCLAIMER": "", + "FORGOT_PASSWORD": "", + "RECOVER_ACCOUNT": "", + "RECOVERY_KEY_HINT": "", + "RECOVER": "", + "NO_RECOVERY_KEY": "", + "INCORRECT_RECOVERY_KEY": "", + "SORRY": "", + "NO_RECOVERY_KEY_MESSAGE": "", + "NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "", + "CONTACT_SUPPORT": "", + "REQUEST_FEATURE": "", + "SUPPORT": "", + "CONFIRM": "", + "CANCEL": "", + "LOGOUT": "", + "delete_account": "", + "delete_account_manually_message": "", + "LOGOUT_MESSAGE": "", + "CHANGE_EMAIL": "", + "OK": "", + "SUCCESS": "", + "ERROR": "", + "MESSAGE": "", + "INSTALL_MOBILE_APP": "", + "DOWNLOAD_APP_MESSAGE": "", + "DOWNLOAD_APP": "", + "EXPORT": "", + "SUBSCRIPTION": "", + "SUBSCRIBE": "", + "MANAGEMENT_PORTAL": "", + "MANAGE_FAMILY_PORTAL": "", + "LEAVE_FAMILY_PLAN": "", + "LEAVE": "", + "LEAVE_FAMILY_CONFIRM": "", + "CHOOSE_PLAN": "", + "MANAGE_PLAN": "", + "ACTIVE": "", + "OFFLINE_MSG": "", + "FREE_SUBSCRIPTION_INFO": "", + "FAMILY_SUBSCRIPTION_INFO": "", + "RENEWAL_ACTIVE_SUBSCRIPTION_STATUS": "", + "RENEWAL_CANCELLED_SUBSCRIPTION_STATUS": "", + "RENEWAL_CANCELLED_SUBSCRIPTION_INFO": "", + "ADD_ON_AVAILABLE_TILL": "", + "STORAGE_QUOTA_EXCEEDED_SUBSCRIPTION_INFO": "", + "SUBSCRIPTION_PURCHASE_SUCCESS": "", + "SUBSCRIPTION_PURCHASE_CANCELLED": "", + "SUBSCRIPTION_PURCHASE_FAILED": "", + "SUBSCRIPTION_UPDATE_FAILED": "", + "UPDATE_PAYMENT_METHOD_MESSAGE": "", + "STRIPE_AUTHENTICATION_FAILED": "", + "UPDATE_PAYMENT_METHOD": "", + "MONTHLY": "", + "YEARLY": "", + "update_subscription_title": "", + "UPDATE_SUBSCRIPTION_MESSAGE": "", + "UPDATE_SUBSCRIPTION": "", + "CANCEL_SUBSCRIPTION": "", + "CANCEL_SUBSCRIPTION_MESSAGE": "", + "CANCEL_SUBSCRIPTION_WITH_ADDON_MESSAGE": "", + "SUBSCRIPTION_CANCEL_FAILED": "", + "SUBSCRIPTION_CANCEL_SUCCESS": "", + "REACTIVATE_SUBSCRIPTION": "", + "REACTIVATE_SUBSCRIPTION_MESSAGE": "", + "SUBSCRIPTION_ACTIVATE_SUCCESS": "", + "SUBSCRIPTION_ACTIVATE_FAILED": "", + "SUBSCRIPTION_PURCHASE_SUCCESS_TITLE": "", + "CANCEL_SUBSCRIPTION_ON_MOBILE": "", + "CANCEL_SUBSCRIPTION_ON_MOBILE_MESSAGE": "", + "MAIL_TO_MANAGE_SUBSCRIPTION": "", + "RENAME": "", + "RENAME_FILE": "", + "RENAME_COLLECTION": "", + "DELETE_COLLECTION_TITLE": "", + "DELETE_COLLECTION": "", + "DELETE_COLLECTION_MESSAGE": "", + "DELETE_PHOTOS": "", + "KEEP_PHOTOS": "", + "SHARE_COLLECTION": "", + "SHARE_WITH_SELF": "", + "ALREADY_SHARED": "", + "SHARING_BAD_REQUEST_ERROR": "", + "SHARING_DISABLED_FOR_FREE_ACCOUNTS": "", + "DOWNLOAD_COLLECTION": "", + "CREATE_ALBUM_FAILED": "", + "SEARCH": "", + "SEARCH_RESULTS": "", + "NO_RESULTS": "", + "SEARCH_HINT": "", + "SEARCH_TYPE": { + "COLLECTION": "", + "LOCATION": "", + "CITY": "", + "DATE": "", + "FILE_NAME": "", + "THING": "", + "FILE_CAPTION": "", + "FILE_TYPE": "", + "CLIP": "" + }, + "photos_count_zero": "", + "photos_count_one": "", + "photos_count_other": "", + "TERMS_AND_CONDITIONS": "", + "ADD_TO_COLLECTION": "", + "SELECTED": "", + "PEOPLE": "", + "INDEXING_SCHEDULED": "", + "ANALYZING_PHOTOS": "", + "INDEXING_PEOPLE": "", + "INDEXING_DONE": "", + "UNIDENTIFIED_FACES": "", + "OBJECTS": "", + "TEXT": "", + "INFO": "", + "INFO_OPTION": "", + "FILE_NAME": "", + "CAPTION_PLACEHOLDER": "", + "LOCATION": "", + "SHOW_ON_MAP": "", + "MAP": "", + "MAP_SETTINGS": "", + "ENABLE_MAPS": "", + "ENABLE_MAP": "", + "DISABLE_MAPS": "", + "ENABLE_MAP_DESCRIPTION": "", + "DISABLE_MAP_DESCRIPTION": "", + "DISABLE_MAP": "", + "DETAILS": "", + "VIEW_EXIF": "", + "NO_EXIF": "", + "EXIF": "", + "ISO": "", + "TWO_FACTOR": "", + "TWO_FACTOR_AUTHENTICATION": "", + "TWO_FACTOR_QR_INSTRUCTION": "", + "ENTER_CODE_MANUALLY": "", + "TWO_FACTOR_MANUAL_CODE_INSTRUCTION": "", + "SCAN_QR_CODE": "", + "ENABLE_TWO_FACTOR": "", + "ENABLE": "", + "LOST_DEVICE": "", + "INCORRECT_CODE": "", + "TWO_FACTOR_INFO": "", + "DISABLE_TWO_FACTOR_LABEL": "", + "UPDATE_TWO_FACTOR_LABEL": "", + "DISABLE": "", + "RECONFIGURE": "", + "UPDATE_TWO_FACTOR": "", + "UPDATE_TWO_FACTOR_MESSAGE": "", + "UPDATE": "", + "DISABLE_TWO_FACTOR": "", + "DISABLE_TWO_FACTOR_MESSAGE": "", + "TWO_FACTOR_DISABLE_FAILED": "", + "EXPORT_DATA": "", + "SELECT_FOLDER": "", + "DESTINATION": "", + "START": "", + "LAST_EXPORT_TIME": "", + "EXPORT_AGAIN": "", + "LOCAL_STORAGE_NOT_ACCESSIBLE": "", + "LOCAL_STORAGE_NOT_ACCESSIBLE_MESSAGE": "", + "SEND_OTT": "", + "EMAIl_ALREADY_OWNED": "", + "ETAGS_BLOCKED": "", + "LIVE_PHOTOS_DETECTED": "", + "RETRY_FAILED": "", + "FAILED_UPLOADS": "", + "SKIPPED_FILES": "", + "THUMBNAIL_GENERATION_FAILED_UPLOADS": "", + "UNSUPPORTED_FILES": "", + "SUCCESSFUL_UPLOADS": "", + "SKIPPED_INFO": "", + "UNSUPPORTED_INFO": "", + "BLOCKED_UPLOADS": "", + "INPROGRESS_METADATA_EXTRACTION": "", + "INPROGRESS_UPLOADS": "", + "TOO_LARGE_UPLOADS": "", + "LARGER_THAN_AVAILABLE_STORAGE_UPLOADS": "", + "LARGER_THAN_AVAILABLE_STORAGE_INFO": "", + "TOO_LARGE_INFO": "", + "THUMBNAIL_GENERATION_FAILED_INFO": "", + "UPLOAD_TO_COLLECTION": "", + "UNCATEGORIZED": "", + "ARCHIVE": "", + "FAVORITES": "", + "ARCHIVE_COLLECTION": "", + "ARCHIVE_SECTION_NAME": "", + "ALL_SECTION_NAME": "", + "MOVE_TO_COLLECTION": "", + "UNARCHIVE": "", + "UNARCHIVE_COLLECTION": "", + "HIDE_COLLECTION": "", + "UNHIDE_COLLECTION": "", + "MOVE": "", + "ADD": "", + "REMOVE": "", + "YES_REMOVE": "", + "REMOVE_FROM_COLLECTION": "", + "TRASH": "", + "MOVE_TO_TRASH": "", + "TRASH_FILES_MESSAGE": "", + "TRASH_FILE_MESSAGE": "", + "DELETE_PERMANENTLY": "", + "RESTORE": "", + "RESTORE_TO_COLLECTION": "", + "EMPTY_TRASH": "", + "EMPTY_TRASH_TITLE": "", + "EMPTY_TRASH_MESSAGE": "", + "LEAVE_SHARED_ALBUM": "", + "LEAVE_ALBUM": "", + "LEAVE_SHARED_ALBUM_TITLE": "", + "LEAVE_SHARED_ALBUM_MESSAGE": "", + "NOT_FILE_OWNER": "", + "CONFIRM_SELF_REMOVE_MESSAGE": "", + "CONFIRM_SELF_AND_OTHER_REMOVE_MESSAGE": "", + "SORT_BY_CREATION_TIME_ASCENDING": "", + "SORT_BY_UPDATION_TIME_DESCENDING": "", + "SORT_BY_NAME": "", + "FIX_CREATION_TIME": "", + "FIX_CREATION_TIME_IN_PROGRESS": "", + "CREATION_TIME_UPDATED": "", + "UPDATE_CREATION_TIME_NOT_STARTED": "", + "UPDATE_CREATION_TIME_COMPLETED": "", + "UPDATE_CREATION_TIME_COMPLETED_WITH_ERROR": "", + "CAPTION_CHARACTER_LIMIT": "", + "DATE_TIME_ORIGINAL": "", + "DATE_TIME_DIGITIZED": "", + "METADATA_DATE": "", + "CUSTOM_TIME": "", + "REOPEN_PLAN_SELECTOR_MODAL": "", + "OPEN_PLAN_SELECTOR_MODAL_FAILED": "", + "INSTALL": "", + "SHARING_DETAILS": "", + "MODIFY_SHARING": "", + "ADD_COLLABORATORS": "", + "ADD_NEW_EMAIL": "", + "shared_with_people_zero": "", + "shared_with_people_one": "", + "shared_with_people_other": "", + "participants_zero": "", + "participants_one": "", + "participants_other": "", + "ADD_VIEWERS": "", + "CHANGE_PERMISSIONS_TO_VIEWER": "", + "CHANGE_PERMISSIONS_TO_COLLABORATOR": "", + "CONVERT_TO_VIEWER": "", + "CONVERT_TO_COLLABORATOR": "", + "CHANGE_PERMISSION": "", + "REMOVE_PARTICIPANT": "", + "CONFIRM_REMOVE": "", + "MANAGE": "", + "ADDED_AS": "", + "COLLABORATOR_RIGHTS": "", + "REMOVE_PARTICIPANT_HEAD": "", + "OWNER": "", + "COLLABORATORS": "", + "ADD_MORE": "", + "VIEWERS": "", + "OR_ADD_EXISTING": "", + "REMOVE_PARTICIPANT_MESSAGE": "", + "NOT_FOUND": "", + "LINK_EXPIRED": "", + "LINK_EXPIRED_MESSAGE": "", + "MANAGE_LINK": "", + "LINK_TOO_MANY_REQUESTS": "", + "FILE_DOWNLOAD": "", + "link_password_lock": "", + "PUBLIC_COLLECT": "", + "LINK_DEVICE_LIMIT": "", + "NO_DEVICE_LIMIT": "", + "LINK_EXPIRY": "", + "NEVER": "", + "DISABLE_FILE_DOWNLOAD": "", + "DISABLE_FILE_DOWNLOAD_MESSAGE": "", + "SHARED_USING": "", + "SHARING_REFERRAL_CODE": "", + "LIVE": "", + "DISABLE_PASSWORD": "", + "DISABLE_PASSWORD_MESSAGE": "", + "PASSWORD_LOCK": "", + "LOCK": "", + "DOWNLOAD_UPLOAD_LOGS": "", + "UPLOAD_FILES": "", + "UPLOAD_DIRS": "", + "UPLOAD_GOOGLE_TAKEOUT": "", + "DEDUPLICATE_FILES": "", + "NO_DUPLICATES_FOUND": "", + "FILES": "", + "EACH": "", + "DEDUPLICATE_BASED_ON_SIZE": "", + "STOP_ALL_UPLOADS_MESSAGE": "", + "STOP_UPLOADS_HEADER": "", + "YES_STOP_UPLOADS": "", + "STOP_DOWNLOADS_HEADER": "", + "YES_STOP_DOWNLOADS": "", + "STOP_ALL_DOWNLOADS_MESSAGE": "", + "albums_one": "", + "albums_other": "", + "ALL_ALBUMS": "", + "ALBUMS": "", + "ALL_HIDDEN_ALBUMS": "", + "HIDDEN_ALBUMS": "", + "HIDDEN_ITEMS": "", + "ENTER_TWO_FACTOR_OTP": "", + "CREATE_ACCOUNT": "", + "COPIED": "", + "WATCH_FOLDERS": "", + "UPGRADE_NOW": "", + "RENEW_NOW": "", + "STORAGE": "", + "USED": "", + "YOU": "", + "FAMILY": "", + "FREE": "", + "OF": "", + "WATCHED_FOLDERS": "", + "NO_FOLDERS_ADDED": "", + "FOLDERS_AUTOMATICALLY_MONITORED": "", + "UPLOAD_NEW_FILES_TO_ENTE": "", + "REMOVE_DELETED_FILES_FROM_ENTE": "", + "ADD_FOLDER": "", + "STOP_WATCHING": "", + "STOP_WATCHING_FOLDER": "", + "STOP_WATCHING_DIALOG_MESSAGE": "", + "YES_STOP": "", + "MONTH_SHORT": "", + "YEAR": "", + "FAMILY_PLAN": "", + "DOWNLOAD_LOGS": "", + "DOWNLOAD_LOGS_MESSAGE": "", + "CHANGE_FOLDER": "", + "TWO_MONTHS_FREE": "", + "POPULAR": "", + "FREE_PLAN_OPTION_LABEL": "", + "free_plan_description": "", + "CURRENT_USAGE": "", + "WEAK_DEVICE": "", + "DRAG_AND_DROP_HINT": "", + "AUTHENTICATE": "", + "UPLOADED_TO_SINGLE_COLLECTION": "", + "UPLOADED_TO_SEPARATE_COLLECTIONS": "", + "NEVERMIND": "", + "UPDATE_AVAILABLE": "", + "UPDATE_INSTALLABLE_MESSAGE": "", + "INSTALL_NOW": "", + "INSTALL_ON_NEXT_LAUNCH": "", + "UPDATE_AVAILABLE_MESSAGE": "", + "DOWNLOAD_AND_INSTALL": "", + "IGNORE_THIS_VERSION": "", + "TODAY": "", + "YESTERDAY": "", + "NAME_PLACEHOLDER": "", + "ROOT_LEVEL_FILE_WITH_FOLDER_NOT_ALLOWED": "", + "ROOT_LEVEL_FILE_WITH_FOLDER_NOT_ALLOWED_MESSAGE": "", + "CHOSE_THEME": "", + "ML_SEARCH": "", + "ENABLE_ML_SEARCH_DESCRIPTION": "", + "ML_MORE_DETAILS": "", + "ENABLE_FACE_SEARCH": "", + "ENABLE_FACE_SEARCH_TITLE": "", + "ENABLE_FACE_SEARCH_DESCRIPTION": "", + "DISABLE_BETA": "", + "DISABLE_FACE_SEARCH": "", + "DISABLE_FACE_SEARCH_TITLE": "", + "DISABLE_FACE_SEARCH_DESCRIPTION": "", + "ADVANCED": "", + "FACE_SEARCH_CONFIRMATION": "", + "LABS": "", + "YOURS": "", + "PASSPHRASE_STRENGTH_WEAK": "", + "PASSPHRASE_STRENGTH_MODERATE": "", + "PASSPHRASE_STRENGTH_STRONG": "", + "PREFERENCES": "", + "LANGUAGE": "", + "EXPORT_DIRECTORY_DOES_NOT_EXIST": "", + "EXPORT_DIRECTORY_DOES_NOT_EXIST_MESSAGE": "", + "SUBSCRIPTION_VERIFICATION_ERROR": "", + "storage_unit": { + "b": "", + "kb": "", + "mb": "", + "gb": "", + "tb": "" + }, + "AFTER_TIME": { + "HOUR": "", + "DAY": "", + "WEEK": "", + "MONTH": "", + "YEAR": "" + }, + "COPY_LINK": "", + "DONE": "", + "LINK_SHARE_TITLE": "", + "REMOVE_LINK": "", + "CREATE_PUBLIC_SHARING": "", + "PUBLIC_LINK_CREATED": "", + "PUBLIC_LINK_ENABLED": "", + "COLLECT_PHOTOS": "", + "PUBLIC_COLLECT_SUBTEXT": "", + "STOP_EXPORT": "", + "EXPORT_PROGRESS": "", + "MIGRATING_EXPORT": "", + "RENAMING_COLLECTION_FOLDERS": "", + "TRASHING_DELETED_FILES": "", + "TRASHING_DELETED_COLLECTIONS": "", + "CONTINUOUS_EXPORT": "", + "PENDING_ITEMS": "", + "EXPORT_STARTING": "", + "delete_account_reason_label": "", + "delete_account_reason_placeholder": "", + "delete_reason": { + "missing_feature": "", + "behaviour": "", + "found_another_service": "", + "not_listed": "" + }, + "delete_account_feedback_label": "", + "delete_account_feedback_placeholder": "", + "delete_account_confirm_checkbox_label": "", + "delete_account_confirm": "", + "delete_account_confirm_message": "", + "feedback_required": "", + "feedback_required_found_another_service": "", + "RECOVER_TWO_FACTOR": "", + "at": "", + "AUTH_NEXT": "", + "AUTH_DOWNLOAD_MOBILE_APP": "", + "HIDDEN": "", + "HIDE": "", + "UNHIDE": "", + "UNHIDE_TO_COLLECTION": "", + "SORT_BY": "", + "NEWEST_FIRST": "", + "OLDEST_FIRST": "", + "CONVERSION_FAILED_NOTIFICATION_MESSAGE": "", + "SELECT_COLLECTION": "", + "PIN_ALBUM": "", + "UNPIN_ALBUM": "", + "DOWNLOAD_COMPLETE": "", + "DOWNLOADING_COLLECTION": "", + "DOWNLOAD_FAILED": "", + "DOWNLOAD_PROGRESS": "", + "CHRISTMAS": "", + "CHRISTMAS_EVE": "", + "NEW_YEAR": "", + "NEW_YEAR_EVE": "", + "IMAGE": "", + "VIDEO": "", + "LIVE_PHOTO": "", + "editor": { + "crop": "" + }, + "CONVERT": "", + "CONFIRM_EDITOR_CLOSE_MESSAGE": "", + "CONFIRM_EDITOR_CLOSE_DESCRIPTION": "", + "BRIGHTNESS": "", + "CONTRAST": "", + "SATURATION": "", + "BLUR": "", + "INVERT_COLORS": "", + "ASPECT_RATIO": "", + "SQUARE": "", + "ROTATE_LEFT": "", + "ROTATE_RIGHT": "", + "FLIP_VERTICALLY": "", + "FLIP_HORIZONTALLY": "", + "DOWNLOAD_EDITED": "", + "SAVE_A_COPY_TO_ENTE": "", + "RESTORE_ORIGINAL": "", + "TRANSFORM": "", + "COLORS": "", + "FLIP": "", + "ROTATION": "", + "RESET": "", + "PHOTO_EDITOR": "", + "FASTER_UPLOAD": "", + "FASTER_UPLOAD_DESCRIPTION": "", + "MAGIC_SEARCH_STATUS": "", + "INDEXED_ITEMS": "", + "CAST_ALBUM_TO_TV": "", + "ENTER_CAST_PIN_CODE": "", + "PAIR_DEVICE_TO_TV": "", + "TV_NOT_FOUND": "", + "AUTO_CAST_PAIR": "", + "AUTO_CAST_PAIR_DESC": "", + "PAIR_WITH_PIN": "", + "CHOOSE_DEVICE_FROM_BROWSER": "", + "PAIR_WITH_PIN_DESC": "", + "VISIT_CAST_ENTE_IO": "", + "CAST_AUTO_PAIR_FAILED": "", + "FREEHAND": "", + "APPLY_CROP": "", + "PHOTO_EDIT_REQUIRED_TO_SAVE": "", + "passkeys": "", + "passkey_fetch_failed": "", + "manage_passkey": "", + "delete_passkey": "", + "delete_passkey_confirmation": "", + "rename_passkey": "", + "add_passkey": "", + "enter_passkey_name": "", + "passkeys_description": "", + "CREATED_AT": "", + "passkey_add_failed": "", + "passkey_login_failed": "", + "passkey_login_invalid_url": "", + "passkey_login_already_claimed_session": "", + "passkey_login_generic_error": "", + "passkey_login_credential_hint": "", + "passkeys_not_supported": "", + "try_again": "", + "check_status": "", + "passkey_login_instructions": "", + "passkey_login": "", + "passkey": "", + "passkey_verify_description": "", + "waiting_for_verification": "", + "verification_still_pending": "", + "passkey_verified": "", + "redirecting_back_to_app": "", + "redirect_close_instructions": "", + "redirect_again": "", + "autogenerated_first_album_name": "", + "autogenerated_default_album_name": "", + "developer_settings": "", + "server_endpoint": "", + "more_information": "", + "save": "" +} diff --git a/web/packages/next/locales/id-ID/translation.json b/web/packages/next/locales/id-ID/translation.json index 81644e4b49..31e2a5c34b 100644 --- a/web/packages/next/locales/id-ID/translation.json +++ b/web/packages/next/locales/id-ID/translation.json @@ -2,7 +2,7 @@ "HERO_SLIDE_1_TITLE": "
    Cadangan pribadi
    untuk kenanganmu
    ", "HERO_SLIDE_1": "Dirancang dengan enkripsi ujung ke ujung", "HERO_SLIDE_2_TITLE": "
    Tersimpan aman
    di tempat pengungsian
    ", - "HERO_SLIDE_2": "Dirancang untuk waktu lebih lama", + "HERO_SLIDE_2": "Dibuat untuk melestarikan", "HERO_SLIDE_3_TITLE": "
    Tersedia
    di mana saja
    ", "HERO_SLIDE_3": "Android, iOS, Web, Desktop", "LOGIN": "Masuk", @@ -83,7 +83,7 @@ "ZOOM_IN_OUT": "Perbesar/perkecil", "PREVIOUS": "Sebelumnya (←)", "NEXT": "Berikutnya (→)", - "title_photos": "Ente Photos", + "title_photos": "Ente Foto", "title_auth": "Ente Auth", "title_accounts": "Akun Ente", "UPLOAD_FIRST_PHOTO": "Unggah foto pertama Anda", @@ -639,5 +639,9 @@ "redirect_close_instructions": "Kamu dapat menutup jendela ini setelah app terbuka.", "redirect_again": "Arahkan ulang", "autogenerated_first_album_name": "Album Pertamaku", - "autogenerated_default_album_name": "Album Baru" + "autogenerated_default_album_name": "Album Baru", + "developer_settings": "", + "server_endpoint": "", + "more_information": "", + "save": "" } diff --git a/web/packages/next/locales/is-IS/translation.json b/web/packages/next/locales/is-IS/translation.json index 8fb66d78da..edd188c1ab 100644 --- a/web/packages/next/locales/is-IS/translation.json +++ b/web/packages/next/locales/is-IS/translation.json @@ -639,5 +639,9 @@ "redirect_close_instructions": "", "redirect_again": "", "autogenerated_first_album_name": "", - "autogenerated_default_album_name": "" + "autogenerated_default_album_name": "", + "developer_settings": "", + "server_endpoint": "", + "more_information": "", + "save": "" } diff --git a/web/packages/next/locales/it-IT/translation.json b/web/packages/next/locales/it-IT/translation.json index e54860c7a7..a8c4761772 100644 --- a/web/packages/next/locales/it-IT/translation.json +++ b/web/packages/next/locales/it-IT/translation.json @@ -639,5 +639,9 @@ "redirect_close_instructions": "", "redirect_again": "", "autogenerated_first_album_name": "", - "autogenerated_default_album_name": "" + "autogenerated_default_album_name": "", + "developer_settings": "", + "server_endpoint": "", + "more_information": "", + "save": "" } diff --git a/web/packages/next/locales/ko-KR/translation.json b/web/packages/next/locales/ko-KR/translation.json index 652b58c9ae..e21508e42e 100644 --- a/web/packages/next/locales/ko-KR/translation.json +++ b/web/packages/next/locales/ko-KR/translation.json @@ -639,5 +639,9 @@ "redirect_close_instructions": "", "redirect_again": "", "autogenerated_first_album_name": "", - "autogenerated_default_album_name": "" + "autogenerated_default_album_name": "", + "developer_settings": "", + "server_endpoint": "", + "more_information": "", + "save": "" } diff --git a/web/packages/next/locales/nl-NL/translation.json b/web/packages/next/locales/nl-NL/translation.json index 5a60f309c0..d07b556994 100644 --- a/web/packages/next/locales/nl-NL/translation.json +++ b/web/packages/next/locales/nl-NL/translation.json @@ -639,5 +639,9 @@ "redirect_close_instructions": "Je kunt dit venster sluiten na het openen van de app.", "redirect_again": "Opnieuw doorverwijzen", "autogenerated_first_album_name": "Mijn eerste album", - "autogenerated_default_album_name": "Nieuw album" + "autogenerated_default_album_name": "Nieuw album", + "developer_settings": "", + "server_endpoint": "", + "more_information": "", + "save": "" } diff --git a/web/packages/next/locales/pt-BR/translation.json b/web/packages/next/locales/pt-BR/translation.json index ea48fd57c3..ad7189208b 100644 --- a/web/packages/next/locales/pt-BR/translation.json +++ b/web/packages/next/locales/pt-BR/translation.json @@ -536,7 +536,7 @@ "delete_account_feedback_placeholder": "Comentários", "delete_account_confirm_checkbox_label": "Sim, desejo excluir permanentemente esta conta e todos os seus dados", "delete_account_confirm": "Confirmar exclusão da conta", - "delete_account_confirm_message": "", + "delete_account_confirm_message": "

    Essa conta está vinculada a outros aplicativos Ente, se você usa algum.

    Seus dados enviados, em todos os aplicativos Ente, serão agendados para exclusão e sua conta será excluída permanentemente.

    ", "feedback_required": "Por favor, ajude-nos com esta informação", "feedback_required_found_another_service": "O que o outro serviço faz melhor?", "RECOVER_TWO_FACTOR": "Recuperar dois fatores", @@ -639,5 +639,9 @@ "redirect_close_instructions": "Você pode fechar esta janela após a aplicação ser aberta.", "redirect_again": "Redirecionar novamente", "autogenerated_first_album_name": "Meu Primeiro Álbum", - "autogenerated_default_album_name": "Novo Álbum" + "autogenerated_default_album_name": "Novo Álbum", + "developer_settings": "", + "server_endpoint": "", + "more_information": "", + "save": "" } diff --git a/web/packages/next/locales/pt-PT/translation.json b/web/packages/next/locales/pt-PT/translation.json index d24d54dab8..907d9da5e2 100644 --- a/web/packages/next/locales/pt-PT/translation.json +++ b/web/packages/next/locales/pt-PT/translation.json @@ -639,5 +639,9 @@ "redirect_close_instructions": "", "redirect_again": "", "autogenerated_first_album_name": "", - "autogenerated_default_album_name": "" + "autogenerated_default_album_name": "", + "developer_settings": "", + "server_endpoint": "", + "more_information": "", + "save": "" } diff --git a/web/packages/next/locales/ru-RU/translation.json b/web/packages/next/locales/ru-RU/translation.json index a56c7c879f..ee556f47d9 100644 --- a/web/packages/next/locales/ru-RU/translation.json +++ b/web/packages/next/locales/ru-RU/translation.json @@ -639,5 +639,9 @@ "redirect_close_instructions": "Вы можете закрыть это окно после открытия приложения.", "redirect_again": "Перенаправить снова", "autogenerated_first_album_name": "Мой первый альбом", - "autogenerated_default_album_name": "Новый альбом" + "autogenerated_default_album_name": "Новый альбом", + "developer_settings": "", + "server_endpoint": "", + "more_information": "", + "save": "" } diff --git a/web/packages/next/locales/sv-SE/translation.json b/web/packages/next/locales/sv-SE/translation.json index 871ac0fe3f..d66ec7cff4 100644 --- a/web/packages/next/locales/sv-SE/translation.json +++ b/web/packages/next/locales/sv-SE/translation.json @@ -639,5 +639,9 @@ "redirect_close_instructions": "", "redirect_again": "", "autogenerated_first_album_name": "", - "autogenerated_default_album_name": "" + "autogenerated_default_album_name": "", + "developer_settings": "", + "server_endpoint": "", + "more_information": "", + "save": "" } diff --git a/web/packages/next/locales/te-IN/translation.json b/web/packages/next/locales/te-IN/translation.json index 0e2bc528c4..a644dd4a17 100644 --- a/web/packages/next/locales/te-IN/translation.json +++ b/web/packages/next/locales/te-IN/translation.json @@ -639,5 +639,9 @@ "redirect_close_instructions": "", "redirect_again": "", "autogenerated_first_album_name": "", - "autogenerated_default_album_name": "" + "autogenerated_default_album_name": "", + "developer_settings": "", + "server_endpoint": "", + "more_information": "", + "save": "" } diff --git a/web/packages/next/locales/th-TH/translation.json b/web/packages/next/locales/th-TH/translation.json index 0e2bc528c4..a644dd4a17 100644 --- a/web/packages/next/locales/th-TH/translation.json +++ b/web/packages/next/locales/th-TH/translation.json @@ -639,5 +639,9 @@ "redirect_close_instructions": "", "redirect_again": "", "autogenerated_first_album_name": "", - "autogenerated_default_album_name": "" + "autogenerated_default_album_name": "", + "developer_settings": "", + "server_endpoint": "", + "more_information": "", + "save": "" } diff --git a/web/packages/next/locales/ti-ER/translation.json b/web/packages/next/locales/ti-ER/translation.json new file mode 100644 index 0000000000..a644dd4a17 --- /dev/null +++ b/web/packages/next/locales/ti-ER/translation.json @@ -0,0 +1,647 @@ +{ + "HERO_SLIDE_1_TITLE": "", + "HERO_SLIDE_1": "", + "HERO_SLIDE_2_TITLE": "", + "HERO_SLIDE_2": "", + "HERO_SLIDE_3_TITLE": "", + "HERO_SLIDE_3": "", + "LOGIN": "", + "SIGN_UP": "", + "NEW_USER": "", + "EXISTING_USER": "", + "ENTER_NAME": "", + "PUBLIC_UPLOADER_NAME_MESSAGE": "", + "ENTER_EMAIL": "", + "EMAIL_ERROR": "", + "REQUIRED": "", + "EMAIL_SENT": "", + "CHECK_INBOX": "", + "ENTER_OTT": "", + "RESEND_MAIL": "", + "VERIFY": "", + "UNKNOWN_ERROR": "", + "INVALID_CODE": "", + "EXPIRED_CODE": "", + "SENDING": "", + "SENT": "", + "password": "", + "link_password_description": "", + "unlock": "", + "SET_PASSPHRASE": "", + "VERIFY_PASSPHRASE": "", + "INCORRECT_PASSPHRASE": "", + "ENTER_ENC_PASSPHRASE": "", + "PASSPHRASE_DISCLAIMER": "", + "WELCOME_TO_ENTE_HEADING": "", + "WELCOME_TO_ENTE_SUBHEADING": "", + "WHERE_YOUR_BEST_PHOTOS_LIVE": "", + "KEY_GENERATION_IN_PROGRESS_MESSAGE": "", + "PASSPHRASE_HINT": "", + "CONFIRM_PASSPHRASE": "", + "REFERRAL_CODE_HINT": "", + "REFERRAL_INFO": "", + "PASSPHRASE_MATCH_ERROR": "", + "CREATE_COLLECTION": "", + "ENTER_ALBUM_NAME": "", + "CLOSE_OPTION": "", + "ENTER_FILE_NAME": "", + "CLOSE": "", + "NO": "", + "NOTHING_HERE": "", + "UPLOAD": "", + "IMPORT": "", + "ADD_PHOTOS": "", + "ADD_MORE_PHOTOS": "", + "add_photos_one": "", + "add_photos_other": "", + "SELECT_PHOTOS": "", + "FILE_UPLOAD": "", + "UPLOAD_STAGE_MESSAGE": { + "0": "", + "1": "", + "2": "", + "3": "", + "4": "", + "5": "" + }, + "FILE_NOT_UPLOADED_LIST": "", + "SUBSCRIPTION_EXPIRED": "", + "SUBSCRIPTION_EXPIRED_MESSAGE": "", + "STORAGE_QUOTA_EXCEEDED": "", + "INITIAL_LOAD_DELAY_WARNING": "", + "USER_DOES_NOT_EXIST": "", + "NO_ACCOUNT": "", + "ACCOUNT_EXISTS": "", + "CREATE": "", + "DOWNLOAD": "", + "DOWNLOAD_OPTION": "", + "DOWNLOAD_FAVORITES": "", + "DOWNLOAD_UNCATEGORIZED": "", + "DOWNLOAD_HIDDEN_ITEMS": "", + "COPY_OPTION": "", + "TOGGLE_FULLSCREEN": "", + "ZOOM_IN_OUT": "", + "PREVIOUS": "", + "NEXT": "", + "title_photos": "", + "title_auth": "", + "title_accounts": "", + "UPLOAD_FIRST_PHOTO": "", + "IMPORT_YOUR_FOLDERS": "", + "UPLOAD_DROPZONE_MESSAGE": "", + "WATCH_FOLDER_DROPZONE_MESSAGE": "", + "TRASH_FILES_TITLE": "", + "TRASH_FILE_TITLE": "", + "DELETE_FILES_TITLE": "", + "DELETE_FILES_MESSAGE": "", + "DELETE": "", + "DELETE_OPTION": "", + "FAVORITE_OPTION": "", + "UNFAVORITE_OPTION": "", + "MULTI_FOLDER_UPLOAD": "", + "UPLOAD_STRATEGY_CHOICE": "", + "UPLOAD_STRATEGY_SINGLE_COLLECTION": "", + "OR": "", + "UPLOAD_STRATEGY_COLLECTION_PER_FOLDER": "", + "SESSION_EXPIRED_MESSAGE": "", + "SESSION_EXPIRED": "", + "PASSWORD_GENERATION_FAILED": "", + "CHANGE_PASSWORD": "", + "GO_BACK": "", + "RECOVERY_KEY": "", + "SAVE_LATER": "", + "SAVE": "", + "RECOVERY_KEY_DESCRIPTION": "", + "RECOVER_KEY_GENERATION_FAILED": "", + "KEY_NOT_STORED_DISCLAIMER": "", + "FORGOT_PASSWORD": "", + "RECOVER_ACCOUNT": "", + "RECOVERY_KEY_HINT": "", + "RECOVER": "", + "NO_RECOVERY_KEY": "", + "INCORRECT_RECOVERY_KEY": "", + "SORRY": "", + "NO_RECOVERY_KEY_MESSAGE": "", + "NO_TWO_FACTOR_RECOVERY_KEY_MESSAGE": "", + "CONTACT_SUPPORT": "", + "REQUEST_FEATURE": "", + "SUPPORT": "", + "CONFIRM": "", + "CANCEL": "", + "LOGOUT": "", + "delete_account": "", + "delete_account_manually_message": "", + "LOGOUT_MESSAGE": "", + "CHANGE_EMAIL": "", + "OK": "", + "SUCCESS": "", + "ERROR": "", + "MESSAGE": "", + "INSTALL_MOBILE_APP": "", + "DOWNLOAD_APP_MESSAGE": "", + "DOWNLOAD_APP": "", + "EXPORT": "", + "SUBSCRIPTION": "", + "SUBSCRIBE": "", + "MANAGEMENT_PORTAL": "", + "MANAGE_FAMILY_PORTAL": "", + "LEAVE_FAMILY_PLAN": "", + "LEAVE": "", + "LEAVE_FAMILY_CONFIRM": "", + "CHOOSE_PLAN": "", + "MANAGE_PLAN": "", + "ACTIVE": "", + "OFFLINE_MSG": "", + "FREE_SUBSCRIPTION_INFO": "", + "FAMILY_SUBSCRIPTION_INFO": "", + "RENEWAL_ACTIVE_SUBSCRIPTION_STATUS": "", + "RENEWAL_CANCELLED_SUBSCRIPTION_STATUS": "", + "RENEWAL_CANCELLED_SUBSCRIPTION_INFO": "", + "ADD_ON_AVAILABLE_TILL": "", + "STORAGE_QUOTA_EXCEEDED_SUBSCRIPTION_INFO": "", + "SUBSCRIPTION_PURCHASE_SUCCESS": "", + "SUBSCRIPTION_PURCHASE_CANCELLED": "", + "SUBSCRIPTION_PURCHASE_FAILED": "", + "SUBSCRIPTION_UPDATE_FAILED": "", + "UPDATE_PAYMENT_METHOD_MESSAGE": "", + "STRIPE_AUTHENTICATION_FAILED": "", + "UPDATE_PAYMENT_METHOD": "", + "MONTHLY": "", + "YEARLY": "", + "update_subscription_title": "", + "UPDATE_SUBSCRIPTION_MESSAGE": "", + "UPDATE_SUBSCRIPTION": "", + "CANCEL_SUBSCRIPTION": "", + "CANCEL_SUBSCRIPTION_MESSAGE": "", + "CANCEL_SUBSCRIPTION_WITH_ADDON_MESSAGE": "", + "SUBSCRIPTION_CANCEL_FAILED": "", + "SUBSCRIPTION_CANCEL_SUCCESS": "", + "REACTIVATE_SUBSCRIPTION": "", + "REACTIVATE_SUBSCRIPTION_MESSAGE": "", + "SUBSCRIPTION_ACTIVATE_SUCCESS": "", + "SUBSCRIPTION_ACTIVATE_FAILED": "", + "SUBSCRIPTION_PURCHASE_SUCCESS_TITLE": "", + "CANCEL_SUBSCRIPTION_ON_MOBILE": "", + "CANCEL_SUBSCRIPTION_ON_MOBILE_MESSAGE": "", + "MAIL_TO_MANAGE_SUBSCRIPTION": "", + "RENAME": "", + "RENAME_FILE": "", + "RENAME_COLLECTION": "", + "DELETE_COLLECTION_TITLE": "", + "DELETE_COLLECTION": "", + "DELETE_COLLECTION_MESSAGE": "", + "DELETE_PHOTOS": "", + "KEEP_PHOTOS": "", + "SHARE_COLLECTION": "", + "SHARE_WITH_SELF": "", + "ALREADY_SHARED": "", + "SHARING_BAD_REQUEST_ERROR": "", + "SHARING_DISABLED_FOR_FREE_ACCOUNTS": "", + "DOWNLOAD_COLLECTION": "", + "CREATE_ALBUM_FAILED": "", + "SEARCH": "", + "SEARCH_RESULTS": "", + "NO_RESULTS": "", + "SEARCH_HINT": "", + "SEARCH_TYPE": { + "COLLECTION": "", + "LOCATION": "", + "CITY": "", + "DATE": "", + "FILE_NAME": "", + "THING": "", + "FILE_CAPTION": "", + "FILE_TYPE": "", + "CLIP": "" + }, + "photos_count_zero": "", + "photos_count_one": "", + "photos_count_other": "", + "TERMS_AND_CONDITIONS": "", + "ADD_TO_COLLECTION": "", + "SELECTED": "", + "PEOPLE": "", + "INDEXING_SCHEDULED": "", + "ANALYZING_PHOTOS": "", + "INDEXING_PEOPLE": "", + "INDEXING_DONE": "", + "UNIDENTIFIED_FACES": "", + "OBJECTS": "", + "TEXT": "", + "INFO": "", + "INFO_OPTION": "", + "FILE_NAME": "", + "CAPTION_PLACEHOLDER": "", + "LOCATION": "", + "SHOW_ON_MAP": "", + "MAP": "", + "MAP_SETTINGS": "", + "ENABLE_MAPS": "", + "ENABLE_MAP": "", + "DISABLE_MAPS": "", + "ENABLE_MAP_DESCRIPTION": "", + "DISABLE_MAP_DESCRIPTION": "", + "DISABLE_MAP": "", + "DETAILS": "", + "VIEW_EXIF": "", + "NO_EXIF": "", + "EXIF": "", + "ISO": "", + "TWO_FACTOR": "", + "TWO_FACTOR_AUTHENTICATION": "", + "TWO_FACTOR_QR_INSTRUCTION": "", + "ENTER_CODE_MANUALLY": "", + "TWO_FACTOR_MANUAL_CODE_INSTRUCTION": "", + "SCAN_QR_CODE": "", + "ENABLE_TWO_FACTOR": "", + "ENABLE": "", + "LOST_DEVICE": "", + "INCORRECT_CODE": "", + "TWO_FACTOR_INFO": "", + "DISABLE_TWO_FACTOR_LABEL": "", + "UPDATE_TWO_FACTOR_LABEL": "", + "DISABLE": "", + "RECONFIGURE": "", + "UPDATE_TWO_FACTOR": "", + "UPDATE_TWO_FACTOR_MESSAGE": "", + "UPDATE": "", + "DISABLE_TWO_FACTOR": "", + "DISABLE_TWO_FACTOR_MESSAGE": "", + "TWO_FACTOR_DISABLE_FAILED": "", + "EXPORT_DATA": "", + "SELECT_FOLDER": "", + "DESTINATION": "", + "START": "", + "LAST_EXPORT_TIME": "", + "EXPORT_AGAIN": "", + "LOCAL_STORAGE_NOT_ACCESSIBLE": "", + "LOCAL_STORAGE_NOT_ACCESSIBLE_MESSAGE": "", + "SEND_OTT": "", + "EMAIl_ALREADY_OWNED": "", + "ETAGS_BLOCKED": "", + "LIVE_PHOTOS_DETECTED": "", + "RETRY_FAILED": "", + "FAILED_UPLOADS": "", + "SKIPPED_FILES": "", + "THUMBNAIL_GENERATION_FAILED_UPLOADS": "", + "UNSUPPORTED_FILES": "", + "SUCCESSFUL_UPLOADS": "", + "SKIPPED_INFO": "", + "UNSUPPORTED_INFO": "", + "BLOCKED_UPLOADS": "", + "INPROGRESS_METADATA_EXTRACTION": "", + "INPROGRESS_UPLOADS": "", + "TOO_LARGE_UPLOADS": "", + "LARGER_THAN_AVAILABLE_STORAGE_UPLOADS": "", + "LARGER_THAN_AVAILABLE_STORAGE_INFO": "", + "TOO_LARGE_INFO": "", + "THUMBNAIL_GENERATION_FAILED_INFO": "", + "UPLOAD_TO_COLLECTION": "", + "UNCATEGORIZED": "", + "ARCHIVE": "", + "FAVORITES": "", + "ARCHIVE_COLLECTION": "", + "ARCHIVE_SECTION_NAME": "", + "ALL_SECTION_NAME": "", + "MOVE_TO_COLLECTION": "", + "UNARCHIVE": "", + "UNARCHIVE_COLLECTION": "", + "HIDE_COLLECTION": "", + "UNHIDE_COLLECTION": "", + "MOVE": "", + "ADD": "", + "REMOVE": "", + "YES_REMOVE": "", + "REMOVE_FROM_COLLECTION": "", + "TRASH": "", + "MOVE_TO_TRASH": "", + "TRASH_FILES_MESSAGE": "", + "TRASH_FILE_MESSAGE": "", + "DELETE_PERMANENTLY": "", + "RESTORE": "", + "RESTORE_TO_COLLECTION": "", + "EMPTY_TRASH": "", + "EMPTY_TRASH_TITLE": "", + "EMPTY_TRASH_MESSAGE": "", + "LEAVE_SHARED_ALBUM": "", + "LEAVE_ALBUM": "", + "LEAVE_SHARED_ALBUM_TITLE": "", + "LEAVE_SHARED_ALBUM_MESSAGE": "", + "NOT_FILE_OWNER": "", + "CONFIRM_SELF_REMOVE_MESSAGE": "", + "CONFIRM_SELF_AND_OTHER_REMOVE_MESSAGE": "", + "SORT_BY_CREATION_TIME_ASCENDING": "", + "SORT_BY_UPDATION_TIME_DESCENDING": "", + "SORT_BY_NAME": "", + "FIX_CREATION_TIME": "", + "FIX_CREATION_TIME_IN_PROGRESS": "", + "CREATION_TIME_UPDATED": "", + "UPDATE_CREATION_TIME_NOT_STARTED": "", + "UPDATE_CREATION_TIME_COMPLETED": "", + "UPDATE_CREATION_TIME_COMPLETED_WITH_ERROR": "", + "CAPTION_CHARACTER_LIMIT": "", + "DATE_TIME_ORIGINAL": "", + "DATE_TIME_DIGITIZED": "", + "METADATA_DATE": "", + "CUSTOM_TIME": "", + "REOPEN_PLAN_SELECTOR_MODAL": "", + "OPEN_PLAN_SELECTOR_MODAL_FAILED": "", + "INSTALL": "", + "SHARING_DETAILS": "", + "MODIFY_SHARING": "", + "ADD_COLLABORATORS": "", + "ADD_NEW_EMAIL": "", + "shared_with_people_zero": "", + "shared_with_people_one": "", + "shared_with_people_other": "", + "participants_zero": "", + "participants_one": "", + "participants_other": "", + "ADD_VIEWERS": "", + "CHANGE_PERMISSIONS_TO_VIEWER": "", + "CHANGE_PERMISSIONS_TO_COLLABORATOR": "", + "CONVERT_TO_VIEWER": "", + "CONVERT_TO_COLLABORATOR": "", + "CHANGE_PERMISSION": "", + "REMOVE_PARTICIPANT": "", + "CONFIRM_REMOVE": "", + "MANAGE": "", + "ADDED_AS": "", + "COLLABORATOR_RIGHTS": "", + "REMOVE_PARTICIPANT_HEAD": "", + "OWNER": "", + "COLLABORATORS": "", + "ADD_MORE": "", + "VIEWERS": "", + "OR_ADD_EXISTING": "", + "REMOVE_PARTICIPANT_MESSAGE": "", + "NOT_FOUND": "", + "LINK_EXPIRED": "", + "LINK_EXPIRED_MESSAGE": "", + "MANAGE_LINK": "", + "LINK_TOO_MANY_REQUESTS": "", + "FILE_DOWNLOAD": "", + "link_password_lock": "", + "PUBLIC_COLLECT": "", + "LINK_DEVICE_LIMIT": "", + "NO_DEVICE_LIMIT": "", + "LINK_EXPIRY": "", + "NEVER": "", + "DISABLE_FILE_DOWNLOAD": "", + "DISABLE_FILE_DOWNLOAD_MESSAGE": "", + "SHARED_USING": "", + "SHARING_REFERRAL_CODE": "", + "LIVE": "", + "DISABLE_PASSWORD": "", + "DISABLE_PASSWORD_MESSAGE": "", + "PASSWORD_LOCK": "", + "LOCK": "", + "DOWNLOAD_UPLOAD_LOGS": "", + "UPLOAD_FILES": "", + "UPLOAD_DIRS": "", + "UPLOAD_GOOGLE_TAKEOUT": "", + "DEDUPLICATE_FILES": "", + "NO_DUPLICATES_FOUND": "", + "FILES": "", + "EACH": "", + "DEDUPLICATE_BASED_ON_SIZE": "", + "STOP_ALL_UPLOADS_MESSAGE": "", + "STOP_UPLOADS_HEADER": "", + "YES_STOP_UPLOADS": "", + "STOP_DOWNLOADS_HEADER": "", + "YES_STOP_DOWNLOADS": "", + "STOP_ALL_DOWNLOADS_MESSAGE": "", + "albums_one": "", + "albums_other": "", + "ALL_ALBUMS": "", + "ALBUMS": "", + "ALL_HIDDEN_ALBUMS": "", + "HIDDEN_ALBUMS": "", + "HIDDEN_ITEMS": "", + "ENTER_TWO_FACTOR_OTP": "", + "CREATE_ACCOUNT": "", + "COPIED": "", + "WATCH_FOLDERS": "", + "UPGRADE_NOW": "", + "RENEW_NOW": "", + "STORAGE": "", + "USED": "", + "YOU": "", + "FAMILY": "", + "FREE": "", + "OF": "", + "WATCHED_FOLDERS": "", + "NO_FOLDERS_ADDED": "", + "FOLDERS_AUTOMATICALLY_MONITORED": "", + "UPLOAD_NEW_FILES_TO_ENTE": "", + "REMOVE_DELETED_FILES_FROM_ENTE": "", + "ADD_FOLDER": "", + "STOP_WATCHING": "", + "STOP_WATCHING_FOLDER": "", + "STOP_WATCHING_DIALOG_MESSAGE": "", + "YES_STOP": "", + "MONTH_SHORT": "", + "YEAR": "", + "FAMILY_PLAN": "", + "DOWNLOAD_LOGS": "", + "DOWNLOAD_LOGS_MESSAGE": "", + "CHANGE_FOLDER": "", + "TWO_MONTHS_FREE": "", + "POPULAR": "", + "FREE_PLAN_OPTION_LABEL": "", + "free_plan_description": "", + "CURRENT_USAGE": "", + "WEAK_DEVICE": "", + "DRAG_AND_DROP_HINT": "", + "AUTHENTICATE": "", + "UPLOADED_TO_SINGLE_COLLECTION": "", + "UPLOADED_TO_SEPARATE_COLLECTIONS": "", + "NEVERMIND": "", + "UPDATE_AVAILABLE": "", + "UPDATE_INSTALLABLE_MESSAGE": "", + "INSTALL_NOW": "", + "INSTALL_ON_NEXT_LAUNCH": "", + "UPDATE_AVAILABLE_MESSAGE": "", + "DOWNLOAD_AND_INSTALL": "", + "IGNORE_THIS_VERSION": "", + "TODAY": "", + "YESTERDAY": "", + "NAME_PLACEHOLDER": "", + "ROOT_LEVEL_FILE_WITH_FOLDER_NOT_ALLOWED": "", + "ROOT_LEVEL_FILE_WITH_FOLDER_NOT_ALLOWED_MESSAGE": "", + "CHOSE_THEME": "", + "ML_SEARCH": "", + "ENABLE_ML_SEARCH_DESCRIPTION": "", + "ML_MORE_DETAILS": "", + "ENABLE_FACE_SEARCH": "", + "ENABLE_FACE_SEARCH_TITLE": "", + "ENABLE_FACE_SEARCH_DESCRIPTION": "", + "DISABLE_BETA": "", + "DISABLE_FACE_SEARCH": "", + "DISABLE_FACE_SEARCH_TITLE": "", + "DISABLE_FACE_SEARCH_DESCRIPTION": "", + "ADVANCED": "", + "FACE_SEARCH_CONFIRMATION": "", + "LABS": "", + "YOURS": "", + "PASSPHRASE_STRENGTH_WEAK": "", + "PASSPHRASE_STRENGTH_MODERATE": "", + "PASSPHRASE_STRENGTH_STRONG": "", + "PREFERENCES": "", + "LANGUAGE": "", + "EXPORT_DIRECTORY_DOES_NOT_EXIST": "", + "EXPORT_DIRECTORY_DOES_NOT_EXIST_MESSAGE": "", + "SUBSCRIPTION_VERIFICATION_ERROR": "", + "storage_unit": { + "b": "", + "kb": "", + "mb": "", + "gb": "", + "tb": "" + }, + "AFTER_TIME": { + "HOUR": "", + "DAY": "", + "WEEK": "", + "MONTH": "", + "YEAR": "" + }, + "COPY_LINK": "", + "DONE": "", + "LINK_SHARE_TITLE": "", + "REMOVE_LINK": "", + "CREATE_PUBLIC_SHARING": "", + "PUBLIC_LINK_CREATED": "", + "PUBLIC_LINK_ENABLED": "", + "COLLECT_PHOTOS": "", + "PUBLIC_COLLECT_SUBTEXT": "", + "STOP_EXPORT": "", + "EXPORT_PROGRESS": "", + "MIGRATING_EXPORT": "", + "RENAMING_COLLECTION_FOLDERS": "", + "TRASHING_DELETED_FILES": "", + "TRASHING_DELETED_COLLECTIONS": "", + "CONTINUOUS_EXPORT": "", + "PENDING_ITEMS": "", + "EXPORT_STARTING": "", + "delete_account_reason_label": "", + "delete_account_reason_placeholder": "", + "delete_reason": { + "missing_feature": "", + "behaviour": "", + "found_another_service": "", + "not_listed": "" + }, + "delete_account_feedback_label": "", + "delete_account_feedback_placeholder": "", + "delete_account_confirm_checkbox_label": "", + "delete_account_confirm": "", + "delete_account_confirm_message": "", + "feedback_required": "", + "feedback_required_found_another_service": "", + "RECOVER_TWO_FACTOR": "", + "at": "", + "AUTH_NEXT": "", + "AUTH_DOWNLOAD_MOBILE_APP": "", + "HIDDEN": "", + "HIDE": "", + "UNHIDE": "", + "UNHIDE_TO_COLLECTION": "", + "SORT_BY": "", + "NEWEST_FIRST": "", + "OLDEST_FIRST": "", + "CONVERSION_FAILED_NOTIFICATION_MESSAGE": "", + "SELECT_COLLECTION": "", + "PIN_ALBUM": "", + "UNPIN_ALBUM": "", + "DOWNLOAD_COMPLETE": "", + "DOWNLOADING_COLLECTION": "", + "DOWNLOAD_FAILED": "", + "DOWNLOAD_PROGRESS": "", + "CHRISTMAS": "", + "CHRISTMAS_EVE": "", + "NEW_YEAR": "", + "NEW_YEAR_EVE": "", + "IMAGE": "", + "VIDEO": "", + "LIVE_PHOTO": "", + "editor": { + "crop": "" + }, + "CONVERT": "", + "CONFIRM_EDITOR_CLOSE_MESSAGE": "", + "CONFIRM_EDITOR_CLOSE_DESCRIPTION": "", + "BRIGHTNESS": "", + "CONTRAST": "", + "SATURATION": "", + "BLUR": "", + "INVERT_COLORS": "", + "ASPECT_RATIO": "", + "SQUARE": "", + "ROTATE_LEFT": "", + "ROTATE_RIGHT": "", + "FLIP_VERTICALLY": "", + "FLIP_HORIZONTALLY": "", + "DOWNLOAD_EDITED": "", + "SAVE_A_COPY_TO_ENTE": "", + "RESTORE_ORIGINAL": "", + "TRANSFORM": "", + "COLORS": "", + "FLIP": "", + "ROTATION": "", + "RESET": "", + "PHOTO_EDITOR": "", + "FASTER_UPLOAD": "", + "FASTER_UPLOAD_DESCRIPTION": "", + "MAGIC_SEARCH_STATUS": "", + "INDEXED_ITEMS": "", + "CAST_ALBUM_TO_TV": "", + "ENTER_CAST_PIN_CODE": "", + "PAIR_DEVICE_TO_TV": "", + "TV_NOT_FOUND": "", + "AUTO_CAST_PAIR": "", + "AUTO_CAST_PAIR_DESC": "", + "PAIR_WITH_PIN": "", + "CHOOSE_DEVICE_FROM_BROWSER": "", + "PAIR_WITH_PIN_DESC": "", + "VISIT_CAST_ENTE_IO": "", + "CAST_AUTO_PAIR_FAILED": "", + "FREEHAND": "", + "APPLY_CROP": "", + "PHOTO_EDIT_REQUIRED_TO_SAVE": "", + "passkeys": "", + "passkey_fetch_failed": "", + "manage_passkey": "", + "delete_passkey": "", + "delete_passkey_confirmation": "", + "rename_passkey": "", + "add_passkey": "", + "enter_passkey_name": "", + "passkeys_description": "", + "CREATED_AT": "", + "passkey_add_failed": "", + "passkey_login_failed": "", + "passkey_login_invalid_url": "", + "passkey_login_already_claimed_session": "", + "passkey_login_generic_error": "", + "passkey_login_credential_hint": "", + "passkeys_not_supported": "", + "try_again": "", + "check_status": "", + "passkey_login_instructions": "", + "passkey_login": "", + "passkey": "", + "passkey_verify_description": "", + "waiting_for_verification": "", + "verification_still_pending": "", + "passkey_verified": "", + "redirecting_back_to_app": "", + "redirect_close_instructions": "", + "redirect_again": "", + "autogenerated_first_album_name": "", + "autogenerated_default_album_name": "", + "developer_settings": "", + "server_endpoint": "", + "more_information": "", + "save": "" +} diff --git a/web/packages/next/locales/tr-TR/translation.json b/web/packages/next/locales/tr-TR/translation.json index 0e2bc528c4..a644dd4a17 100644 --- a/web/packages/next/locales/tr-TR/translation.json +++ b/web/packages/next/locales/tr-TR/translation.json @@ -639,5 +639,9 @@ "redirect_close_instructions": "", "redirect_again": "", "autogenerated_first_album_name": "", - "autogenerated_default_album_name": "" + "autogenerated_default_album_name": "", + "developer_settings": "", + "server_endpoint": "", + "more_information": "", + "save": "" } diff --git a/web/packages/next/locales/zh-CN/translation.json b/web/packages/next/locales/zh-CN/translation.json index db6e5ca1fa..29f980da0a 100644 --- a/web/packages/next/locales/zh-CN/translation.json +++ b/web/packages/next/locales/zh-CN/translation.json @@ -639,5 +639,9 @@ "redirect_close_instructions": "在应用程序打开后您可以关闭此窗口。", "redirect_again": "再次重定向", "autogenerated_first_album_name": "我的第一个相册", - "autogenerated_default_album_name": "新建相册" + "autogenerated_default_album_name": "新建相册", + "developer_settings": "", + "server_endpoint": "", + "more_information": "", + "save": "" }