Compare commits

..

1 commit

Author SHA1 Message Date
Jason Rasmussen
905ea4048d
fix(deps): bump sharp 2023-12-05 16:39:13 -05:00
16 changed files with 744 additions and 562 deletions

View file

@ -68,20 +68,21 @@ class AlbumThumbnailListTile extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)),
borderRadius: BorderRadius.circular(8),
child: album.thumbnail.value == null
? buildEmptyThumbnail()
: buildAlbumThumbnail(),
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
Padding(
padding: const EdgeInsets.only(
left: 8.0,
right: 8.0,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
album.name,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontWeight: FontWeight.bold,
),
@ -109,7 +110,6 @@ class AlbumThumbnailListTile extends StatelessWidget {
],
),
),
),
],
),
),

View file

@ -1,13 +1,10 @@
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
import 'package:immich_mobile/shared/models/asset.dart';
import 'package:immich_mobile/shared/models/server_info/server_version.model.dart';
import 'package:immich_mobile/shared/models/store.dart';
import 'package:immich_mobile/shared/providers/asset.provider.dart';
import 'package:immich_mobile/shared/providers/db.provider.dart';
import 'package:immich_mobile/shared/providers/server_info.provider.dart';
import 'package:immich_mobile/shared/services/sync.service.dart';
import 'package:immich_mobile/utils/debounce.dart';
@ -17,33 +14,13 @@ import 'package:socket_io_client/socket_io_client.dart';
enum PendingAction {
assetDelete,
assetUploaded,
assetHidden,
}
class PendingChange {
final String id;
final PendingAction action;
final dynamic value;
const PendingChange(
this.id,
this.action,
this.value,
);
@override
String toString() => 'PendingChange(id: $id, action: $action, value: $value)';
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is PendingChange && other.id == id && other.action == action;
}
@override
int get hashCode => id.hashCode ^ action.hashCode;
const PendingChange(this.action, this.value);
}
class WebsocketState {
@ -154,7 +131,6 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
socket.on('on_asset_trash', _handleServerUpdates);
socket.on('on_asset_restore', _handleServerUpdates);
socket.on('on_asset_update', _handleServerUpdates);
socket.on('on_asset_hidden', _handleOnAssetHidden);
socket.on('on_new_release', _handleReleaseUpdates);
} catch (e) {
debugPrint("[WEBSOCKET] Catch Websocket Error - ${e.toString()}");
@ -187,77 +163,34 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
}
void addPendingChange(PendingAction action, dynamic value) {
final now = DateTime.now();
state = state.copyWith(
pendingChanges: [
...state.pendingChanges,
PendingChange(now.millisecondsSinceEpoch.toString(), action, value),
],
pendingChanges: [...state.pendingChanges, PendingChange(action, value)],
);
_debounce(handlePendingChanges);
}
Future<void> _handlePendingDeletes() async {
void handlePendingChanges() {
final deleteChanges = state.pendingChanges
.where((c) => c.action == PendingAction.assetDelete)
.toList();
if (deleteChanges.isNotEmpty) {
List<String> remoteIds =
deleteChanges.map((a) => a.value.toString()).toList();
await _ref.read(syncServiceProvider).handleRemoteAssetRemoval(remoteIds);
_ref.read(syncServiceProvider).handleRemoteAssetRemoval(remoteIds);
state = state.copyWith(
pendingChanges: state.pendingChanges
.whereNot((c) => deleteChanges.contains(c))
.where((c) => c.action != PendingAction.assetDelete)
.toList(),
);
}
}
Future<void> _handlePendingUploaded() async {
final uploadedChanges = state.pendingChanges
.where((c) => c.action == PendingAction.assetUploaded)
.toList();
if (uploadedChanges.isNotEmpty) {
List<AssetResponseDto?> remoteAssets = uploadedChanges
.map((a) => AssetResponseDto.fromJson(a.value))
.toList();
for (final dto in remoteAssets) {
void _handleOnUploadSuccess(dynamic data) {
final dto = AssetResponseDto.fromJson(data);
if (dto != null) {
final newAsset = Asset.remote(dto);
await _ref.watch(assetProvider.notifier).onNewAssetUploaded(newAsset);
_ref.watch(assetProvider.notifier).onNewAssetUploaded(newAsset);
}
}
state = state.copyWith(
pendingChanges: state.pendingChanges
.whereNot((c) => uploadedChanges.contains(c))
.toList(),
);
}
}
Future<void> _handlingPendingHidden() async {
final hiddenChanges = state.pendingChanges
.where((c) => c.action == PendingAction.assetHidden)
.toList();
if (hiddenChanges.isNotEmpty) {
List<String> remoteIds =
hiddenChanges.map((a) => a.value.toString()).toList();
final db = _ref.watch(dbProvider);
await db.writeTxn(() => db.assets.deleteAllByRemoteId(remoteIds));
state = state.copyWith(
pendingChanges: state.pendingChanges
.whereNot((c) => hiddenChanges.contains(c))
.toList(),
);
}
}
void handlePendingChanges() async {
await _handlePendingUploaded();
await _handlePendingDeletes();
await _handlingPendingHidden();
}
void _handleOnConfigUpdate(dynamic _) {
_ref.read(serverInfoProvider.notifier).getServerFeatures();
@ -269,14 +202,10 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
_ref.read(assetProvider.notifier).getAllAsset();
}
void _handleOnUploadSuccess(dynamic data) =>
addPendingChange(PendingAction.assetUploaded, data);
void _handleOnAssetDelete(dynamic data) =>
void _handleOnAssetDelete(dynamic data) {
addPendingChange(PendingAction.assetDelete, data);
void _handleOnAssetHidden(dynamic data) =>
addPendingChange(PendingAction.assetHidden, data);
_debounce(handlePendingChanges);
}
_handleReleaseUpdates(dynamic data) {
// Json guard

View file

@ -86,14 +86,11 @@ class _DateTimePicker extends HookWidget {
final timeZones = useMemoized(() => getAllTimeZones(), const []);
void pickDate() async {
final now = DateTime.now();
// Handles cases where the date from the asset is far off in the future
final initialDate = date.value.isAfter(now) ? now : date.value;
final newDate = await showDatePicker(
context: context,
initialDate: initialDate,
initialDate: date.value,
firstDate: DateTime(1800),
lastDate: now,
lastDate: DateTime.now(),
);
if (newDate == null) {
return;

View file

@ -62,9 +62,6 @@
"versioning": "node"
}
],
"ignorePaths": [
"mobile/openapi/pubspec.yaml"
],
"ignoreDeps": [
"http",
"latlong2",

View file

@ -1,5 +1,5 @@
# dev build
FROM ghcr.io/immich-app/base-server-dev:20231201@sha256:4701c0c5920c78e73040dd2b74d22042ffce393f1a9d3453d90a0ecf81ff8649 as dev
FROM ghcr.io/immich-app/base-server-dev:main as dev
WORKDIR /usr/src/app
COPY server/package.json server/package-lock.json ./
@ -23,7 +23,7 @@ RUN npm run build
# prod build
FROM ghcr.io/immich-app/base-server-prod:20231201@sha256:b8e86cf4c3cad872f54bab25a83f7503480049eea5c0ae36a8b8460b13cad3b5
FROM ghcr.io/immich-app/base-server-prod:main
WORKDIR /usr/src/app
ENV NODE_ENV=production

1051
server/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -69,12 +69,13 @@
"luxon": "^3.4.2",
"mv": "^2.1.1",
"nest-commander": "^3.11.1",
"node-addon-api": "^7.0.0",
"openid-client": "^5.4.3",
"pg": "^8.11.3",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1",
"sanitize-filename": "^1.6.3",
"sharp": "^0.32.6",
"sharp": "^0.33.0",
"thumbhash": "^0.1.1",
"typeorm": "^0.3.17",
"typesense": "^1.7.1",

View file

@ -3,7 +3,6 @@ import {
assetStub,
newAlbumRepositoryMock,
newAssetRepositoryMock,
newCommunicationRepositoryMock,
newCryptoRepositoryMock,
newJobRepositoryMock,
newMediaRepositoryMock,
@ -20,10 +19,8 @@ import { constants } from 'fs/promises';
import { when } from 'jest-when';
import { JobName } from '../job';
import {
CommunicationEvent,
IAlbumRepository,
IAssetRepository,
ICommunicationRepository,
ICryptoRepository,
IJobRepository,
IMediaRepository,
@ -49,7 +46,6 @@ describe(MetadataService.name, () => {
let mediaMock: jest.Mocked<IMediaRepository>;
let personMock: jest.Mocked<IPersonRepository>;
let storageMock: jest.Mocked<IStorageRepository>;
let communicationMock: jest.Mocked<ICommunicationRepository>;
let sut: MetadataService;
beforeEach(async () => {
@ -61,7 +57,6 @@ describe(MetadataService.name, () => {
metadataMock = newMetadataRepositoryMock();
moveMock = newMoveRepositoryMock();
personMock = newPersonRepositoryMock();
communicationMock = newCommunicationRepositoryMock();
storageMock = newStorageRepositoryMock();
mediaMock = newMediaRepositoryMock();
@ -75,7 +70,6 @@ describe(MetadataService.name, () => {
configMock,
mediaMock,
moveMock,
communicationMock,
personMock,
);
});
@ -178,23 +172,6 @@ describe(MetadataService.name, () => {
expect(assetMock.save).toHaveBeenCalledWith({ id: assetStub.livePhotoMotionAsset.id, isVisible: false });
expect(albumMock.removeAsset).toHaveBeenCalledWith(assetStub.livePhotoMotionAsset.id);
});
it('should notify clients on live photo link', async () => {
assetMock.getByIds.mockResolvedValue([
{
...assetStub.livePhotoStillAsset,
exifInfo: { livePhotoCID: assetStub.livePhotoMotionAsset.id } as ExifEntity,
},
]);
assetMock.findLivePhotoMatch.mockResolvedValue(assetStub.livePhotoMotionAsset);
await expect(sut.handleLivePhotoLinking({ id: assetStub.livePhotoStillAsset.id })).resolves.toBe(true);
expect(communicationMock.send).toHaveBeenCalledWith(
CommunicationEvent.ASSET_HIDDEN,
assetStub.livePhotoMotionAsset.ownerId,
assetStub.livePhotoMotionAsset.id,
);
});
});
describe('handleQueueMetadataExtraction', () => {

View file

@ -9,11 +9,9 @@ import { Subscription } from 'rxjs';
import { usePagination } from '../domain.util';
import { IBaseJob, IEntityJob, ISidecarWriteJob, JOBS_ASSET_PAGINATION_SIZE, JobName, QueueName } from '../job';
import {
CommunicationEvent,
ExifDuration,
IAlbumRepository,
IAssetRepository,
ICommunicationRepository,
ICryptoRepository,
IJobRepository,
IMediaRepository,
@ -106,7 +104,6 @@ export class MetadataService {
@Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository,
@Inject(IMediaRepository) private mediaRepository: IMediaRepository,
@Inject(IMoveRepository) moveRepository: IMoveRepository,
@Inject(ICommunicationRepository) private communicationRepository: ICommunicationRepository,
@Inject(IPersonRepository) personRepository: IPersonRepository,
) {
this.configCore = SystemConfigCore.create(configRepository);
@ -170,9 +167,6 @@ export class MetadataService {
await this.assetRepository.save({ id: motionAsset.id, isVisible: false });
await this.albumRepository.removeAsset(motionAsset.id);
// Notify clients to hide the linked live photo asset
this.communicationRepository.send(CommunicationEvent.ASSET_HIDDEN, motionAsset.ownerId, motionAsset.id);
return true;
}

View file

@ -5,7 +5,6 @@ export enum CommunicationEvent {
ASSET_DELETE = 'on_asset_delete',
ASSET_TRASH = 'on_asset_trash',
ASSET_UPDATE = 'on_asset_update',
ASSET_HIDDEN = 'on_asset_hidden',
ASSET_RESTORE = 'on_asset_restore',
PERSON_THUMBNAIL = 'on_person_thumbnail',
SERVER_VERSION = 'on_server_version',

View file

@ -336,18 +336,14 @@ export class AssetService {
res.set('Cache-Control', 'private, max-age=86400, no-transform');
res.header('Content-Type', mimeTypes.lookup(filepath));
return new Promise((resolve, reject) => {
res.sendFile(filepath, options, (error: Error) => {
if (!error) {
resolve();
return;
}
if (error.message !== 'Request aborted') {
this.logger.error(`Unable to send file: ${error.name}`, error.stack);
}
reject(error);
});
});
}

6
web/package-lock.json generated
View file

@ -11866,9 +11866,9 @@
"dev": true
},
"node_modules/vite": {
"version": "4.5.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.1.tgz",
"integrity": "sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA==",
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/vite/-/vite-4.5.0.tgz",
"integrity": "sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==",
"dev": true,
"dependencies": {
"esbuild": "^0.18.10",

View file

@ -205,13 +205,12 @@
<section class="px-4 py-4 text-sm">
<div class="flex h-10 w-full items-center justify-between">
<h2>PEOPLE</h2>
<div class="flex gap-2 items-center">
<div class="flex gap-2">
{#if people.some((person) => person.isHidden)}
<CircleIconButton
title="Show hidden people"
icon={showingHiddenPeople ? mdiEyeOff : mdiEye}
padding="1"
buttonSize="32"
on:click={() => (showingHiddenPeople = !showingHiddenPeople)}
/>
{/if}
@ -220,7 +219,6 @@
icon={mdiPencil}
padding="1"
size="20"
buttonSize="32"
on:click={() => (showEditFaces = true)}
/>
</div>
@ -339,7 +337,7 @@
</div>
{#if isOwner}
<button class="focus:outline-none p-1">
<button class="focus:outline-none">
<Icon path={mdiPencil} size="20" />
</button>
{/if}
@ -351,7 +349,7 @@
<Icon path={mdiCalendar} size="24" />
</div>
</div>
<button class="focus:outline-none p-1">
<button class="focus:outline-none">
<Icon path={mdiPencil} size="20" />
</button>
</div>
@ -509,7 +507,7 @@
</div>
{:else if !asset.exifInfo?.city && !asset.isReadOnly && $user && asset.ownerId === $user.id}
<div
class="flex justify-between place-items-start gap-4 py-4 rounded-lg hover:dark:text-immich-dark-primary hover:text-immich-primary"
class="flex justify-between place-items-start gap-4 py-4 rounded-lg pr-2 hover:dark:text-immich-dark-primary hover:text-immich-primary"
on:click={() => (isShowChangeLocation = true)}
on:keydown={(event) => event.key === 'Enter' && (isShowChangeLocation = true)}
tabindex="0"
@ -523,7 +521,7 @@
<p>Add a location</p>
</div>
<div class="focus:outline-none p-1">
<div class="focus:outline-none">
<Icon path={mdiPencil} size="20" />
</div>
</div>

View file

@ -11,13 +11,10 @@
export let forceDark = false;
export let hideMobile = false;
export let iconColor = 'currentColor';
export let buttonSize: string | undefined = undefined;
</script>
<button
{title}
style:width={buttonSize ? buttonSize + 'px' : ''}
style:height={buttonSize ? buttonSize + 'px' : ''}
style:background-color={backgroundColor}
style:--immich-icon-button-hover-color={hoverColor}
class:dark:text-immich-dark-fg={!forceDark}

View file

@ -111,7 +111,7 @@
<section
transition:fly={{ x: 360, duration: 100, easing: linear }}
class="absolute top-0 z-[2001] h-full w-[360px] overflow-x-hidden p-2 bg-immich-bg dark:bg-immich-dark-bg dark:text-immich-dark-fg"
class="absolute top-0 z-[2001] h-full w-[360px] overflow-x-hidden p-2 dark:bg-immich-dark-bg dark:text-immich-dark-fg"
>
<div class="flex place-items-center justify-between gap-2">
{#if !searchFaces}

View file

@ -160,7 +160,7 @@
<section
transition:fly={{ x: 360, duration: 100, easing: linear }}
class="absolute top-0 z-[2000] h-full w-[360px] overflow-x-hidden p-2 bg-immich-bg dark:bg-immich-dark-bg dark:text-immich-dark-fg"
class="absolute top-0 z-[2000] h-full w-[360px] overflow-x-hidden p-2 dark:bg-immich-dark-bg dark:text-immich-dark-fg"
>
<div class="flex place-items-center justify-between gap-2">
<div class="flex items-center gap-2">