mirror of
https://github.com/immich-app/immich.git
synced 2025-05-24 01:12:58 -04:00
Merge remote-tracking branch 'origin/main' into keynav_timeline
This commit is contained in:
commit
e11a8d3730
5
.github/workflows/test.yml
vendored
5
.github/workflows/test.yml
vendored
@ -504,6 +504,11 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
channel: 'stable'
|
channel: 'stable'
|
||||||
flutter-version-file: ./mobile/pubspec.yaml
|
flutter-version-file: ./mobile/pubspec.yaml
|
||||||
|
|
||||||
|
- name: Generate translation file
|
||||||
|
run: make translation
|
||||||
|
working-directory: ./mobile
|
||||||
|
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
working-directory: ./mobile
|
working-directory: ./mobile
|
||||||
run: flutter test -j 1
|
run: flutter test -j 1
|
||||||
|
@ -218,7 +218,7 @@ const roadmap: Item[] = [
|
|||||||
iconColor: 'indianred',
|
iconColor: 'indianred',
|
||||||
title: 'Stable release',
|
title: 'Stable release',
|
||||||
description: 'Immich goes stable',
|
description: 'Immich goes stable',
|
||||||
getDateLabel: () => 'Planned for early 2025',
|
getDateLabel: () => 'Planned for 2025',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
done: false,
|
done: false,
|
||||||
|
16
i18n/en.json
16
i18n/en.json
@ -26,7 +26,7 @@
|
|||||||
"add_to_album": "Add to album",
|
"add_to_album": "Add to album",
|
||||||
"add_to_album_bottom_sheet_added": "Added to {album}",
|
"add_to_album_bottom_sheet_added": "Added to {album}",
|
||||||
"add_to_album_bottom_sheet_already_exists": "Already in {album}",
|
"add_to_album_bottom_sheet_already_exists": "Already in {album}",
|
||||||
"add_to_locked_folder": "Add to Locked Folder",
|
"add_to_locked_folder": "Add to locked folder",
|
||||||
"add_to_shared_album": "Add to shared album",
|
"add_to_shared_album": "Add to shared album",
|
||||||
"add_url": "Add URL",
|
"add_url": "Add URL",
|
||||||
"added_to_archive": "Added to archive",
|
"added_to_archive": "Added to archive",
|
||||||
@ -1157,7 +1157,7 @@
|
|||||||
"location_picker_longitude_error": "Enter a valid longitude",
|
"location_picker_longitude_error": "Enter a valid longitude",
|
||||||
"location_picker_longitude_hint": "Enter your longitude here",
|
"location_picker_longitude_hint": "Enter your longitude here",
|
||||||
"lock": "Lock",
|
"lock": "Lock",
|
||||||
"locked_folder": "Locked Folder",
|
"locked_folder": "Locked folder",
|
||||||
"log_out": "Log out",
|
"log_out": "Log out",
|
||||||
"log_out_all_devices": "Log Out All Devices",
|
"log_out_all_devices": "Log Out All Devices",
|
||||||
"logged_out_all_devices": "Logged out all devices",
|
"logged_out_all_devices": "Logged out all devices",
|
||||||
@ -1254,9 +1254,9 @@
|
|||||||
"monthly_title_text_date_format": "MMMM y",
|
"monthly_title_text_date_format": "MMMM y",
|
||||||
"more": "More",
|
"more": "More",
|
||||||
"move": "Move",
|
"move": "Move",
|
||||||
"move_off_locked_folder": "Move out of Locked Folder",
|
"move_off_locked_folder": "Move out of locked folder",
|
||||||
"move_to_locked_folder": "Move to Locked Folder",
|
"move_to_locked_folder": "Move to locked folder",
|
||||||
"move_to_locked_folder_confirmation": "These photos and video will be removed from all albums, and only viewable from the Locked Folder",
|
"move_to_locked_folder_confirmation": "These photos and video will be removed from all albums, and only viewable from the locked folder",
|
||||||
"moved_to_archive": "Moved {count, plural, one {# asset} other {# assets}} to archive",
|
"moved_to_archive": "Moved {count, plural, one {# asset} other {# assets}} to archive",
|
||||||
"moved_to_library": "Moved {count, plural, one {# asset} other {# assets}} to library",
|
"moved_to_library": "Moved {count, plural, one {# asset} other {# assets}} to library",
|
||||||
"moved_to_trash": "Moved to trash",
|
"moved_to_trash": "Moved to trash",
|
||||||
@ -1292,7 +1292,7 @@
|
|||||||
"no_explore_results_message": "Upload more photos to explore your collection.",
|
"no_explore_results_message": "Upload more photos to explore your collection.",
|
||||||
"no_favorites_message": "Add favorites to quickly find your best pictures and videos",
|
"no_favorites_message": "Add favorites to quickly find your best pictures and videos",
|
||||||
"no_libraries_message": "Create an external library to view your photos and videos",
|
"no_libraries_message": "Create an external library to view your photos and videos",
|
||||||
"no_locked_photos_message": "Photos and videos in Locked Folder are hidden and won't show up as you browse your library.",
|
"no_locked_photos_message": "Photos and videos in the locked folder are hidden and won't show up as you browse or search your library.",
|
||||||
"no_name": "No Name",
|
"no_name": "No Name",
|
||||||
"no_notifications": "No notifications",
|
"no_notifications": "No notifications",
|
||||||
"no_people_found": "No matching people found",
|
"no_people_found": "No matching people found",
|
||||||
@ -1502,8 +1502,8 @@
|
|||||||
"remove_deleted_assets": "Remove Deleted Assets",
|
"remove_deleted_assets": "Remove Deleted Assets",
|
||||||
"remove_from_album": "Remove from album",
|
"remove_from_album": "Remove from album",
|
||||||
"remove_from_favorites": "Remove from favorites",
|
"remove_from_favorites": "Remove from favorites",
|
||||||
"remove_from_locked_folder": "Remove from Locked Folder",
|
"remove_from_locked_folder": "Remove from locked folder",
|
||||||
"remove_from_locked_folder_confirmation": "Are you sure you want to move these photos and videos out of Locked Folder? They will be visible in your library",
|
"remove_from_locked_folder_confirmation": "Are you sure you want to move these photos and videos out of the locked folder? They will be visible in your library",
|
||||||
"remove_from_shared_link": "Remove from shared link",
|
"remove_from_shared_link": "Remove from shared link",
|
||||||
"remove_memory": "Remove memory",
|
"remove_memory": "Remove memory",
|
||||||
"remove_photo_from_memory": "Remove photo from this memory",
|
"remove_photo_from_memory": "Remove photo from this memory",
|
||||||
|
2
mobile/.vscode/settings.json
vendored
2
mobile/.vscode/settings.json
vendored
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"dart.flutterSdkPath": ".fvm/versions/3.24.3",
|
"dart.flutterSdkPath": ".fvm/versions/3.29.3",
|
||||||
"search.exclude": {
|
"search.exclude": {
|
||||||
"**/.fvm": true
|
"**/.fvm": true
|
||||||
},
|
},
|
||||||
|
@ -6,4 +6,4 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|||||||
part 'map_service.provider.g.dart';
|
part 'map_service.provider.g.dart';
|
||||||
|
|
||||||
@riverpod
|
@riverpod
|
||||||
MapSerivce mapService(Ref ref) => MapSerivce(ref.watch(apiServiceProvider));
|
MapService mapService(Ref ref) => MapService(ref.watch(apiServiceProvider));
|
||||||
|
@ -6,11 +6,11 @@ part of 'map_service.provider.dart';
|
|||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$mapServiceHash() => r'7b26bcd231ed5728ac51fe015dddbf8f91491abb';
|
String _$mapServiceHash() => r'ffc8f38b726083452b9df236ed58903879348987';
|
||||||
|
|
||||||
/// See also [mapService].
|
/// See also [mapService].
|
||||||
@ProviderFor(mapService)
|
@ProviderFor(mapService)
|
||||||
final mapServiceProvider = AutoDisposeProvider<MapSerivce>.internal(
|
final mapServiceProvider = AutoDisposeProvider<MapService>.internal(
|
||||||
mapService,
|
mapService,
|
||||||
name: r'mapServiceProvider',
|
name: r'mapServiceProvider',
|
||||||
debugGetCreateSourceHash:
|
debugGetCreateSourceHash:
|
||||||
@ -21,6 +21,6 @@ final mapServiceProvider = AutoDisposeProvider<MapSerivce>.internal(
|
|||||||
|
|
||||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
// ignore: unused_element
|
// ignore: unused_element
|
||||||
typedef MapServiceRef = AutoDisposeProviderRef<MapSerivce>;
|
typedef MapServiceRef = AutoDisposeProviderRef<MapService>;
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||||
|
@ -10,6 +10,7 @@ import 'package:immich_mobile/entities/store.entity.dart';
|
|||||||
import 'package:immich_mobile/utils/url_helper.dart';
|
import 'package:immich_mobile/utils/url_helper.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:openapi/api.dart';
|
import 'package:openapi/api.dart';
|
||||||
|
import 'package:immich_mobile/utils/user_agent.dart';
|
||||||
|
|
||||||
class ApiService implements Authentication {
|
class ApiService implements Authentication {
|
||||||
late ApiClient _apiClient;
|
late ApiClient _apiClient;
|
||||||
@ -48,6 +49,7 @@ class ApiService implements Authentication {
|
|||||||
|
|
||||||
setEndpoint(String endpoint) {
|
setEndpoint(String endpoint) {
|
||||||
_apiClient = ApiClient(basePath: endpoint, authentication: this);
|
_apiClient = ApiClient(basePath: endpoint, authentication: this);
|
||||||
|
_setUserAgentHeader();
|
||||||
if (_accessToken != null) {
|
if (_accessToken != null) {
|
||||||
setAccessToken(_accessToken!);
|
setAccessToken(_accessToken!);
|
||||||
}
|
}
|
||||||
@ -72,6 +74,11 @@ class ApiService implements Authentication {
|
|||||||
memoriesApi = MemoriesApi(_apiClient);
|
memoriesApi = MemoriesApi(_apiClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _setUserAgentHeader() async {
|
||||||
|
final userAgent = await getUserAgentString();
|
||||||
|
_apiClient.addDefaultHeader('User-Agent', userAgent);
|
||||||
|
}
|
||||||
|
|
||||||
Future<String> resolveAndSetEndpoint(String serverUrl) async {
|
Future<String> resolveAndSetEndpoint(String serverUrl) async {
|
||||||
final endpoint = await resolveEndpoint(serverUrl);
|
final endpoint = await resolveEndpoint(serverUrl);
|
||||||
setEndpoint(endpoint);
|
setEndpoint(endpoint);
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
// ignore_for_file: implementation_imports
|
// ignore_for_file: implementation_imports
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:easy_localization/src/asset_loader.dart';
|
|
||||||
import 'package:easy_localization/src/easy_localization_controller.dart';
|
import 'package:easy_localization/src/easy_localization_controller.dart';
|
||||||
import 'package:easy_localization/src/localization.dart';
|
import 'package:easy_localization/src/localization.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:immich_mobile/constants/locales.dart';
|
import 'package:immich_mobile/constants/locales.dart';
|
||||||
|
import 'package:immich_mobile/generated/codegen_loader.g.dart';
|
||||||
|
|
||||||
/// Workaround to manually load translations in another Isolate
|
/// Workaround to manually load translations in another Isolate
|
||||||
Future<bool> loadTranslations() async {
|
Future<bool> loadTranslations() async {
|
||||||
@ -14,7 +14,7 @@ Future<bool> loadTranslations() async {
|
|||||||
supportedLocales: locales.values.toList(),
|
supportedLocales: locales.values.toList(),
|
||||||
useFallbackTranslations: true,
|
useFallbackTranslations: true,
|
||||||
saveLocale: true,
|
saveLocale: true,
|
||||||
assetLoader: const RootBundleAssetLoader(),
|
assetLoader: const CodegenLoader(),
|
||||||
path: translationsPath,
|
path: translationsPath,
|
||||||
useOnlyLangCode: false,
|
useOnlyLangCode: false,
|
||||||
onLoadError: (e) => debugPrint(e.toString()),
|
onLoadError: (e) => debugPrint(e.toString()),
|
||||||
|
@ -2,13 +2,22 @@ import 'package:immich_mobile/mixins/error_logger.mixin.dart';
|
|||||||
import 'package:immich_mobile/models/map/map_marker.model.dart';
|
import 'package:immich_mobile/models/map/map_marker.model.dart';
|
||||||
import 'package:immich_mobile/services/api.service.dart';
|
import 'package:immich_mobile/services/api.service.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:maplibre_gl/maplibre_gl.dart';
|
||||||
|
import 'package:immich_mobile/utils/user_agent.dart';
|
||||||
|
|
||||||
class MapSerivce with ErrorLoggerMixin {
|
class MapService with ErrorLoggerMixin {
|
||||||
final ApiService _apiService;
|
final ApiService _apiService;
|
||||||
@override
|
@override
|
||||||
final logger = Logger("MapService");
|
final logger = Logger("MapService");
|
||||||
|
|
||||||
MapSerivce(this._apiService);
|
MapService(this._apiService) {
|
||||||
|
_setMapUserAgentHeader();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _setMapUserAgentHeader() async {
|
||||||
|
final userAgent = await getUserAgentString();
|
||||||
|
setHttpHeaders({'User-Agent': userAgent});
|
||||||
|
}
|
||||||
|
|
||||||
Future<Iterable<MapMarker>> getMapMarkers({
|
Future<Iterable<MapMarker>> getMapMarkers({
|
||||||
bool? isFavorite,
|
bool? isFavorite,
|
||||||
|
15
mobile/lib/utils/user_agent.dart
Normal file
15
mobile/lib/utils/user_agent.dart
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import 'dart:io' show Platform;
|
||||||
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
|
|
||||||
|
Future<String> getUserAgentString() async {
|
||||||
|
final packageInfo = await PackageInfo.fromPlatform();
|
||||||
|
String platform;
|
||||||
|
if (Platform.isAndroid) {
|
||||||
|
platform = 'Android';
|
||||||
|
} else if (Platform.isIOS) {
|
||||||
|
platform = 'iOS';
|
||||||
|
} else {
|
||||||
|
platform = 'Unknown';
|
||||||
|
}
|
||||||
|
return 'Immich_${platform}_${packageInfo.version}';
|
||||||
|
}
|
@ -495,6 +495,7 @@
|
|||||||
zoom={12.5}
|
zoom={12.5}
|
||||||
simplified
|
simplified
|
||||||
useLocationPin
|
useLocationPin
|
||||||
|
showSimpleControls={!showEditFaces}
|
||||||
onOpenInMapView={() => goto(`${AppRoute.MAP}#12.5/${latlng.lat}/${latlng.lng}`)}
|
onOpenInMapView={() => goto(`${AppRoute.MAP}#12.5/${latlng.lat}/${latlng.lng}`)}
|
||||||
>
|
>
|
||||||
{#snippet popup({ marker })}
|
{#snippet popup({ marker })}
|
||||||
|
@ -55,7 +55,7 @@
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="absolute end-0 top-0 flex place-items-center gap-1 text-xs font-medium text-white">
|
<div class="absolute end-0 top-0 z-1 flex place-items-center gap-1 text-xs font-medium text-white">
|
||||||
{#if showTime}
|
{#if showTime}
|
||||||
<span class="pt-2">
|
<span class="pt-2">
|
||||||
{#if remainingSeconds < 60}
|
{#if remainingSeconds < 60}
|
||||||
|
@ -577,12 +577,9 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select/deselect assets in range (start,end]
|
// Select/deselect assets in range (start,end)
|
||||||
let started = false;
|
let started = false;
|
||||||
for (const bucket of assetStore.buckets) {
|
for (const bucket of assetStore.buckets) {
|
||||||
if (bucket === startBucket) {
|
|
||||||
started = true;
|
|
||||||
}
|
|
||||||
if (bucket === endBucket) {
|
if (bucket === endBucket) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -596,27 +593,31 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (bucket === startBucket) {
|
||||||
|
started = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update date group selection
|
// Update date group selection in range [start,end]
|
||||||
started = false;
|
started = false;
|
||||||
for (const bucket of assetStore.buckets) {
|
for (const bucket of assetStore.buckets) {
|
||||||
if (bucket === startBucket) {
|
if (bucket === startBucket) {
|
||||||
started = true;
|
started = true;
|
||||||
}
|
}
|
||||||
|
if (started) {
|
||||||
|
// Split bucket into date groups and check each group
|
||||||
|
for (const dateGroup of bucket.dateGroups) {
|
||||||
|
const dateGroupTitle = dateGroup.groupTitle;
|
||||||
|
if (dateGroup.getAssets().every((a) => assetInteraction.hasSelectedAsset(a.id))) {
|
||||||
|
assetInteraction.addGroupToMultiselectGroup(dateGroupTitle);
|
||||||
|
} else {
|
||||||
|
assetInteraction.removeGroupFromMultiselectGroup(dateGroupTitle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (bucket === endBucket) {
|
if (bucket === endBucket) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split bucket into date groups and check each group
|
|
||||||
for (const dateGroup of bucket.dateGroups) {
|
|
||||||
const dateGroupTitle = dateGroup.groupTitle;
|
|
||||||
if (dateGroup.getAssets().every((a) => assetInteraction.hasSelectedAsset(a.id))) {
|
|
||||||
assetInteraction.addGroupToMultiselectGroup(dateGroupTitle);
|
|
||||||
} else {
|
|
||||||
assetInteraction.removeGroupFromMultiselectGroup(dateGroupTitle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@
|
|||||||
onscroll={onScroll}
|
onscroll={onScroll}
|
||||||
>
|
>
|
||||||
{#if canScrollLeft || canScrollRight}
|
{#if canScrollLeft || canScrollRight}
|
||||||
<div class="sticky start-0">
|
<div class="sticky start-0 z-1">
|
||||||
{#if canScrollLeft}
|
{#if canScrollLeft}
|
||||||
<div class="absolute start-4 top-24" transition:fade={{ duration: 200 }}>
|
<div class="absolute start-4 top-24" transition:fade={{ duration: 200 }}>
|
||||||
<button
|
<button
|
||||||
@ -60,7 +60,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if canScrollRight}
|
{#if canScrollRight}
|
||||||
<div class="absolute end-4 top-24" transition:fade={{ duration: 200 }}>
|
<div class="absolute end-4 top-24 z-1" transition:fade={{ duration: 200 }}>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="rounded-full border border-gray-500 bg-gray-100 p-2 text-gray-500 opacity-50 hover:opacity-100"
|
class="rounded-full border border-gray-500 bg-gray-100 p-2 text-gray-500 opacity-50 hover:opacity-100"
|
||||||
|
@ -54,6 +54,7 @@
|
|||||||
onClickPoint?: ({ lat, lng }: { lat: number; lng: number }) => void;
|
onClickPoint?: ({ lat, lng }: { lat: number; lng: number }) => void;
|
||||||
popup?: import('svelte').Snippet<[{ marker: MapMarkerResponseDto }]>;
|
popup?: import('svelte').Snippet<[{ marker: MapMarkerResponseDto }]>;
|
||||||
rounded?: boolean;
|
rounded?: boolean;
|
||||||
|
showSimpleControls?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
let {
|
||||||
@ -70,6 +71,7 @@
|
|||||||
onClickPoint = () => {},
|
onClickPoint = () => {},
|
||||||
popup,
|
popup,
|
||||||
rounded = false,
|
rounded = false,
|
||||||
|
showSimpleControls = true,
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
let map: maplibregl.Map | undefined = $state();
|
let map: maplibregl.Map | undefined = $state();
|
||||||
@ -266,13 +268,15 @@
|
|||||||
bind:map
|
bind:map
|
||||||
>
|
>
|
||||||
{#snippet children({ map }: { map: maplibregl.Map })}
|
{#snippet children({ map }: { map: maplibregl.Map })}
|
||||||
<NavigationControl position="top-left" showCompass={!simplified} />
|
{#if showSimpleControls}
|
||||||
|
<NavigationControl position="top-left" showCompass={!simplified} />
|
||||||
|
|
||||||
{#if !simplified}
|
{#if !simplified}
|
||||||
<GeolocateControl position="top-left" />
|
<GeolocateControl position="top-left" />
|
||||||
<FullscreenControl position="top-left" />
|
<FullscreenControl position="top-left" />
|
||||||
<ScaleControl />
|
<ScaleControl />
|
||||||
<AttributionControl compact={false} />
|
<AttributionControl compact={false} />
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if showSettings}
|
{#if showSettings}
|
||||||
@ -285,7 +289,7 @@
|
|||||||
</Control>
|
</Control>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if onOpenInMapView}
|
{#if onOpenInMapView && showSimpleControls}
|
||||||
<Control position="top-right">
|
<Control position="top-right">
|
||||||
<ControlGroup>
|
<ControlGroup>
|
||||||
<ControlButton onclick={() => onOpenInMapView()}>
|
<ControlButton onclick={() => onOpenInMapView()}>
|
||||||
|
@ -88,6 +88,7 @@
|
|||||||
} else if (target.value !== '') {
|
} else if (target.value !== '') {
|
||||||
pinValues[index] = '';
|
pinValues[index] = '';
|
||||||
}
|
}
|
||||||
|
value = pinValues.join('').trim();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case 'ArrowLeft': {
|
case 'ArrowLeft': {
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
import FavoriteAction from '$lib/components/photos-page/actions/favorite-action.svelte';
|
import FavoriteAction from '$lib/components/photos-page/actions/favorite-action.svelte';
|
||||||
import RemoveFromAlbum from '$lib/components/photos-page/actions/remove-from-album.svelte';
|
import RemoveFromAlbum from '$lib/components/photos-page/actions/remove-from-album.svelte';
|
||||||
import SelectAllAssets from '$lib/components/photos-page/actions/select-all-assets.svelte';
|
import SelectAllAssets from '$lib/components/photos-page/actions/select-all-assets.svelte';
|
||||||
|
import SetVisibilityAction from '$lib/components/photos-page/actions/set-visibility-action.svelte';
|
||||||
import TagAction from '$lib/components/photos-page/actions/tag-action.svelte';
|
import TagAction from '$lib/components/photos-page/actions/tag-action.svelte';
|
||||||
import AssetGrid from '$lib/components/photos-page/asset-grid.svelte';
|
import AssetGrid from '$lib/components/photos-page/asset-grid.svelte';
|
||||||
import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
|
import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
|
||||||
@ -306,6 +307,11 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSetVisibility = (assetIds: string[]) => {
|
||||||
|
assetStore.removeAssets(assetIds);
|
||||||
|
assetInteraction.clearMultiselect();
|
||||||
|
};
|
||||||
|
|
||||||
const handleRemoveAssets = async (assetIds: string[]) => {
|
const handleRemoveAssets = async (assetIds: string[]) => {
|
||||||
assetStore.removeAssets(assetIds);
|
assetStore.removeAssets(assetIds);
|
||||||
await refreshAlbum();
|
await refreshAlbum();
|
||||||
@ -603,6 +609,7 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
<ArchiveAction menuItem unarchive={assetInteraction.isAllArchived} />
|
<ArchiveAction menuItem unarchive={assetInteraction.isAllArchived} />
|
||||||
|
<SetVisibilityAction menuItem onVisibilitySet={handleSetVisibility} />
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if $preferences.tags.enabled && assetInteraction.isAllUserOwned}
|
{#if $preferences.tags.enabled && assetInteraction.isAllUserOwned}
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
import { onDestroy } from 'svelte';
|
import { onDestroy } from 'svelte';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
|
import SetVisibilityAction from '$lib/components/photos-page/actions/set-visibility-action.svelte';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
data: PageData;
|
data: PageData;
|
||||||
@ -38,6 +39,11 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSetVisibility = (assetIds: string[]) => {
|
||||||
|
assetStore.removeAssets(assetIds);
|
||||||
|
assetInteraction.clearMultiselect();
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<UserPageLayout hideNavbar={assetInteraction.selectionActive} title={data.meta.title} scrollbar={false}>
|
<UserPageLayout hideNavbar={assetInteraction.selectionActive} title={data.meta.title} scrollbar={false}>
|
||||||
@ -83,6 +89,7 @@
|
|||||||
/>
|
/>
|
||||||
<ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}>
|
<ButtonContextMenu icon={mdiDotsVertical} title={$t('menu')}>
|
||||||
<DownloadAction menuItem />
|
<DownloadAction menuItem />
|
||||||
|
<SetVisibilityAction menuItem onVisibilitySet={handleSetVisibility} />
|
||||||
<DeleteAssets menuItem onAssetDelete={(assetIds) => assetStore.removeAssets(assetIds)} />
|
<DeleteAssets menuItem onAssetDelete={(assetIds) => assetStore.removeAssets(assetIds)} />
|
||||||
</ButtonContextMenu>
|
</ButtonContextMenu>
|
||||||
</AssetSelectControlBar>
|
</AssetSelectControlBar>
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
import DownloadAction from '$lib/components/photos-page/actions/download-action.svelte';
|
import DownloadAction from '$lib/components/photos-page/actions/download-action.svelte';
|
||||||
import FavoriteAction from '$lib/components/photos-page/actions/favorite-action.svelte';
|
import FavoriteAction from '$lib/components/photos-page/actions/favorite-action.svelte';
|
||||||
import SelectAllAssets from '$lib/components/photos-page/actions/select-all-assets.svelte';
|
import SelectAllAssets from '$lib/components/photos-page/actions/select-all-assets.svelte';
|
||||||
|
import SetVisibilityAction from '$lib/components/photos-page/actions/set-visibility-action.svelte';
|
||||||
import TagAction from '$lib/components/photos-page/actions/tag-action.svelte';
|
import TagAction from '$lib/components/photos-page/actions/tag-action.svelte';
|
||||||
import AssetGrid from '$lib/components/photos-page/asset-grid.svelte';
|
import AssetGrid from '$lib/components/photos-page/asset-grid.svelte';
|
||||||
import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
|
import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
|
||||||
@ -42,6 +43,11 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSetVisibility = (assetIds: string[]) => {
|
||||||
|
assetStore.removeAssets(assetIds);
|
||||||
|
assetInteraction.clearMultiselect();
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<UserPageLayout hideNavbar={assetInteraction.selectionActive} title={data.meta.title} scrollbar={false}>
|
<UserPageLayout hideNavbar={assetInteraction.selectionActive} title={data.meta.title} scrollbar={false}>
|
||||||
@ -85,6 +91,7 @@
|
|||||||
{#if $preferences.tags.enabled}
|
{#if $preferences.tags.enabled}
|
||||||
<TagAction menuItem />
|
<TagAction menuItem />
|
||||||
{/if}
|
{/if}
|
||||||
|
<SetVisibilityAction menuItem onVisibilitySet={handleSetVisibility} />
|
||||||
<DeleteAssets menuItem onAssetDelete={(assetIds) => assetStore.removeAssets(assetIds)} />
|
<DeleteAssets menuItem onAssetDelete={(assetIds) => assetStore.removeAssets(assetIds)} />
|
||||||
</ButtonContextMenu>
|
</ButtonContextMenu>
|
||||||
</AssetSelectControlBar>
|
</AssetSelectControlBar>
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
import DownloadAction from '$lib/components/photos-page/actions/download-action.svelte';
|
import DownloadAction from '$lib/components/photos-page/actions/download-action.svelte';
|
||||||
import FavoriteAction from '$lib/components/photos-page/actions/favorite-action.svelte';
|
import FavoriteAction from '$lib/components/photos-page/actions/favorite-action.svelte';
|
||||||
import SelectAllAssets from '$lib/components/photos-page/actions/select-all-assets.svelte';
|
import SelectAllAssets from '$lib/components/photos-page/actions/select-all-assets.svelte';
|
||||||
|
import SetVisibilityAction from '$lib/components/photos-page/actions/set-visibility-action.svelte';
|
||||||
import TagAction from '$lib/components/photos-page/actions/tag-action.svelte';
|
import TagAction from '$lib/components/photos-page/actions/tag-action.svelte';
|
||||||
import AssetGrid from '$lib/components/photos-page/asset-grid.svelte';
|
import AssetGrid from '$lib/components/photos-page/asset-grid.svelte';
|
||||||
import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
|
import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
|
||||||
@ -359,6 +360,11 @@
|
|||||||
handlePromiseError(updateAssetCount());
|
handlePromiseError(updateAssetCount());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleSetVisibility = (assetIds: string[]) => {
|
||||||
|
assetStore.removeAssets(assetIds);
|
||||||
|
assetInteraction.clearMultiselect();
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<main
|
<main
|
||||||
@ -446,7 +452,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
{#if isEditingName}
|
{#if isEditingName}
|
||||||
<div class="absolute w-64 sm:w-96">
|
<div class="absolute w-64 sm:w-96 z-1">
|
||||||
{#if isSearchingPeople}
|
{#if isSearchingPeople}
|
||||||
<div
|
<div
|
||||||
class="flex border h-14 rounded-b-lg border-gray-400 dark:border-immich-dark-gray place-items-center bg-gray-200 p-2 dark:bg-gray-700"
|
class="flex border h-14 rounded-b-lg border-gray-400 dark:border-immich-dark-gray place-items-center bg-gray-200 p-2 dark:bg-gray-700"
|
||||||
@ -525,6 +531,7 @@
|
|||||||
{#if $preferences.tags.enabled && assetInteraction.isAllUserOwned}
|
{#if $preferences.tags.enabled && assetInteraction.isAllUserOwned}
|
||||||
<TagAction menuItem />
|
<TagAction menuItem />
|
||||||
{/if}
|
{/if}
|
||||||
|
<SetVisibilityAction menuItem onVisibilitySet={handleSetVisibility} />
|
||||||
<DeleteAssets menuItem onAssetDelete={(assetIds) => handleDeleteAssets(assetIds)} />
|
<DeleteAssets menuItem onAssetDelete={(assetIds) => handleDeleteAssets(assetIds)} />
|
||||||
</ButtonContextMenu>
|
</ButtonContextMenu>
|
||||||
</AssetSelectControlBar>
|
</AssetSelectControlBar>
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
import DeleteAssets from '$lib/components/photos-page/actions/delete-assets.svelte';
|
import DeleteAssets from '$lib/components/photos-page/actions/delete-assets.svelte';
|
||||||
import DownloadAction from '$lib/components/photos-page/actions/download-action.svelte';
|
import DownloadAction from '$lib/components/photos-page/actions/download-action.svelte';
|
||||||
import FavoriteAction from '$lib/components/photos-page/actions/favorite-action.svelte';
|
import FavoriteAction from '$lib/components/photos-page/actions/favorite-action.svelte';
|
||||||
|
import SetVisibilityAction from '$lib/components/photos-page/actions/set-visibility-action.svelte';
|
||||||
import TagAction from '$lib/components/photos-page/actions/tag-action.svelte';
|
import TagAction from '$lib/components/photos-page/actions/tag-action.svelte';
|
||||||
import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
|
import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
|
||||||
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
|
import ButtonContextMenu from '$lib/components/shared-components/context-menu/button-context-menu.svelte';
|
||||||
@ -25,7 +26,7 @@
|
|||||||
import { AppRoute, QueryParameter } from '$lib/constants';
|
import { AppRoute, QueryParameter } from '$lib/constants';
|
||||||
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
import { AssetInteraction } from '$lib/stores/asset-interaction.svelte';
|
||||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||||
import type { TimelineAsset, Viewport } from '$lib/stores/assets-store.svelte';
|
import { AssetStore, type TimelineAsset, type Viewport } from '$lib/stores/assets-store.svelte';
|
||||||
import { lang, locale } from '$lib/stores/preferences.store';
|
import { lang, locale } from '$lib/stores/preferences.store';
|
||||||
import { featureFlags } from '$lib/stores/server-config.store';
|
import { featureFlags } from '$lib/stores/server-config.store';
|
||||||
import { preferences } from '$lib/stores/user.store';
|
import { preferences } from '$lib/stores/user.store';
|
||||||
@ -79,6 +80,8 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let assetStore = new AssetStore();
|
||||||
|
|
||||||
const onEscape = () => {
|
const onEscape = () => {
|
||||||
if ($showAssetViewer) {
|
if ($showAssetViewer) {
|
||||||
return;
|
return;
|
||||||
@ -125,6 +128,13 @@
|
|||||||
const assetIdSet = new Set(assetIds);
|
const assetIdSet = new Set(assetIds);
|
||||||
searchResultAssets = searchResultAssets.filter((asset: TimelineAsset) => !assetIdSet.has(asset.id));
|
searchResultAssets = searchResultAssets.filter((asset: TimelineAsset) => !assetIdSet.has(asset.id));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSetVisibility = (assetIds: string[]) => {
|
||||||
|
assetStore.removeAssets(assetIds);
|
||||||
|
assetInteraction.clearMultiselect();
|
||||||
|
onAssetDelete(assetIds);
|
||||||
|
};
|
||||||
|
|
||||||
const handleSelectAll = () => {
|
const handleSelectAll = () => {
|
||||||
assetInteraction.selectAssets(searchResultAssets);
|
assetInteraction.selectAssets(searchResultAssets);
|
||||||
};
|
};
|
||||||
@ -414,6 +424,9 @@
|
|||||||
<ChangeDescription menuItem />
|
<ChangeDescription menuItem />
|
||||||
<ChangeLocation menuItem />
|
<ChangeLocation menuItem />
|
||||||
<ArchiveAction menuItem unarchive={assetInteraction.isAllArchived} />
|
<ArchiveAction menuItem unarchive={assetInteraction.isAllArchived} />
|
||||||
|
{#if assetInteraction.isAllUserOwned}
|
||||||
|
<SetVisibilityAction menuItem onVisibilitySet={handleSetVisibility} />
|
||||||
|
{/if}
|
||||||
{#if $preferences.tags.enabled && assetInteraction.isAllUserOwned}
|
{#if $preferences.tags.enabled && assetInteraction.isAllUserOwned}
|
||||||
<TagAction menuItem />
|
<TagAction menuItem />
|
||||||
{/if}
|
{/if}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user