mirror of
https://github.com/immich-app/immich.git
synced 2025-12-13 17:23:10 +03:00
Compare commits
23 Commits
feat/syste
...
revert-sve
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
27fdf31646 | ||
|
|
4cbce072be | ||
|
|
5e5bb7e87d | ||
|
|
b052893a1e | ||
|
|
15e58595fd | ||
|
|
6d499c782a | ||
|
|
7af99b8606 | ||
|
|
01e39277e0 | ||
|
|
06e79703da | ||
|
|
c360781565 | ||
|
|
287f6d5c94 | ||
|
|
fe9125a3d1 | ||
|
|
8b31936bb6 | ||
|
|
19958dfd83 | ||
|
|
1e1cf0d1fe | ||
|
|
879e0ea131 | ||
|
|
42136f9091 | ||
|
|
1109c32891 | ||
|
|
3c80049192 | ||
|
|
8f1669efbe | ||
|
|
146bf65d02 | ||
|
|
75a7c9c06c | ||
|
|
ae8f5a6673 |
2
.github/package.json
vendored
2
.github/package.json
vendored
@@ -4,6 +4,6 @@
|
||||
"format:fix": "prettier --write ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"prettier": "^3.5.3"
|
||||
"prettier": "^3.7.4"
|
||||
}
|
||||
}
|
||||
|
||||
8
.github/workflows/build-mobile.yml
vendored
8
.github/workflows/build-mobile.yml
vendored
@@ -222,6 +222,7 @@ jobs:
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
ruby-version: '3.3'
|
||||
bundler-cache: true
|
||||
working-directory: ./mobile/ios
|
||||
|
||||
- name: Install CocoaPods dependencies
|
||||
@@ -229,13 +230,6 @@ jobs:
|
||||
run: |
|
||||
pod install
|
||||
|
||||
- name: Install Fastlane
|
||||
working-directory: ./mobile/ios
|
||||
run: |
|
||||
gem install bundler
|
||||
bundle config set --local path 'vendor/bundle'
|
||||
bundle install
|
||||
|
||||
- name: Create API Key
|
||||
env:
|
||||
API_KEY_ID: ${{ secrets.APP_STORE_CONNECT_API_KEY_ID }}
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
"eslint-plugin-unicorn": "^62.0.0",
|
||||
"globals": "^16.0.0",
|
||||
"mock-fs": "^5.2.0",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier": "^3.7.4",
|
||||
"prettier-plugin-organize-imports": "^4.0.0",
|
||||
"typescript": "^5.3.3",
|
||||
"typescript-eslint": "^8.28.0",
|
||||
|
||||
@@ -58,10 +58,6 @@ services:
|
||||
IMMICH_THIRD_PARTY_BUG_FEATURE_URL: https://github.com/immich-app/immich/issues
|
||||
IMMICH_THIRD_PARTY_DOCUMENTATION_URL: https://docs.immich.app
|
||||
IMMICH_THIRD_PARTY_SUPPORT_URL: https://docs.immich.app/community-guides
|
||||
ulimits:
|
||||
nofile:
|
||||
soft: 1048576
|
||||
hard: 1048576
|
||||
ports:
|
||||
- 9230:9230
|
||||
- 9231:9231
|
||||
@@ -100,10 +96,6 @@ services:
|
||||
- app-node_modules:/usr/src/app/node_modules
|
||||
- sveltekit:/usr/src/app/web/.svelte-kit
|
||||
- coverage:/usr/src/app/web/coverage
|
||||
ulimits:
|
||||
nofile:
|
||||
soft: 1048576
|
||||
hard: 1048576
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
immich-server:
|
||||
|
||||
@@ -24,6 +24,9 @@ server {
|
||||
# disable buffering uploads to prevent OOM on reverse proxy server and make uploads twice as fast (no pause)
|
||||
proxy_request_buffering off;
|
||||
|
||||
# increase body buffer to avoid limiting upload speed
|
||||
client_body_buffer_size 1024k;
|
||||
|
||||
# Set headers
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
@@ -32,8 +35,6 @@ server {
|
||||
|
||||
# enable websockets: http://nginx.org/en/docs/http/websocket.html
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_redirect off;
|
||||
|
||||
# set timeout
|
||||
@@ -43,6 +44,8 @@ server {
|
||||
|
||||
location / {
|
||||
proxy_pass http://<backend_url>:2283;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
}
|
||||
|
||||
# useful when using Let's Encrypt http-01 challenge
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
"@docusaurus/module-type-aliases": "~3.9.0",
|
||||
"@docusaurus/tsconfig": "^3.7.0",
|
||||
"@docusaurus/types": "^3.7.0",
|
||||
"prettier": "^3.2.4",
|
||||
"prettier": "^3.7.4",
|
||||
"typescript": "^5.1.6"
|
||||
},
|
||||
"browserslist": {
|
||||
|
||||
@@ -36,14 +36,14 @@
|
||||
"eslint-config-prettier": "^10.1.8",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-unicorn": "^62.0.0",
|
||||
"exiftool-vendored": "^33.0.0",
|
||||
"exiftool-vendored": "^34.0.0",
|
||||
"globals": "^16.0.0",
|
||||
"jose": "^5.6.3",
|
||||
"luxon": "^3.4.4",
|
||||
"oidc-provider": "^9.0.0",
|
||||
"pg": "^8.11.3",
|
||||
"pngjs": "^7.0.0",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier": "^3.7.4",
|
||||
"prettier-plugin-organize-imports": "^4.0.0",
|
||||
"sharp": "^0.34.5",
|
||||
"socket.io-client": "^4.7.4",
|
||||
|
||||
@@ -652,6 +652,7 @@
|
||||
"backup_options_page_title": "Backup options",
|
||||
"backup_setting_subtitle": "Manage background and foreground upload settings",
|
||||
"backup_settings_subtitle": "Manage upload settings",
|
||||
"backup_upload_details_page_more_details": "Tap for more details",
|
||||
"backward": "Backward",
|
||||
"biometric_auth_enabled": "Biometric authentication enabled",
|
||||
"biometric_locked_out": "You are locked out of biometric authentication",
|
||||
@@ -718,6 +719,7 @@
|
||||
"check_corrupt_asset_backup_button": "Perform check",
|
||||
"check_corrupt_asset_backup_description": "Run this check only over Wi-Fi and once all assets have been backed-up. The procedure might take a few minutes.",
|
||||
"check_logs": "Check Logs",
|
||||
"checksum": "Checksum",
|
||||
"choose_matching_people_to_merge": "Choose matching people to merge",
|
||||
"city": "City",
|
||||
"clear": "Clear",
|
||||
@@ -1166,6 +1168,7 @@
|
||||
"header_settings_header_name_input": "Header name",
|
||||
"header_settings_header_value_input": "Header value",
|
||||
"headers_settings_tile_title": "Custom proxy headers",
|
||||
"height": "Height",
|
||||
"hi_user": "Hi {name} ({email})",
|
||||
"hide_all_people": "Hide all people",
|
||||
"hide_gallery": "Hide gallery",
|
||||
@@ -1288,6 +1291,7 @@
|
||||
"local": "Local",
|
||||
"local_asset_cast_failed": "Unable to cast an asset that is not uploaded to the server",
|
||||
"local_assets": "Local Assets",
|
||||
"local_id": "Local ID",
|
||||
"local_media_summary": "Local Media Summary",
|
||||
"local_network": "Local network",
|
||||
"local_network_sheet_info": "The app will connect to the server through this URL when using the specified Wi-Fi network",
|
||||
@@ -2218,6 +2222,7 @@
|
||||
"week": "Week",
|
||||
"welcome": "Welcome",
|
||||
"welcome_to_immich": "Welcome to Immich",
|
||||
"width": "Width",
|
||||
"wifi_name": "Wi-Fi Name",
|
||||
"workflow": "Workflow",
|
||||
"wrong_pin_code": "Wrong PIN code",
|
||||
|
||||
@@ -137,8 +137,7 @@
|
||||
<key>NSFaceIDUsageDescription</key>
|
||||
<string>We need to use FaceID to allow access to your locked folder</string>
|
||||
<key>NSLocalNetworkUsageDescription</key>
|
||||
<string>We need local network permission to connect to the local server using IP address and
|
||||
allow the casting feature to work</string>
|
||||
<string>We need local network permission to connect to the local server using IP address and allow the casting feature to work</string>
|
||||
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
|
||||
<string>We require this permission to access the local WiFi name for background upload mechanism</string>
|
||||
<key>NSLocationUsageDescription</key>
|
||||
|
||||
@@ -98,7 +98,7 @@ class DriftUploadDetailPage extends ConsumerWidget {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Tap for more details',
|
||||
"backup_upload_details_page_more_details".t(context: context),
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: context.colorScheme.onSurface.withValues(alpha: 0.6),
|
||||
),
|
||||
@@ -239,14 +239,20 @@ class FileDetailDialog extends ConsumerWidget {
|
||||
const SizedBox(height: 24),
|
||||
if (asset != null) ...[
|
||||
_buildInfoSection(context, [
|
||||
_buildInfoRow(context, "Filename", path.basename(uploadStatus.filename)),
|
||||
_buildInfoRow(context, "Local ID", asset.id),
|
||||
_buildInfoRow(context, "File Size", formatHumanReadableBytes(uploadStatus.fileSize, 2)),
|
||||
if (asset.width != null) _buildInfoRow(context, "Width", "${asset.width}px"),
|
||||
if (asset.height != null) _buildInfoRow(context, "Height", "${asset.height}px"),
|
||||
_buildInfoRow(context, "Created At", asset.createdAt.toString()),
|
||||
_buildInfoRow(context, "Updated At", asset.updatedAt.toString()),
|
||||
if (asset.checksum != null) _buildInfoRow(context, "Checksum", asset.checksum!),
|
||||
_buildInfoRow(context, "filename".t(context: context), path.basename(uploadStatus.filename)),
|
||||
_buildInfoRow(context, "local_id".t(context: context), asset.id),
|
||||
_buildInfoRow(
|
||||
context,
|
||||
"file_size".t(context: context),
|
||||
formatHumanReadableBytes(uploadStatus.fileSize, 2),
|
||||
),
|
||||
if (asset.width != null) _buildInfoRow(context, "width".t(context: context), "${asset.width}px"),
|
||||
if (asset.height != null)
|
||||
_buildInfoRow(context, "height".t(context: context), "${asset.height}px"),
|
||||
_buildInfoRow(context, "created_at".t(context: context), asset.createdAt.toString()),
|
||||
_buildInfoRow(context, "updated_at".t(context: context), asset.updatedAt.toString()),
|
||||
if (asset.checksum != null)
|
||||
_buildInfoRow(context, "checksum".t(context: context), asset.checksum!),
|
||||
]),
|
||||
],
|
||||
],
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:drift/drift.dart' hide Column;
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/providers/background_sync.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/db.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/platform.provider.dart';
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
final _features = [
|
||||
_Feature(
|
||||
name: 'Main Timeline',
|
||||
icon: Icons.timeline_rounded,
|
||||
onTap: (ctx, _) => ctx.pushRoute(const TabShellRoute()),
|
||||
),
|
||||
_Feature(
|
||||
name: 'Selection Mode Timeline',
|
||||
icon: Icons.developer_mode_rounded,
|
||||
onTap: (ctx, ref) async {
|
||||
final user = ref.watch(currentUserProvider);
|
||||
if (user == null) {
|
||||
return Future.value();
|
||||
}
|
||||
|
||||
final assets = await ref.read(remoteAssetRepositoryProvider).getSome(user.id);
|
||||
|
||||
final selectedAssets = await ctx.pushRoute<Set<BaseAsset>>(
|
||||
DriftAssetSelectionTimelineRoute(lockedSelectionAssets: assets.toSet()),
|
||||
);
|
||||
|
||||
Logger("FeaturesInDevelopment").fine("Selected ${selectedAssets?.length ?? 0} assets");
|
||||
|
||||
return Future.value();
|
||||
},
|
||||
),
|
||||
_Feature(name: '', icon: Icons.vertical_align_center_sharp, onTap: (_, __) => Future.value()),
|
||||
_Feature(
|
||||
name: 'Sync Local',
|
||||
icon: Icons.photo_album_rounded,
|
||||
onTap: (_, ref) => ref.read(backgroundSyncProvider).syncLocal(),
|
||||
),
|
||||
_Feature(
|
||||
name: 'Sync Local Full (1)',
|
||||
icon: Icons.photo_library_rounded,
|
||||
onTap: (_, ref) => ref.read(backgroundSyncProvider).syncLocal(full: true),
|
||||
),
|
||||
_Feature(
|
||||
name: 'Hash Local Assets (2)',
|
||||
icon: Icons.numbers_outlined,
|
||||
onTap: (_, ref) => ref.read(backgroundSyncProvider).hashAssets(),
|
||||
),
|
||||
_Feature(
|
||||
name: 'Sync Remote (3)',
|
||||
icon: Icons.refresh_rounded,
|
||||
onTap: (_, ref) => ref.read(backgroundSyncProvider).syncRemote(),
|
||||
),
|
||||
_Feature(
|
||||
name: 'WAL Checkpoint',
|
||||
icon: Icons.save_rounded,
|
||||
onTap: (_, ref) => ref.read(driftProvider).customStatement("pragma wal_checkpoint(truncate)"),
|
||||
),
|
||||
_Feature(name: '', icon: Icons.vertical_align_center_sharp, onTap: (_, __) => Future.value()),
|
||||
_Feature(
|
||||
name: 'Clear Delta Checkpoint',
|
||||
icon: Icons.delete_rounded,
|
||||
onTap: (_, ref) => ref.read(nativeSyncApiProvider).clearSyncCheckpoint(),
|
||||
),
|
||||
_Feature(
|
||||
name: 'Clear Local Data',
|
||||
style: const TextStyle(color: Colors.orange, fontWeight: FontWeight.bold),
|
||||
icon: Icons.delete_forever_rounded,
|
||||
onTap: (_, ref) async {
|
||||
final db = ref.read(driftProvider);
|
||||
await db.localAssetEntity.deleteAll();
|
||||
await db.localAlbumEntity.deleteAll();
|
||||
await db.localAlbumAssetEntity.deleteAll();
|
||||
},
|
||||
),
|
||||
_Feature(
|
||||
name: 'Clear Remote Data',
|
||||
style: const TextStyle(color: Colors.orange, fontWeight: FontWeight.bold),
|
||||
icon: Icons.delete_sweep_rounded,
|
||||
onTap: (_, ref) async {
|
||||
final db = ref.read(driftProvider);
|
||||
await db.remoteAssetEntity.deleteAll();
|
||||
await db.remoteExifEntity.deleteAll();
|
||||
await db.remoteAlbumEntity.deleteAll();
|
||||
await db.remoteAlbumUserEntity.deleteAll();
|
||||
await db.remoteAlbumAssetEntity.deleteAll();
|
||||
await db.memoryEntity.deleteAll();
|
||||
await db.memoryAssetEntity.deleteAll();
|
||||
await db.stackEntity.deleteAll();
|
||||
await db.personEntity.deleteAll();
|
||||
await db.assetFaceEntity.deleteAll();
|
||||
},
|
||||
),
|
||||
_Feature(
|
||||
name: 'Local Media Summary',
|
||||
style: const TextStyle(color: Colors.indigo, fontWeight: FontWeight.bold),
|
||||
icon: Icons.table_chart_rounded,
|
||||
onTap: (ctx, _) => ctx.pushRoute(const LocalMediaSummaryRoute()),
|
||||
),
|
||||
_Feature(
|
||||
name: 'Remote Media Summary',
|
||||
style: const TextStyle(color: Colors.indigo, fontWeight: FontWeight.bold),
|
||||
icon: Icons.summarize_rounded,
|
||||
onTap: (ctx, _) => ctx.pushRoute(const RemoteMediaSummaryRoute()),
|
||||
),
|
||||
_Feature(
|
||||
name: 'Reset Sqlite',
|
||||
icon: Icons.table_view_rounded,
|
||||
style: const TextStyle(color: Colors.red, fontWeight: FontWeight.bold),
|
||||
onTap: (_, ref) async {
|
||||
final drift = ref.read(driftProvider);
|
||||
// ignore: invalid_use_of_protected_member, invalid_use_of_visible_for_testing_member
|
||||
final migrator = drift.createMigrator();
|
||||
for (final entity in drift.allSchemaEntities) {
|
||||
await migrator.drop(entity);
|
||||
await migrator.create(entity);
|
||||
}
|
||||
},
|
||||
),
|
||||
];
|
||||
|
||||
@RoutePage()
|
||||
class FeatInDevPage extends StatelessWidget {
|
||||
const FeatInDevPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text('features_in_development'.tr()), centerTitle: true),
|
||||
body: Column(
|
||||
children: [
|
||||
Flexible(
|
||||
flex: 1,
|
||||
child: ListView.builder(
|
||||
itemBuilder: (_, index) {
|
||||
final feat = _features[index];
|
||||
return Consumer(
|
||||
builder: (ctx, ref, _) => ListTile(
|
||||
title: Text(feat.name, style: feat.style),
|
||||
trailing: Icon(feat.icon),
|
||||
visualDensity: VisualDensity.compact,
|
||||
onTap: () => unawaited(feat.onTap(ctx, ref)),
|
||||
),
|
||||
);
|
||||
},
|
||||
itemCount: _features.length,
|
||||
),
|
||||
),
|
||||
const Divider(height: 0),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Feature {
|
||||
const _Feature({required this.name, required this.icon, required this.onTap, this.style});
|
||||
|
||||
final String name;
|
||||
final IconData icon;
|
||||
final TextStyle? style;
|
||||
final Future<void> Function(BuildContext, WidgetRef _) onTap;
|
||||
}
|
||||
51
mobile/lib/presentation/pages/dev/ui_showcase.page.dart
Normal file
51
mobile/lib/presentation/pages/dev/ui_showcase.page.dart
Normal file
@@ -0,0 +1,51 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_ui/immich_ui.dart';
|
||||
|
||||
List<Widget> _showcaseBuilder(Function(ImmichVariant variant, ImmichColor color) builder) {
|
||||
final children = <Widget>[];
|
||||
|
||||
final items = [
|
||||
(variant: ImmichVariant.filled, title: "Filled Variant"),
|
||||
(variant: ImmichVariant.ghost, title: "Ghost Variant"),
|
||||
];
|
||||
|
||||
for (final (:variant, :title) in items) {
|
||||
children.add(Text(title));
|
||||
children.add(Row(spacing: 10, children: [for (var color in ImmichColor.values) builder(variant, color)]));
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
@RoutePage()
|
||||
class ImmichUIShowcasePage extends StatelessWidget {
|
||||
const ImmichUIShowcasePage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Immich UI Showcase')),
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
spacing: 10,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text("IconButton", style: context.textTheme.titleLarge),
|
||||
..._showcaseBuilder(
|
||||
(variant, color) =>
|
||||
ImmichIconButton(icon: Icons.favorite, color: color, variant: variant, onTap: () {}),
|
||||
),
|
||||
Text("CloseButton", style: context.textTheme.titleLarge),
|
||||
..._showcaseBuilder((variant, color) => ImmichCloseButton(color: color, variant: variant, onTap: () {})),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -37,7 +37,7 @@ class DriftActivitiesPage extends HookConsumerWidget {
|
||||
child: Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(album.name),
|
||||
actions: [const LikeActivityActionButton(menuItem: true)],
|
||||
actions: [const LikeActivityActionButton(iconOnly: true)],
|
||||
actionsPadding: const EdgeInsets.only(right: 8),
|
||||
),
|
||||
body: activities.widgetWhen(
|
||||
|
||||
@@ -27,8 +27,19 @@ class _DriftCreateAlbumPageState extends ConsumerState<DriftCreateAlbumPage> {
|
||||
bool isAlbumTitleTextFieldFocus = false;
|
||||
Set<BaseAsset> selectedAssets = {};
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
albumTitleController.addListener(_onTitleChanged);
|
||||
}
|
||||
|
||||
void _onTitleChanged() {
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
albumTitleController.removeListener(_onTitleChanged);
|
||||
albumTitleController.dispose();
|
||||
albumDescriptionController.dispose();
|
||||
albumTitleTextFieldFocusNode.dispose();
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/utils/hooks/crop_controller_hook.dart';
|
||||
import 'package:immich_ui/immich_ui.dart';
|
||||
|
||||
/// A widget for cropping an image.
|
||||
/// This widget uses [HookWidget] to manage its lifecycle and state. It allows
|
||||
@@ -30,11 +31,13 @@ class DriftCropImagePage extends HookWidget {
|
||||
appBar: AppBar(
|
||||
backgroundColor: context.scaffoldBackgroundColor,
|
||||
title: Text("crop".tr()),
|
||||
leading: CloseButton(color: context.primaryColor),
|
||||
leading: const ImmichCloseButton(),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.done_rounded, color: context.primaryColor, size: 24),
|
||||
onPressed: () async {
|
||||
ImmichIconButton(
|
||||
icon: Icons.done_rounded,
|
||||
color: ImmichColor.primary,
|
||||
variant: ImmichVariant.ghost,
|
||||
onTap: () async {
|
||||
final croppedImage = await cropController.croppedImage();
|
||||
unawaited(context.pushRoute(DriftEditImageRoute(asset: asset, image: croppedImage, isEdited: true)));
|
||||
},
|
||||
@@ -72,17 +75,17 @@ class DriftCropImagePage extends HookWidget {
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
IconButton(
|
||||
icon: Icon(Icons.rotate_left, color: context.themeData.iconTheme.color),
|
||||
onPressed: () {
|
||||
cropController.rotateLeft();
|
||||
},
|
||||
ImmichIconButton(
|
||||
icon: Icons.rotate_left,
|
||||
variant: ImmichVariant.ghost,
|
||||
color: ImmichColor.secondary,
|
||||
onTap: () => cropController.rotateLeft(),
|
||||
),
|
||||
IconButton(
|
||||
icon: Icon(Icons.rotate_right, color: context.themeData.iconTheme.color),
|
||||
onPressed: () {
|
||||
cropController.rotateRight();
|
||||
},
|
||||
ImmichIconButton(
|
||||
icon: Icons.rotate_right,
|
||||
variant: ImmichVariant.ghost,
|
||||
color: ImmichColor.secondary,
|
||||
onTap: () => cropController.rotateRight(),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -21,12 +21,36 @@ import 'package:immich_mobile/presentation/widgets/bottom_sheet/base_bottom_shee
|
||||
|
||||
enum AddToMenuItem { album, archive, unarchive, lockedFolder }
|
||||
|
||||
class AddActionButton extends ConsumerWidget {
|
||||
const AddActionButton({super.key});
|
||||
class AddActionButton extends ConsumerStatefulWidget {
|
||||
const AddActionButton({super.key, this.originalTheme});
|
||||
|
||||
Future<void> _showAddOptions(BuildContext context, WidgetRef ref) async {
|
||||
final ThemeData? originalTheme;
|
||||
|
||||
@override
|
||||
ConsumerState<AddActionButton> createState() => _AddActionButtonState();
|
||||
}
|
||||
|
||||
class _AddActionButtonState extends ConsumerState<AddActionButton> {
|
||||
void _handleMenuSelection(AddToMenuItem selected) {
|
||||
switch (selected) {
|
||||
case AddToMenuItem.album:
|
||||
_openAlbumSelector();
|
||||
break;
|
||||
case AddToMenuItem.archive:
|
||||
performArchiveAction(context, ref, source: ActionSource.viewer);
|
||||
break;
|
||||
case AddToMenuItem.unarchive:
|
||||
performUnArchiveAction(context, ref, source: ActionSource.viewer);
|
||||
break;
|
||||
case AddToMenuItem.lockedFolder:
|
||||
performMoveToLockFolderAction(context, ref, source: ActionSource.viewer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
List<Widget> _buildMenuChildren() {
|
||||
final asset = ref.read(currentAssetNotifier);
|
||||
if (asset == null) return;
|
||||
if (asset == null) return [];
|
||||
|
||||
final user = ref.read(currentUserProvider);
|
||||
final isOwner = asset is RemoteAsset && asset.ownerId == user?.id;
|
||||
@@ -35,93 +59,57 @@ class AddActionButton extends ConsumerWidget {
|
||||
final hasRemote = asset is RemoteAsset;
|
||||
final showArchive = isOwner && !isInLockedView && hasRemote && !isArchived;
|
||||
final showUnarchive = isOwner && !isInLockedView && hasRemote && isArchived;
|
||||
final menuItemHeight = 30.0;
|
||||
|
||||
final List<PopupMenuEntry<AddToMenuItem>> items = [
|
||||
PopupMenuItem(
|
||||
enabled: false,
|
||||
textStyle: context.textTheme.labelMedium,
|
||||
height: 40,
|
||||
child: Text("add_to_bottom_bar".tr()),
|
||||
return [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Text("add_to_bottom_bar".tr(), style: context.textTheme.labelMedium),
|
||||
),
|
||||
PopupMenuItem(
|
||||
height: menuItemHeight,
|
||||
value: AddToMenuItem.album,
|
||||
child: ListTile(leading: const Icon(Icons.photo_album_outlined), title: Text("album".tr())),
|
||||
BaseActionButton(
|
||||
iconData: Icons.photo_album_outlined,
|
||||
label: "album".tr(),
|
||||
menuItem: true,
|
||||
onPressed: () => _handleMenuSelection(AddToMenuItem.album),
|
||||
),
|
||||
const PopupMenuDivider(),
|
||||
PopupMenuItem(enabled: false, textStyle: context.textTheme.labelMedium, height: 40, child: Text("move_to".tr())),
|
||||
|
||||
if (isOwner) ...[
|
||||
const Divider(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: Text("move_to".tr(), style: context.textTheme.labelMedium),
|
||||
),
|
||||
if (showArchive)
|
||||
PopupMenuItem(
|
||||
height: menuItemHeight,
|
||||
value: AddToMenuItem.archive,
|
||||
child: ListTile(leading: const Icon(Icons.archive_outlined), title: Text("archive".tr())),
|
||||
BaseActionButton(
|
||||
iconData: Icons.archive_outlined,
|
||||
label: "archive".tr(),
|
||||
menuItem: true,
|
||||
onPressed: () => _handleMenuSelection(AddToMenuItem.archive),
|
||||
),
|
||||
if (showUnarchive)
|
||||
PopupMenuItem(
|
||||
height: menuItemHeight,
|
||||
value: AddToMenuItem.unarchive,
|
||||
child: ListTile(leading: const Icon(Icons.unarchive_outlined), title: Text("unarchive".tr())),
|
||||
BaseActionButton(
|
||||
iconData: Icons.unarchive_outlined,
|
||||
label: "unarchive".tr(),
|
||||
menuItem: true,
|
||||
onPressed: () => _handleMenuSelection(AddToMenuItem.unarchive),
|
||||
),
|
||||
PopupMenuItem(
|
||||
height: menuItemHeight,
|
||||
value: AddToMenuItem.lockedFolder,
|
||||
child: ListTile(leading: const Icon(Icons.lock_outline), title: Text("locked_folder".tr())),
|
||||
BaseActionButton(
|
||||
iconData: Icons.lock_outline,
|
||||
label: "locked_folder".tr(),
|
||||
menuItem: true,
|
||||
onPressed: () => _handleMenuSelection(AddToMenuItem.lockedFolder),
|
||||
),
|
||||
],
|
||||
];
|
||||
|
||||
final AddToMenuItem? selected = await showMenu<AddToMenuItem>(
|
||||
context: context,
|
||||
color: context.themeData.scaffoldBackgroundColor,
|
||||
position: _menuPosition(context),
|
||||
items: items,
|
||||
popUpAnimationStyle: AnimationStyle.noAnimation,
|
||||
);
|
||||
|
||||
if (selected == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (selected) {
|
||||
case AddToMenuItem.album:
|
||||
_openAlbumSelector(context, ref);
|
||||
break;
|
||||
case AddToMenuItem.archive:
|
||||
await performArchiveAction(context, ref, source: ActionSource.viewer);
|
||||
break;
|
||||
case AddToMenuItem.unarchive:
|
||||
await performUnArchiveAction(context, ref, source: ActionSource.viewer);
|
||||
break;
|
||||
case AddToMenuItem.lockedFolder:
|
||||
await performMoveToLockFolderAction(context, ref, source: ActionSource.viewer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
RelativeRect _menuPosition(BuildContext context) {
|
||||
final renderObject = context.findRenderObject();
|
||||
if (renderObject is! RenderBox) {
|
||||
return RelativeRect.fill;
|
||||
}
|
||||
|
||||
final size = renderObject.size;
|
||||
final position = renderObject.localToGlobal(Offset.zero);
|
||||
|
||||
return RelativeRect.fromLTRB(position.dx, position.dy - size.height - 200, position.dx + size.width, position.dy);
|
||||
}
|
||||
|
||||
void _openAlbumSelector(BuildContext context, WidgetRef ref) {
|
||||
void _openAlbumSelector() {
|
||||
final currentAsset = ref.read(currentAssetNotifier);
|
||||
if (currentAsset == null) {
|
||||
ImmichToast.show(context: context, msg: "Cannot load asset information.", toastType: ToastType.error);
|
||||
return;
|
||||
}
|
||||
|
||||
final List<Widget> slivers = [
|
||||
AlbumSelector(onAlbumSelected: (album) => _addCurrentAssetToAlbum(context, ref, album)),
|
||||
];
|
||||
final List<Widget> slivers = [AlbumSelector(onAlbumSelected: (album) => _addCurrentAssetToAlbum(album))];
|
||||
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
@@ -141,7 +129,7 @@ class AddActionButton extends ConsumerWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _addCurrentAssetToAlbum(BuildContext context, WidgetRef ref, RemoteAlbum album) async {
|
||||
Future<void> _addCurrentAssetToAlbum(RemoteAlbum album) async {
|
||||
final latest = ref.read(currentAssetNotifier);
|
||||
|
||||
if (latest == null) {
|
||||
@@ -174,17 +162,38 @@ class AddActionButton extends ConsumerWidget {
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
Widget build(BuildContext context) {
|
||||
final asset = ref.watch(currentAssetNotifier);
|
||||
if (asset == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Builder(
|
||||
builder: (buttonContext) {
|
||||
|
||||
final themeData = widget.originalTheme ?? context.themeData;
|
||||
|
||||
return MenuAnchor(
|
||||
consumeOutsideTap: true,
|
||||
style: MenuStyle(
|
||||
backgroundColor: WidgetStatePropertyAll(themeData.scaffoldBackgroundColor),
|
||||
surfaceTintColor: const WidgetStatePropertyAll(Colors.grey),
|
||||
elevation: const WidgetStatePropertyAll(4),
|
||||
shape: const WidgetStatePropertyAll(
|
||||
RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12))),
|
||||
),
|
||||
padding: const WidgetStatePropertyAll(EdgeInsets.symmetric(vertical: 6)),
|
||||
),
|
||||
menuChildren: widget.originalTheme != null
|
||||
? [
|
||||
Theme(
|
||||
data: widget.originalTheme!,
|
||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: _buildMenuChildren()),
|
||||
),
|
||||
]
|
||||
: _buildMenuChildren(),
|
||||
builder: (context, controller, child) {
|
||||
return BaseActionButton(
|
||||
iconData: Icons.add,
|
||||
label: "add_to_bottom_bar".tr(),
|
||||
onPressed: () => _showAddOptions(buttonContext, ref),
|
||||
onPressed: () => controller.isOpen ? controller.close() : controller.open(),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
|
||||
class BaseActionButton extends StatelessWidget {
|
||||
class BaseActionButton extends ConsumerWidget {
|
||||
const BaseActionButton({
|
||||
super.key,
|
||||
required this.label,
|
||||
@@ -11,6 +12,7 @@ class BaseActionButton extends StatelessWidget {
|
||||
this.onLongPressed,
|
||||
this.maxWidth = 90.0,
|
||||
this.minWidth,
|
||||
this.iconOnly = false,
|
||||
this.menuItem = false,
|
||||
});
|
||||
|
||||
@@ -19,25 +21,42 @@ class BaseActionButton extends StatelessWidget {
|
||||
final Color? iconColor;
|
||||
final double maxWidth;
|
||||
final double? minWidth;
|
||||
|
||||
/// When true, renders only an IconButton without text label
|
||||
final bool iconOnly;
|
||||
|
||||
/// When true, renders as a MenuItemButton for use in MenuAnchor menus
|
||||
final bool menuItem;
|
||||
final void Function()? onPressed;
|
||||
final void Function()? onLongPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final miniWidth = minWidth ?? (context.isMobile ? context.width / 4.5 : 75.0);
|
||||
final iconTheme = IconTheme.of(context);
|
||||
final iconSize = iconTheme.size ?? 24.0;
|
||||
final iconColor = this.iconColor ?? iconTheme.color ?? context.themeData.iconTheme.color;
|
||||
final textColor = context.themeData.textTheme.labelLarge?.color;
|
||||
|
||||
if (menuItem) {
|
||||
if (iconOnly) {
|
||||
return IconButton(
|
||||
onPressed: onPressed,
|
||||
icon: Icon(iconData, size: iconSize, color: iconColor),
|
||||
);
|
||||
}
|
||||
|
||||
if (menuItem) {
|
||||
final theme = context.themeData;
|
||||
final effectiveIconColor = iconColor ?? theme.iconTheme.color ?? theme.colorScheme.onSurfaceVariant;
|
||||
|
||||
return MenuItemButton(
|
||||
style: MenuItemButton.styleFrom(alignment: Alignment.centerLeft, padding: const EdgeInsets.all(16)),
|
||||
leadingIcon: Icon(iconData, color: effectiveIconColor),
|
||||
onPressed: onPressed,
|
||||
child: Text(label, style: theme.textTheme.labelLarge?.copyWith(fontSize: 16)),
|
||||
);
|
||||
}
|
||||
|
||||
return ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: maxWidth),
|
||||
child: MaterialButton(
|
||||
|
||||
@@ -7,8 +7,9 @@ import 'package:immich_mobile/providers/cast.provider.dart';
|
||||
import 'package:immich_mobile/widgets/asset_viewer/cast_dialog.dart';
|
||||
|
||||
class CastActionButton extends ConsumerWidget {
|
||||
const CastActionButton({super.key, this.menuItem = true});
|
||||
const CastActionButton({super.key, this.iconOnly = false, this.menuItem = false});
|
||||
|
||||
final bool iconOnly;
|
||||
final bool menuItem;
|
||||
|
||||
@override
|
||||
@@ -22,6 +23,7 @@ class CastActionButton extends ConsumerWidget {
|
||||
onPressed: () {
|
||||
showDialog(context: context, builder: (context) => const CastDialog());
|
||||
},
|
||||
iconOnly: iconOnly,
|
||||
menuItem: menuItem,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,8 +10,9 @@ import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
|
||||
|
||||
class DownloadActionButton extends ConsumerWidget {
|
||||
final ActionSource source;
|
||||
final bool iconOnly;
|
||||
final bool menuItem;
|
||||
const DownloadActionButton({super.key, required this.source, this.menuItem = false});
|
||||
const DownloadActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false});
|
||||
|
||||
void _onTap(BuildContext context, WidgetRef ref, BackgroundSyncManager backgroundSyncManager) async {
|
||||
if (!context.mounted) {
|
||||
@@ -38,6 +39,7 @@ class DownloadActionButton extends ConsumerWidget {
|
||||
iconData: Icons.download,
|
||||
maxWidth: 95,
|
||||
label: "download".t(context: context),
|
||||
iconOnly: iconOnly,
|
||||
menuItem: menuItem,
|
||||
onPressed: () => _onTap(context, ref, backgroundManager),
|
||||
);
|
||||
|
||||
@@ -10,9 +10,10 @@ import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||
|
||||
class FavoriteActionButton extends ConsumerWidget {
|
||||
final ActionSource source;
|
||||
final bool iconOnly;
|
||||
final bool menuItem;
|
||||
|
||||
const FavoriteActionButton({super.key, required this.source, this.menuItem = false});
|
||||
const FavoriteActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false});
|
||||
|
||||
void _onTap(BuildContext context, WidgetRef ref) async {
|
||||
if (!context.mounted) {
|
||||
@@ -44,6 +45,7 @@ class FavoriteActionButton extends ConsumerWidget {
|
||||
return BaseActionButton(
|
||||
iconData: Icons.favorite_border_rounded,
|
||||
label: "favorite".t(context: context),
|
||||
iconOnly: iconOnly,
|
||||
menuItem: menuItem,
|
||||
onPressed: () => _onTap(context, ref),
|
||||
);
|
||||
|
||||
@@ -12,8 +12,9 @@ import 'package:immich_mobile/providers/infrastructure/current_album.provider.da
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
|
||||
class LikeActivityActionButton extends ConsumerWidget {
|
||||
const LikeActivityActionButton({super.key, this.menuItem = false});
|
||||
const LikeActivityActionButton({super.key, this.iconOnly = false, this.menuItem = false});
|
||||
|
||||
final bool iconOnly;
|
||||
final bool menuItem;
|
||||
|
||||
@override
|
||||
@@ -49,6 +50,7 @@ class LikeActivityActionButton extends ConsumerWidget {
|
||||
iconData: liked != null ? Icons.favorite : Icons.favorite_border,
|
||||
label: "like".t(context: context),
|
||||
onPressed: () => onTap(liked),
|
||||
iconOnly: iconOnly,
|
||||
menuItem: menuItem,
|
||||
);
|
||||
},
|
||||
@@ -57,6 +59,7 @@ class LikeActivityActionButton extends ConsumerWidget {
|
||||
loading: () => BaseActionButton(
|
||||
iconData: Icons.favorite_border,
|
||||
label: "like".t(context: context),
|
||||
iconOnly: iconOnly,
|
||||
menuItem: menuItem,
|
||||
),
|
||||
error: (error, stack) => Text('error_saving_image'.tr(args: [error.toString()])),
|
||||
|
||||
@@ -5,8 +5,9 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_bu
|
||||
import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart';
|
||||
|
||||
class MotionPhotoActionButton extends ConsumerWidget {
|
||||
const MotionPhotoActionButton({super.key, this.menuItem = true});
|
||||
const MotionPhotoActionButton({super.key, this.iconOnly = false, this.menuItem = false});
|
||||
|
||||
final bool iconOnly;
|
||||
final bool menuItem;
|
||||
|
||||
@override
|
||||
@@ -17,6 +18,7 @@ class MotionPhotoActionButton extends ConsumerWidget {
|
||||
iconData: isPlaying ? Icons.motion_photos_pause_outlined : Icons.play_circle_outline_rounded,
|
||||
label: "play_motion_photo".t(context: context),
|
||||
onPressed: ref.read(isPlayingMotionVideoProvider.notifier).toggle,
|
||||
iconOnly: iconOnly,
|
||||
menuItem: menuItem,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,9 +10,10 @@ import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
||||
|
||||
class UnFavoriteActionButton extends ConsumerWidget {
|
||||
final ActionSource source;
|
||||
final bool iconOnly;
|
||||
final bool menuItem;
|
||||
|
||||
const UnFavoriteActionButton({super.key, required this.source, this.menuItem = false});
|
||||
const UnFavoriteActionButton({super.key, required this.source, this.iconOnly = false, this.menuItem = false});
|
||||
|
||||
void _onTap(BuildContext context, WidgetRef ref) async {
|
||||
if (!context.mounted) {
|
||||
@@ -45,6 +46,7 @@ class UnFavoriteActionButton extends ConsumerWidget {
|
||||
iconData: Icons.favorite_rounded,
|
||||
label: "unfavorite".t(context: context),
|
||||
onPressed: () => _onTap(context, ref),
|
||||
iconOnly: iconOnly,
|
||||
menuItem: menuItem,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -38,11 +38,13 @@ class ViewerBottomBar extends ConsumerWidget {
|
||||
opacity = 0;
|
||||
}
|
||||
|
||||
final originalTheme = context.themeData;
|
||||
|
||||
final actions = <Widget>[
|
||||
const ShareActionButton(source: ActionSource.viewer),
|
||||
if (asset.isLocalOnly) const UploadActionButton(source: ActionSource.viewer),
|
||||
if (asset.type == AssetType.image) const EditImageActionButton(),
|
||||
if (asset.hasRemote) const AddActionButton(),
|
||||
if (asset.hasRemote) AddActionButton(originalTheme: originalTheme),
|
||||
|
||||
if (isOwner) ...[
|
||||
asset.isLocalOnly
|
||||
|
||||
@@ -4,25 +4,19 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/enums.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/domain/models/events.model.dart';
|
||||
import 'package:immich_mobile/domain/services/timeline.service.dart';
|
||||
import 'package:immich_mobile/domain/utils/event_stream.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/cast_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/download_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/favorite_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/motion_photo_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/unfavorite_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/asset_viewer/viewer_kebab_menu.widget.dart';
|
||||
import 'package:immich_mobile/providers/activity.provider.dart';
|
||||
import 'package:immich_mobile/providers/cast.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/current_album.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/readonly_mode.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||
import 'package:immich_mobile/providers/routes.provider.dart';
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
|
||||
class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
||||
const ViewerTopAppBar({super.key});
|
||||
@@ -41,15 +35,6 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
||||
final isInLockedView = ref.watch(inLockedViewProvider);
|
||||
final isReadonlyModeEnabled = ref.watch(readonlyModeProvider);
|
||||
|
||||
final timelineOrigin = ref.read(timelineServiceProvider).origin;
|
||||
final showViewInTimelineButton =
|
||||
timelineOrigin != TimelineOrigin.main &&
|
||||
timelineOrigin != TimelineOrigin.deepLink &&
|
||||
timelineOrigin != TimelineOrigin.trash &&
|
||||
timelineOrigin != TimelineOrigin.archive &&
|
||||
timelineOrigin != TimelineOrigin.localAlbum &&
|
||||
isOwner;
|
||||
|
||||
final isShowingSheet = ref.watch(assetViewerProvider.select((state) => state.showingBottomSheet));
|
||||
int opacity = ref.watch(assetViewerProvider.select((state) => state.backgroundOpacity));
|
||||
final showControls = ref.watch(assetViewerProvider.select((s) => s.showingControls));
|
||||
@@ -62,11 +47,10 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
||||
opacity = 0;
|
||||
}
|
||||
|
||||
final isCasting = ref.watch(castProvider.select((c) => c.isCasting));
|
||||
final originalTheme = context.themeData;
|
||||
|
||||
final actions = <Widget>[
|
||||
if (asset.isRemoteOnly) const DownloadActionButton(source: ActionSource.viewer, menuItem: true),
|
||||
if (isCasting || (asset.hasRemote)) const CastActionButton(menuItem: true),
|
||||
if (asset.isMotionPhoto) const MotionPhotoActionButton(iconOnly: true),
|
||||
if (album != null && album.isActivityEnabled && album.isShared)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.chat_outlined),
|
||||
@@ -74,28 +58,16 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
||||
EventStream.shared.emit(const ViewerOpenBottomSheetEvent(activitiesMode: true));
|
||||
},
|
||||
),
|
||||
if (showViewInTimelineButton)
|
||||
IconButton(
|
||||
onPressed: () async {
|
||||
await context.maybePop();
|
||||
await context.navigateTo(const TabShellRoute(children: [MainTimelineRoute()]));
|
||||
EventStream.shared.emit(ScrollToDateEvent(asset.createdAt));
|
||||
},
|
||||
icon: const Icon(Icons.image_search),
|
||||
tooltip: 'view_in_timeline'.t(context: context),
|
||||
),
|
||||
|
||||
if (asset.hasRemote && isOwner && !asset.isFavorite)
|
||||
const FavoriteActionButton(source: ActionSource.viewer, menuItem: true),
|
||||
const FavoriteActionButton(source: ActionSource.viewer, iconOnly: true),
|
||||
if (asset.hasRemote && isOwner && asset.isFavorite)
|
||||
const UnFavoriteActionButton(source: ActionSource.viewer, menuItem: true),
|
||||
if (asset.isMotionPhoto) const MotionPhotoActionButton(menuItem: true),
|
||||
const _KebabMenu(),
|
||||
const UnFavoriteActionButton(source: ActionSource.viewer, iconOnly: true),
|
||||
|
||||
ViewerKebabMenu(originalTheme: originalTheme),
|
||||
];
|
||||
|
||||
final lockedViewActions = <Widget>[
|
||||
if (isCasting || (asset.hasRemote)) const CastActionButton(menuItem: true),
|
||||
const _KebabMenu(),
|
||||
];
|
||||
final lockedViewActions = <Widget>[ViewerKebabMenu(originalTheme: originalTheme)];
|
||||
|
||||
return IgnorePointer(
|
||||
ignoring: opacity < 255,
|
||||
@@ -122,20 +94,6 @@ class ViewerTopAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
||||
Size get preferredSize => const Size.fromHeight(60.0);
|
||||
}
|
||||
|
||||
class _KebabMenu extends ConsumerWidget {
|
||||
const _KebabMenu();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return IconButton(
|
||||
onPressed: () {
|
||||
EventStream.shared.emit(const ViewerOpenBottomSheetEvent());
|
||||
},
|
||||
icon: const Icon(Icons.more_vert_rounded),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _AppBarBackButton extends ConsumerWidget {
|
||||
const _AppBarBackButton();
|
||||
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/providers/cast.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/asset_viewer/current_asset.provider.dart';
|
||||
import 'package:immich_mobile/providers/infrastructure/timeline.provider.dart';
|
||||
import 'package:immich_mobile/providers/user.provider.dart';
|
||||
import 'package:immich_mobile/utils/action_button.utils.dart';
|
||||
|
||||
class ViewerKebabMenu extends ConsumerWidget {
|
||||
const ViewerKebabMenu({super.key, this.originalTheme});
|
||||
|
||||
final ThemeData? originalTheme;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final asset = ref.watch(currentAssetNotifier);
|
||||
if (asset == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final user = ref.watch(currentUserProvider);
|
||||
final isOwner = asset is RemoteAsset && asset.ownerId == user?.id;
|
||||
final isCasting = ref.watch(castProvider.select((c) => c.isCasting));
|
||||
final timelineOrigin = ref.read(timelineServiceProvider).origin;
|
||||
|
||||
final kebabContext = ViewerKebabMenuButtonContext(
|
||||
asset: asset,
|
||||
isOwner: isOwner,
|
||||
isCasting: isCasting,
|
||||
timelineOrigin: timelineOrigin,
|
||||
originalTheme: originalTheme,
|
||||
);
|
||||
|
||||
final menuChildren = ViewerKebabMenuButtonBuilder.build(kebabContext, context, ref);
|
||||
|
||||
return MenuAnchor(
|
||||
consumeOutsideTap: true,
|
||||
style: MenuStyle(
|
||||
backgroundColor: WidgetStatePropertyAll(context.themeData.scaffoldBackgroundColor),
|
||||
surfaceTintColor: const WidgetStatePropertyAll(Colors.grey),
|
||||
elevation: const WidgetStatePropertyAll(4),
|
||||
shape: const WidgetStatePropertyAll(
|
||||
RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12))),
|
||||
),
|
||||
padding: const WidgetStatePropertyAll(EdgeInsets.symmetric(vertical: 6)),
|
||||
),
|
||||
menuChildren: [
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(minWidth: 150),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: menuChildren,
|
||||
),
|
||||
),
|
||||
],
|
||||
builder: (context, controller, child) {
|
||||
return IconButton(
|
||||
icon: const Icon(Icons.more_vert_rounded),
|
||||
onPressed: () => controller.isOpen ? controller.close() : controller.open(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -324,7 +324,11 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
||||
final topPadding = context.padding.top + (widget.appBar == null ? 0 : kToolbarHeight) + 10;
|
||||
|
||||
const scrubberBottomPadding = 100.0;
|
||||
final bottomPadding = context.padding.bottom + (widget.appBar == null ? 0 : scrubberBottomPadding);
|
||||
const bottomSheetOpenModifier = 120.0;
|
||||
final bottomPadding =
|
||||
context.padding.bottom +
|
||||
(widget.appBar == null ? 0 : scrubberBottomPadding) +
|
||||
(isMultiSelectEnabled ? bottomSheetOpenModifier : 0);
|
||||
|
||||
final grid = CustomScrollView(
|
||||
primary: true,
|
||||
@@ -347,7 +351,7 @@ class _SliverTimelineState extends ConsumerState<_SliverTimeline> {
|
||||
addRepaintBoundaries: false,
|
||||
),
|
||||
),
|
||||
const SliverPadding(padding: EdgeInsets.only(bottom: scrubberBottomPadding)),
|
||||
SliverPadding(padding: EdgeInsets.only(bottom: bottomPadding)),
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -78,9 +78,9 @@ import 'package:immich_mobile/pages/search/recently_taken.page.dart';
|
||||
import 'package:immich_mobile/pages/search/search.page.dart';
|
||||
import 'package:immich_mobile/pages/settings/sync_status.page.dart';
|
||||
import 'package:immich_mobile/pages/share_intent/share_intent.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/dev/feat_in_development.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/dev/main_timeline.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/dev/media_stat.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/dev/ui_showcase.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/download_info.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/drift_activities.page.dart';
|
||||
import 'package:immich_mobile/presentation/pages/drift_album.page.dart';
|
||||
@@ -167,7 +167,7 @@ class AppRouter extends RootStackRouter {
|
||||
AutoRoute(page: LoginRoute.page, guards: [_duplicateGuard]),
|
||||
AutoRoute(page: ChangePasswordRoute.page),
|
||||
AutoRoute(page: SearchRoute.page, guards: [_authGuard, _duplicateGuard], maintainState: false),
|
||||
CustomRoute(
|
||||
AutoRoute(
|
||||
page: TabControllerRoute.page,
|
||||
guards: [_authGuard, _duplicateGuard],
|
||||
children: [
|
||||
@@ -176,9 +176,8 @@ class AppRouter extends RootStackRouter {
|
||||
AutoRoute(page: LibraryRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||
AutoRoute(page: AlbumsRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||
],
|
||||
transitionsBuilder: TransitionsBuilders.fadeIn,
|
||||
),
|
||||
CustomRoute(
|
||||
AutoRoute(
|
||||
page: TabShellRoute.page,
|
||||
guards: [_authGuard, _duplicateGuard],
|
||||
children: [
|
||||
@@ -187,7 +186,6 @@ class AppRouter extends RootStackRouter {
|
||||
AutoRoute(page: DriftLibraryRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||
AutoRoute(page: DriftAlbumsRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||
],
|
||||
transitionsBuilder: TransitionsBuilders.fadeIn,
|
||||
),
|
||||
CustomRoute(
|
||||
page: GalleryViewerRoute.page,
|
||||
@@ -288,7 +286,6 @@ class AppRouter extends RootStackRouter {
|
||||
AutoRoute(page: ShareIntentRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||
AutoRoute(page: LockedRoute.page, guards: [_authGuard, _lockedGuard, _duplicateGuard]),
|
||||
AutoRoute(page: PinAuthRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||
AutoRoute(page: FeatInDevRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||
AutoRoute(page: LocalMediaSummaryRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||
AutoRoute(page: RemoteMediaSummaryRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||
AutoRoute(page: DriftBackupRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||
@@ -340,6 +337,7 @@ class AppRouter extends RootStackRouter {
|
||||
AutoRoute(page: DriftBackupAssetDetailRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||
AutoRoute(page: AssetTroubleshootRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||
AutoRoute(page: DownloadInfoRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||
AutoRoute(page: ImmichUIShowcaseRoute.page, guards: [_authGuard, _duplicateGuard]),
|
||||
// required to handle all deeplinks in deep_link.service.dart
|
||||
// auto_route_library#1722
|
||||
RedirectRoute(path: '*', redirectTo: '/'),
|
||||
|
||||
@@ -1648,22 +1648,6 @@ class FavoritesRoute extends PageRouteInfo<void> {
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [FeatInDevPage]
|
||||
class FeatInDevRoute extends PageRouteInfo<void> {
|
||||
const FeatInDevRoute({List<PageRouteInfo>? children})
|
||||
: super(FeatInDevRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'FeatInDevRoute';
|
||||
|
||||
static PageInfo page = PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const FeatInDevPage();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [FilterImagePage]
|
||||
class FilterImageRoute extends PageRouteInfo<FilterImageRouteArgs> {
|
||||
@@ -1831,6 +1815,22 @@ class HeaderSettingsRoute extends PageRouteInfo<void> {
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [ImmichUIShowcasePage]
|
||||
class ImmichUIShowcaseRoute extends PageRouteInfo<void> {
|
||||
const ImmichUIShowcaseRoute({List<PageRouteInfo>? children})
|
||||
: super(ImmichUIShowcaseRoute.name, initialChildren: children);
|
||||
|
||||
static const String name = 'ImmichUIShowcaseRoute';
|
||||
|
||||
static PageInfo page = PageInfo(
|
||||
name,
|
||||
builder: (data) {
|
||||
return const ImmichUIShowcasePage();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// generated route for
|
||||
/// [LibraryPage]
|
||||
class LibraryRoute extends PageRouteInfo<void> {
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:immich_mobile/constants/enums.dart';
|
||||
import 'package:immich_mobile/domain/models/album/album.model.dart';
|
||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||
import 'package:immich_mobile/domain/models/events.model.dart';
|
||||
import 'package:immich_mobile/domain/services/timeline.service.dart';
|
||||
import 'package:immich_mobile/domain/utils/event_stream.dart';
|
||||
import 'package:immich_mobile/extensions/translate_extensions.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/advanced_info_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/archive_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/cast_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_local_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart';
|
||||
@@ -19,6 +28,7 @@ import 'package:immich_mobile/presentation/widgets/action_buttons/trash_action_b
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/unarchive_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/unstack_action_button.widget.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/action_buttons/upload_action_button.widget.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
|
||||
class ActionButtonContext {
|
||||
final BaseAsset asset;
|
||||
@@ -164,3 +174,98 @@ class ActionButtonBuilder {
|
||||
return _actionTypes.where((type) => type.shouldShow(context)).map((type) => type.buildButton(context)).toList();
|
||||
}
|
||||
}
|
||||
|
||||
class ViewerKebabMenuButtonContext {
|
||||
final BaseAsset asset;
|
||||
final bool isOwner;
|
||||
final bool isCasting;
|
||||
final TimelineOrigin timelineOrigin;
|
||||
final ThemeData? originalTheme;
|
||||
|
||||
const ViewerKebabMenuButtonContext({
|
||||
required this.asset,
|
||||
required this.isOwner,
|
||||
required this.isCasting,
|
||||
required this.timelineOrigin,
|
||||
this.originalTheme,
|
||||
});
|
||||
}
|
||||
|
||||
enum ViewerKebabMenuButtonType {
|
||||
openInfo,
|
||||
viewInTimeline,
|
||||
cast,
|
||||
download;
|
||||
|
||||
/// Defines which group each button belongs to.
|
||||
/// Buttons in the same group will be displayed together,
|
||||
/// with dividers separating different groups.
|
||||
int get group => switch (this) {
|
||||
ViewerKebabMenuButtonType.openInfo => 0,
|
||||
ViewerKebabMenuButtonType.viewInTimeline => 1,
|
||||
ViewerKebabMenuButtonType.cast => 1,
|
||||
ViewerKebabMenuButtonType.download => 1,
|
||||
};
|
||||
|
||||
bool shouldShow(ViewerKebabMenuButtonContext context) {
|
||||
return switch (this) {
|
||||
ViewerKebabMenuButtonType.openInfo => true,
|
||||
ViewerKebabMenuButtonType.viewInTimeline =>
|
||||
context.timelineOrigin != TimelineOrigin.main &&
|
||||
context.timelineOrigin != TimelineOrigin.deepLink &&
|
||||
context.timelineOrigin != TimelineOrigin.trash &&
|
||||
context.timelineOrigin != TimelineOrigin.archive &&
|
||||
context.timelineOrigin != TimelineOrigin.localAlbum &&
|
||||
context.isOwner,
|
||||
ViewerKebabMenuButtonType.cast => context.isCasting || context.asset.hasRemote,
|
||||
ViewerKebabMenuButtonType.download => context.asset.isRemoteOnly,
|
||||
};
|
||||
}
|
||||
|
||||
ConsumerWidget buildButton(ViewerKebabMenuButtonContext context, BuildContext buildContext) {
|
||||
return switch (this) {
|
||||
ViewerKebabMenuButtonType.openInfo => BaseActionButton(
|
||||
label: 'info'.tr(),
|
||||
iconData: Icons.info_outline,
|
||||
iconColor: context.originalTheme?.iconTheme.color,
|
||||
menuItem: true,
|
||||
onPressed: () => EventStream.shared.emit(const ViewerOpenBottomSheetEvent()),
|
||||
),
|
||||
|
||||
ViewerKebabMenuButtonType.viewInTimeline => BaseActionButton(
|
||||
label: 'view_in_timeline'.t(context: buildContext),
|
||||
iconData: Icons.image_search,
|
||||
iconColor: context.originalTheme?.iconTheme.color,
|
||||
menuItem: true,
|
||||
onPressed: () async {
|
||||
await buildContext.maybePop();
|
||||
await buildContext.navigateTo(const TabShellRoute(children: [MainTimelineRoute()]));
|
||||
EventStream.shared.emit(ScrollToDateEvent(context.asset.createdAt));
|
||||
},
|
||||
),
|
||||
ViewerKebabMenuButtonType.cast => const CastActionButton(menuItem: true),
|
||||
ViewerKebabMenuButtonType.download => const DownloadActionButton(source: ActionSource.viewer, menuItem: true),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class ViewerKebabMenuButtonBuilder {
|
||||
static List<Widget> build(ViewerKebabMenuButtonContext context, BuildContext buildContext, WidgetRef ref) {
|
||||
final visibleButtons = ViewerKebabMenuButtonType.values.where((type) => type.shouldShow(context)).toList();
|
||||
|
||||
if (visibleButtons.isEmpty) return [];
|
||||
|
||||
final List<Widget> result = [];
|
||||
int? lastGroup;
|
||||
|
||||
for (final type in visibleButtons) {
|
||||
if (lastGroup != null && type.group != lastGroup) {
|
||||
result.add(const Divider(height: 1));
|
||||
}
|
||||
result.add(type.buildButton(context, buildContext).build(buildContext, ref));
|
||||
lastGroup = type.group;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,16 +53,18 @@ class ServerUpdateNotification extends HookConsumerWidget {
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
serverInfoState.versionStatus.message,
|
||||
textAlign: TextAlign.start,
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: context.textTheme.labelLarge,
|
||||
Expanded(
|
||||
child: Text(
|
||||
serverInfoState.versionStatus.message,
|
||||
textAlign: TextAlign.start,
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: context.textTheme.labelLarge,
|
||||
),
|
||||
),
|
||||
if (serverInfoState.versionStatus == VersionStatus.serverOutOfDate ||
|
||||
serverInfoState.versionStatus == VersionStatus.clientOutOfDate) ...[
|
||||
const Spacer(),
|
||||
const SizedBox(width: 8),
|
||||
TextButton(
|
||||
onPressed: openUpdateLink,
|
||||
style: TextButton.styleFrom(
|
||||
|
||||
@@ -155,8 +155,8 @@ class ImmichAppBar extends ConsumerWidget implements PreferredSizeWidget {
|
||||
...actions!.map((action) => Padding(padding: const EdgeInsets.only(right: 16), child: action)),
|
||||
if (kDebugMode || kProfileMode)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.science_rounded),
|
||||
onPressed: () => context.pushRoute(const FeatInDevRoute()),
|
||||
icon: const Icon(Icons.palette_rounded),
|
||||
onPressed: () => context.pushRoute(const ImmichUIShowcaseRoute()),
|
||||
),
|
||||
if (isCasting)
|
||||
Padding(
|
||||
|
||||
@@ -74,8 +74,8 @@ class ImmichSliverAppBar extends ConsumerWidget {
|
||||
...actions!.map((action) => Padding(padding: const EdgeInsets.only(right: 16), child: action)),
|
||||
if ((kDebugMode || kProfileMode) && !isReadonlyModeEnabled)
|
||||
IconButton(
|
||||
icon: const Icon(Icons.science_rounded),
|
||||
onPressed: () => context.pushRoute(const FeatInDevRoute()),
|
||||
icon: const Icon(Icons.palette_rounded),
|
||||
onPressed: () => context.pushRoute(const ImmichUIShowcaseRoute()),
|
||||
),
|
||||
if (showUploadButton && !isReadonlyModeEnabled)
|
||||
const Padding(padding: EdgeInsets.only(right: 20), child: _BackupIndicator()),
|
||||
|
||||
@@ -2,26 +2,27 @@ import 'package:flutter/material.dart';
|
||||
import 'package:immich_mobile/extensions/build_context_extensions.dart';
|
||||
import 'package:immich_mobile/extensions/theme_extensions.dart';
|
||||
|
||||
class EntitiyCountTile extends StatelessWidget {
|
||||
class EntityCountTile extends StatelessWidget {
|
||||
final int count;
|
||||
final String label;
|
||||
final IconData icon;
|
||||
|
||||
const EntitiyCountTile({super.key, required this.count, required this.label, required this.icon});
|
||||
const EntityCountTile({super.key, required this.count, required this.label, required this.icon});
|
||||
|
||||
String zeroPadding(int number, int targetWidth) {
|
||||
final numStr = number.toString();
|
||||
return numStr.length < targetWidth ? "0" * (targetWidth - numStr.length) : "";
|
||||
}
|
||||
|
||||
int calculateMaxDigits(double availableWidth) {
|
||||
const double charWidth = 11.0;
|
||||
return (availableWidth / charWidth).floor().clamp(1, 8);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final screenWidth = MediaQuery.of(context).size.width;
|
||||
final availableWidth = (screenWidth - 32 - 8) / 2;
|
||||
const double charWidth = 11.0;
|
||||
final maxDigits = ((availableWidth - 32) / charWidth).floor().clamp(1, 8);
|
||||
|
||||
return Container(
|
||||
height: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: context.colorScheme.surfaceContainerLow,
|
||||
@@ -29,7 +30,6 @@ class EntitiyCountTile extends StatelessWidget {
|
||||
border: Border.all(width: 0.5, color: context.colorScheme.outline.withAlpha(25)),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
// Icon and Label
|
||||
@@ -38,33 +38,30 @@ class EntitiyCountTile extends StatelessWidget {
|
||||
children: [
|
||||
Icon(icon, color: context.primaryColor),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(color: context.primaryColor, fontWeight: FontWeight.bold, fontSize: 16),
|
||||
Flexible(
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(color: context.primaryColor, fontWeight: FontWeight.bold, fontSize: 16),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
// Number
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final maxDigits = calculateMaxDigits(constraints.maxWidth);
|
||||
return RichText(
|
||||
text: TextSpan(
|
||||
style: const TextStyle(fontSize: 18, fontFamily: 'OverpassMono', fontWeight: FontWeight.w600),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: zeroPadding(count, maxDigits),
|
||||
style: TextStyle(color: context.colorScheme.onSurfaceSecondary.withAlpha(75)),
|
||||
),
|
||||
TextSpan(
|
||||
text: count.toString(),
|
||||
style: TextStyle(color: context.primaryColor),
|
||||
),
|
||||
],
|
||||
const Spacer(),
|
||||
RichText(
|
||||
text: TextSpan(
|
||||
style: const TextStyle(fontSize: 18, fontFamily: 'OverpassMono', fontWeight: FontWeight.w600),
|
||||
children: [
|
||||
TextSpan(
|
||||
text: zeroPadding(count, maxDigits),
|
||||
style: TextStyle(color: context.colorScheme.onSurfaceSecondary.withAlpha(75)),
|
||||
),
|
||||
);
|
||||
},
|
||||
TextSpan(
|
||||
text: count.toString(),
|
||||
style: TextStyle(color: context.primaryColor),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -282,76 +282,87 @@ class _SyncStatsCounts extends ConsumerWidget {
|
||||
_SectionHeaderText(text: "assets".t(context: context)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
|
||||
child: Flex(
|
||||
direction: Axis.horizontal,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
Expanded(
|
||||
child: EntitiyCountTile(
|
||||
label: "local".t(context: context),
|
||||
count: localAssetCount,
|
||||
icon: Icons.smartphone,
|
||||
// 1. Wrap in IntrinsicHeight
|
||||
child: IntrinsicHeight(
|
||||
child: Flex(
|
||||
direction: Axis.horizontal,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
// 2. Stretch children vertically to fill the IntrinsicHeight
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
Expanded(
|
||||
child: EntityCountTile(
|
||||
label: "local".t(context: context),
|
||||
count: localAssetCount,
|
||||
icon: Icons.smartphone,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: EntitiyCountTile(
|
||||
label: "remote".t(context: context),
|
||||
count: remoteAssetCount,
|
||||
icon: Icons.cloud,
|
||||
Expanded(
|
||||
child: EntityCountTile(
|
||||
label: "remote".t(context: context),
|
||||
count: remoteAssetCount,
|
||||
icon: Icons.cloud,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
_SectionHeaderText(text: "albums".t(context: context)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
|
||||
child: Flex(
|
||||
direction: Axis.horizontal,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
Expanded(
|
||||
child: EntitiyCountTile(
|
||||
label: "local".t(context: context),
|
||||
count: localAlbumCount,
|
||||
icon: Icons.smartphone,
|
||||
child: IntrinsicHeight(
|
||||
child: Flex(
|
||||
direction: Axis.horizontal,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch, // Added
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
Expanded(
|
||||
child: EntityCountTile(
|
||||
label: "local".t(context: context),
|
||||
count: localAlbumCount,
|
||||
icon: Icons.smartphone,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: EntitiyCountTile(
|
||||
label: "remote".t(context: context),
|
||||
count: remoteAlbumCount,
|
||||
icon: Icons.cloud,
|
||||
Expanded(
|
||||
child: EntityCountTile(
|
||||
label: "remote".t(context: context),
|
||||
count: remoteAlbumCount,
|
||||
icon: Icons.cloud,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
_SectionHeaderText(text: "other".t(context: context)),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
|
||||
child: Flex(
|
||||
direction: Axis.horizontal,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
Expanded(
|
||||
child: EntitiyCountTile(
|
||||
label: "memories".t(context: context),
|
||||
count: memoryCount,
|
||||
icon: Icons.calendar_today,
|
||||
child: IntrinsicHeight(
|
||||
child: Flex(
|
||||
direction: Axis.horizontal,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch, // Added
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
Expanded(
|
||||
child: EntityCountTile(
|
||||
label: "memories".t(context: context),
|
||||
count: memoryCount,
|
||||
icon: Icons.calendar_today,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: EntitiyCountTile(
|
||||
label: "hashed_assets".t(context: context),
|
||||
count: localHashedCount,
|
||||
icon: Icons.tag,
|
||||
Expanded(
|
||||
child: EntityCountTile(
|
||||
label: "hashed_assets".t(context: context),
|
||||
count: localHashedCount,
|
||||
icon: Icons.tag,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
// To be removed once the experimental feature is stable
|
||||
@@ -364,26 +375,29 @@ class _SyncStatsCounts extends ConsumerWidget {
|
||||
return counts.when(
|
||||
data: (c) => Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
|
||||
child: Flex(
|
||||
direction: Axis.horizontal,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
Expanded(
|
||||
child: EntitiyCountTile(
|
||||
label: "local".t(context: context),
|
||||
count: c.total,
|
||||
icon: Icons.delete_outline,
|
||||
child: IntrinsicHeight(
|
||||
child: Flex(
|
||||
direction: Axis.horizontal,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch, // Added
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
Expanded(
|
||||
child: EntityCountTile(
|
||||
label: "local".t(context: context),
|
||||
count: c.total,
|
||||
icon: Icons.delete_outline,
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: EntitiyCountTile(
|
||||
label: "hashed_assets".t(context: context),
|
||||
count: c.hashed,
|
||||
icon: Icons.tag,
|
||||
Expanded(
|
||||
child: EntityCountTile(
|
||||
label: "hashed_assets".t(context: context),
|
||||
count: c.hashed,
|
||||
icon: Icons.tag,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
loading: () => const CircularProgressIndicator(),
|
||||
|
||||
3
mobile/packages/ui/lib/immich_ui.dart
Normal file
3
mobile/packages/ui/lib/immich_ui.dart
Normal file
@@ -0,0 +1,3 @@
|
||||
export 'src/buttons/close_button.dart';
|
||||
export 'src/buttons/icon_button.dart';
|
||||
export 'src/types.dart';
|
||||
25
mobile/packages/ui/lib/src/buttons/close_button.dart
Normal file
25
mobile/packages/ui/lib/src/buttons/close_button.dart
Normal file
@@ -0,0 +1,25 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_ui/src/buttons/icon_button.dart';
|
||||
import 'package:immich_ui/src/types.dart';
|
||||
|
||||
class ImmichCloseButton extends StatelessWidget {
|
||||
final VoidCallback? onTap;
|
||||
final ImmichVariant variant;
|
||||
final ImmichColor color;
|
||||
|
||||
const ImmichCloseButton({
|
||||
super.key,
|
||||
this.onTap,
|
||||
this.color = ImmichColor.primary,
|
||||
this.variant = ImmichVariant.ghost,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => ImmichIconButton(
|
||||
key: key,
|
||||
icon: Icons.close,
|
||||
color: color,
|
||||
variant: variant,
|
||||
onTap: onTap ?? () => Navigator.of(context).pop(),
|
||||
);
|
||||
}
|
||||
48
mobile/packages/ui/lib/src/buttons/icon_button.dart
Normal file
48
mobile/packages/ui/lib/src/buttons/icon_button.dart
Normal file
@@ -0,0 +1,48 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:immich_ui/src/types.dart';
|
||||
|
||||
class ImmichIconButton extends StatelessWidget {
|
||||
final IconData icon;
|
||||
final VoidCallback onTap;
|
||||
final ImmichVariant variant;
|
||||
final ImmichColor color;
|
||||
|
||||
const ImmichIconButton({
|
||||
super.key,
|
||||
required this.icon,
|
||||
required this.onTap,
|
||||
this.color = ImmichColor.primary,
|
||||
this.variant = ImmichVariant.filled,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final background = switch (variant) {
|
||||
ImmichVariant.filled => switch (color) {
|
||||
ImmichColor.primary => Theme.of(context).colorScheme.primary,
|
||||
ImmichColor.secondary => Theme.of(context).colorScheme.secondary,
|
||||
},
|
||||
ImmichVariant.ghost => Colors.transparent,
|
||||
};
|
||||
|
||||
final foreground = switch (variant) {
|
||||
ImmichVariant.filled => switch (color) {
|
||||
ImmichColor.primary => Theme.of(context).colorScheme.onPrimary,
|
||||
ImmichColor.secondary => Theme.of(context).colorScheme.onSecondary,
|
||||
},
|
||||
ImmichVariant.ghost => switch (color) {
|
||||
ImmichColor.primary => Theme.of(context).colorScheme.primary,
|
||||
ImmichColor.secondary => Theme.of(context).colorScheme.secondary,
|
||||
},
|
||||
};
|
||||
|
||||
return IconButton(
|
||||
icon: Icon(icon),
|
||||
onPressed: onTap,
|
||||
style: IconButton.styleFrom(
|
||||
backgroundColor: background,
|
||||
foregroundColor: foreground,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
9
mobile/packages/ui/lib/src/types.dart
Normal file
9
mobile/packages/ui/lib/src/types.dart
Normal file
@@ -0,0 +1,9 @@
|
||||
enum ImmichVariant {
|
||||
filled,
|
||||
ghost,
|
||||
}
|
||||
|
||||
enum ImmichColor {
|
||||
primary,
|
||||
secondary,
|
||||
}
|
||||
55
mobile/packages/ui/pubspec.lock
Normal file
55
mobile/packages/ui/pubspec.lock
Normal file
@@ -0,0 +1,55 @@
|
||||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.1"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.16.0"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
sdks:
|
||||
dart: ">=3.8.0-0 <4.0.0"
|
||||
12
mobile/packages/ui/pubspec.yaml
Normal file
12
mobile/packages/ui/pubspec.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
name: immich_ui
|
||||
publish_to: none
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
@@ -1015,6 +1015,13 @@ packages:
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.0"
|
||||
immich_ui:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "packages/ui"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.0"
|
||||
integration_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
|
||||
@@ -43,6 +43,8 @@ dependencies:
|
||||
hooks_riverpod: ^2.6.1
|
||||
http: ^1.5.0
|
||||
image_picker: ^1.2.0
|
||||
immich_ui:
|
||||
path: './packages/ui'
|
||||
intl: ^0.20.2
|
||||
isar:
|
||||
git:
|
||||
|
||||
252
pnpm-lock.yaml
generated
252
pnpm-lock.yaml
generated
@@ -20,8 +20,8 @@ importers:
|
||||
.github:
|
||||
devDependencies:
|
||||
prettier:
|
||||
specifier: ^3.5.3
|
||||
version: 3.7.1
|
||||
specifier: ^3.7.4
|
||||
version: 3.7.4
|
||||
|
||||
cli:
|
||||
dependencies:
|
||||
@@ -85,7 +85,7 @@ importers:
|
||||
version: 10.1.8(eslint@9.39.1(jiti@2.6.1))
|
||||
eslint-plugin-prettier:
|
||||
specifier: ^5.1.3
|
||||
version: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))(prettier@3.7.1)
|
||||
version: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))(prettier@3.7.4)
|
||||
eslint-plugin-unicorn:
|
||||
specifier: ^62.0.0
|
||||
version: 62.0.0(eslint@9.39.1(jiti@2.6.1))
|
||||
@@ -96,11 +96,11 @@ importers:
|
||||
specifier: ^5.2.0
|
||||
version: 5.5.0
|
||||
prettier:
|
||||
specifier: ^3.2.5
|
||||
version: 3.7.1
|
||||
specifier: ^3.7.4
|
||||
version: 3.7.4
|
||||
prettier-plugin-organize-imports:
|
||||
specifier: ^4.0.0
|
||||
version: 4.3.0(prettier@3.7.1)(typescript@5.9.3)
|
||||
version: 4.3.0(prettier@3.7.4)(typescript@5.9.3)
|
||||
typescript:
|
||||
specifier: ^5.3.3
|
||||
version: 5.9.3
|
||||
@@ -184,8 +184,8 @@ importers:
|
||||
specifier: ^3.7.0
|
||||
version: 3.9.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
prettier:
|
||||
specifier: ^3.2.4
|
||||
version: 3.7.1
|
||||
specifier: ^3.7.4
|
||||
version: 3.7.4
|
||||
typescript:
|
||||
specifier: ^5.1.6
|
||||
version: 5.9.3
|
||||
@@ -239,13 +239,13 @@ importers:
|
||||
version: 10.1.8(eslint@9.39.1(jiti@2.6.1))
|
||||
eslint-plugin-prettier:
|
||||
specifier: ^5.1.3
|
||||
version: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))(prettier@3.7.1)
|
||||
version: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))(prettier@3.7.4)
|
||||
eslint-plugin-unicorn:
|
||||
specifier: ^62.0.0
|
||||
version: 62.0.0(eslint@9.39.1(jiti@2.6.1))
|
||||
exiftool-vendored:
|
||||
specifier: ^33.0.0
|
||||
version: 33.5.0
|
||||
specifier: ^34.0.0
|
||||
version: 34.0.0
|
||||
globals:
|
||||
specifier: ^16.0.0
|
||||
version: 16.5.0
|
||||
@@ -265,11 +265,11 @@ importers:
|
||||
specifier: ^7.0.0
|
||||
version: 7.0.0
|
||||
prettier:
|
||||
specifier: ^3.2.5
|
||||
version: 3.7.1
|
||||
specifier: ^3.7.4
|
||||
version: 3.7.4
|
||||
prettier-plugin-organize-imports:
|
||||
specifier: ^4.0.0
|
||||
version: 4.3.0(prettier@3.7.1)(typescript@5.9.3)
|
||||
version: 4.3.0(prettier@3.7.4)(typescript@5.9.3)
|
||||
sharp:
|
||||
specifier: ^0.34.5
|
||||
version: 0.34.5
|
||||
@@ -428,8 +428,8 @@ importers:
|
||||
specifier: 4.3.3
|
||||
version: 4.3.3
|
||||
exiftool-vendored:
|
||||
specifier: ^33.0.0
|
||||
version: 33.5.0
|
||||
specifier: ^34.0.0
|
||||
version: 34.0.0
|
||||
express:
|
||||
specifier: ^5.1.0
|
||||
version: 5.2.0
|
||||
@@ -655,7 +655,7 @@ importers:
|
||||
version: 10.1.8(eslint@9.39.1(jiti@2.6.1))
|
||||
eslint-plugin-prettier:
|
||||
specifier: ^5.1.3
|
||||
version: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))(prettier@3.7.1)
|
||||
version: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))(prettier@3.7.4)
|
||||
eslint-plugin-unicorn:
|
||||
specifier: ^62.0.0
|
||||
version: 62.0.0(eslint@9.39.1(jiti@2.6.1))
|
||||
@@ -672,11 +672,11 @@ importers:
|
||||
specifier: ^7.0.0
|
||||
version: 7.0.0
|
||||
prettier:
|
||||
specifier: ^3.0.2
|
||||
version: 3.7.1
|
||||
specifier: ^3.7.4
|
||||
version: 3.7.4
|
||||
prettier-plugin-organize-imports:
|
||||
specifier: ^4.0.0
|
||||
version: 4.3.0(prettier@3.7.1)(typescript@5.9.3)
|
||||
version: 4.3.0(prettier@3.7.4)(typescript@5.9.3)
|
||||
sql-formatter:
|
||||
specifier: ^15.0.0
|
||||
version: 15.6.10
|
||||
@@ -717,8 +717,8 @@ importers:
|
||||
specifier: file:../open-api/typescript-sdk
|
||||
version: link:../open-api/typescript-sdk
|
||||
'@immich/ui':
|
||||
specifier: ^0.50.0
|
||||
version: 0.50.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)
|
||||
specifier: ^0.50.1
|
||||
version: 0.50.1(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.43.3)
|
||||
'@mapbox/mapbox-gl-rtl-text':
|
||||
specifier: 0.2.3
|
||||
version: 0.2.3(mapbox-gl@1.13.3)
|
||||
@@ -751,7 +751,7 @@ importers:
|
||||
version: 0.41.4
|
||||
'@zoom-image/svelte':
|
||||
specifier: ^0.3.0
|
||||
version: 0.3.8(svelte@5.45.2)
|
||||
version: 0.3.8(svelte@5.43.3)
|
||||
async-mutex:
|
||||
specifier: ^0.5.0
|
||||
version: 0.5.0
|
||||
@@ -805,13 +805,13 @@ importers:
|
||||
version: 5.2.2
|
||||
svelte-i18n:
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.1(svelte@5.45.2)
|
||||
version: 4.0.1(svelte@5.43.3)
|
||||
svelte-maplibre:
|
||||
specifier: ^1.2.5
|
||||
version: 1.2.5(svelte@5.45.2)
|
||||
version: 1.2.5(svelte@5.43.3)
|
||||
svelte-persisted-store:
|
||||
specifier: ^0.12.0
|
||||
version: 0.12.0(svelte@5.45.2)
|
||||
version: 0.12.0(svelte@5.43.3)
|
||||
tabbable:
|
||||
specifier: ^6.2.0
|
||||
version: 6.3.0
|
||||
@@ -836,16 +836,16 @@ importers:
|
||||
version: 3.1.2
|
||||
'@sveltejs/adapter-static':
|
||||
specifier: ^3.0.8
|
||||
version: 3.0.10(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))
|
||||
version: 3.0.10(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))
|
||||
'@sveltejs/enhanced-img':
|
||||
specifier: ^0.9.0
|
||||
version: 0.9.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(rollup@4.53.3)(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))
|
||||
version: 0.9.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(rollup@4.53.3)(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))
|
||||
'@sveltejs/kit':
|
||||
specifier: ^2.27.1
|
||||
version: 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))
|
||||
version: 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))
|
||||
'@sveltejs/vite-plugin-svelte':
|
||||
specifier: 6.2.1
|
||||
version: 6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))
|
||||
version: 6.2.1(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))
|
||||
'@tailwindcss/vite':
|
||||
specifier: ^4.1.7
|
||||
version: 4.1.17(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))
|
||||
@@ -854,7 +854,7 @@ importers:
|
||||
version: 6.9.1
|
||||
'@testing-library/svelte':
|
||||
specifier: ^5.2.8
|
||||
version: 5.2.9(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))
|
||||
version: 5.2.9(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))
|
||||
'@testing-library/user-event':
|
||||
specifier: ^14.5.2
|
||||
version: 14.6.1(@testing-library/dom@10.4.1)
|
||||
@@ -893,7 +893,7 @@ importers:
|
||||
version: 6.0.2(eslint@9.39.1(jiti@2.6.1))
|
||||
eslint-plugin-svelte:
|
||||
specifier: ^3.12.4
|
||||
version: 3.13.0(eslint@9.39.1(jiti@2.6.1))(svelte@5.45.2)
|
||||
version: 3.13.0(eslint@9.39.1(jiti@2.6.1))(svelte@5.43.3)
|
||||
eslint-plugin-unicorn:
|
||||
specifier: ^62.0.0
|
||||
version: 62.0.0(eslint@9.39.1(jiti@2.6.1))
|
||||
@@ -904,29 +904,29 @@ importers:
|
||||
specifier: ^16.0.0
|
||||
version: 16.5.0
|
||||
prettier:
|
||||
specifier: ^3.4.2
|
||||
version: 3.7.1
|
||||
specifier: ^3.7.4
|
||||
version: 3.7.4
|
||||
prettier-plugin-organize-imports:
|
||||
specifier: ^4.0.0
|
||||
version: 4.3.0(prettier@3.7.1)(typescript@5.9.3)
|
||||
version: 4.3.0(prettier@3.7.4)(typescript@5.9.3)
|
||||
prettier-plugin-sort-json:
|
||||
specifier: ^4.1.1
|
||||
version: 4.1.1(prettier@3.7.1)
|
||||
version: 4.1.1(prettier@3.7.4)
|
||||
prettier-plugin-svelte:
|
||||
specifier: ^3.3.3
|
||||
version: 3.4.0(prettier@3.7.1)(svelte@5.45.2)
|
||||
version: 3.4.0(prettier@3.7.4)(svelte@5.43.3)
|
||||
rollup-plugin-visualizer:
|
||||
specifier: ^6.0.0
|
||||
version: 6.0.5(rollup@4.53.3)
|
||||
svelte:
|
||||
specifier: 5.45.2
|
||||
version: 5.45.2
|
||||
specifier: 5.43.3
|
||||
version: 5.43.3
|
||||
svelte-check:
|
||||
specifier: ^4.1.5
|
||||
version: 4.3.4(picomatch@4.0.3)(svelte@5.45.2)(typescript@5.9.3)
|
||||
version: 4.3.4(picomatch@4.0.3)(svelte@5.43.3)(typescript@5.9.3)
|
||||
svelte-eslint-parser:
|
||||
specifier: ^1.3.3
|
||||
version: 1.4.0(svelte@5.45.2)
|
||||
version: 1.4.0(svelte@5.43.3)
|
||||
tailwindcss:
|
||||
specifier: ^4.1.7
|
||||
version: 4.1.17
|
||||
@@ -2989,8 +2989,8 @@ packages:
|
||||
peerDependencies:
|
||||
svelte: ^5.0.0
|
||||
|
||||
'@immich/ui@0.50.0':
|
||||
resolution: {integrity: sha512-7AW9SRZTAgal8xlkUAxm7o4+pSG7HcKb+Bh9JpWLaDRRdGyPCZMmsNa9CjZglOQ7wkAD07tQ9u4+zezBLe0dlQ==}
|
||||
'@immich/ui@0.50.1':
|
||||
resolution: {integrity: sha512-fNlQGh75ZFa/UZAgJaYk9/ItHOXHNNzN4CunjCmE7WocVVkUZbUxopN9Ku3F5GULSqD/zJ5gNO6PQAZ1ZoSaaQ==}
|
||||
peerDependencies:
|
||||
svelte: ^5.0.0
|
||||
|
||||
@@ -3236,6 +3236,7 @@ packages:
|
||||
'@koa/router@14.0.0':
|
||||
resolution: {integrity: sha512-LBSu5K0qAaaQcXX/0WIB9PGDevyCxxpnc1uq13vV/CgObaVxuis5hKl3Eboq/8gcb6ebnkAStW9NB/Em2eYyFA==}
|
||||
engines: {node: '>= 20'}
|
||||
deprecated: Please upgrade to v15 or higher. All reported bugs in this version are fixed in newer releases, dependencies have been updated, and security has been improved.
|
||||
|
||||
'@koddsson/eslint-plugin-tscompat@0.2.0':
|
||||
resolution: {integrity: sha512-Oqd4kWSX0LiO9wWHjcmDfXZNC7TotFV/tLRhwCFU3XUeb//KYvJ75c9OmeSJ+vBv5lkCeB+xYsqyNrBc5j18XA==}
|
||||
@@ -5503,8 +5504,8 @@ packages:
|
||||
resolution: {integrity: sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==}
|
||||
hasBin: true
|
||||
|
||||
batch-cluster@15.0.1:
|
||||
resolution: {integrity: sha512-eUmh0ld1AUPKTEmdzwGF9QTSexXAyt9rA1F5zDfW1wUi3okA3Tal4NLdCeFI6aiKpBenQhR6NmK9bW9tBHTGPQ==}
|
||||
batch-cluster@16.0.0:
|
||||
resolution: {integrity: sha512-+T7Ho09ikx/kP4P8M+GEnpuePzRQa4gTUhtPIu6ApFC8+0GY0sri1y1PuB+yfXlQWl5DkHC/e58z3U6g0qCz/A==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
batch@0.6.1:
|
||||
@@ -6848,17 +6849,17 @@ packages:
|
||||
resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
exiftool-vendored.exe@13.42.0:
|
||||
resolution: {integrity: sha512-6AFybe5IakduMWleuQBfep9OWGSVZSedt2uKL+LzufRsATp+beOF7tZyKtMztjb6VRH1GF/4F9EvBVam6zm70w==}
|
||||
exiftool-vendored.exe@13.43.0:
|
||||
resolution: {integrity: sha512-EENHNz86tYY5yHGPtGB2mto3FIGstQvEhrcU34f7fm4RMxBKNfTWYOGkhU1jzvjOi+V4575LQX/FUES1TwgUbQ==}
|
||||
os: [win32]
|
||||
|
||||
exiftool-vendored.pl@13.42.0:
|
||||
resolution: {integrity: sha512-EF5IdxQNIJIvZjHf4bG4jnwAHVVSLkYZToo2q+Mm89kSuppKfRvHz/lngIxN0JALE8rFdC4zt6NWY/PKqRdCcg==}
|
||||
exiftool-vendored.pl@13.43.0:
|
||||
resolution: {integrity: sha512-0ApWaQ/pxaliPK7HzTxVA0sg/wZ8vl7UtFVhCyWhGQg01WfZkFrKwKmELB0Bnn01WTfgIuMadba8ccmFvpmJag==}
|
||||
os: ['!win32']
|
||||
hasBin: true
|
||||
|
||||
exiftool-vendored@33.5.0:
|
||||
resolution: {integrity: sha512-7cCh6izwdmC5ZaCxpHFehnExIr2Yp7CJuxHg4WFiGcm81yyxXLtvSE+85ep9VsNwhlOtSpk+XxiqrlddjY5lAw==}
|
||||
exiftool-vendored@34.0.0:
|
||||
resolution: {integrity: sha512-rhIe4XGE7kh76nwytwHtq6qK/pc1mpOBHRV++gudFeG2PfAp3XIVQbFWCLK3S4l9I4AWYOe4mxk8mW8l1oHRTw==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
expect-type@1.2.1:
|
||||
@@ -9765,8 +9766,8 @@ packages:
|
||||
prettier: ^3.0.0
|
||||
svelte: ^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0
|
||||
|
||||
prettier@3.7.1:
|
||||
resolution: {integrity: sha512-RWKXE4qB3u5Z6yz7omJkjWwmTfLdcbv44jUVHC5NpfXwFGzvpQM798FGv/6WNK879tc+Cn0AAyherCl1KjbyZQ==}
|
||||
prettier@3.7.4:
|
||||
resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==}
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
|
||||
@@ -10780,8 +10781,8 @@ packages:
|
||||
peerDependencies:
|
||||
svelte: ^5.30.2
|
||||
|
||||
svelte@5.45.2:
|
||||
resolution: {integrity: sha512-yyXdW2u3H0H/zxxWoGwJoQlRgaSJLp+Vhktv12iRw2WRDlKqUPT54Fi0K/PkXqrdkcQ98aBazpy0AH4BCBVfoA==}
|
||||
svelte@5.43.3:
|
||||
resolution: {integrity: sha512-kjkAjCk41mJfvJZG56XcJNOdJSke94JxtcX8zFzzz2vrt47E0LnoBzU6azIZ1aBxJgUep8qegAkguSf1GjxLXQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
svg-parser@2.0.4:
|
||||
@@ -14517,7 +14518,7 @@ snapshots:
|
||||
'@fig/complete-commander@3.2.0(commander@11.1.0)':
|
||||
dependencies:
|
||||
commander: 11.1.0
|
||||
prettier: 3.7.1
|
||||
prettier: 3.7.4
|
||||
|
||||
'@floating-ui/core@1.7.3':
|
||||
dependencies:
|
||||
@@ -14696,19 +14697,19 @@ snapshots:
|
||||
|
||||
'@immich/justified-layout-wasm@0.4.3': {}
|
||||
|
||||
'@immich/svelte-markdown-preprocess@0.1.0(svelte@5.45.2)':
|
||||
'@immich/svelte-markdown-preprocess@0.1.0(svelte@5.43.3)':
|
||||
dependencies:
|
||||
svelte: 5.45.2
|
||||
svelte: 5.43.3
|
||||
|
||||
'@immich/ui@0.50.0(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)':
|
||||
'@immich/ui@0.50.1(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.43.3)':
|
||||
dependencies:
|
||||
'@immich/svelte-markdown-preprocess': 0.1.0(svelte@5.45.2)
|
||||
'@immich/svelte-markdown-preprocess': 0.1.0(svelte@5.43.3)
|
||||
'@internationalized/date': 3.10.0
|
||||
'@mdi/js': 7.4.47
|
||||
bits-ui: 2.14.4(@internationalized/date@3.10.0)(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)
|
||||
bits-ui: 2.14.4(@internationalized/date@3.10.0)(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.43.3)
|
||||
luxon: 3.7.2
|
||||
simple-icons: 15.22.0
|
||||
svelte: 5.45.2
|
||||
svelte: 5.43.3
|
||||
svelte-highlight: 7.9.0
|
||||
tailwind-merge: 3.4.0
|
||||
tailwind-variants: 3.2.2(tailwind-merge@3.4.0)(tailwindcss@4.1.17)
|
||||
@@ -15788,7 +15789,7 @@ snapshots:
|
||||
'@react-email/render@1.4.0(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
|
||||
dependencies:
|
||||
html-to-text: 9.0.5
|
||||
prettier: 3.7.1
|
||||
prettier: 3.7.4
|
||||
react: 19.2.0
|
||||
react-dom: 19.2.0(react@19.2.0)
|
||||
react-promise-suspense: 0.3.4
|
||||
@@ -16211,17 +16212,17 @@ snapshots:
|
||||
dependencies:
|
||||
acorn: 8.15.0
|
||||
|
||||
'@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))':
|
||||
'@sveltejs/adapter-static@3.0.10(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))':
|
||||
dependencies:
|
||||
'@sveltejs/kit': 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))
|
||||
'@sveltejs/kit': 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))
|
||||
|
||||
'@sveltejs/enhanced-img@0.9.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(rollup@4.53.3)(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))':
|
||||
'@sveltejs/enhanced-img@0.9.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(rollup@4.53.3)(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))':
|
||||
dependencies:
|
||||
'@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))
|
||||
'@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))
|
||||
magic-string: 0.30.21
|
||||
sharp: 0.34.5
|
||||
svelte: 5.45.2
|
||||
svelte-parse-markup: 0.1.5(svelte@5.45.2)
|
||||
svelte: 5.43.3
|
||||
svelte-parse-markup: 0.1.5(svelte@5.43.3)
|
||||
vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)
|
||||
vite-imagetools: 9.0.2(rollup@4.53.3)
|
||||
zimmerframe: 1.1.4
|
||||
@@ -16229,11 +16230,11 @@ snapshots:
|
||||
- rollup
|
||||
- supports-color
|
||||
|
||||
'@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))':
|
||||
'@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))':
|
||||
dependencies:
|
||||
'@standard-schema/spec': 1.0.0
|
||||
'@sveltejs/acorn-typescript': 1.0.8(acorn@8.15.0)
|
||||
'@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))
|
||||
'@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))
|
||||
'@types/cookie': 0.6.0
|
||||
acorn: 8.15.0
|
||||
cookie: 0.6.0
|
||||
@@ -16245,27 +16246,27 @@ snapshots:
|
||||
sade: 1.8.1
|
||||
set-cookie-parser: 2.7.2
|
||||
sirv: 3.0.2
|
||||
svelte: 5.45.2
|
||||
svelte: 5.43.3
|
||||
vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)
|
||||
optionalDependencies:
|
||||
'@opentelemetry/api': 1.9.0
|
||||
|
||||
'@sveltejs/vite-plugin-svelte-inspector@5.0.0(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))':
|
||||
'@sveltejs/vite-plugin-svelte-inspector@5.0.0(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))':
|
||||
dependencies:
|
||||
'@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))
|
||||
'@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))
|
||||
debug: 4.4.3
|
||||
svelte: 5.45.2
|
||||
svelte: 5.43.3
|
||||
vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))':
|
||||
'@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))':
|
||||
dependencies:
|
||||
'@sveltejs/vite-plugin-svelte-inspector': 5.0.0(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))
|
||||
'@sveltejs/vite-plugin-svelte-inspector': 5.0.0(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))
|
||||
debug: 4.4.3
|
||||
deepmerge: 4.3.1
|
||||
magic-string: 0.30.21
|
||||
svelte: 5.45.2
|
||||
svelte: 5.43.3
|
||||
vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)
|
||||
vitefu: 1.1.1(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))
|
||||
transitivePeerDependencies:
|
||||
@@ -16513,10 +16514,10 @@ snapshots:
|
||||
picocolors: 1.1.1
|
||||
redent: 3.0.0
|
||||
|
||||
'@testing-library/svelte@5.2.9(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))':
|
||||
'@testing-library/svelte@5.2.9(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))(vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))':
|
||||
dependencies:
|
||||
'@testing-library/dom': 10.4.1
|
||||
svelte: 5.45.2
|
||||
svelte: 5.43.3
|
||||
optionalDependencies:
|
||||
vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)
|
||||
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.1)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@26.1.0(canvas@2.11.2))(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)
|
||||
@@ -17236,10 +17237,10 @@ snapshots:
|
||||
dependencies:
|
||||
'@namnode/store': 0.1.0
|
||||
|
||||
'@zoom-image/svelte@0.3.8(svelte@5.45.2)':
|
||||
'@zoom-image/svelte@0.3.8(svelte@5.43.3)':
|
||||
dependencies:
|
||||
'@zoom-image/core': 0.41.4
|
||||
svelte: 5.45.2
|
||||
svelte: 5.43.3
|
||||
|
||||
abab@2.0.6:
|
||||
optional: true
|
||||
@@ -17580,7 +17581,7 @@ snapshots:
|
||||
|
||||
baseline-browser-mapping@2.8.31: {}
|
||||
|
||||
batch-cluster@15.0.1: {}
|
||||
batch-cluster@16.0.0: {}
|
||||
|
||||
batch@0.6.1: {}
|
||||
|
||||
@@ -17602,15 +17603,15 @@ snapshots:
|
||||
|
||||
binary-extensions@2.3.0: {}
|
||||
|
||||
bits-ui@2.14.4(@internationalized/date@3.10.0)(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2):
|
||||
bits-ui@2.14.4(@internationalized/date@3.10.0)(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.43.3):
|
||||
dependencies:
|
||||
'@floating-ui/core': 1.7.3
|
||||
'@floating-ui/dom': 1.7.4
|
||||
'@internationalized/date': 3.10.0
|
||||
esm-env: 1.2.2
|
||||
runed: 0.35.1(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)
|
||||
svelte: 5.45.2
|
||||
svelte-toolbelt: 0.10.6(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)
|
||||
runed: 0.35.1(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.43.3)
|
||||
svelte: 5.43.3
|
||||
svelte-toolbelt: 0.10.6(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.43.3)
|
||||
tabbable: 6.3.0
|
||||
transitivePeerDependencies:
|
||||
- '@sveltejs/kit'
|
||||
@@ -18907,17 +18908,17 @@ snapshots:
|
||||
lodash.memoize: 4.1.2
|
||||
semver: 7.7.3
|
||||
|
||||
eslint-plugin-prettier@5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))(prettier@3.7.1):
|
||||
eslint-plugin-prettier@5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@10.1.8(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))(prettier@3.7.4):
|
||||
dependencies:
|
||||
eslint: 9.39.1(jiti@2.6.1)
|
||||
prettier: 3.7.1
|
||||
prettier: 3.7.4
|
||||
prettier-linter-helpers: 1.0.0
|
||||
synckit: 0.11.11
|
||||
optionalDependencies:
|
||||
'@types/eslint': 9.6.1
|
||||
eslint-config-prettier: 10.1.8(eslint@9.39.1(jiti@2.6.1))
|
||||
|
||||
eslint-plugin-svelte@3.13.0(eslint@9.39.1(jiti@2.6.1))(svelte@5.45.2):
|
||||
eslint-plugin-svelte@3.13.0(eslint@9.39.1(jiti@2.6.1))(svelte@5.43.3):
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1))
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
@@ -18929,9 +18930,9 @@ snapshots:
|
||||
postcss-load-config: 3.1.4(postcss@8.5.6)
|
||||
postcss-safe-parser: 7.0.1(postcss@8.5.6)
|
||||
semver: 7.7.3
|
||||
svelte-eslint-parser: 1.4.0(svelte@5.45.2)
|
||||
svelte-eslint-parser: 1.4.0(svelte@5.43.3)
|
||||
optionalDependencies:
|
||||
svelte: 5.45.2
|
||||
svelte: 5.43.3
|
||||
transitivePeerDependencies:
|
||||
- ts-node
|
||||
|
||||
@@ -19128,21 +19129,21 @@ snapshots:
|
||||
signal-exit: 3.0.7
|
||||
strip-final-newline: 2.0.0
|
||||
|
||||
exiftool-vendored.exe@13.42.0:
|
||||
exiftool-vendored.exe@13.43.0:
|
||||
optional: true
|
||||
|
||||
exiftool-vendored.pl@13.42.0: {}
|
||||
exiftool-vendored.pl@13.43.0: {}
|
||||
|
||||
exiftool-vendored@33.5.0:
|
||||
exiftool-vendored@34.0.0:
|
||||
dependencies:
|
||||
'@photostructure/tz-lookup': 11.3.0
|
||||
'@types/luxon': 3.7.1
|
||||
batch-cluster: 15.0.1
|
||||
exiftool-vendored.pl: 13.42.0
|
||||
batch-cluster: 16.0.0
|
||||
exiftool-vendored.pl: 13.43.0
|
||||
he: 1.2.0
|
||||
luxon: 3.7.2
|
||||
optionalDependencies:
|
||||
exiftool-vendored.exe: 13.42.0
|
||||
exiftool-vendored.exe: 13.43.0
|
||||
|
||||
expect-type@1.2.1: {}
|
||||
|
||||
@@ -22636,21 +22637,21 @@ snapshots:
|
||||
dependencies:
|
||||
fast-diff: 1.3.0
|
||||
|
||||
prettier-plugin-organize-imports@4.3.0(prettier@3.7.1)(typescript@5.9.3):
|
||||
prettier-plugin-organize-imports@4.3.0(prettier@3.7.4)(typescript@5.9.3):
|
||||
dependencies:
|
||||
prettier: 3.7.1
|
||||
prettier: 3.7.4
|
||||
typescript: 5.9.3
|
||||
|
||||
prettier-plugin-sort-json@4.1.1(prettier@3.7.1):
|
||||
prettier-plugin-sort-json@4.1.1(prettier@3.7.4):
|
||||
dependencies:
|
||||
prettier: 3.7.1
|
||||
prettier: 3.7.4
|
||||
|
||||
prettier-plugin-svelte@3.4.0(prettier@3.7.1)(svelte@5.45.2):
|
||||
prettier-plugin-svelte@3.4.0(prettier@3.7.4)(svelte@5.43.3):
|
||||
dependencies:
|
||||
prettier: 3.7.1
|
||||
svelte: 5.45.2
|
||||
prettier: 3.7.4
|
||||
svelte: 5.43.3
|
||||
|
||||
prettier@3.7.1: {}
|
||||
prettier@3.7.4: {}
|
||||
|
||||
pretty-error@4.0.0:
|
||||
dependencies:
|
||||
@@ -23250,14 +23251,14 @@ snapshots:
|
||||
dependencies:
|
||||
queue-microtask: 1.2.3
|
||||
|
||||
runed@0.35.1(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2):
|
||||
runed@0.35.1(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.43.3):
|
||||
dependencies:
|
||||
dequal: 2.0.3
|
||||
esm-env: 1.2.2
|
||||
lz-string: 1.5.0
|
||||
svelte: 5.45.2
|
||||
svelte: 5.43.3
|
||||
optionalDependencies:
|
||||
'@sveltejs/kit': 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))
|
||||
'@sveltejs/kit': 2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2))
|
||||
|
||||
rw@1.3.3: {}
|
||||
|
||||
@@ -23895,19 +23896,19 @@ snapshots:
|
||||
|
||||
supports-preserve-symlinks-flag@1.0.0: {}
|
||||
|
||||
svelte-check@4.3.4(picomatch@4.0.3)(svelte@5.45.2)(typescript@5.9.3):
|
||||
svelte-check@4.3.4(picomatch@4.0.3)(svelte@5.43.3)(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
chokidar: 4.0.3
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
picocolors: 1.1.1
|
||||
sade: 1.8.1
|
||||
svelte: 5.45.2
|
||||
svelte: 5.43.3
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- picomatch
|
||||
|
||||
svelte-eslint-parser@1.4.0(svelte@5.45.2):
|
||||
svelte-eslint-parser@1.4.0(svelte@5.43.3):
|
||||
dependencies:
|
||||
eslint-scope: 8.4.0
|
||||
eslint-visitor-keys: 4.2.1
|
||||
@@ -23916,7 +23917,7 @@ snapshots:
|
||||
postcss-scss: 4.0.9(postcss@8.5.6)
|
||||
postcss-selector-parser: 7.1.0
|
||||
optionalDependencies:
|
||||
svelte: 5.45.2
|
||||
svelte: 5.43.3
|
||||
|
||||
svelte-gestures@5.2.2: {}
|
||||
|
||||
@@ -23924,7 +23925,7 @@ snapshots:
|
||||
dependencies:
|
||||
highlight.js: 11.11.1
|
||||
|
||||
svelte-i18n@4.0.1(svelte@5.45.2):
|
||||
svelte-i18n@4.0.1(svelte@5.43.3):
|
||||
dependencies:
|
||||
cli-color: 2.0.4
|
||||
deepmerge: 4.3.1
|
||||
@@ -23932,36 +23933,36 @@ snapshots:
|
||||
estree-walker: 2.0.2
|
||||
intl-messageformat: 10.7.18
|
||||
sade: 1.8.1
|
||||
svelte: 5.45.2
|
||||
svelte: 5.43.3
|
||||
tiny-glob: 0.2.9
|
||||
|
||||
svelte-maplibre@1.2.5(svelte@5.45.2):
|
||||
svelte-maplibre@1.2.5(svelte@5.43.3):
|
||||
dependencies:
|
||||
d3-geo: 3.1.1
|
||||
dequal: 2.0.3
|
||||
just-compare: 2.3.0
|
||||
maplibre-gl: 5.13.0
|
||||
pmtiles: 3.2.1
|
||||
svelte: 5.45.2
|
||||
svelte: 5.43.3
|
||||
|
||||
svelte-parse-markup@0.1.5(svelte@5.45.2):
|
||||
svelte-parse-markup@0.1.5(svelte@5.43.3):
|
||||
dependencies:
|
||||
svelte: 5.45.2
|
||||
svelte: 5.43.3
|
||||
|
||||
svelte-persisted-store@0.12.0(svelte@5.45.2):
|
||||
svelte-persisted-store@0.12.0(svelte@5.43.3):
|
||||
dependencies:
|
||||
svelte: 5.45.2
|
||||
svelte: 5.43.3
|
||||
|
||||
svelte-toolbelt@0.10.6(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2):
|
||||
svelte-toolbelt@0.10.6(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.43.3):
|
||||
dependencies:
|
||||
clsx: 2.1.1
|
||||
runed: 0.35.1(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.45.2)
|
||||
runed: 0.35.1(@sveltejs/kit@2.49.0(@opentelemetry/api@1.9.0)(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.43.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(yaml@2.8.2)))(svelte@5.43.3)
|
||||
style-to-object: 1.0.14
|
||||
svelte: 5.45.2
|
||||
svelte: 5.43.3
|
||||
transitivePeerDependencies:
|
||||
- '@sveltejs/kit'
|
||||
|
||||
svelte@5.45.2:
|
||||
svelte@5.43.3:
|
||||
dependencies:
|
||||
'@jridgewell/remapping': 2.3.5
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
@@ -23971,7 +23972,6 @@ snapshots:
|
||||
aria-query: 5.3.2
|
||||
axobject-query: 4.1.0
|
||||
clsx: 2.1.1
|
||||
devalue: 5.5.0
|
||||
esm-env: 1.2.2
|
||||
esrap: 2.2.0
|
||||
is-reference: 3.0.3
|
||||
|
||||
@@ -50,13 +50,15 @@ RUN --mount=type=cache,id=pnpm-cli,target=/buildcache/pnpm-store \
|
||||
|
||||
FROM builder AS plugins
|
||||
|
||||
ARG TARGETPLATFORM
|
||||
|
||||
COPY --from=ghcr.io/jdx/mise:2025.11.3@sha256:ac26f5978c0e2783f3e68e58ce75eddb83e41b89bf8747c503bac2aa9baf22c5 /usr/local/bin/mise /usr/local/bin/mise
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
COPY ./plugins/mise.toml ./plugins/
|
||||
ENV MISE_TRUSTED_CONFIG_PATHS=/usr/src/app/plugins/mise.toml
|
||||
ENV MISE_DATA_DIR=/buildcache/mise
|
||||
RUN --mount=type=cache,id=mise-tools,target=/buildcache/mise \
|
||||
RUN --mount=type=cache,id=mise-tools-${TARGETPLATFORM},target=/buildcache/mise \
|
||||
mise install --cd plugins
|
||||
|
||||
COPY ./plugins ./plugins/
|
||||
@@ -66,7 +68,7 @@ RUN --mount=type=cache,id=pnpm-plugins,target=/buildcache/pnpm-store \
|
||||
--mount=type=bind,source=.pnpmfile.cjs,target=.pnpmfile.cjs \
|
||||
--mount=type=bind,source=pnpm-lock.yaml,target=pnpm-lock.yaml \
|
||||
--mount=type=bind,source=pnpm-workspace.yaml,target=pnpm-workspace.yaml \
|
||||
--mount=type=cache,id=mise-tools,target=/buildcache/mise \
|
||||
--mount=type=cache,id=mise-tools-${TARGETPLATFORM},target=/buildcache/mise \
|
||||
cd plugins && mise run build
|
||||
|
||||
FROM ghcr.io/immich-app/base-server-prod:202511261514@sha256:c04c1c38dd90e53455b180aedf93c3c63474c8d20ffe2c6d7a3a61a2181e6d29
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
"cookie": "^1.0.2",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cron": "4.3.3",
|
||||
"exiftool-vendored": "^33.0.0",
|
||||
"exiftool-vendored": "^34.0.0",
|
||||
"express": "^5.1.0",
|
||||
"fast-glob": "^3.3.2",
|
||||
"fluent-ffmpeg": "^2.1.2",
|
||||
@@ -153,7 +153,7 @@
|
||||
"mock-fs": "^5.2.0",
|
||||
"node-gyp": "^12.0.0",
|
||||
"pngjs": "^7.0.0",
|
||||
"prettier": "^3.0.2",
|
||||
"prettier": "^3.7.4",
|
||||
"prettier-plugin-organize-imports": "^4.0.0",
|
||||
"sql-formatter": "^15.0.0",
|
||||
"supertest": "^7.1.0",
|
||||
|
||||
@@ -2,7 +2,7 @@ import { asOptions } from 'src/sql-tools/helpers';
|
||||
import { register } from 'src/sql-tools/register';
|
||||
import { ColumnStorage, ColumnType, DatabaseEnum } from 'src/sql-tools/types';
|
||||
|
||||
export type ColumnValue = null | boolean | string | number | object | Date | (() => string);
|
||||
export type ColumnValue = null | boolean | string | number | Array<unknown> | object | Date | (() => string);
|
||||
|
||||
export type ColumnBaseOptions = {
|
||||
name?: string;
|
||||
|
||||
@@ -39,6 +39,10 @@ export const fromColumnValue = (columnValue?: ColumnValue) => {
|
||||
return `'${value.toISOString()}'`;
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return "'{}'";
|
||||
}
|
||||
|
||||
return `'${String(value)}'`;
|
||||
};
|
||||
|
||||
|
||||
@@ -394,6 +394,20 @@ describe(schemaDiff.name, () => {
|
||||
|
||||
expect(diff.items).toEqual([]);
|
||||
});
|
||||
|
||||
it('should support arrays, ignoring types', () => {
|
||||
const diff = schemaDiff(
|
||||
fromColumn({ name: 'column1', type: 'character varying', isArray: true, default: "'{}'" }),
|
||||
fromColumn({
|
||||
name: 'column1',
|
||||
type: 'character varying',
|
||||
isArray: true,
|
||||
default: "'{}'::character varying[]",
|
||||
}),
|
||||
);
|
||||
|
||||
expect(diff.items).toEqual([]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
40
server/test/sql-tools/column-default-array.stub.ts
Normal file
40
server/test/sql-tools/column-default-array.stub.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { Column, DatabaseSchema, Table } from 'src/sql-tools';
|
||||
|
||||
@Table()
|
||||
export class Table1 {
|
||||
@Column({ type: 'character varying', array: true, default: [] })
|
||||
column1!: string[];
|
||||
}
|
||||
|
||||
export const description = 'should register a table with a column with a default value (array)';
|
||||
export const schema: DatabaseSchema = {
|
||||
databaseName: 'postgres',
|
||||
schemaName: 'public',
|
||||
functions: [],
|
||||
enums: [],
|
||||
extensions: [],
|
||||
parameters: [],
|
||||
overrides: [],
|
||||
tables: [
|
||||
{
|
||||
name: 'table1',
|
||||
columns: [
|
||||
{
|
||||
name: 'column1',
|
||||
tableName: 'table1',
|
||||
type: 'character varying',
|
||||
nullable: false,
|
||||
isArray: true,
|
||||
primary: false,
|
||||
synchronize: true,
|
||||
default: "'{}'",
|
||||
},
|
||||
],
|
||||
indexes: [],
|
||||
triggers: [],
|
||||
constraints: [],
|
||||
synchronize: true,
|
||||
},
|
||||
],
|
||||
warnings: [],
|
||||
};
|
||||
@@ -28,7 +28,7 @@
|
||||
"@formatjs/icu-messageformat-parser": "^2.9.8",
|
||||
"@immich/justified-layout-wasm": "^0.4.3",
|
||||
"@immich/sdk": "file:../open-api/typescript-sdk",
|
||||
"@immich/ui": "^0.50.0",
|
||||
"@immich/ui": "^0.50.1",
|
||||
"@mapbox/mapbox-gl-rtl-text": "0.2.3",
|
||||
"@mdi/js": "^7.4.47",
|
||||
"@photo-sphere-viewer/core": "^5.14.0",
|
||||
@@ -93,12 +93,12 @@
|
||||
"factory.ts": "^1.4.1",
|
||||
"globals": "^16.0.0",
|
||||
"happy-dom": "^20.0.0",
|
||||
"prettier": "^3.4.2",
|
||||
"prettier": "^3.7.4",
|
||||
"prettier-plugin-organize-imports": "^4.0.0",
|
||||
"prettier-plugin-sort-json": "^4.1.1",
|
||||
"prettier-plugin-svelte": "^3.3.3",
|
||||
"rollup-plugin-visualizer": "^6.0.0",
|
||||
"svelte": "5.45.2",
|
||||
"svelte": "5.43.3",
|
||||
"svelte-check": "^4.1.5",
|
||||
"svelte-eslint-parser": "^1.3.3",
|
||||
"tailwindcss": "^4.1.7",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
</script>
|
||||
|
||||
<tr
|
||||
class="flex h-12 w-full place-items-center border-3 border-transparent p-2 text-center even:bg-subtle/20 odd:bg-subtle/80 hover:cursor-pointer hover:border-immich-primary/75 odd:dark:bg-immich-dark-gray/75 even:dark:bg-immich-dark-gray/50 dark:hover:border-immich-dark-primary/75 md:p-5"
|
||||
class="flex w-full place-items-center border-3 border-transparent p-2 text-center even:bg-subtle/20 odd:bg-subtle/80 hover:cursor-pointer hover:border-immich-primary/75 odd:dark:bg-immich-dark-gray/75 even:dark:bg-immich-dark-gray/50 dark:hover:border-immich-dark-primary/75 md:px-5 md:py-2"
|
||||
onclick={() => goto(resolve(`${AppRoute.ALBUMS}/${album.id}`))}
|
||||
{oncontextmenu}
|
||||
>
|
||||
|
||||
@@ -126,6 +126,7 @@
|
||||
|
||||
const onMouseLeave = () => {
|
||||
mouseOver = false;
|
||||
onMouseEvent?.({ isMouseOver: false, selectedGroupIndex: groupIndex });
|
||||
};
|
||||
|
||||
let timer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
Reference in New Issue
Block a user