mirror of
https://github.com/immich-app/immich.git
synced 2026-06-17 04:12:16 -07:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a9993428e5 |
@@ -12,6 +12,10 @@ on:
|
||||
default: 'development'
|
||||
type: string
|
||||
secrets:
|
||||
PUSH_O_MATIC_APP_CLIENT_ID:
|
||||
required: true
|
||||
PUSH_O_MATIC_APP_KEY:
|
||||
required: true
|
||||
KEY_JKS:
|
||||
required: true
|
||||
ALIAS:
|
||||
|
||||
@@ -50,7 +50,6 @@ jobs:
|
||||
outputs:
|
||||
ref: ${{ steps.push-tag.outputs.commit_long_sha }}
|
||||
version: ${{ steps.output.outputs.version }}
|
||||
rc: ${{ steps.output.outputs.rc }}
|
||||
permissions: {} # No job-level permissions are needed because it uses the app-token
|
||||
steps:
|
||||
- id: token
|
||||
@@ -82,13 +81,7 @@ jobs:
|
||||
run: pnpm --silent release -s "${SERVER_BUMP}" -m "${MOBILE_BUMP}"
|
||||
|
||||
- id: output
|
||||
run: |
|
||||
echo "version=$IMMICH_VERSION" >> $GITHUB_OUTPUT
|
||||
if [[ "$IMMICH_VERSION" =~ -rc\.[0-9]+$ ]]; then
|
||||
echo "rc=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "rc=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
run: echo "version=$IMMICH_VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Commit and tag
|
||||
id: push-tag
|
||||
@@ -106,6 +99,8 @@ jobs:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
secrets:
|
||||
PUSH_O_MATIC_APP_CLIENT_ID: ${{ secrets.PUSH_O_MATIC_APP_CLIENT_ID }}
|
||||
PUSH_O_MATIC_APP_KEY: ${{ secrets.PUSH_O_MATIC_APP_KEY }}
|
||||
KEY_JKS: ${{ secrets.KEY_JKS }}
|
||||
ALIAS: ${{ secrets.ALIAS }}
|
||||
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}
|
||||
@@ -152,7 +147,6 @@ jobs:
|
||||
uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
|
||||
with:
|
||||
draft: true
|
||||
prerelease: ${{ needs.bump_version.outputs.rc }}
|
||||
tag_name: ${{ needs.bump_version.outputs.version }}
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
generate_release_notes: true
|
||||
|
||||
@@ -2,7 +2,6 @@ import { AssetVisibility, LoginResponseDto } from '@immich/sdk';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { basename, join } from 'node:path';
|
||||
import { Socket } from 'socket.io-client';
|
||||
import { createUserDto } from 'src/fixtures';
|
||||
import { errorDto } from 'src/responses';
|
||||
import { app, testAssetDir, utils } from 'src/utils';
|
||||
import request from 'supertest';
|
||||
@@ -10,48 +9,28 @@ import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
||||
|
||||
describe('/map', () => {
|
||||
let websocket: Socket;
|
||||
let partnerWebsocket: Socket;
|
||||
let admin: LoginResponseDto;
|
||||
let partner: LoginResponseDto;
|
||||
let partnerArchivedAssetId: string;
|
||||
let adminArchivedAssetId: string;
|
||||
|
||||
beforeAll(async () => {
|
||||
await utils.resetDatabase();
|
||||
admin = await utils.adminSetup({ onboarding: false });
|
||||
partner = await utils.userSetup(admin.accessToken, createUserDto.user1);
|
||||
|
||||
websocket = await utils.connectWebsocket(admin.accessToken);
|
||||
partnerWebsocket = await utils.connectWebsocket(partner.accessToken);
|
||||
|
||||
const adminFiles = ['formats/heic/IMG_2682.heic', 'metadata/gps-position/thompson-springs.jpg'];
|
||||
const adminArchivedFile = 'metadata/dates/datetimeoriginal-gps.jpg';
|
||||
const partnerFile = 'metadata/gps-position/thompson-springs.jpg';
|
||||
const files = ['formats/heic/IMG_2682.heic', 'metadata/gps-position/thompson-springs.jpg'];
|
||||
utils.resetEvents();
|
||||
const uploadFile = async (accessToken: string, input: string) => {
|
||||
const uploadFile = async (input: string) => {
|
||||
const filepath = join(testAssetDir, input);
|
||||
const { id } = await utils.createAsset(accessToken, {
|
||||
const { id } = await utils.createAsset(admin.accessToken, {
|
||||
assetData: { bytes: await readFile(filepath), filename: basename(filepath) },
|
||||
});
|
||||
await utils.waitForWebsocketEvent({ event: 'assetUpload', id });
|
||||
return id;
|
||||
};
|
||||
await Promise.all(adminFiles.map((f) => uploadFile(admin.accessToken, f)));
|
||||
[adminArchivedAssetId, partnerArchivedAssetId] = await Promise.all([
|
||||
uploadFile(admin.accessToken, adminArchivedFile),
|
||||
uploadFile(partner.accessToken, partnerFile),
|
||||
]);
|
||||
|
||||
await Promise.all([
|
||||
utils.archiveAssets(admin.accessToken, [adminArchivedAssetId]),
|
||||
utils.archiveAssets(partner.accessToken, [partnerArchivedAssetId]),
|
||||
utils.createPartner(partner.accessToken, admin.userId),
|
||||
]);
|
||||
await Promise.all(files.map((f) => uploadFile(f)));
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
utils.disconnectWebsocket(websocket);
|
||||
utils.disconnectWebsocket(partnerWebsocket);
|
||||
});
|
||||
|
||||
describe('GET /map/markers', () => {
|
||||
@@ -61,6 +40,7 @@ describe('/map', () => {
|
||||
expect(body).toEqual(errorDto.unauthorized);
|
||||
});
|
||||
|
||||
// TODO archive one of these assets
|
||||
it('should get map markers for all non-archived assets', async () => {
|
||||
const { status, body } = await request(app)
|
||||
.get('/map/markers')
|
||||
@@ -89,28 +69,7 @@ describe('/map', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('should not expose partner archived asset locations', async () => {
|
||||
const { status, body } = await request(app)
|
||||
.get('/map/markers')
|
||||
.query({ withPartners: true, isArchived: true })
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||
|
||||
expect(status).toBe(200);
|
||||
const ids = body.map((m: { id: string }) => m.id);
|
||||
expect(ids).not.toContain(partnerArchivedAssetId);
|
||||
expect(ids).toContain(adminArchivedAssetId);
|
||||
});
|
||||
|
||||
it('should include own archived asset locations', async () => {
|
||||
const { status, body } = await request(app)
|
||||
.get('/map/markers')
|
||||
.query({ isArchived: true })
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||
|
||||
expect(status).toBe(200);
|
||||
expect(body.map((m: { id: string }) => m.id)).toContain(adminArchivedAssetId);
|
||||
});
|
||||
|
||||
// TODO archive one of these assets
|
||||
it('should get all map markers', async () => {
|
||||
const { status, body } = await request(app)
|
||||
.get('/map/markers')
|
||||
|
||||
@@ -95,7 +95,7 @@ describe('/server', () => {
|
||||
major: expect.any(Number),
|
||||
minor: expect.any(Number),
|
||||
patch: expect.any(Number),
|
||||
prerelease: expect.anything(),
|
||||
prerelease: null,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -15,7 +15,6 @@ const Map<String, Locale> locales = {
|
||||
'Czech (cs)': Locale('cs'),
|
||||
'Danish (da)': Locale('da'),
|
||||
'Dutch (nl)': Locale('nl'),
|
||||
'English (United Kingdom) (en_GB)': Locale('en', 'GB'),
|
||||
'Estonian (et)': Locale('et'),
|
||||
'Filipino (tl)': Locale('tl'),
|
||||
'Finnish (fi)': Locale('fi'),
|
||||
|
||||
@@ -142,6 +142,7 @@ class AppConfig {
|
||||
.cleanupCutoffDaysAgo => cleanup.cutoffDaysAgo,
|
||||
.cleanupDefaultsInitialized => cleanup.defaultsInitialized,
|
||||
.shareFileType => share.fileType,
|
||||
.slideshowTransition => slideshow.transition,
|
||||
.slideshowRepeat => slideshow.repeat,
|
||||
.slideshowDuration => slideshow.duration,
|
||||
.slideshowLook => slideshow.look,
|
||||
@@ -195,6 +196,7 @@ class AppConfig {
|
||||
.cleanupCutoffDaysAgo => copyWith(cleanup: cleanup.copyWith(cutoffDaysAgo: value as int)),
|
||||
.cleanupDefaultsInitialized => copyWith(cleanup: cleanup.copyWith(defaultsInitialized: value as bool)),
|
||||
.shareFileType => copyWith(share: share.copyWith(fileType: value as ShareAssetType)),
|
||||
.slideshowTransition => copyWith(slideshow: slideshow.copyWith(transition: value as bool)),
|
||||
.slideshowRepeat => copyWith(slideshow: slideshow.copyWith(repeat: value as bool)),
|
||||
.slideshowDuration => copyWith(slideshow: slideshow.copyWith(duration: value as int)),
|
||||
.slideshowLook => copyWith(slideshow: slideshow.copyWith(look: value as SlideshowLook)),
|
||||
|
||||
@@ -1,38 +1,48 @@
|
||||
import 'package:immich_mobile/constants/enums.dart';
|
||||
|
||||
class SlideshowConfig {
|
||||
final bool transition;
|
||||
final bool repeat;
|
||||
final int duration;
|
||||
final SlideshowLook look;
|
||||
final SlideshowDirection direction;
|
||||
|
||||
const SlideshowConfig({
|
||||
this.transition = true,
|
||||
this.repeat = true,
|
||||
this.duration = 5,
|
||||
this.look = SlideshowLook.blurredBackground,
|
||||
this.look = SlideshowLook.contain,
|
||||
this.direction = SlideshowDirection.forward,
|
||||
});
|
||||
|
||||
SlideshowConfig copyWith({bool? repeat, int? duration, SlideshowLook? look, SlideshowDirection? direction}) =>
|
||||
SlideshowConfig(
|
||||
repeat: repeat ?? this.repeat,
|
||||
duration: duration ?? this.duration,
|
||||
look: look ?? this.look,
|
||||
direction: direction ?? this.direction,
|
||||
);
|
||||
SlideshowConfig copyWith({
|
||||
bool? transition,
|
||||
bool? repeat,
|
||||
int? duration,
|
||||
SlideshowLook? look,
|
||||
SlideshowDirection? direction,
|
||||
}) => SlideshowConfig(
|
||||
transition: transition ?? this.transition,
|
||||
repeat: repeat ?? this.repeat,
|
||||
duration: duration ?? this.duration,
|
||||
look: look ?? this.look,
|
||||
direction: direction ?? this.direction,
|
||||
);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
(other is SlideshowConfig &&
|
||||
other.transition == transition &&
|
||||
other.repeat == repeat &&
|
||||
other.duration == duration &&
|
||||
other.look == look &&
|
||||
other.direction == direction);
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(repeat, duration, look, direction);
|
||||
int get hashCode => Object.hash(transition, repeat, duration, look, direction);
|
||||
|
||||
@override
|
||||
String toString() => 'SlideshowConfig(repeat: $repeat, duration: $duration, look: $look, direction: $direction)';
|
||||
String toString() =>
|
||||
'SlideshowConfig(transition: $transition, repeat: $repeat, duration: $duration, look: $look, direction: $direction)';
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ enum SettingsKey<T> {
|
||||
shareFileType<ShareAssetType>(codec: _EnumCodec(ShareAssetType.values)),
|
||||
|
||||
// Slideshow
|
||||
slideshowTransition<bool>(),
|
||||
slideshowRepeat<bool>(),
|
||||
slideshowDuration<int>(),
|
||||
slideshowLook<SlideshowLook>(codec: _EnumCodec(SlideshowLook.values)),
|
||||
|
||||
@@ -33,9 +33,7 @@ class DriftSlideshowPage extends ConsumerStatefulWidget {
|
||||
ConsumerState<DriftSlideshowPage> createState() => _DriftSlideshowPageState();
|
||||
}
|
||||
|
||||
class _DriftSlideshowPageState extends ConsumerState<DriftSlideshowPage> with SingleTickerProviderStateMixin {
|
||||
static const double _kenBurnsZoom = 0.1;
|
||||
|
||||
class _DriftSlideshowPageState extends ConsumerState<DriftSlideshowPage> {
|
||||
late SlideshowConfig _config;
|
||||
late final PageController _pageController;
|
||||
late final Stopwatch _stopwatch;
|
||||
@@ -45,12 +43,6 @@ class _DriftSlideshowPageState extends ConsumerState<DriftSlideshowPage> with Si
|
||||
bool _paused = false;
|
||||
bool _showAppBar = false;
|
||||
|
||||
late final AnimationController _crossfadeController;
|
||||
late final Animation<double> _crossfadeOpacity;
|
||||
int? _crossfadeFromIndex;
|
||||
int? _crossfadeToIndex;
|
||||
int _zoomCycle = 0;
|
||||
|
||||
@override
|
||||
initState() {
|
||||
super.initState();
|
||||
@@ -58,8 +50,6 @@ class _DriftSlideshowPageState extends ConsumerState<DriftSlideshowPage> with Si
|
||||
final asset = ref.read(assetViewerProvider).currentAsset;
|
||||
_index = asset == null ? 0 : widget.timeline.getIndex(asset.heroTag) ?? 0;
|
||||
_pageController = PageController(initialPage: _index);
|
||||
_crossfadeController = AnimationController(vsync: this, duration: Durations.extralong2);
|
||||
_crossfadeOpacity = Tween<double>(begin: 1.0, end: 0.0).animate(_crossfadeController);
|
||||
_stopwatch = Stopwatch();
|
||||
_createTimer();
|
||||
_updateNextIndex();
|
||||
@@ -74,7 +64,6 @@ class _DriftSlideshowPageState extends ConsumerState<DriftSlideshowPage> with Si
|
||||
_timer.cancel();
|
||||
_stopwatch.stop();
|
||||
_pageController.dispose();
|
||||
_crossfadeController.dispose();
|
||||
unawaited(WakelockPlus.disable());
|
||||
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
|
||||
super.dispose();
|
||||
@@ -161,64 +150,11 @@ class _DriftSlideshowPageState extends ConsumerState<DriftSlideshowPage> with Si
|
||||
await widget.timeline.preloadAssets(_nextIndex);
|
||||
}
|
||||
|
||||
_crossFadeToPage(_nextIndex);
|
||||
}
|
||||
|
||||
void _crossFadeToPage(int page) {
|
||||
final previousIndex = _index;
|
||||
_pageController.jumpToPage(page);
|
||||
setState(() {
|
||||
_crossfadeFromIndex = previousIndex;
|
||||
_crossfadeToIndex = page;
|
||||
});
|
||||
_crossfadeController.forward(from: 0.0).whenComplete(() {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_crossfadeFromIndex = null;
|
||||
_crossfadeToIndex = null;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Widget _getCrossfadeLayer(BuildContext context, int index, {required bool isIncoming}) {
|
||||
final asset = widget.timeline.getAssetSafe(index);
|
||||
|
||||
final Widget child;
|
||||
if (isIncoming && asset?.isImage == true) {
|
||||
child = _getPhotoView(context, index);
|
||||
if (_config.direction == SlideshowDirection.shuffle || !_config.transition) {
|
||||
_pageController.jumpToPage(_nextIndex);
|
||||
} else {
|
||||
final zoomOut = isIncoming ? _zoomCycle.isOdd : _zoomCycle.isEven;
|
||||
final zoom = isIncoming ? (zoomOut ? 1.0 : 0.0) : (zoomOut ? 0.0 : 1.0);
|
||||
child = _getCrossfadeChild(context, index, zoom);
|
||||
unawaited(_pageController.animateToPage(_nextIndex, duration: Durations.long2, curve: Curves.easeIn));
|
||||
}
|
||||
|
||||
return Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [if (_config.look == SlideshowLook.blurredBackground) _getBlur(context, index), child],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getCrossfadeChild(BuildContext context, int index, double zoom) {
|
||||
final asset = widget.timeline.getAssetSafe(index);
|
||||
|
||||
if (asset == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
final scale = _config.look == SlideshowLook.cover
|
||||
? PhotoViewComputedScale.covered
|
||||
: PhotoViewComputedScale.contained;
|
||||
|
||||
return PhotoView(
|
||||
imageProvider: getFullImageProvider(asset, size: context.sizeData),
|
||||
index: index,
|
||||
disableScaleGestures: true,
|
||||
gaplessPlayback: true,
|
||||
filterQuality: FilterQuality.high,
|
||||
initialScale: scale * (1.0 + zoom * _kenBurnsZoom),
|
||||
controller: PhotoViewController(),
|
||||
);
|
||||
}
|
||||
|
||||
void _createTimer() {
|
||||
@@ -236,7 +172,6 @@ class _DriftSlideshowPageState extends ConsumerState<DriftSlideshowPage> with Si
|
||||
|
||||
setState(() {
|
||||
_index = page;
|
||||
_zoomCycle++;
|
||||
|
||||
if (!asset.isImage) {
|
||||
_paused = false;
|
||||
@@ -333,7 +268,7 @@ class _DriftSlideshowPageState extends ConsumerState<DriftSlideshowPage> with Si
|
||||
final imageProvider = getFullImageProvider(asset, size: context.sizeData);
|
||||
|
||||
if (asset.isImage) {
|
||||
final zoomOut = _zoomCycle.isOdd;
|
||||
final zoomOut = index % 2 == 1;
|
||||
final elapsed = _stopwatch.elapsedMilliseconds;
|
||||
final duration = _config.duration * 1000;
|
||||
final progress = zoomOut ? 1.0 - elapsed / duration.toDouble() : elapsed / duration.toDouble();
|
||||
@@ -354,7 +289,7 @@ class _DriftSlideshowPageState extends ConsumerState<DriftSlideshowPage> with Si
|
||||
disableScaleGestures: true,
|
||||
gaplessPlayback: true,
|
||||
filterQuality: FilterQuality.high,
|
||||
initialScale: scale * (1.0 + value * _kenBurnsZoom),
|
||||
initialScale: scale * (1.0 + value / 10.0),
|
||||
controller: PhotoViewController(),
|
||||
onTapUp: (_, _, _) => _onTapUp(),
|
||||
),
|
||||
@@ -421,43 +356,20 @@ class _DriftSlideshowPageState extends ConsumerState<DriftSlideshowPage> with Si
|
||||
extendBody: true,
|
||||
extendBodyBehindAppBar: true,
|
||||
backgroundColor: Colors.black,
|
||||
body: Stack(
|
||||
children: [
|
||||
PhotoViewGestureDetectorScope(
|
||||
axis: Axis.horizontal,
|
||||
child: PageView.builder(
|
||||
controller: _pageController,
|
||||
physics: const FastClampingScrollPhysics(),
|
||||
itemCount: widget.timeline.totalAssets,
|
||||
onPageChanged: _pageChanged,
|
||||
itemBuilder: (context, index) => Stack(
|
||||
children: [
|
||||
if (_config.look == SlideshowLook.blurredBackground) _getBlur(context, index),
|
||||
_getPhotoView(context, index),
|
||||
],
|
||||
),
|
||||
),
|
||||
body: PhotoViewGestureDetectorScope(
|
||||
axis: Axis.horizontal,
|
||||
child: PageView.builder(
|
||||
controller: _pageController,
|
||||
physics: const FastClampingScrollPhysics(),
|
||||
itemCount: widget.timeline.totalAssets,
|
||||
onPageChanged: _pageChanged,
|
||||
itemBuilder: (context, index) => Stack(
|
||||
children: [
|
||||
if (_config.look == SlideshowLook.blurredBackground) _getBlur(context, index),
|
||||
_getPhotoView(context, index),
|
||||
],
|
||||
),
|
||||
if (_crossfadeFromIndex != null && _crossfadeToIndex != null)
|
||||
Positioned.fill(
|
||||
child: IgnorePointer(
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
const ColoredBox(color: Colors.black),
|
||||
FadeTransition(
|
||||
opacity: _crossfadeController,
|
||||
child: _getCrossfadeLayer(context, _crossfadeToIndex!, isIncoming: true),
|
||||
),
|
||||
FadeTransition(
|
||||
opacity: _crossfadeOpacity,
|
||||
child: _getCrossfadeLayer(context, _crossfadeFromIndex!, isIncoming: false),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -70,10 +70,7 @@ class DeepLinkService {
|
||||
|
||||
if (assetRegex.hasMatch(path)) {
|
||||
final assetId = assetRegex.firstMatch(path)?.group(1) ?? '';
|
||||
// /albums/<albumId>/photos/<assetId> links carry the album context,
|
||||
// which drives the like/comment UI in the viewer
|
||||
final albumId = albumRegex.firstMatch(path)?.group(1);
|
||||
return _buildAssetDeepLink(assetId, ref, albumId: albumId);
|
||||
return _buildAssetDeepLink(assetId, ref);
|
||||
}
|
||||
if (albumRegex.hasMatch(path)) {
|
||||
final albumId = albumRegex.firstMatch(path)?.group(1) ?? '';
|
||||
@@ -110,19 +107,16 @@ class DeepLinkService {
|
||||
return DriftMemoryRoute(memories: memories, memoryIndex: 0);
|
||||
}
|
||||
|
||||
Future<PageRouteInfo?> _buildAssetDeepLink(String assetId, WidgetRef ref, {String? albumId}) async {
|
||||
Future<PageRouteInfo?> _buildAssetDeepLink(String assetId, WidgetRef ref) async {
|
||||
final asset = await _betaAssetService.getRemoteAsset(assetId);
|
||||
if (asset == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final album = albumId != null ? await _betaRemoteAlbumService.get(albumId) : null;
|
||||
|
||||
AssetViewer.setAsset(ref, asset);
|
||||
return AssetViewerRoute(
|
||||
initialIndex: 0,
|
||||
timelineService: _betaTimelineFactory.fromAssets([asset], TimelineOrigin.deepLink),
|
||||
currentAlbum: album,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,11 +16,15 @@ class SlideshowSettings extends HookConsumerWidget {
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final slideshow = ref.read(appConfigProvider).slideshow;
|
||||
final useTransition = useState(slideshow.transition);
|
||||
final useRepeat = useState(slideshow.repeat);
|
||||
final useDuration = useState(slideshow.duration);
|
||||
final useLook = useState(slideshow.look);
|
||||
final useDirection = useState(slideshow.direction);
|
||||
|
||||
useValueChanged<bool, void>(useTransition.value, (_, __) {
|
||||
ref.read(settingsProvider).write(.slideshowTransition, useTransition.value);
|
||||
});
|
||||
useValueChanged<bool, void>(useRepeat.value, (_, __) {
|
||||
ref.read(settingsProvider).write(.slideshowRepeat, useRepeat.value);
|
||||
});
|
||||
@@ -41,6 +45,11 @@ class SlideshowSettings extends HookConsumerWidget {
|
||||
title: 'slideshow'.t(context: context),
|
||||
icon: Icons.slideshow_outlined,
|
||||
),
|
||||
SettingsSwitchListTile(
|
||||
valueNotifier: useTransition,
|
||||
title: "show_slideshow_transition".t(context: context),
|
||||
enabled: useDirection.value != SlideshowDirection.shuffle,
|
||||
),
|
||||
SettingsSwitchListTile(
|
||||
valueNotifier: useRepeat,
|
||||
title: "slideshow_repeat".t(context: context),
|
||||
|
||||
@@ -1,140 +0,0 @@
|
||||
import 'package:auto_route/auto_route.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.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/timeline.model.dart';
|
||||
import 'package:immich_mobile/domain/services/asset.service.dart';
|
||||
import 'package:immich_mobile/domain/services/memory.service.dart';
|
||||
import 'package:immich_mobile/domain/services/people.service.dart';
|
||||
import 'package:immich_mobile/domain/services/remote_album.service.dart';
|
||||
import 'package:immich_mobile/domain/services/timeline.service.dart';
|
||||
import 'package:immich_mobile/providers/asset_viewer/asset_viewer.provider.dart';
|
||||
import 'package:immich_mobile/routing/router.dart';
|
||||
import 'package:immich_mobile/services/deep_link.service.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
|
||||
class MockTimelineFactory extends Mock implements TimelineFactory {}
|
||||
|
||||
class MockAssetService extends Mock implements AssetService {}
|
||||
|
||||
class MockRemoteAlbumService extends Mock implements RemoteAlbumService {}
|
||||
|
||||
class MockDriftMemoryService extends Mock implements DriftMemoryService {}
|
||||
|
||||
class MockDriftPeopleService extends Mock implements DriftPeopleService {}
|
||||
|
||||
class MockPlatformDeepLink extends Mock implements PlatformDeepLink {}
|
||||
|
||||
class MockWidgetRef extends Mock implements WidgetRef {}
|
||||
|
||||
class MockAssetViewerStateNotifier extends Mock implements AssetViewerStateNotifier {}
|
||||
|
||||
const _assetId = 'aaaaaaaa-1111-2222-3333-bbbbbbbbbbbb';
|
||||
const _albumId = 'cccccccc-4444-5555-6666-dddddddddddd';
|
||||
|
||||
final _asset = RemoteAsset(
|
||||
id: _assetId,
|
||||
name: 'photo.jpg',
|
||||
ownerId: 'user-1',
|
||||
checksum: 'checksum-1',
|
||||
type: AssetType.image,
|
||||
createdAt: DateTime(2026, 6, 12),
|
||||
updatedAt: DateTime(2026, 6, 12),
|
||||
isEdited: false,
|
||||
);
|
||||
|
||||
final _album = RemoteAlbum(
|
||||
id: _albumId,
|
||||
name: 'Shared Album',
|
||||
ownerId: 'user-1',
|
||||
description: '',
|
||||
createdAt: DateTime(2026, 6, 12),
|
||||
updatedAt: DateTime(2026, 6, 12),
|
||||
isActivityEnabled: true,
|
||||
isShared: true,
|
||||
order: AlbumAssetOrder.asc,
|
||||
assetCount: 1,
|
||||
ownerName: 'Owner',
|
||||
);
|
||||
|
||||
void main() {
|
||||
late MockTimelineFactory timelineFactory;
|
||||
late MockAssetService assetService;
|
||||
late MockRemoteAlbumService remoteAlbumService;
|
||||
late MockWidgetRef ref;
|
||||
late List<TimelineService> createdTimelineServices;
|
||||
late DeepLinkService sut;
|
||||
|
||||
setUp(() {
|
||||
timelineFactory = MockTimelineFactory();
|
||||
assetService = MockAssetService();
|
||||
remoteAlbumService = MockRemoteAlbumService();
|
||||
ref = MockWidgetRef();
|
||||
createdTimelineServices = [];
|
||||
|
||||
when(() => timelineFactory.fromAssets(any(), TimelineOrigin.deepLink)).thenAnswer((invocation) {
|
||||
final assets = List<BaseAsset>.from(invocation.positionalArguments[0] as List<BaseAsset>);
|
||||
final timelineService = TimelineService((
|
||||
assetSource: (index, count) async => assets.skip(index).take(count).toList(),
|
||||
bucketSource: () => Stream.value([Bucket(assetCount: assets.length)]),
|
||||
origin: TimelineOrigin.deepLink,
|
||||
));
|
||||
createdTimelineServices.add(timelineService);
|
||||
return timelineService;
|
||||
});
|
||||
|
||||
when(() => ref.read(assetViewerProvider.notifier)).thenReturn(MockAssetViewerStateNotifier());
|
||||
|
||||
sut = DeepLinkService(
|
||||
timelineFactory,
|
||||
assetService,
|
||||
remoteAlbumService,
|
||||
MockDriftMemoryService(),
|
||||
MockDriftPeopleService(),
|
||||
null,
|
||||
);
|
||||
|
||||
addTearDown(() async {
|
||||
for (final timelineService in createdTimelineServices) {
|
||||
await timelineService.dispose();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
PlatformDeepLink link(String path) {
|
||||
final deepLink = MockPlatformDeepLink();
|
||||
when(() => deepLink.uri).thenReturn(Uri.parse('https://my.immich.app$path'));
|
||||
return deepLink;
|
||||
}
|
||||
|
||||
test('album photo link carries the album into the viewer route', () async {
|
||||
when(() => assetService.getRemoteAsset(_assetId)).thenAnswer((_) async => _asset);
|
||||
when(() => remoteAlbumService.get(_albumId)).thenAnswer((_) async => _album);
|
||||
|
||||
final route = await sut.handleMyImmichApp(link('/albums/$_albumId/photos/$_assetId'), ref);
|
||||
|
||||
expect(route, isA<AssetViewerRoute>());
|
||||
expect((route!.args as AssetViewerRouteArgs).currentAlbum, _album);
|
||||
});
|
||||
|
||||
test('still opens the viewer when the album cannot be resolved', () async {
|
||||
when(() => assetService.getRemoteAsset(_assetId)).thenAnswer((_) async => _asset);
|
||||
when(() => remoteAlbumService.get(_albumId)).thenAnswer((_) async => null);
|
||||
|
||||
final route = await sut.handleMyImmichApp(link('/albums/$_albumId/photos/$_assetId'), ref);
|
||||
|
||||
expect(route, isA<AssetViewerRoute>());
|
||||
expect((route!.args as AssetViewerRouteArgs).currentAlbum, isNull);
|
||||
});
|
||||
|
||||
test('plain photo link has no album', () async {
|
||||
when(() => assetService.getRemoteAsset(_assetId)).thenAnswer((_) async => _asset);
|
||||
|
||||
final route = await sut.handleMyImmichApp(link('/photos/$_assetId'), ref);
|
||||
|
||||
expect(route, isA<AssetViewerRoute>());
|
||||
expect((route!.args as AssetViewerRouteArgs).currentAlbum, isNull);
|
||||
verifyNever(() => remoteAlbumService.get(any()));
|
||||
});
|
||||
}
|
||||
@@ -16508,9 +16508,7 @@
|
||||
},
|
||||
"albumThumbnailAssetId": {
|
||||
"description": "Thumbnail asset ID",
|
||||
"format": "uuid",
|
||||
"nullable": true,
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"albumUsers": {
|
||||
@@ -16553,8 +16551,6 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Album ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"isActivityEnabled": {
|
||||
@@ -16797,8 +16793,6 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "API key ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
@@ -17007,8 +17001,6 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"isTrashed": {
|
||||
@@ -17387,8 +17379,6 @@
|
||||
"properties": {
|
||||
"assetId": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"error": {
|
||||
@@ -17504,8 +17494,6 @@
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "Asset media ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
@@ -17574,8 +17562,6 @@
|
||||
"properties": {
|
||||
"assetId": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
@@ -17823,9 +17809,7 @@
|
||||
},
|
||||
"duplicateId": {
|
||||
"description": "Duplicate group ID",
|
||||
"format": "uuid",
|
||||
"nullable": true,
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"duration": {
|
||||
@@ -17861,8 +17845,6 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"isArchived": {
|
||||
@@ -17941,8 +17923,6 @@
|
||||
},
|
||||
"ownerId": {
|
||||
"description": "Owner user ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"people": {
|
||||
@@ -18040,14 +18020,10 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Stack ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"primaryAssetId": {
|
||||
"description": "Primary asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -18183,8 +18159,6 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"success": {
|
||||
@@ -18363,8 +18337,6 @@
|
||||
},
|
||||
"userId": {
|
||||
"description": "User ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -18469,8 +18441,6 @@
|
||||
},
|
||||
"userId": {
|
||||
"description": "User ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -18631,8 +18601,6 @@
|
||||
"assetIds": {
|
||||
"description": "Asset IDs in this archive",
|
||||
"items": {
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
@@ -18818,8 +18786,6 @@
|
||||
},
|
||||
"duplicateId": {
|
||||
"description": "Duplicate group ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"suggestedKeepAssetIds": {
|
||||
@@ -19136,8 +19102,6 @@
|
||||
"properties": {
|
||||
"id": {
|
||||
"description": "Integrity report item id",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
@@ -19312,8 +19276,6 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Library ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"importPaths": {
|
||||
@@ -19329,8 +19291,6 @@
|
||||
},
|
||||
"ownerId": {
|
||||
"description": "Owner user ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"refreshedAt": {
|
||||
@@ -19485,8 +19445,6 @@
|
||||
},
|
||||
"userId": {
|
||||
"description": "User ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -19677,8 +19635,6 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"lat": {
|
||||
@@ -19879,8 +19835,6 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Memory ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"isSaved": {
|
||||
@@ -19896,8 +19850,6 @@
|
||||
},
|
||||
"ownerId": {
|
||||
"description": "Owner user ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"seenAt": {
|
||||
@@ -20359,8 +20311,6 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Notification ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"level": {
|
||||
@@ -20805,8 +20755,6 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Person ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"isFavorite": {
|
||||
@@ -21042,8 +20990,6 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Person ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"isFavorite": {
|
||||
@@ -21299,8 +21245,6 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Plugin ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"methods": {
|
||||
@@ -22696,8 +22640,6 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Version history entry ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
@@ -22802,8 +22744,6 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Session ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"isPendingSyncReset": {
|
||||
@@ -22861,8 +22801,6 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Session ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"isPendingSyncReset": {
|
||||
@@ -23085,8 +23023,6 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Shared link ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
@@ -23112,8 +23048,6 @@
|
||||
},
|
||||
"userId": {
|
||||
"description": "Owner user ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -23455,14 +23389,10 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Stack ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"primaryAssetId": {
|
||||
"description": "Primary asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -23736,8 +23666,6 @@
|
||||
"properties": {
|
||||
"albumId": {
|
||||
"description": "Album ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -23750,14 +23678,10 @@
|
||||
"properties": {
|
||||
"albumId": {
|
||||
"description": "Album ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"assetId": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -23771,14 +23695,10 @@
|
||||
"properties": {
|
||||
"albumId": {
|
||||
"description": "Album ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"assetId": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -23792,14 +23712,10 @@
|
||||
"properties": {
|
||||
"albumId": {
|
||||
"description": "Album ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"userId": {
|
||||
"description": "User ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -23813,8 +23729,6 @@
|
||||
"properties": {
|
||||
"albumId": {
|
||||
"description": "Album ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"role": {
|
||||
@@ -23822,8 +23736,6 @@
|
||||
},
|
||||
"userId": {
|
||||
"description": "User ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -23849,8 +23761,6 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Album ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"isActivityEnabled": {
|
||||
@@ -23866,8 +23776,6 @@
|
||||
},
|
||||
"ownerId": {
|
||||
"description": "Owner ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"thumbnailAssetId": {
|
||||
@@ -23911,8 +23819,6 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Album ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"isActivityEnabled": {
|
||||
@@ -23955,8 +23861,6 @@
|
||||
"properties": {
|
||||
"assetId": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -23969,8 +23873,6 @@
|
||||
"properties": {
|
||||
"editId": {
|
||||
"description": "Edit ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -23986,14 +23888,10 @@
|
||||
},
|
||||
"assetId": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"description": "Edit ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"parameters": {
|
||||
@@ -24021,8 +23919,6 @@
|
||||
"properties": {
|
||||
"assetId": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"city": {
|
||||
@@ -24200,8 +24096,6 @@
|
||||
"properties": {
|
||||
"assetFaceId": {
|
||||
"description": "Asset face ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -24214,8 +24108,6 @@
|
||||
"properties": {
|
||||
"assetId": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"boundingBoxX1": {
|
||||
@@ -24244,8 +24136,6 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Asset face ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"imageHeight": {
|
||||
@@ -24288,8 +24178,6 @@
|
||||
"properties": {
|
||||
"assetId": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"boundingBoxX1": {
|
||||
@@ -24326,8 +24214,6 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Asset face ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"imageHeight": {
|
||||
@@ -24376,8 +24262,6 @@
|
||||
"properties": {
|
||||
"assetId": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
@@ -24395,8 +24279,6 @@
|
||||
"properties": {
|
||||
"assetId": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"key": {
|
||||
@@ -24445,8 +24327,6 @@
|
||||
"properties": {
|
||||
"assetId": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"boxScore": {
|
||||
@@ -24456,8 +24336,6 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "OCR entry ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"isVisible": {
|
||||
@@ -24584,8 +24462,6 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"isEdited": {
|
||||
@@ -24620,8 +24496,6 @@
|
||||
},
|
||||
"ownerId": {
|
||||
"description": "Owner ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"stackId": {
|
||||
@@ -24726,8 +24600,6 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"isEdited": {
|
||||
@@ -24762,8 +24634,6 @@
|
||||
},
|
||||
"ownerId": {
|
||||
"description": "Owner ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"stackId": {
|
||||
@@ -24842,8 +24712,6 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "User ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"isAdmin": {
|
||||
@@ -24978,14 +24846,10 @@
|
||||
"properties": {
|
||||
"assetId": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"memoryId": {
|
||||
"description": "Memory ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -24999,14 +24863,10 @@
|
||||
"properties": {
|
||||
"assetId": {
|
||||
"description": "Asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"memoryId": {
|
||||
"description": "Memory ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -25020,8 +24880,6 @@
|
||||
"properties": {
|
||||
"memoryId": {
|
||||
"description": "Memory ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -25062,8 +24920,6 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Memory ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"isSaved": {
|
||||
@@ -25079,8 +24935,6 @@
|
||||
},
|
||||
"ownerId": {
|
||||
"description": "Owner ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"seenAt": {
|
||||
@@ -25130,14 +24984,10 @@
|
||||
"properties": {
|
||||
"sharedById": {
|
||||
"description": "Shared by ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"sharedWithId": {
|
||||
"description": "Shared with ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -25155,14 +25005,10 @@
|
||||
},
|
||||
"sharedById": {
|
||||
"description": "Shared by ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"sharedWithId": {
|
||||
"description": "Shared with ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -25177,8 +25023,6 @@
|
||||
"properties": {
|
||||
"personId": {
|
||||
"description": "Person ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -25216,8 +25060,6 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Person ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"isFavorite": {
|
||||
@@ -25234,8 +25076,6 @@
|
||||
},
|
||||
"ownerId": {
|
||||
"description": "Owner ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"updatedAt": {
|
||||
@@ -25301,8 +25141,6 @@
|
||||
"properties": {
|
||||
"stackId": {
|
||||
"description": "Stack ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -25322,20 +25160,14 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Stack ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"ownerId": {
|
||||
"description": "Owner ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"primaryAssetId": {
|
||||
"description": "Primary asset ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"updatedAt": {
|
||||
@@ -25378,8 +25210,6 @@
|
||||
"properties": {
|
||||
"userId": {
|
||||
"description": "User ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -25395,8 +25225,6 @@
|
||||
},
|
||||
"userId": {
|
||||
"description": "User ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@@ -25413,8 +25241,6 @@
|
||||
},
|
||||
"userId": {
|
||||
"description": "User ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"value": {
|
||||
@@ -25458,8 +25284,6 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "User ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
@@ -26646,8 +26470,6 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Tag ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
@@ -27171,8 +26993,6 @@
|
||||
},
|
||||
"userId": {
|
||||
"description": "User ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"userName": {
|
||||
@@ -27815,8 +27635,6 @@
|
||||
},
|
||||
"id": {
|
||||
"description": "Workflow ID",
|
||||
"format": "uuid",
|
||||
"pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
|
||||
@@ -100,21 +100,21 @@ const AlbumUserResponseSchema = z
|
||||
|
||||
const ContributorCountResponseSchema = z
|
||||
.object({
|
||||
userId: z.uuidv4().describe('User ID'),
|
||||
userId: z.string().describe('User ID'),
|
||||
assetCount: z.int().min(0).describe('Number of assets contributed'),
|
||||
})
|
||||
.meta({ id: 'ContributorCountResponseDto' });
|
||||
|
||||
export const AlbumResponseSchema = z
|
||||
.object({
|
||||
id: z.uuidv4().describe('Album ID'),
|
||||
id: z.string().describe('Album ID'),
|
||||
albumName: z.string().describe('Album name'),
|
||||
description: z.string().describe('Album description'),
|
||||
// TODO: use `isoDatetimeToDate` when using `ZodSerializerDto` on the controllers.
|
||||
createdAt: z.string().meta({ format: 'date-time' }).describe('Creation date'),
|
||||
// TODO: use `isoDatetimeToDate` when using `ZodSerializerDto` on the controllers.
|
||||
updatedAt: z.string().meta({ format: 'date-time' }).describe('Last update date'),
|
||||
albumThumbnailAssetId: z.uuidv4().nullable().describe('Thumbnail asset ID'),
|
||||
albumThumbnailAssetId: z.string().nullable().describe('Thumbnail asset ID'),
|
||||
shared: z.boolean().describe('Is shared album'),
|
||||
albumUsers: z
|
||||
.array(AlbumUserResponseSchema)
|
||||
|
||||
@@ -21,7 +21,7 @@ const ApiKeyUpdateSchema = z
|
||||
|
||||
const ApiKeyResponseSchema = z
|
||||
.object({
|
||||
id: z.uuidv4().describe('API key ID'),
|
||||
id: z.string().describe('API key ID'),
|
||||
name: z.string().describe('API key name'),
|
||||
createdAt: isoDatetimeToDate.describe('Creation date'),
|
||||
updatedAt: isoDatetimeToDate.describe('Last update date'),
|
||||
|
||||
@@ -16,7 +16,7 @@ const AssetIdErrorReasonSchema = z
|
||||
/** @deprecated Use `BulkIdResponseDto` instead */
|
||||
const AssetIdsResponseSchema = z
|
||||
.object({
|
||||
assetId: z.uuidv4().describe('Asset ID'),
|
||||
assetId: z.string().describe('Asset ID'),
|
||||
success: z.boolean().describe('Whether operation succeeded'),
|
||||
error: AssetIdErrorReasonSchema.optional(),
|
||||
})
|
||||
@@ -43,7 +43,7 @@ export const BulkIdsSchema = z
|
||||
|
||||
const BulkIdResponseSchema = z
|
||||
.object({
|
||||
id: z.uuidv4().describe('ID'),
|
||||
id: z.string().describe('ID'),
|
||||
success: z.boolean().describe('Whether operation succeeded'),
|
||||
error: BulkIdErrorReasonSchema.optional(),
|
||||
errorMessage: z.string().optional(),
|
||||
|
||||
@@ -11,7 +11,7 @@ const AssetMediaStatusSchema = z.enum(AssetMediaStatus).describe('Upload status'
|
||||
const AssetMediaResponseSchema = z
|
||||
.object({
|
||||
status: AssetMediaStatusSchema,
|
||||
id: z.uuidv4().describe('Asset media ID'),
|
||||
id: z.string().describe('Asset media ID'),
|
||||
})
|
||||
.meta({ id: 'AssetMediaResponseDto' });
|
||||
|
||||
@@ -34,7 +34,7 @@ const AssetRejectReasonSchema = z
|
||||
|
||||
const AssetBulkUploadCheckResultSchema = z
|
||||
.object({
|
||||
id: z.uuidv4().describe('Asset ID'),
|
||||
id: z.string().describe('Asset ID'),
|
||||
action: AssetUploadActionSchema,
|
||||
reason: AssetRejectReasonSchema.optional(),
|
||||
assetId: z.string().optional().describe('Existing asset ID if duplicate'),
|
||||
|
||||
@@ -24,7 +24,7 @@ import z from 'zod';
|
||||
|
||||
const SanitizedAssetResponseSchema = z
|
||||
.object({
|
||||
id: z.uuidv4().describe('Asset ID'),
|
||||
id: z.string().describe('Asset ID'),
|
||||
type: AssetTypeSchema,
|
||||
thumbhash: z
|
||||
.string()
|
||||
@@ -52,8 +52,8 @@ export class SanitizedAssetResponseDto extends createZodDto(SanitizedAssetRespon
|
||||
|
||||
const AssetStackResponseSchema = z
|
||||
.object({
|
||||
id: z.uuidv4().describe('Stack ID'),
|
||||
primaryAssetId: z.uuidv4().describe('Primary asset ID'),
|
||||
id: z.string().describe('Stack ID'),
|
||||
primaryAssetId: z.string().describe('Primary asset ID'),
|
||||
assetCount: z.int().min(0).describe('Number of assets in stack'),
|
||||
})
|
||||
.meta({ id: 'AssetStackResponseDto' });
|
||||
@@ -65,7 +65,7 @@ export const AssetResponseSchema = SanitizedAssetResponseSchema.extend(
|
||||
.string()
|
||||
.meta({ format: 'date-time' })
|
||||
.describe('The UTC timestamp when the asset was originally uploaded to Immich.'),
|
||||
ownerId: z.uuidv4().describe('Owner user ID'),
|
||||
ownerId: z.string().describe('Owner user ID'),
|
||||
owner: UserResponseSchema.optional(),
|
||||
libraryId: z
|
||||
.uuidv4()
|
||||
@@ -103,7 +103,7 @@ export const AssetResponseSchema = SanitizedAssetResponseSchema.extend(
|
||||
people: z.array(PersonResponseSchema).optional(),
|
||||
checksum: z.string().describe('Base64 encoded SHA1 hash'),
|
||||
stack: AssetStackResponseSchema.nullish(),
|
||||
duplicateId: z.uuidv4().nullish().describe('Duplicate group ID'),
|
||||
duplicateId: z.string().nullish().describe('Duplicate group ID'),
|
||||
resized: z
|
||||
.boolean()
|
||||
.optional()
|
||||
|
||||
@@ -148,7 +148,7 @@ const AssetMetadataResponseSchema = z
|
||||
.meta({ id: 'AssetMetadataResponseDto' });
|
||||
|
||||
const AssetMetadataBulkResponseSchema = AssetMetadataResponseSchema.extend({
|
||||
assetId: z.uuidv4().describe('Asset ID'),
|
||||
assetId: z.string().describe('Asset ID'),
|
||||
}).meta({ id: 'AssetMetadataBulkResponseDto' });
|
||||
|
||||
const AssetCopySchema = z
|
||||
|
||||
@@ -29,7 +29,7 @@ const LoginCredentialSchema = z
|
||||
const LoginResponseSchema = z
|
||||
.object({
|
||||
accessToken: z.string().describe('Access token'),
|
||||
userId: z.uuidv4().describe('User ID'),
|
||||
userId: z.string().describe('User ID'),
|
||||
userEmail: toEmail.describe('User email'),
|
||||
name: z.string().describe('User name'),
|
||||
profileImagePath: z.string().describe('Profile image path'),
|
||||
|
||||
@@ -14,7 +14,7 @@ const DownloadInfoSchema = z
|
||||
const DownloadArchiveInfoSchema = z
|
||||
.object({
|
||||
size: z.int().describe('Archive size in bytes'),
|
||||
assetIds: z.array(z.uuidv4()).describe('Asset IDs in this archive'),
|
||||
assetIds: z.array(z.string()).describe('Asset IDs in this archive'),
|
||||
})
|
||||
.meta({ id: 'DownloadArchiveInfo' });
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import z from 'zod';
|
||||
|
||||
const DuplicateResponseSchema = z
|
||||
.object({
|
||||
duplicateId: z.uuidv4().describe('Duplicate group ID'),
|
||||
duplicateId: z.string().describe('Duplicate group ID'),
|
||||
assets: z.array(AssetResponseSchema).describe('Duplicate assets'),
|
||||
suggestedKeepAssetIds: z.array(z.uuidv4()).describe('Suggested asset IDs to keep based on file size and EXIF data'),
|
||||
})
|
||||
|
||||
@@ -27,7 +27,7 @@ const IntegrityDeleteReportSchema = z.object({ type: IntegrityReport }).meta({ i
|
||||
export class IntegrityDeleteReportDto extends createZodDto(IntegrityDeleteReportSchema) {}
|
||||
|
||||
const IntegrityReportResponseItemSchema = z.object({
|
||||
id: z.uuidv4().describe('Integrity report item id'),
|
||||
id: z.string().describe('Integrity report item id'),
|
||||
type: IntegrityReportSchema,
|
||||
path: z.string().describe('Integrity report item path'),
|
||||
});
|
||||
|
||||
@@ -62,8 +62,8 @@ const ValidateLibraryResponseSchema = z
|
||||
|
||||
const LibraryResponseSchema = z
|
||||
.object({
|
||||
id: z.uuidv4().describe('Library ID'),
|
||||
ownerId: z.uuidv4().describe('Owner user ID'),
|
||||
id: z.string().describe('Library ID'),
|
||||
ownerId: z.string().describe('Owner user ID'),
|
||||
name: z.string().describe('Library name'),
|
||||
assetCount: z.int().describe('Number of assets'),
|
||||
importPaths: z.array(z.string()).describe('Import paths'),
|
||||
|
||||
@@ -30,7 +30,7 @@ const MapMarkerSchema = z
|
||||
|
||||
const MapMarkerResponseSchema = z
|
||||
.object({
|
||||
id: z.uuidv4().describe('Asset ID'),
|
||||
id: z.string().describe('Asset ID'),
|
||||
lat: z.number().meta({ format: 'double' }).describe('Latitude'),
|
||||
lon: z.number().meta({ format: 'double' }).describe('Longitude'),
|
||||
city: z.string().nullable().describe('City name'),
|
||||
|
||||
@@ -59,7 +59,7 @@ const MemoryStatisticsResponseSchema = z
|
||||
|
||||
const MemoryResponseSchema = z
|
||||
.object({
|
||||
id: z.uuidv4().describe('Memory ID'),
|
||||
id: z.string().describe('Memory ID'),
|
||||
createdAt: isoDatetimeToDate.describe('Creation date'),
|
||||
updatedAt: isoDatetimeToDate.describe('Last update date'),
|
||||
deletedAt: isoDatetimeToDate.optional().describe('Deletion date'),
|
||||
@@ -67,7 +67,7 @@ const MemoryResponseSchema = z
|
||||
seenAt: isoDatetimeToDate.optional().describe('Date when memory was seen'),
|
||||
showAt: isoDatetimeToDate.optional().describe('Date when memory should be shown'),
|
||||
hideAt: isoDatetimeToDate.optional().describe('Date when memory should be hidden'),
|
||||
ownerId: z.uuidv4().describe('Owner user ID'),
|
||||
ownerId: z.string().describe('Owner user ID'),
|
||||
type: MemoryTypeSchema,
|
||||
data: OnThisDaySchema,
|
||||
isSaved: z.boolean().describe('Is memory saved'),
|
||||
|
||||
@@ -24,7 +24,7 @@ const TemplateSchema = z
|
||||
|
||||
const NotificationSchema = z
|
||||
.object({
|
||||
id: z.uuidv4().describe('Notification ID'),
|
||||
id: z.string().describe('Notification ID'),
|
||||
createdAt: isoDatetimeToDate.describe('Creation date'),
|
||||
level: NotificationLevelSchema,
|
||||
type: NotificationTypeSchema,
|
||||
|
||||
@@ -33,7 +33,7 @@ const PersonUpdateSchema = PersonCreateSchema.extend({
|
||||
}).meta({ id: 'PersonUpdateDto' });
|
||||
|
||||
const PeopleUpdateItemSchema = PersonUpdateSchema.extend({
|
||||
id: z.uuidv4().describe('Person ID'),
|
||||
id: z.string().describe('Person ID'),
|
||||
}).meta({ id: 'PeopleUpdateItem' });
|
||||
|
||||
const PeopleUpdateSchema = z
|
||||
@@ -60,7 +60,7 @@ const PersonSearchSchema = z
|
||||
|
||||
export const PersonResponseSchema = z
|
||||
.object({
|
||||
id: z.uuidv4().describe('Person ID'),
|
||||
id: z.string().describe('Person ID'),
|
||||
name: z.string().describe('Person name'),
|
||||
// TODO: use `isoDateToDate` when using `ZodSerializerDto` on the controllers.
|
||||
birthDate: z.string().meta({ format: 'date' }).describe('Person date of birth').nullable(),
|
||||
|
||||
@@ -32,7 +32,7 @@ const PluginMethodResponseSchema = z
|
||||
|
||||
const PluginResponseSchema = z
|
||||
.object({
|
||||
id: z.uuidv4().describe('Plugin ID'),
|
||||
id: z.string().describe('Plugin ID'),
|
||||
name: z.string().describe('Plugin name'),
|
||||
title: z.string().describe('Plugin title'),
|
||||
description: z.string().describe('Plugin description'),
|
||||
|
||||
@@ -73,7 +73,7 @@ const ServerVersionResponseSchema = z
|
||||
|
||||
const ServerVersionHistoryResponseSchema = z
|
||||
.object({
|
||||
id: z.uuidv4().describe('Version history entry ID'),
|
||||
id: z.string().describe('Version history entry ID'),
|
||||
createdAt: isoDatetimeToDate.describe('When this version was first seen'),
|
||||
version: z.string().describe('Version string'),
|
||||
})
|
||||
@@ -81,7 +81,7 @@ const ServerVersionHistoryResponseSchema = z
|
||||
|
||||
const UsageByUserSchema = z
|
||||
.object({
|
||||
userId: z.uuidv4().describe('User ID'),
|
||||
userId: z.string().describe('User ID'),
|
||||
userName: z.string().describe('User name'),
|
||||
photos: z.int().describe('Number of photos'),
|
||||
videos: z.int().describe('Number of videos'),
|
||||
|
||||
@@ -18,7 +18,7 @@ const SessionUpdateSchema = z
|
||||
|
||||
const SessionResponseSchema = z
|
||||
.object({
|
||||
id: z.uuidv4().describe('Session ID'),
|
||||
id: z.string().describe('Session ID'),
|
||||
createdAt: z.string().describe('Creation date'),
|
||||
updatedAt: z.string().describe('Last update date'),
|
||||
expiresAt: z.string().optional().describe('Expiration date'),
|
||||
|
||||
@@ -53,10 +53,10 @@ const SharedLinkLoginSchema = z
|
||||
|
||||
const SharedLinkResponseSchema = z
|
||||
.object({
|
||||
id: z.uuidv4().describe('Shared link ID'),
|
||||
id: z.string().describe('Shared link ID'),
|
||||
description: z.string().nullable().describe('Link description'),
|
||||
password: z.string().nullable().describe('Has password'),
|
||||
userId: z.uuidv4().describe('Owner user ID'),
|
||||
userId: z.string().describe('Owner user ID'),
|
||||
key: z.string().describe('Encryption key (base64url)'),
|
||||
type: SharedLinkTypeSchema,
|
||||
createdAt: isoDatetimeToDate.describe('Creation date'),
|
||||
|
||||
@@ -24,8 +24,8 @@ const StackUpdateSchema = z
|
||||
|
||||
const StackResponseSchema = z
|
||||
.object({
|
||||
id: z.uuidv4().describe('Stack ID'),
|
||||
primaryAssetId: z.uuidv4().describe('Primary asset ID'),
|
||||
id: z.string().describe('Stack ID'),
|
||||
primaryAssetId: z.string().describe('Primary asset ID'),
|
||||
assets: z.array(AssetResponseSchema),
|
||||
})
|
||||
.describe('Stack response')
|
||||
|
||||
+50
-50
@@ -19,7 +19,7 @@ import z from 'zod';
|
||||
|
||||
const SyncUserV1Schema = z
|
||||
.object({
|
||||
id: z.uuidv4().describe('User ID'),
|
||||
id: z.string().describe('User ID'),
|
||||
name: z.string().describe('User name'),
|
||||
email: z.string().describe('User email'),
|
||||
avatarColor: UserAvatarColorSchema.nullish(),
|
||||
@@ -40,27 +40,27 @@ const SyncAuthUserV1Schema = SyncUserV1Schema.merge(
|
||||
}),
|
||||
).meta({ id: 'SyncAuthUserV1' });
|
||||
|
||||
const SyncUserDeleteV1Schema = z.object({ userId: z.uuidv4().describe('User ID') }).meta({ id: 'SyncUserDeleteV1' });
|
||||
const SyncUserDeleteV1Schema = z.object({ userId: z.string().describe('User ID') }).meta({ id: 'SyncUserDeleteV1' });
|
||||
|
||||
const SyncPartnerV1Schema = z
|
||||
.object({
|
||||
sharedById: z.uuidv4().describe('Shared by ID'),
|
||||
sharedWithId: z.uuidv4().describe('Shared with ID'),
|
||||
sharedById: z.string().describe('Shared by ID'),
|
||||
sharedWithId: z.string().describe('Shared with ID'),
|
||||
inTimeline: z.boolean().describe('In timeline'),
|
||||
})
|
||||
.meta({ id: 'SyncPartnerV1' });
|
||||
|
||||
const SyncPartnerDeleteV1Schema = z
|
||||
.object({
|
||||
sharedById: z.uuidv4().describe('Shared by ID'),
|
||||
sharedWithId: z.uuidv4().describe('Shared with ID'),
|
||||
sharedById: z.string().describe('Shared by ID'),
|
||||
sharedWithId: z.string().describe('Shared with ID'),
|
||||
})
|
||||
.meta({ id: 'SyncPartnerDeleteV1' });
|
||||
|
||||
const SyncAssetV1Schema = z
|
||||
.object({
|
||||
id: z.uuidv4().describe('Asset ID'),
|
||||
ownerId: z.uuidv4().describe('Owner ID'),
|
||||
id: z.string().describe('Asset ID'),
|
||||
ownerId: z.string().describe('Owner ID'),
|
||||
originalFileName: z.string().describe('Original file name'),
|
||||
thumbhash: z.string().nullable().describe('Thumbhash'),
|
||||
checksum: z.string().describe('Checksum'),
|
||||
@@ -84,8 +84,8 @@ const SyncAssetV1Schema = z
|
||||
|
||||
const SyncAssetV2Schema = z
|
||||
.object({
|
||||
id: z.uuidv4().describe('Asset ID'),
|
||||
ownerId: z.uuidv4().describe('Owner ID'),
|
||||
id: z.string().describe('Asset ID'),
|
||||
ownerId: z.string().describe('Owner ID'),
|
||||
originalFileName: z.string().describe('Original file name'),
|
||||
thumbhash: z.string().nullable().describe('Thumbhash'),
|
||||
checksum: z.string().describe('Checksum'),
|
||||
@@ -123,12 +123,12 @@ export class SyncAssetV1 extends createZodDto(SyncAssetV1Schema) {}
|
||||
export class SyncAssetV2 extends createZodDto(SyncAssetV2Schema) {}
|
||||
|
||||
const SyncAssetDeleteV1Schema = z
|
||||
.object({ assetId: z.uuidv4().describe('Asset ID') })
|
||||
.object({ assetId: z.string().describe('Asset ID') })
|
||||
.meta({ id: 'SyncAssetDeleteV1' });
|
||||
|
||||
const SyncAssetExifV1Schema = z
|
||||
.object({
|
||||
assetId: z.uuidv4().describe('Asset ID'),
|
||||
assetId: z.string().describe('Asset ID'),
|
||||
description: z.string().nullable().describe('Description'),
|
||||
exifImageWidth: z.int().nullable().describe('Exif image width'),
|
||||
exifImageHeight: z.int().nullable().describe('Exif image height'),
|
||||
@@ -158,7 +158,7 @@ const SyncAssetExifV1Schema = z
|
||||
|
||||
const SyncAssetMetadataV1Schema = z
|
||||
.object({
|
||||
assetId: z.uuidv4().describe('Asset ID'),
|
||||
assetId: z.string().describe('Asset ID'),
|
||||
key: z.string().describe('Key'),
|
||||
value: z.record(z.string(), z.unknown()).describe('Value'),
|
||||
})
|
||||
@@ -166,15 +166,15 @@ const SyncAssetMetadataV1Schema = z
|
||||
|
||||
const SyncAssetMetadataDeleteV1Schema = z
|
||||
.object({
|
||||
assetId: z.uuidv4().describe('Asset ID'),
|
||||
assetId: z.string().describe('Asset ID'),
|
||||
key: z.string().describe('Key'),
|
||||
})
|
||||
.meta({ id: 'SyncAssetMetadataDeleteV1' });
|
||||
|
||||
const SyncAssetEditV1Schema = z
|
||||
.object({
|
||||
id: z.uuidv4().describe('Edit ID'),
|
||||
assetId: z.uuidv4().describe('Asset ID'),
|
||||
id: z.string().describe('Edit ID'),
|
||||
assetId: z.string().describe('Asset ID'),
|
||||
action: AssetEditActionSchema,
|
||||
parameters: z.record(z.string(), z.unknown()).describe('Edit parameters'),
|
||||
sequence: z.int().describe('Edit sequence'),
|
||||
@@ -182,7 +182,7 @@ const SyncAssetEditV1Schema = z
|
||||
.meta({ id: 'SyncAssetEditV1' });
|
||||
|
||||
const SyncAssetEditDeleteV1Schema = z
|
||||
.object({ editId: z.uuidv4().describe('Edit ID') })
|
||||
.object({ editId: z.string().describe('Edit ID') })
|
||||
.meta({ id: 'SyncAssetEditDeleteV1' });
|
||||
|
||||
@ExtraModel()
|
||||
@@ -199,28 +199,28 @@ export class SyncAssetEditV1 extends createZodDto(SyncAssetEditV1Schema) {}
|
||||
class SyncAssetEditDeleteV1 extends createZodDto(SyncAssetEditDeleteV1Schema) {}
|
||||
|
||||
const SyncAlbumDeleteV1Schema = z
|
||||
.object({ albumId: z.uuidv4().describe('Album ID') })
|
||||
.object({ albumId: z.string().describe('Album ID') })
|
||||
.meta({ id: 'SyncAlbumDeleteV1' });
|
||||
|
||||
const SyncAlbumUserDeleteV1Schema = z
|
||||
.object({
|
||||
albumId: z.uuidv4().describe('Album ID'),
|
||||
userId: z.uuidv4().describe('User ID'),
|
||||
albumId: z.string().describe('Album ID'),
|
||||
userId: z.string().describe('User ID'),
|
||||
})
|
||||
.meta({ id: 'SyncAlbumUserDeleteV1' });
|
||||
|
||||
const SyncAlbumUserV1Schema = z
|
||||
.object({
|
||||
albumId: z.uuidv4().describe('Album ID'),
|
||||
userId: z.uuidv4().describe('User ID'),
|
||||
albumId: z.string().describe('Album ID'),
|
||||
userId: z.string().describe('User ID'),
|
||||
role: AlbumUserRoleSchema,
|
||||
})
|
||||
.meta({ id: 'SyncAlbumUserV1' });
|
||||
|
||||
const SyncAlbumV1Schema = z
|
||||
.object({
|
||||
id: z.uuidv4().describe('Album ID'),
|
||||
ownerId: z.uuidv4().describe('Owner ID'),
|
||||
id: z.string().describe('Album ID'),
|
||||
ownerId: z.string().describe('Owner ID'),
|
||||
name: z.string().describe('Album name'),
|
||||
description: z.string().describe('Album description'),
|
||||
createdAt: isoDatetimeToDate.describe('Created at'),
|
||||
@@ -233,7 +233,7 @@ const SyncAlbumV1Schema = z
|
||||
|
||||
const SyncAlbumV2Schema = z
|
||||
.object({
|
||||
id: z.uuidv4().describe('Album ID'),
|
||||
id: z.string().describe('Album ID'),
|
||||
name: z.string().describe('Album name'),
|
||||
description: z.string().describe('Album description'),
|
||||
createdAt: isoDatetimeToDate.describe('Created at'),
|
||||
@@ -246,15 +246,15 @@ const SyncAlbumV2Schema = z
|
||||
|
||||
const SyncAlbumToAssetV1Schema = z
|
||||
.object({
|
||||
albumId: z.uuidv4().describe('Album ID'),
|
||||
assetId: z.uuidv4().describe('Asset ID'),
|
||||
albumId: z.string().describe('Album ID'),
|
||||
assetId: z.string().describe('Asset ID'),
|
||||
})
|
||||
.meta({ id: 'SyncAlbumToAssetV1' });
|
||||
|
||||
const SyncAlbumToAssetDeleteV1Schema = z
|
||||
.object({
|
||||
albumId: z.uuidv4().describe('Album ID'),
|
||||
assetId: z.uuidv4().describe('Asset ID'),
|
||||
albumId: z.string().describe('Album ID'),
|
||||
assetId: z.string().describe('Asset ID'),
|
||||
})
|
||||
.meta({ id: 'SyncAlbumToAssetDeleteV1' });
|
||||
|
||||
@@ -284,11 +284,11 @@ export function syncAlbumV2ToV1(
|
||||
|
||||
const SyncMemoryV1Schema = z
|
||||
.object({
|
||||
id: z.uuidv4().describe('Memory ID'),
|
||||
id: z.string().describe('Memory ID'),
|
||||
createdAt: isoDatetimeToDate.describe('Created at'),
|
||||
updatedAt: isoDatetimeToDate.describe('Updated at'),
|
||||
deletedAt: isoDatetimeToDate.nullable().describe('Deleted at'),
|
||||
ownerId: z.uuidv4().describe('Owner ID'),
|
||||
ownerId: z.string().describe('Owner ID'),
|
||||
type: MemoryTypeSchema,
|
||||
data: z.record(z.string(), z.unknown()).describe('Data'),
|
||||
isSaved: z.boolean().describe('Is saved'),
|
||||
@@ -300,43 +300,43 @@ const SyncMemoryV1Schema = z
|
||||
.meta({ id: 'SyncMemoryV1' });
|
||||
|
||||
const SyncMemoryDeleteV1Schema = z
|
||||
.object({ memoryId: z.uuidv4().describe('Memory ID') })
|
||||
.object({ memoryId: z.string().describe('Memory ID') })
|
||||
.meta({ id: 'SyncMemoryDeleteV1' });
|
||||
|
||||
const SyncMemoryAssetV1Schema = z
|
||||
.object({
|
||||
memoryId: z.uuidv4().describe('Memory ID'),
|
||||
assetId: z.uuidv4().describe('Asset ID'),
|
||||
memoryId: z.string().describe('Memory ID'),
|
||||
assetId: z.string().describe('Asset ID'),
|
||||
})
|
||||
.meta({ id: 'SyncMemoryAssetV1' });
|
||||
|
||||
const SyncMemoryAssetDeleteV1Schema = z
|
||||
.object({
|
||||
memoryId: z.uuidv4().describe('Memory ID'),
|
||||
assetId: z.uuidv4().describe('Asset ID'),
|
||||
memoryId: z.string().describe('Memory ID'),
|
||||
assetId: z.string().describe('Asset ID'),
|
||||
})
|
||||
.meta({ id: 'SyncMemoryAssetDeleteV1' });
|
||||
|
||||
const SyncStackV1Schema = z
|
||||
.object({
|
||||
id: z.uuidv4().describe('Stack ID'),
|
||||
id: z.string().describe('Stack ID'),
|
||||
createdAt: isoDatetimeToDate.describe('Created at'),
|
||||
updatedAt: isoDatetimeToDate.describe('Updated at'),
|
||||
primaryAssetId: z.uuidv4().describe('Primary asset ID'),
|
||||
ownerId: z.uuidv4().describe('Owner ID'),
|
||||
primaryAssetId: z.string().describe('Primary asset ID'),
|
||||
ownerId: z.string().describe('Owner ID'),
|
||||
})
|
||||
.meta({ id: 'SyncStackV1' });
|
||||
|
||||
const SyncStackDeleteV1Schema = z
|
||||
.object({ stackId: z.uuidv4().describe('Stack ID') })
|
||||
.object({ stackId: z.string().describe('Stack ID') })
|
||||
.meta({ id: 'SyncStackDeleteV1' });
|
||||
|
||||
const SyncPersonV1Schema = z
|
||||
.object({
|
||||
id: z.uuidv4().describe('Person ID'),
|
||||
id: z.string().describe('Person ID'),
|
||||
createdAt: isoDatetimeToDate.describe('Created at'),
|
||||
updatedAt: isoDatetimeToDate.describe('Updated at'),
|
||||
ownerId: z.uuidv4().describe('Owner ID'),
|
||||
ownerId: z.string().describe('Owner ID'),
|
||||
name: z.string().describe('Person name'),
|
||||
birthDate: isoDatetimeToDate.nullable().describe('Birth date'),
|
||||
isHidden: z.boolean().describe('Is hidden'),
|
||||
@@ -347,13 +347,13 @@ const SyncPersonV1Schema = z
|
||||
.meta({ id: 'SyncPersonV1' });
|
||||
|
||||
const SyncPersonDeleteV1Schema = z
|
||||
.object({ personId: z.uuidv4().describe('Person ID') })
|
||||
.object({ personId: z.string().describe('Person ID') })
|
||||
.meta({ id: 'SyncPersonDeleteV1' });
|
||||
|
||||
const SyncAssetFaceV1Schema = z
|
||||
.object({
|
||||
id: z.uuidv4().describe('Asset face ID'),
|
||||
assetId: z.uuidv4().describe('Asset ID'),
|
||||
id: z.string().describe('Asset face ID'),
|
||||
assetId: z.string().describe('Asset ID'),
|
||||
personId: z.string().nullable().describe('Person ID'),
|
||||
imageWidth: z.int().describe('Image width'),
|
||||
imageHeight: z.int().describe('Image height'),
|
||||
@@ -371,12 +371,12 @@ const SyncAssetFaceV2Schema = SyncAssetFaceV1Schema.extend({
|
||||
}).meta({ id: 'SyncAssetFaceV2' });
|
||||
|
||||
const SyncAssetFaceDeleteV1Schema = z
|
||||
.object({ assetFaceId: z.uuidv4().describe('Asset face ID') })
|
||||
.object({ assetFaceId: z.string().describe('Asset face ID') })
|
||||
.meta({ id: 'SyncAssetFaceDeleteV1' });
|
||||
|
||||
const SyncUserMetadataV1Schema = z
|
||||
.object({
|
||||
userId: z.uuidv4().describe('User ID'),
|
||||
userId: z.string().describe('User ID'),
|
||||
key: UserMetadataKeySchema,
|
||||
value: z.record(z.string(), z.unknown()).describe('User metadata value'),
|
||||
})
|
||||
@@ -384,7 +384,7 @@ const SyncUserMetadataV1Schema = z
|
||||
|
||||
const SyncUserMetadataDeleteV1Schema = z
|
||||
.object({
|
||||
userId: z.uuidv4().describe('User ID'),
|
||||
userId: z.string().describe('User ID'),
|
||||
key: UserMetadataKeySchema,
|
||||
})
|
||||
.meta({ id: 'SyncUserMetadataDeleteV1' });
|
||||
@@ -404,8 +404,8 @@ class SyncMemoryAssetDeleteV1 extends createZodDto(SyncMemoryAssetDeleteV1Schema
|
||||
|
||||
const SyncAssetOcrV1Schema = z
|
||||
.object({
|
||||
id: z.uuidv4().describe('OCR entry ID'),
|
||||
assetId: z.uuidv4().describe('Asset ID'),
|
||||
id: z.string().describe('OCR entry ID'),
|
||||
assetId: z.string().describe('Asset ID'),
|
||||
|
||||
x1: z.number().meta({ format: 'double' }).describe('Top-left X coordinate (normalized 0–1)'),
|
||||
y1: z.number().meta({ format: 'double' }).describe('Top-left Y coordinate (normalized 0–1)'),
|
||||
|
||||
@@ -40,7 +40,7 @@ const TagBulkAssetsResponseSchema = z
|
||||
|
||||
export const TagResponseSchema = z
|
||||
.object({
|
||||
id: z.uuidv4().describe('Tag ID'),
|
||||
id: z.string().describe('Tag ID'),
|
||||
parentId: z.string().optional().describe('Parent tag ID'),
|
||||
name: z.string().describe('Tag name'),
|
||||
value: z.string().describe('Tag value (full path)'),
|
||||
|
||||
@@ -11,7 +11,7 @@ export class CreateProfileImageDto {
|
||||
|
||||
const CreateProfileImageResponseSchema = z
|
||||
.object({
|
||||
userId: z.uuidv4().describe('User ID'),
|
||||
userId: z.string().describe('User ID'),
|
||||
profileChangedAt: isoDatetimeToDate.describe('Profile image change date'),
|
||||
profileImagePath: z.string().describe('Profile image file path'),
|
||||
})
|
||||
|
||||
@@ -58,7 +58,7 @@ const WorkflowUpdateSchema = z
|
||||
|
||||
const WorkflowResponseSchema = z
|
||||
.object({
|
||||
id: z.uuidv4().describe('Workflow ID'),
|
||||
id: z.string().describe('Workflow ID'),
|
||||
trigger: WorkflowTriggerSchema.describe('Workflow trigger type'),
|
||||
name: z.string().nullable().describe('Workflow name'),
|
||||
description: z.string().nullable().describe('Workflow description'),
|
||||
|
||||
@@ -78,9 +78,8 @@ export class MapRepository {
|
||||
.execute();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, [DummyValue.UUID], [DummyValue.UUID]] })
|
||||
@GenerateSql({ params: [[DummyValue.UUID], [DummyValue.UUID]] })
|
||||
getMapMarkers(
|
||||
authUserId: string,
|
||||
ownerIds: string[],
|
||||
albumIds: string[],
|
||||
{ isArchived, isFavorite, fileCreatedAfter, fileCreatedBefore }: MapMarkerSearchOptions = {},
|
||||
@@ -90,7 +89,7 @@ export class MapRepository {
|
||||
qb.where((eb) =>
|
||||
eb.or([
|
||||
eb('asset.visibility', '=', AssetVisibility.Timeline),
|
||||
eb.and([eb('asset.ownerId', '=', authUserId), eb('asset.visibility', '=', AssetVisibility.Archive)]),
|
||||
eb('asset.visibility', '=', AssetVisibility.Archive),
|
||||
]),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -369,26 +369,6 @@ describe(DuplicateService.name, () => {
|
||||
expect(mocks.job.queueAll).toHaveBeenCalledWith([{ name: JobName.SidecarWrite, data: { id: asset1.id } }]);
|
||||
});
|
||||
|
||||
it('should not merge metadata when multiple assets are kept', async () => {
|
||||
const asset1 = AssetFactory.create({ isFavorite: true });
|
||||
const asset2 = AssetFactory.create();
|
||||
mocks.access.duplicate.checkOwnerAccess.mockResolvedValue(new Set(['group-1']));
|
||||
mocks.duplicateRepository.get.mockResolvedValue({
|
||||
duplicateId: 'group-1',
|
||||
assets: [asset1 as unknown as MapAsset, asset2 as unknown as MapAsset],
|
||||
});
|
||||
|
||||
const result = await sut.resolve(authStub.admin, {
|
||||
groups: [{ duplicateId: 'group-1', keepAssetIds: [asset1.id, asset2.id], trashAssetIds: [] }],
|
||||
});
|
||||
|
||||
expect(result[0].success).toBe(true);
|
||||
expect(mocks.album.addAssetIdsToAlbums).not.toHaveBeenCalled();
|
||||
expect(mocks.tag.replaceAssetTags).not.toHaveBeenCalled();
|
||||
expect(mocks.asset.updateAllExif).not.toHaveBeenCalled();
|
||||
expect(mocks.asset.updateAll).toHaveBeenCalledWith([asset1.id, asset2.id], { duplicateId: null });
|
||||
});
|
||||
|
||||
// NOTE: The following integration-style tests are covered by E2E tests instead
|
||||
// to avoid complex mock setup. The validation and error-handling logic above
|
||||
// is thoroughly unit tested.
|
||||
|
||||
@@ -156,51 +156,51 @@ export class DuplicateService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
// Only merge metadata into the keeper when exactly one asset can absorb trashed duplicates.
|
||||
if (idsToKeep.length === 1 && idsToTrash.length > 0) {
|
||||
const assetAlbumMap = await this.albumRepository.getByAssetIds(auth.user.id, [...groupAssetIds]);
|
||||
const assetAlbumMap = await this.albumRepository.getByAssetIds(auth.user.id, [...groupAssetIds]);
|
||||
|
||||
const { assetUpdate, exifUpdate, mergedAlbumIds, mergedTagIds, mergedTagValues } = this.getSyncMergeResult(
|
||||
duplicateGroup.assets,
|
||||
assetAlbumMap,
|
||||
);
|
||||
const { assetUpdate, exifUpdate, mergedAlbumIds, mergedTagIds, mergedTagValues } = this.getSyncMergeResult(
|
||||
duplicateGroup.assets,
|
||||
assetAlbumMap,
|
||||
);
|
||||
|
||||
if (mergedAlbumIds.length > 0) {
|
||||
const allowedAlbumIds = await this.checkAccess({
|
||||
auth,
|
||||
permission: Permission.AlbumAssetCreate,
|
||||
ids: mergedAlbumIds,
|
||||
});
|
||||
if (mergedAlbumIds.length > 0) {
|
||||
const allowedAlbumIds = await this.checkAccess({
|
||||
auth,
|
||||
permission: Permission.AlbumAssetCreate,
|
||||
ids: mergedAlbumIds,
|
||||
});
|
||||
|
||||
const allowedShareIds = await this.checkAccess({
|
||||
auth,
|
||||
permission: Permission.AssetShare,
|
||||
ids: idsToKeep,
|
||||
});
|
||||
const allowedShareIds = await this.checkAccess({
|
||||
auth,
|
||||
permission: Permission.AssetShare,
|
||||
ids: idsToKeep,
|
||||
});
|
||||
|
||||
if (allowedAlbumIds.size > 0 && allowedShareIds.size > 0) {
|
||||
await this.albumRepository.addAssetIdsToAlbums(
|
||||
[...allowedAlbumIds].flatMap((albumId) => [...allowedShareIds].map((assetId) => ({ albumId, assetId }))),
|
||||
);
|
||||
}
|
||||
if (allowedAlbumIds.size > 0 && allowedShareIds.size > 0) {
|
||||
await this.albumRepository.addAssetIdsToAlbums(
|
||||
[...allowedAlbumIds].flatMap((albumId) => [...allowedShareIds].map((assetId) => ({ albumId, assetId }))),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (mergedTagIds.length > 0) {
|
||||
const allowedTagIds = await this.checkAccess({
|
||||
auth,
|
||||
permission: Permission.TagAsset,
|
||||
ids: mergedTagIds,
|
||||
});
|
||||
if (mergedTagIds.length > 0) {
|
||||
const allowedTagIds = await this.checkAccess({
|
||||
auth,
|
||||
permission: Permission.TagAsset,
|
||||
ids: mergedTagIds,
|
||||
});
|
||||
|
||||
if (allowedTagIds.size > 0) {
|
||||
await Promise.all(
|
||||
idsToKeep.map((assetId) => this.tagRepository.replaceAssetTags(assetId, [...allowedTagIds])),
|
||||
);
|
||||
if (allowedTagIds.size > 0) {
|
||||
// Replace tags for each keeper asset to ensure all merged tags are applied
|
||||
await Promise.all(idsToKeep.map((assetId) => this.tagRepository.replaceAssetTags(assetId, [...allowedTagIds])));
|
||||
|
||||
await this.assetRepository.updateAllExif(idsToKeep, { tags: mergedTagValues });
|
||||
}
|
||||
// Update asset_exif.tags so the subsequent SidecarWrite + MetadataExtraction
|
||||
// cycle preserves the merged tags (updateAllExif locks the property automatically)
|
||||
await this.assetRepository.updateAllExif(idsToKeep, { tags: mergedTagValues });
|
||||
}
|
||||
}
|
||||
|
||||
if (idsToKeep.length > 0) {
|
||||
const hasExifUpdate = Object.keys(exifUpdate).length > 0;
|
||||
const hasTagUpdate = mergedTagIds.length > 0;
|
||||
|
||||
@@ -213,8 +213,6 @@ export class DuplicateService extends BaseService {
|
||||
}
|
||||
|
||||
await this.assetRepository.updateAll(idsToKeep, { duplicateId: null, ...assetUpdate });
|
||||
} else if (idsToKeep.length > 0) {
|
||||
await this.assetRepository.updateAll(idsToKeep, { duplicateId: null });
|
||||
}
|
||||
|
||||
if (idsToTrash.length > 0) {
|
||||
|
||||
@@ -59,7 +59,6 @@ describe(MapService.name, () => {
|
||||
const markers = await sut.getMapMarkers(auth, { withPartners: true });
|
||||
|
||||
expect(mocks.map.getMapMarkers).toHaveBeenCalledWith(
|
||||
auth.user.id,
|
||||
[auth.user.id, partner.sharedById],
|
||||
expect.arrayContaining([]),
|
||||
{ withPartners: true },
|
||||
|
||||
@@ -15,7 +15,7 @@ export class MapService extends BaseService {
|
||||
|
||||
const albumIds = options.withSharedAlbums ? await this.albumRepository.getAllIds(auth.user.id) : [];
|
||||
|
||||
return this.mapRepository.getMapMarkers(auth.user.id, userIds, albumIds, options);
|
||||
return this.mapRepository.getMapMarkers(userIds, albumIds, options);
|
||||
}
|
||||
|
||||
async reverseGeocode(dto: MapReverseGeocodeDto) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { DateTime } from 'luxon';
|
||||
import { SemVer } from 'semver';
|
||||
import { defaults } from 'src/config';
|
||||
import { serverVersion } from 'src/constants';
|
||||
import { ReleaseChannel } from 'src/dtos/system-config.dto';
|
||||
import { CronJob, JobName, JobStatus, SystemMetadataKey } from 'src/enum';
|
||||
import { VersionService } from 'src/services/version.service';
|
||||
@@ -22,10 +23,16 @@ describe(VersionService.name, () => {
|
||||
mocks.cron.update.mockResolvedValue();
|
||||
});
|
||||
|
||||
vitest.mock(import('src/constants.js'), async (importOriginal) => ({
|
||||
...(await importOriginal()),
|
||||
serverVersion: new SemVer('v3.0.0'),
|
||||
}));
|
||||
beforeAll(() => {
|
||||
vitest.mock(import('src/constants.js'), async () => ({
|
||||
...(await vitest.importActual<typeof import('src/constants.js')>('src/constants.js')),
|
||||
serverVersion: new SemVer('v3.0.0'),
|
||||
}));
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
vitest.unmock(import('src/constants.js'));
|
||||
});
|
||||
|
||||
it('should work', () => {
|
||||
expect(sut).toBeDefined();
|
||||
@@ -46,7 +53,7 @@ describe(VersionService.name, () => {
|
||||
mocks.versionHistory.getLatest.mockResolvedValue({
|
||||
id: 'version-1',
|
||||
createdAt: new Date(),
|
||||
version: '3.0.0',
|
||||
version: serverVersion.toString(),
|
||||
});
|
||||
await expect(sut.onBootstrap()).resolves.toBeUndefined();
|
||||
expect(mocks.versionHistory.create).not.toHaveBeenCalled();
|
||||
@@ -57,7 +64,7 @@ describe(VersionService.name, () => {
|
||||
mocks.versionHistory.getLatest.mockResolvedValue({
|
||||
id: 'version-1',
|
||||
createdAt: new Date(),
|
||||
version: '3.0.0',
|
||||
version: serverVersion.toString(),
|
||||
});
|
||||
await sut.onBootstrap();
|
||||
expect(mocks.cron.create).toHaveBeenCalledWith(
|
||||
@@ -114,7 +121,7 @@ describe(VersionService.name, () => {
|
||||
checkedAt: DateTime.utc().minus({ seconds: 60 }).toISO(),
|
||||
releaseVersion: '1.0.0',
|
||||
});
|
||||
mocks.serverInfo.getLatestRelease.mockResolvedValue(mockVersionResponse('v3.0.0'));
|
||||
mocks.serverInfo.getLatestRelease.mockResolvedValue(mockVersionResponse(serverVersion.toString()));
|
||||
await expect(sut.handleVersionCheck()).resolves.toEqual(JobStatus.Success);
|
||||
expect(mocks.serverInfo.getLatestRelease).toHaveBeenCalled();
|
||||
});
|
||||
@@ -128,11 +135,11 @@ describe(VersionService.name, () => {
|
||||
});
|
||||
|
||||
it('should not notify if the version is equal', async () => {
|
||||
mocks.serverInfo.getLatestRelease.mockResolvedValue(mockVersionResponse('v3.0.0'));
|
||||
mocks.serverInfo.getLatestRelease.mockResolvedValue(mockVersionResponse(serverVersion.toString()));
|
||||
await expect(sut.handleVersionCheck()).resolves.toEqual(JobStatus.Success);
|
||||
expect(mocks.systemMetadata.set).toHaveBeenCalledWith(SystemMetadataKey.VersionCheckState, {
|
||||
checkedAt: expect.any(String),
|
||||
releaseVersion: 'v3.0.0',
|
||||
releaseVersion: serverVersion.toString(),
|
||||
});
|
||||
expect(mocks.websocket.clientBroadcast).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -261,6 +261,10 @@
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if show.brokenAsset}
|
||||
<BrokenAsset class="absolute size-full text-xl" />
|
||||
{/if}
|
||||
|
||||
{#if show.preview}
|
||||
<ImageLayer
|
||||
{adaptiveImageLoader}
|
||||
@@ -286,10 +290,6 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if show.brokenAsset}
|
||||
<BrokenAsset class="absolute inset-0 z-10 size-full text-xl" />
|
||||
{/if}
|
||||
|
||||
{#if overlays}
|
||||
<div class="pointer-events-none absolute inset-0">
|
||||
{@render overlays()}
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
import { navigate } from '$lib/utils/navigation';
|
||||
import { toTimelineAsset } from '$lib/utils/timeline-util';
|
||||
import { type AlbumResponseDto, type AssetResponseDto, type PersonResponseDto, getAssetInfo } from '@immich/sdk';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { onDestroy, onMount, untrack } from 'svelte';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
interface Props {
|
||||
@@ -82,7 +82,7 @@
|
||||
$effect(() => {
|
||||
const asset = assetViewerManager.asset;
|
||||
if (asset) {
|
||||
handlePromiseError(loadCloseAssets(asset));
|
||||
untrack(() => handlePromiseError(loadCloseAssets(asset)));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ export enum OpenQueryParam {
|
||||
PURCHASE_SETTINGS = 'user-purchase-settings',
|
||||
}
|
||||
|
||||
export const maximumLengthSearchPeople = 100;
|
||||
export const maximumLengthSearchPeople = 1000;
|
||||
|
||||
// time to load the map before displaying the loading spinner
|
||||
export const timeToLoadTheMap: number = 100;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import type { SearchOptions } from '$lib/utils/dipatch';
|
||||
import { IconButton, LoadingSpinner } from '@immich/ui';
|
||||
import { mdiClose, mdiMagnify } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
@@ -8,7 +9,7 @@
|
||||
roundedBottom?: boolean;
|
||||
showLoadingSpinner: boolean;
|
||||
placeholder: string;
|
||||
onSearch?: (options: { force?: boolean }) => void;
|
||||
onSearch?: (options: SearchOptions) => void;
|
||||
onReset?: () => void;
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ class AssetViewerManager extends BaseEventManager<Events> {
|
||||
imageLoaderStatus = $state<ImageLoaderStatus | undefined>();
|
||||
#isImageLoading = $derived.by(() => {
|
||||
const quality = this.imageLoaderStatus?.quality;
|
||||
if (!quality || this.imageLoaderStatus?.hasError) {
|
||||
if (!quality) {
|
||||
return false;
|
||||
}
|
||||
const previewOrOriginalReady = quality.preview === 'success' || quality.original === 'success';
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export interface ResetOptions {
|
||||
default?: boolean;
|
||||
}
|
||||
|
||||
export interface SearchOptions {
|
||||
force?: boolean;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { AssetResponseDto } from '@immich/sdk';
|
||||
import { getExifCount } from '$lib/utils/exif-utils';
|
||||
|
||||
describe('getting the exif count', () => {
|
||||
it('returns 0 when exifInfo is undefined', () => {
|
||||
const asset = {};
|
||||
expect(getExifCount(asset as AssetResponseDto)).toBe(0);
|
||||
});
|
||||
|
||||
it('returns 0 when exifInfo is empty', () => {
|
||||
const asset = { exifInfo: {} };
|
||||
expect(getExifCount(asset as AssetResponseDto)).toBe(0);
|
||||
});
|
||||
|
||||
it('returns the correct count of non-null exifInfo properties', () => {
|
||||
const asset = { exifInfo: { fileSizeInByte: 200, rating: 5, fNumber: null } };
|
||||
expect(getExifCount(asset as AssetResponseDto)).toBe(2);
|
||||
});
|
||||
|
||||
it('ignores null, undefined and empty properties in exifInfo', () => {
|
||||
const asset = { exifInfo: { fileSizeInByte: 200, rating: null, fNumber: undefined, description: '' } };
|
||||
expect(getExifCount(asset as AssetResponseDto)).toBe(1);
|
||||
});
|
||||
|
||||
it('returns the correct count when all exifInfo properties are non-null', () => {
|
||||
const asset = { exifInfo: { fileSizeInByte: 200, rating: 5, fNumber: 1, description: 'test' } };
|
||||
expect(getExifCount(asset as AssetResponseDto)).toBe(4);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,5 @@
|
||||
import type { AssetResponseDto } from '@immich/sdk';
|
||||
|
||||
export const getExifCount = (asset: AssetResponseDto) => {
|
||||
return Object.values(asset.exifInfo ?? {}).filter(Boolean).length;
|
||||
};
|
||||
@@ -5,3 +5,13 @@ export const removeAccents = (str: string) => {
|
||||
export const normalizeSearchString = (str: string) => {
|
||||
return removeAccents(str.toLocaleLowerCase());
|
||||
};
|
||||
|
||||
export const buildDateString = (year: number, month?: number, day?: number) => {
|
||||
return [
|
||||
year.toString(),
|
||||
month && !Number.isNaN(month) ? month.toString() : undefined,
|
||||
day && !Number.isNaN(day) ? day.toString() : undefined,
|
||||
]
|
||||
.filter((date) => date !== undefined)
|
||||
.join('-');
|
||||
};
|
||||
|
||||
@@ -30,6 +30,9 @@ export const getTriggerDescription = ($t: MessageFormatter, type: WorkflowTrigge
|
||||
case WorkflowTrigger.AssetMetadataExtraction: {
|
||||
return $t('trigger_asset_metadata_extraction_description');
|
||||
}
|
||||
default: {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -76,6 +79,10 @@ export const getWorkflowDefaultConfig = (schema: JSONSchemaProperty) => {
|
||||
config[key] = property.properties ? getWorkflowDefaultConfig(property) : {};
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
console.log(`Unknown configuration type: ${property.type}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-10
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { lang, locale } from '$lib/stores/preferences.store';
|
||||
import { locale } from '$lib/stores/preferences.store';
|
||||
import { getAssetMediaUrl } from '$lib/utils';
|
||||
import { getAllMetadataItems, type DifferingMetadataFields } from '$lib/utils/duplicate-utils';
|
||||
import { getAltText } from '$lib/utils/thumbnail-util';
|
||||
@@ -30,8 +30,7 @@
|
||||
initialVisibleCount = 5,
|
||||
}: Props = $props();
|
||||
|
||||
const listFormat = $derived(new Intl.ListFormat($lang));
|
||||
const isFromExternalLibrary = $derived(!!asset.libraryId);
|
||||
let isFromExternalLibrary = $derived(!!asset.libraryId);
|
||||
|
||||
const visibleMetadataItems = $derived(
|
||||
getAllMetadataItems(asset, $t, $locale)
|
||||
@@ -117,13 +116,7 @@
|
||||
{#await getAllAlbums({ assetId: asset.id })}
|
||||
{$t('scanning_for_album')}
|
||||
{:then albums}
|
||||
{#if albums.length === 1}
|
||||
{albums[0].albumName}
|
||||
{:else}
|
||||
<span title={listFormat.format(albums.map(({ albumName }) => albumName))}>
|
||||
{$t('in_albums', { values: { count: albums.length } })}
|
||||
</span>
|
||||
{/if}
|
||||
{$t('in_albums', { values: { count: albums.length } })}
|
||||
{/await}
|
||||
</InfoRow>
|
||||
</div>
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
}
|
||||
uploadAssetsStore.reset();
|
||||
}}
|
||||
class="fixed inset-e-16 bottom-6 z-60"
|
||||
class="fixed inset-e-16 bottom-6"
|
||||
>
|
||||
{#if showDetail}
|
||||
<div
|
||||
|
||||
@@ -88,7 +88,6 @@
|
||||
desc={$t('admin.transcoding_accepted_video_codecs_description')}
|
||||
bind:value={configToEdit.ffmpeg.acceptedVideoCodecs}
|
||||
name="videoCodecs"
|
||||
lockedOptions={[configToEdit.ffmpeg.targetVideoCodec]}
|
||||
options={[
|
||||
{ value: VideoCodec.H264, text: 'H.264' },
|
||||
{ value: VideoCodec.Hevc, text: 'HEVC' },
|
||||
@@ -107,7 +106,6 @@
|
||||
desc={$t('admin.transcoding_accepted_audio_codecs_description')}
|
||||
bind:value={configToEdit.ffmpeg.acceptedAudioCodecs}
|
||||
name="audioCodecs"
|
||||
lockedOptions={[configToEdit.ffmpeg.targetAudioCodec]}
|
||||
options={[
|
||||
{ value: AudioCodec.Aac, text: 'AAC' },
|
||||
{ value: AudioCodec.Mp3, text: 'MP3' },
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
<script lang="ts" generics="T extends string">
|
||||
<script lang="ts">
|
||||
import { Checkbox, Label } from '@immich/ui';
|
||||
import { t } from 'svelte-i18n';
|
||||
import { quintOut } from 'svelte/easing';
|
||||
import { fly } from 'svelte/transition';
|
||||
|
||||
interface Props {
|
||||
value: T[];
|
||||
options: { value: T; text: string }[];
|
||||
value: string[];
|
||||
options: { value: string; text: string }[];
|
||||
label?: string;
|
||||
desc?: string;
|
||||
name?: string;
|
||||
isEdited?: boolean;
|
||||
disabled?: boolean;
|
||||
lockedOptions?: T[];
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -23,10 +22,9 @@
|
||||
name = '',
|
||||
isEdited = false,
|
||||
disabled = false,
|
||||
lockedOptions = [],
|
||||
}: Props = $props();
|
||||
|
||||
function handleCheckboxChange(option: T) {
|
||||
function handleCheckboxChange(option: string) {
|
||||
value = value.includes(option) ? value.filter((item) => item !== option) : [...value, option];
|
||||
}
|
||||
</script>
|
||||
@@ -59,7 +57,7 @@
|
||||
size="tiny"
|
||||
id="{option.value}-checkbox"
|
||||
checked={value.includes(option.value)}
|
||||
disabled={disabled || lockedOptions.includes(option.value)}
|
||||
{disabled}
|
||||
onCheckedChange={() => handleCheckboxChange(option.value)}
|
||||
/>
|
||||
<Label label={option.text} for="{option.value}-checkbox" size="small" />
|
||||
|
||||
@@ -141,11 +141,11 @@
|
||||
<Alert color="danger" title={errorMessage} closable />
|
||||
{/if}
|
||||
|
||||
<Field label={$t('email')} required="indicator">
|
||||
<Field label={$t('email')}>
|
||||
<Input id="email" name="email" type="email" autocomplete="email" bind:value={email} />
|
||||
</Field>
|
||||
|
||||
<Field label={$t('password')} required="indicator">
|
||||
<Field label={$t('password')}>
|
||||
<PasswordInput id="password" bind:value={password} autocomplete="current-password" />
|
||||
</Field>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user