mirror of
https://github.com/immich-app/immich.git
synced 2026-02-11 11:27:56 +03:00
change readonly boolean to role enum
This commit is contained in:
3
mobile/openapi/.openapi-generator/FILES
generated
3
mobile/openapi/.openapi-generator/FILES
generated
@@ -17,6 +17,7 @@ doc/AlbumApi.md
|
||||
doc/AlbumCountResponseDto.md
|
||||
doc/AlbumResponseDto.md
|
||||
doc/AlbumUserResponseDto.md
|
||||
doc/AlbumUserRole.md
|
||||
doc/AllJobStatusResponseDto.md
|
||||
doc/AssetApi.md
|
||||
doc/AssetBulkDeleteDto.md
|
||||
@@ -240,6 +241,7 @@ lib/model/add_users_dto.dart
|
||||
lib/model/album_count_response_dto.dart
|
||||
lib/model/album_response_dto.dart
|
||||
lib/model/album_user_response_dto.dart
|
||||
lib/model/album_user_role.dart
|
||||
lib/model/all_job_status_response_dto.dart
|
||||
lib/model/api_key_create_dto.dart
|
||||
lib/model/api_key_create_response_dto.dart
|
||||
@@ -419,6 +421,7 @@ test/album_api_test.dart
|
||||
test/album_count_response_dto_test.dart
|
||||
test/album_response_dto_test.dart
|
||||
test/album_user_response_dto_test.dart
|
||||
test/album_user_role_test.dart
|
||||
test/all_job_status_response_dto_test.dart
|
||||
test/api_key_api_test.dart
|
||||
test/api_key_create_dto_test.dart
|
||||
|
||||
1
mobile/openapi/README.md
generated
1
mobile/openapi/README.md
generated
@@ -235,6 +235,7 @@ Class | Method | HTTP request | Description
|
||||
- [AlbumCountResponseDto](doc//AlbumCountResponseDto.md)
|
||||
- [AlbumResponseDto](doc//AlbumResponseDto.md)
|
||||
- [AlbumUserResponseDto](doc//AlbumUserResponseDto.md)
|
||||
- [AlbumUserRole](doc//AlbumUserRole.md)
|
||||
- [AllJobStatusResponseDto](doc//AllJobStatusResponseDto.md)
|
||||
- [AssetBulkDeleteDto](doc//AssetBulkDeleteDto.md)
|
||||
- [AssetBulkUpdateDto](doc//AssetBulkUpdateDto.md)
|
||||
|
||||
2
mobile/openapi/doc/AlbumResponseDto.md
generated
2
mobile/openapi/doc/AlbumResponseDto.md
generated
@@ -23,7 +23,7 @@ Name | Type | Description | Notes
|
||||
**owner** | [**UserResponseDto**](UserResponseDto.md) | |
|
||||
**ownerId** | **String** | |
|
||||
**shared** | **bool** | |
|
||||
**sharedUsers** | [**List<UserResponseDto>**](UserResponseDto.md) | Deprecated in favor of users | [default to const []]
|
||||
**sharedUsers** | [**List<UserResponseDto>**](UserResponseDto.md) | Deprecated in favor of sharedUsersV2 | [default to const []]
|
||||
**sharedUsersV2** | [**List<AlbumUserResponseDto>**](AlbumUserResponseDto.md) | | [default to const []]
|
||||
**startDate** | [**DateTime**](DateTime.md) | | [optional]
|
||||
**updatedAt** | [**DateTime**](DateTime.md) | |
|
||||
|
||||
2
mobile/openapi/doc/AlbumUserResponseDto.md
generated
2
mobile/openapi/doc/AlbumUserResponseDto.md
generated
@@ -8,7 +8,7 @@ import 'package:openapi/api.dart';
|
||||
## Properties
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**readonly** | **bool** | |
|
||||
**role** | [**AlbumUserRole**](AlbumUserRole.md) | |
|
||||
**user** | [**UserResponseDto**](UserResponseDto.md) | |
|
||||
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
14
mobile/openapi/doc/AlbumUserRole.md
generated
Normal file
14
mobile/openapi/doc/AlbumUserRole.md
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
# openapi.model.AlbumUserRole
|
||||
|
||||
## Load the model package
|
||||
```dart
|
||||
import 'package:openapi/api.dart';
|
||||
```
|
||||
|
||||
## Properties
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
||||
2
mobile/openapi/doc/UpdateAlbumUserDto.md
generated
2
mobile/openapi/doc/UpdateAlbumUserDto.md
generated
@@ -8,7 +8,7 @@ import 'package:openapi/api.dart';
|
||||
## Properties
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**readonly** | **bool** | |
|
||||
**role** | [**AlbumUserRole**](AlbumUserRole.md) | |
|
||||
|
||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||
|
||||
|
||||
1
mobile/openapi/lib/api.dart
generated
1
mobile/openapi/lib/api.dart
generated
@@ -63,6 +63,7 @@ part 'model/add_users_dto.dart';
|
||||
part 'model/album_count_response_dto.dart';
|
||||
part 'model/album_response_dto.dart';
|
||||
part 'model/album_user_response_dto.dart';
|
||||
part 'model/album_user_role.dart';
|
||||
part 'model/all_job_status_response_dto.dart';
|
||||
part 'model/asset_bulk_delete_dto.dart';
|
||||
part 'model/asset_bulk_update_dto.dart';
|
||||
|
||||
2
mobile/openapi/lib/api_client.dart
generated
2
mobile/openapi/lib/api_client.dart
generated
@@ -204,6 +204,8 @@ class ApiClient {
|
||||
return AlbumResponseDto.fromJson(value);
|
||||
case 'AlbumUserResponseDto':
|
||||
return AlbumUserResponseDto.fromJson(value);
|
||||
case 'AlbumUserRole':
|
||||
return AlbumUserRoleTypeTransformer().decode(value);
|
||||
case 'AllJobStatusResponseDto':
|
||||
return AllJobStatusResponseDto.fromJson(value);
|
||||
case 'AssetBulkDeleteDto':
|
||||
|
||||
3
mobile/openapi/lib/api_helper.dart
generated
3
mobile/openapi/lib/api_helper.dart
generated
@@ -55,6 +55,9 @@ String parameterToString(dynamic value) {
|
||||
if (value is DateTime) {
|
||||
return value.toUtc().toIso8601String();
|
||||
}
|
||||
if (value is AlbumUserRole) {
|
||||
return AlbumUserRoleTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is AssetJobName) {
|
||||
return AssetJobNameTypeTransformer().encode(value).toString();
|
||||
}
|
||||
|
||||
2
mobile/openapi/lib/model/album_response_dto.dart
generated
2
mobile/openapi/lib/model/album_response_dto.dart
generated
@@ -82,7 +82,7 @@ class AlbumResponseDto {
|
||||
|
||||
bool shared;
|
||||
|
||||
/// Deprecated in favor of users
|
||||
/// Deprecated in favor of sharedUsersV2
|
||||
List<UserResponseDto> sharedUsers;
|
||||
|
||||
List<AlbumUserResponseDto> sharedUsersV2;
|
||||
|
||||
@@ -13,31 +13,31 @@ part of openapi.api;
|
||||
class AlbumUserResponseDto {
|
||||
/// Returns a new [AlbumUserResponseDto] instance.
|
||||
AlbumUserResponseDto({
|
||||
required this.readonly,
|
||||
required this.role,
|
||||
required this.user,
|
||||
});
|
||||
|
||||
bool readonly;
|
||||
AlbumUserRole role;
|
||||
|
||||
UserResponseDto user;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AlbumUserResponseDto &&
|
||||
other.readonly == readonly &&
|
||||
other.role == role &&
|
||||
other.user == user;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(readonly.hashCode) +
|
||||
(role.hashCode) +
|
||||
(user.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'AlbumUserResponseDto[readonly=$readonly, user=$user]';
|
||||
String toString() => 'AlbumUserResponseDto[role=$role, user=$user]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'readonly'] = this.readonly;
|
||||
json[r'role'] = this.role;
|
||||
json[r'user'] = this.user;
|
||||
return json;
|
||||
}
|
||||
@@ -50,7 +50,7 @@ class AlbumUserResponseDto {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return AlbumUserResponseDto(
|
||||
readonly: mapValueOfType<bool>(json, r'readonly')!,
|
||||
role: AlbumUserRole.fromJson(json[r'role'])!,
|
||||
user: UserResponseDto.fromJson(json[r'user'])!,
|
||||
);
|
||||
}
|
||||
@@ -99,7 +99,7 @@ class AlbumUserResponseDto {
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'readonly',
|
||||
'role',
|
||||
'user',
|
||||
};
|
||||
}
|
||||
|
||||
85
mobile/openapi/lib/model/album_user_role.dart
generated
Normal file
85
mobile/openapi/lib/model/album_user_role.dart
generated
Normal file
@@ -0,0 +1,85 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.12
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
|
||||
class AlbumUserRole {
|
||||
/// Instantiate a new enum with the provided [value].
|
||||
const AlbumUserRole._(this.value);
|
||||
|
||||
/// The underlying value of this enum member.
|
||||
final String value;
|
||||
|
||||
@override
|
||||
String toString() => value;
|
||||
|
||||
String toJson() => value;
|
||||
|
||||
static const editor = AlbumUserRole._(r'editor');
|
||||
static const viewer = AlbumUserRole._(r'viewer');
|
||||
|
||||
/// List of all possible values in this [enum][AlbumUserRole].
|
||||
static const values = <AlbumUserRole>[
|
||||
editor,
|
||||
viewer,
|
||||
];
|
||||
|
||||
static AlbumUserRole? fromJson(dynamic value) => AlbumUserRoleTypeTransformer().decode(value);
|
||||
|
||||
static List<AlbumUserRole> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AlbumUserRole>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AlbumUserRole.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
}
|
||||
|
||||
/// Transformation class that can [encode] an instance of [AlbumUserRole] to String,
|
||||
/// and [decode] dynamic data back to [AlbumUserRole].
|
||||
class AlbumUserRoleTypeTransformer {
|
||||
factory AlbumUserRoleTypeTransformer() => _instance ??= const AlbumUserRoleTypeTransformer._();
|
||||
|
||||
const AlbumUserRoleTypeTransformer._();
|
||||
|
||||
String encode(AlbumUserRole data) => data.value;
|
||||
|
||||
/// Decodes a [dynamic value][data] to a AlbumUserRole.
|
||||
///
|
||||
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
|
||||
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
|
||||
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
|
||||
///
|
||||
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
|
||||
/// and users are still using an old app with the old code.
|
||||
AlbumUserRole? decode(dynamic data, {bool allowNull = true}) {
|
||||
if (data != null) {
|
||||
switch (data) {
|
||||
case r'editor': return AlbumUserRole.editor;
|
||||
case r'viewer': return AlbumUserRole.viewer;
|
||||
default:
|
||||
if (!allowNull) {
|
||||
throw ArgumentError('Unknown enum value to decode: $data');
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Singleton [AlbumUserRoleTypeTransformer] instance.
|
||||
static AlbumUserRoleTypeTransformer? _instance;
|
||||
}
|
||||
|
||||
16
mobile/openapi/lib/model/update_album_user_dto.dart
generated
16
mobile/openapi/lib/model/update_album_user_dto.dart
generated
@@ -13,26 +13,26 @@ part of openapi.api;
|
||||
class UpdateAlbumUserDto {
|
||||
/// Returns a new [UpdateAlbumUserDto] instance.
|
||||
UpdateAlbumUserDto({
|
||||
required this.readonly,
|
||||
required this.role,
|
||||
});
|
||||
|
||||
bool readonly;
|
||||
AlbumUserRole role;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is UpdateAlbumUserDto &&
|
||||
other.readonly == readonly;
|
||||
other.role == role;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(readonly.hashCode);
|
||||
(role.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'UpdateAlbumUserDto[readonly=$readonly]';
|
||||
String toString() => 'UpdateAlbumUserDto[role=$role]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'readonly'] = this.readonly;
|
||||
json[r'role'] = this.role;
|
||||
return json;
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ class UpdateAlbumUserDto {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return UpdateAlbumUserDto(
|
||||
readonly: mapValueOfType<bool>(json, r'readonly')!,
|
||||
role: AlbumUserRole.fromJson(json[r'role'])!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
@@ -92,7 +92,7 @@ class UpdateAlbumUserDto {
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'readonly',
|
||||
'role',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
2
mobile/openapi/test/album_response_dto_test.dart
generated
2
mobile/openapi/test/album_response_dto_test.dart
generated
@@ -91,7 +91,7 @@ void main() {
|
||||
// TODO
|
||||
});
|
||||
|
||||
// Deprecated in favor of users
|
||||
// Deprecated in favor of sharedUsersV2
|
||||
// List<UserResponseDto> sharedUsers (default value: const [])
|
||||
test('to test the property `sharedUsers`', () async {
|
||||
// TODO
|
||||
|
||||
@@ -16,8 +16,8 @@ void main() {
|
||||
// final instance = AlbumUserResponseDto();
|
||||
|
||||
group('test AlbumUserResponseDto', () {
|
||||
// bool readonly
|
||||
test('to test the property `readonly`', () async {
|
||||
// AlbumUserRole role
|
||||
test('to test the property `role`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
|
||||
21
mobile/openapi/test/album_user_role_test.dart
generated
Normal file
21
mobile/openapi/test/album_user_role_test.dart
generated
Normal file
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.12
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
import 'package:openapi/api.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
// tests for AlbumUserRole
|
||||
void main() {
|
||||
|
||||
group('test AlbumUserRole', () {
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
@@ -16,8 +16,8 @@ void main() {
|
||||
// final instance = UpdateAlbumUserDto();
|
||||
|
||||
group('test UpdateAlbumUserDto', () {
|
||||
// bool readonly
|
||||
test('to test the property `readonly`', () async {
|
||||
// AlbumUserRole role
|
||||
test('to test the property `role`', () async {
|
||||
// TODO
|
||||
});
|
||||
|
||||
|
||||
@@ -7174,7 +7174,7 @@
|
||||
},
|
||||
"sharedUsers": {
|
||||
"deprecated": true,
|
||||
"description": "Deprecated in favor of users",
|
||||
"description": "Deprecated in favor of sharedUsersV2",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/UserResponseDto"
|
||||
},
|
||||
@@ -7216,19 +7216,26 @@
|
||||
},
|
||||
"AlbumUserResponseDto": {
|
||||
"properties": {
|
||||
"readonly": {
|
||||
"type": "boolean"
|
||||
"role": {
|
||||
"$ref": "#/components/schemas/AlbumUserRole"
|
||||
},
|
||||
"user": {
|
||||
"$ref": "#/components/schemas/UserResponseDto"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"readonly",
|
||||
"role",
|
||||
"user"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AlbumUserRole": {
|
||||
"enum": [
|
||||
"editor",
|
||||
"viewer"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"AllJobStatusResponseDto": {
|
||||
"properties": {
|
||||
"backgroundTask": {
|
||||
@@ -10962,12 +10969,12 @@
|
||||
},
|
||||
"UpdateAlbumUserDto": {
|
||||
"properties": {
|
||||
"readonly": {
|
||||
"type": "boolean"
|
||||
"role": {
|
||||
"$ref": "#/components/schemas/AlbumUserRole"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"readonly"
|
||||
"role"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
|
||||
@@ -142,7 +142,7 @@ export type AssetResponseDto = {
|
||||
updatedAt: string;
|
||||
};
|
||||
export type AlbumUserResponseDto = {
|
||||
"readonly": boolean;
|
||||
role: AlbumUserRole;
|
||||
user: UserResponseDto;
|
||||
};
|
||||
export type AlbumResponseDto = {
|
||||
@@ -161,7 +161,7 @@ export type AlbumResponseDto = {
|
||||
owner: UserResponseDto;
|
||||
ownerId: string;
|
||||
shared: boolean;
|
||||
/** Deprecated in favor of users */
|
||||
/** Deprecated in favor of sharedUsersV2 */
|
||||
sharedUsers: UserResponseDto[];
|
||||
sharedUsersV2: AlbumUserResponseDto[];
|
||||
startDate?: string;
|
||||
@@ -194,7 +194,7 @@ export type BulkIdResponseDto = {
|
||||
success: boolean;
|
||||
};
|
||||
export type UpdateAlbumUserDto = {
|
||||
"readonly": boolean;
|
||||
role: AlbumUserRole;
|
||||
};
|
||||
export type AddUsersDto = {
|
||||
sharedUserIds: string[];
|
||||
@@ -2901,6 +2901,10 @@ export enum AssetOrder {
|
||||
Asc = "asc",
|
||||
Desc = "desc"
|
||||
}
|
||||
export enum AlbumUserRole {
|
||||
Editor = "editor",
|
||||
Viewer = "viewer"
|
||||
}
|
||||
export enum Error {
|
||||
Duplicate = "duplicate",
|
||||
NoPermission = "no_permission",
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { BadRequestException, UnauthorizedException } from '@nestjs/common';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { AlbumUserRole } from 'src/entities/album-user.entity';
|
||||
import { SharedLinkEntity } from 'src/entities/shared-link.entity';
|
||||
import { IAccessRepository } from 'src/interfaces/access.interface';
|
||||
import { setDifference, setIsEqual, setUnion } from 'src/utils/set';
|
||||
@@ -219,7 +220,7 @@ export class AccessCore {
|
||||
const isShared = await this.repository.album.checkSharedAlbumAccess(
|
||||
auth.user.id,
|
||||
setDifference(ids, isOwner),
|
||||
'read',
|
||||
AlbumUserRole.Viewer,
|
||||
);
|
||||
return setUnion(isOwner, isShared);
|
||||
}
|
||||
@@ -229,7 +230,7 @@ export class AccessCore {
|
||||
const isShared = await this.repository.album.checkSharedAlbumAccess(
|
||||
auth.user.id,
|
||||
setDifference(ids, isOwner),
|
||||
'write',
|
||||
AlbumUserRole.Editor,
|
||||
);
|
||||
return setUnion(isOwner, isShared);
|
||||
}
|
||||
@@ -251,7 +252,7 @@ export class AccessCore {
|
||||
const isShared = await this.repository.album.checkSharedAlbumAccess(
|
||||
auth.user.id,
|
||||
setDifference(ids, isOwner),
|
||||
'read',
|
||||
AlbumUserRole.Viewer,
|
||||
);
|
||||
return setUnion(isOwner, isShared);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { ArrayNotEmpty, IsEnum, IsString } from 'class-validator';
|
||||
import { AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { UserResponseDto, mapUser } from 'src/dtos/user.dto';
|
||||
import { AlbumUserRole } from 'src/entities/album-user.entity';
|
||||
import { AlbumEntity, AssetOrder } from 'src/entities/album.entity';
|
||||
import { Optional, ValidateBoolean, ValidateUUID } from 'src/validation';
|
||||
|
||||
@@ -84,13 +85,15 @@ export class AlbumCountResponseDto {
|
||||
}
|
||||
|
||||
export class UpdateAlbumUserDto {
|
||||
@ValidateBoolean()
|
||||
readonly!: boolean;
|
||||
@IsEnum(AlbumUserRole)
|
||||
@ApiProperty({ enum: AlbumUserRole, enumName: 'AlbumUserRole' })
|
||||
role!: AlbumUserRole;
|
||||
}
|
||||
|
||||
export class AlbumUserResponseDto {
|
||||
user!: UserResponseDto;
|
||||
readonly!: boolean;
|
||||
@ApiProperty({ enum: AlbumUserRole, enumName: 'AlbumUserRole' })
|
||||
role!: AlbumUserRole;
|
||||
}
|
||||
|
||||
export class AlbumResponseDto {
|
||||
@@ -128,7 +131,7 @@ export const mapAlbum = (entity: AlbumEntity, withAssets: boolean, auth?: AuthDt
|
||||
sharedUsers.push(mapUser(permission.user));
|
||||
sharedUsersV2.push({
|
||||
user: mapUser(permission.user),
|
||||
readonly: permission.readonly,
|
||||
role: permission.role,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,11 @@ import { AlbumEntity } from 'src/entities/album.entity';
|
||||
import { UserEntity } from 'src/entities/user.entity';
|
||||
import { Column, Entity, Index, JoinColumn, ManyToOne, PrimaryColumn } from 'typeorm';
|
||||
|
||||
export enum AlbumUserRole {
|
||||
Editor = 'editor',
|
||||
Viewer = 'viewer',
|
||||
}
|
||||
|
||||
@Entity('albums_shared_users_users')
|
||||
// Pre-existing indices from original album <--> user ManyToMany mapping
|
||||
@Index('IDX_427c350ad49bd3935a50baab73', ['album'])
|
||||
@@ -17,6 +22,6 @@ export class AlbumUserEntity {
|
||||
@ManyToOne(() => UserEntity, { onDelete: 'CASCADE', onUpdate: 'CASCADE', nullable: false })
|
||||
user!: UserEntity;
|
||||
|
||||
@Column({ default: true })
|
||||
readonly!: boolean;
|
||||
@Column({ type: 'varchar', default: 'viewer' })
|
||||
role!: AlbumUserRole;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { AlbumUserRole } from 'src/entities/album-user.entity';
|
||||
|
||||
export const IAccessRepository = 'IAccessRepository';
|
||||
|
||||
export type ReadWrite = 'read' | 'write';
|
||||
@@ -22,7 +24,7 @@ export interface IAccessRepository {
|
||||
|
||||
album: {
|
||||
checkOwnerAccess(userId: string, albumIds: Set<string>): Promise<Set<string>>;
|
||||
checkSharedAlbumAccess(userId: string, albumIds: Set<string>, readWrite: ReadWrite): Promise<Set<string>>;
|
||||
checkSharedAlbumAccess(userId: string, albumIds: Set<string>, access: AlbumUserRole): Promise<Set<string>>;
|
||||
checkSharedLinkAccess(sharedLinkId: string, albumIds: Set<string>): Promise<Set<string>>;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class AddAlbumUserReadonly1713298646379 implements MigrationInterface {
|
||||
name = 'AddAlbumUserReadonly1713298646379'
|
||||
export class AddAlbumUserRole1713337511945 implements MigrationInterface {
|
||||
name = 'AddAlbumUserRole1713337511945'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "albums_shared_users_users" ADD "readonly" boolean NOT NULL DEFAULT false`);
|
||||
await queryRunner.query(`ALTER TABLE "albums_shared_users_users" ALTER COLUMN "readonly" SET DEFAULT true`);
|
||||
await queryRunner.query(`ALTER TABLE "albums_shared_users_users" ADD "role" character varying NOT NULL DEFAULT 'editor'`);
|
||||
await queryRunner.query(`ALTER TABLE "albums_shared_users_users" ALTER COLUMN "role" SET DEFAULT 'viewer'`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "albums_shared_users_users" DROP COLUMN "readonly"`);
|
||||
await queryRunner.query(`ALTER TABLE "albums_shared_users_users" DROP COLUMN "role"`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -70,7 +70,7 @@ SELECT
|
||||
"AlbumEntity"."id" AS "AlbumEntity_id",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."albumsId" AS "AlbumEntity__AlbumEntity_sharedUsers_albumsId",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."usersId" AS "AlbumEntity__AlbumEntity_sharedUsers_usersId",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."readonly" AS "AlbumEntity__AlbumEntity_sharedUsers_readonly"
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."role" AS "AlbumEntity__AlbumEntity_sharedUsers_role"
|
||||
FROM
|
||||
"albums" "AlbumEntity"
|
||||
LEFT JOIN "albums_shared_users_users" "AlbumEntity__AlbumEntity_sharedUsers" ON "AlbumEntity__AlbumEntity_sharedUsers"."albumsId" = "AlbumEntity"."id"
|
||||
@@ -83,6 +83,9 @@ WHERE
|
||||
(
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."usersId" = $2
|
||||
)
|
||||
AND (
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."role" IN ($3, $4)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
@@ -34,7 +34,7 @@ FROM
|
||||
"AlbumEntity__AlbumEntity_owner"."quotaUsageInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaUsageInBytes",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."albumsId" AS "AlbumEntity__AlbumEntity_sharedUsers_albumsId",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."usersId" AS "AlbumEntity__AlbumEntity_sharedUsers_usersId",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."readonly" AS "AlbumEntity__AlbumEntity_sharedUsers_readonly",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."role" AS "AlbumEntity__AlbumEntity_sharedUsers_role",
|
||||
"c20102de0f4f51a0efbaca481ef9bb2f99dd7c0a"."id" AS "c20102de0f4f51a0efbaca481ef9bb2f99dd7c0a_id",
|
||||
"c20102de0f4f51a0efbaca481ef9bb2f99dd7c0a"."name" AS "c20102de0f4f51a0efbaca481ef9bb2f99dd7c0a_name",
|
||||
"c20102de0f4f51a0efbaca481ef9bb2f99dd7c0a"."avatarColor" AS "c20102de0f4f51a0efbaca481ef9bb2f99dd7c0a_avatarColor",
|
||||
@@ -114,7 +114,7 @@ SELECT
|
||||
"AlbumEntity__AlbumEntity_owner"."quotaUsageInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaUsageInBytes",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."albumsId" AS "AlbumEntity__AlbumEntity_sharedUsers_albumsId",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."usersId" AS "AlbumEntity__AlbumEntity_sharedUsers_usersId",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."readonly" AS "AlbumEntity__AlbumEntity_sharedUsers_readonly",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."role" AS "AlbumEntity__AlbumEntity_sharedUsers_role",
|
||||
"c20102de0f4f51a0efbaca481ef9bb2f99dd7c0a"."id" AS "c20102de0f4f51a0efbaca481ef9bb2f99dd7c0a_id",
|
||||
"c20102de0f4f51a0efbaca481ef9bb2f99dd7c0a"."name" AS "c20102de0f4f51a0efbaca481ef9bb2f99dd7c0a_name",
|
||||
"c20102de0f4f51a0efbaca481ef9bb2f99dd7c0a"."avatarColor" AS "c20102de0f4f51a0efbaca481ef9bb2f99dd7c0a_avatarColor",
|
||||
@@ -176,7 +176,7 @@ SELECT
|
||||
"AlbumEntity__AlbumEntity_owner"."quotaUsageInBytes" AS "AlbumEntity__AlbumEntity_owner_quotaUsageInBytes",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."albumsId" AS "AlbumEntity__AlbumEntity_sharedUsers_albumsId",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."usersId" AS "AlbumEntity__AlbumEntity_sharedUsers_usersId",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."readonly" AS "AlbumEntity__AlbumEntity_sharedUsers_readonly",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."role" AS "AlbumEntity__AlbumEntity_sharedUsers_role",
|
||||
"c20102de0f4f51a0efbaca481ef9bb2f99dd7c0a"."id" AS "c20102de0f4f51a0efbaca481ef9bb2f99dd7c0a_id",
|
||||
"c20102de0f4f51a0efbaca481ef9bb2f99dd7c0a"."name" AS "c20102de0f4f51a0efbaca481ef9bb2f99dd7c0a_name",
|
||||
"c20102de0f4f51a0efbaca481ef9bb2f99dd7c0a"."avatarColor" AS "c20102de0f4f51a0efbaca481ef9bb2f99dd7c0a_avatarColor",
|
||||
@@ -296,7 +296,7 @@ SELECT
|
||||
"AlbumEntity"."order" AS "AlbumEntity_order",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."albumsId" AS "AlbumEntity__AlbumEntity_sharedUsers_albumsId",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."usersId" AS "AlbumEntity__AlbumEntity_sharedUsers_usersId",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."readonly" AS "AlbumEntity__AlbumEntity_sharedUsers_readonly",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."role" AS "AlbumEntity__AlbumEntity_sharedUsers_role",
|
||||
"c20102de0f4f51a0efbaca481ef9bb2f99dd7c0a"."id" AS "c20102de0f4f51a0efbaca481ef9bb2f99dd7c0a_id",
|
||||
"c20102de0f4f51a0efbaca481ef9bb2f99dd7c0a"."name" AS "c20102de0f4f51a0efbaca481ef9bb2f99dd7c0a_name",
|
||||
"c20102de0f4f51a0efbaca481ef9bb2f99dd7c0a"."avatarColor" AS "c20102de0f4f51a0efbaca481ef9bb2f99dd7c0a_avatarColor",
|
||||
@@ -373,7 +373,7 @@ SELECT
|
||||
"AlbumEntity"."order" AS "AlbumEntity_order",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."albumsId" AS "AlbumEntity__AlbumEntity_sharedUsers_albumsId",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."usersId" AS "AlbumEntity__AlbumEntity_sharedUsers_usersId",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."readonly" AS "AlbumEntity__AlbumEntity_sharedUsers_readonly",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."role" AS "AlbumEntity__AlbumEntity_sharedUsers_role",
|
||||
"c20102de0f4f51a0efbaca481ef9bb2f99dd7c0a"."id" AS "c20102de0f4f51a0efbaca481ef9bb2f99dd7c0a_id",
|
||||
"c20102de0f4f51a0efbaca481ef9bb2f99dd7c0a"."name" AS "c20102de0f4f51a0efbaca481ef9bb2f99dd7c0a_name",
|
||||
"c20102de0f4f51a0efbaca481ef9bb2f99dd7c0a"."avatarColor" AS "c20102de0f4f51a0efbaca481ef9bb2f99dd7c0a_avatarColor",
|
||||
@@ -489,7 +489,7 @@ SELECT
|
||||
"AlbumEntity"."order" AS "AlbumEntity_order",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."albumsId" AS "AlbumEntity__AlbumEntity_sharedUsers_albumsId",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."usersId" AS "AlbumEntity__AlbumEntity_sharedUsers_usersId",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."readonly" AS "AlbumEntity__AlbumEntity_sharedUsers_readonly",
|
||||
"AlbumEntity__AlbumEntity_sharedUsers"."role" AS "AlbumEntity__AlbumEntity_sharedUsers_role",
|
||||
"AlbumEntity__AlbumEntity_sharedLinks"."id" AS "AlbumEntity__AlbumEntity_sharedLinks_id",
|
||||
"AlbumEntity__AlbumEntity_sharedLinks"."description" AS "AlbumEntity__AlbumEntity_sharedLinks_description",
|
||||
"AlbumEntity__AlbumEntity_sharedLinks"."password" AS "AlbumEntity__AlbumEntity_sharedLinks_password",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { ChunkedSet, DummyValue, GenerateSql } from 'src/decorators';
|
||||
import { ActivityEntity } from 'src/entities/activity.entity';
|
||||
import { AlbumUserRole } from 'src/entities/album-user.entity';
|
||||
import { AlbumEntity } from 'src/entities/album.entity';
|
||||
import { AssetFaceEntity } from 'src/entities/asset-face.entity';
|
||||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
@@ -10,7 +11,7 @@ import { PartnerEntity } from 'src/entities/partner.entity';
|
||||
import { PersonEntity } from 'src/entities/person.entity';
|
||||
import { SharedLinkEntity } from 'src/entities/shared-link.entity';
|
||||
import { UserTokenEntity } from 'src/entities/user-token.entity';
|
||||
import { IAccessRepository, ReadWrite } from 'src/interfaces/access.interface';
|
||||
import { IAccessRepository } from 'src/interfaces/access.interface';
|
||||
import { Instrumentation } from 'src/utils/instrumentation';
|
||||
import { Brackets, In, Repository } from 'typeorm';
|
||||
|
||||
@@ -119,7 +120,7 @@ class AlbumAccess implements IAlbumAccess {
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID, DummyValue.UUID_SET] })
|
||||
@ChunkedSet({ paramIndex: 1 })
|
||||
async checkSharedAlbumAccess(userId: string, albumIds: Set<string>, readWrite: ReadWrite): Promise<Set<string>> {
|
||||
async checkSharedAlbumAccess(userId: string, albumIds: Set<string>, access: AlbumUserRole): Promise<Set<string>> {
|
||||
if (albumIds.size === 0) {
|
||||
return new Set();
|
||||
}
|
||||
@@ -132,8 +133,9 @@ class AlbumAccess implements IAlbumAccess {
|
||||
id: In([...albumIds]),
|
||||
sharedUsers: {
|
||||
user: { id: userId },
|
||||
// If write is needed we check for it, otherwise both are accepted
|
||||
readonly: readWrite === 'write' ? false : undefined,
|
||||
// If editor access is needed we check for it, otherwise both are accepted
|
||||
role:
|
||||
access === AlbumUserRole.Editor ? AlbumUserRole.Editor : In([AlbumUserRole.Editor, AlbumUserRole.Viewer]),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -273,7 +273,7 @@ export class AlbumService {
|
||||
throw new BadRequestException('Album not shared with user');
|
||||
}
|
||||
|
||||
await this.albumPermissionRepository.update({ albumId: id, userId }, { readonly: dto.readonly });
|
||||
await this.albumPermissionRepository.update({ albumId: id, userId }, { role: dto.role });
|
||||
}
|
||||
|
||||
private async findOrFail(id: string, options: AlbumInfoOptions) {
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
removeUserFromAlbum,
|
||||
type AlbumResponseDto,
|
||||
type UserResponseDto,
|
||||
updateAlbumUser,
|
||||
} from '@immich/sdk';
|
||||
updateAlbumUser, AlbumUserRole,
|
||||
} from '@immich/sdk'
|
||||
import { mdiDotsVertical } from '@mdi/js';
|
||||
import { createEventDispatcher, onMount } from 'svelte';
|
||||
import { getContextMenuPosition } from '../../utils/context-menu';
|
||||
@@ -71,10 +71,10 @@
|
||||
}
|
||||
};
|
||||
|
||||
const handleSetReadonly = async (user: UserResponseDto, readonly: boolean) => {
|
||||
const handleSetReadonly = async (user: UserResponseDto, role: AlbumUserRole) => {
|
||||
try {
|
||||
await updateAlbumUser({ id: album.id, userId: user.id, updateAlbumUserDto: { readonly } });
|
||||
const message = readonly ? `Set ${user.name} as viewer` : `Set ${user.name} as editor`;
|
||||
await updateAlbumUser({ id: album.id, userId: user.id, updateAlbumUserDto: { role } });
|
||||
const message = `Set ${user.name} as ${role}`;
|
||||
dispatch('refreshAlbum');
|
||||
notificationController.show({ type: NotificationType.Info, message });
|
||||
} catch (error) {
|
||||
@@ -99,14 +99,14 @@
|
||||
</div>
|
||||
</div>
|
||||
{#each album.sharedUsersV2.toSorted((a, b) => {
|
||||
if (a.readonly && !b.readonly) {
|
||||
if (a.role === AlbumUserRole.Viewer && b.role === AlbumUserRole.Editor) {
|
||||
return 1;
|
||||
}
|
||||
if (!a.readonly && b.readonly) {
|
||||
if (a.role === AlbumUserRole.Editor && b.role === AlbumUserRole.Viewer) {
|
||||
return -1;
|
||||
}
|
||||
return a.user.name.localeCompare(b.user.name);
|
||||
}) as { user, readonly }}
|
||||
}) as { user, role }}
|
||||
<div
|
||||
class="flex w-full place-items-center justify-between gap-4 p-5 transition-colors hover:bg-gray-50 dark:hover:bg-gray-700"
|
||||
>
|
||||
@@ -117,7 +117,7 @@
|
||||
|
||||
<div id="icon-{user.id}" class="flex place-items-center gap-2">
|
||||
<div>
|
||||
{#if readonly}
|
||||
{#if role === AlbumUserRole.Viewer}
|
||||
Viewer
|
||||
{:else}
|
||||
Editor
|
||||
@@ -136,10 +136,10 @@
|
||||
|
||||
{#if selectedMenuUser === user}
|
||||
<ContextMenu {...position} on:outclick={() => (selectedMenuUser = null)}>
|
||||
{#if readonly}
|
||||
<MenuOption on:click={() => handleSetReadonly(user, false)} text="Allow edits" />
|
||||
{#if role === AlbumUserRole.Viewer}
|
||||
<MenuOption on:click={() => handleSetReadonly(user, AlbumUserRole.Editor)} text="Allow edits" />
|
||||
{:else}
|
||||
<MenuOption on:click={() => handleSetReadonly(user, true)} text="Disallow edits" />
|
||||
<MenuOption on:click={() => handleSetReadonly(user, AlbumUserRole.Viewer)} text="Disallow edits" />
|
||||
{/if}
|
||||
<MenuOption on:click={handleMenuRemove} text="Remove" />
|
||||
</ContextMenu>
|
||||
|
||||
@@ -1,84 +1,83 @@
|
||||
<script lang="ts">
|
||||
import { afterNavigate, goto } from '$app/navigation';
|
||||
import AlbumOptions from '$lib/components/album-page/album-options.svelte';
|
||||
import ShareInfoModal from '$lib/components/album-page/share-info-modal.svelte';
|
||||
import UserSelectionModal from '$lib/components/album-page/user-selection-modal.svelte';
|
||||
import ActivityStatus from '$lib/components/asset-viewer/activity-status.svelte';
|
||||
import ActivityViewer from '$lib/components/asset-viewer/activity-viewer.svelte';
|
||||
import Button from '$lib/components/elements/buttons/button.svelte';
|
||||
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte';
|
||||
import Icon from '$lib/components/elements/icon.svelte';
|
||||
import AddToAlbum from '$lib/components/photos-page/actions/add-to-album.svelte';
|
||||
import ArchiveAction from '$lib/components/photos-page/actions/archive-action.svelte';
|
||||
import ChangeDate from '$lib/components/photos-page/actions/change-date-action.svelte';
|
||||
import ChangeLocation from '$lib/components/photos-page/actions/change-location-action.svelte';
|
||||
import CreateSharedLink from '$lib/components/photos-page/actions/create-shared-link.svelte';
|
||||
import DeleteAssets from '$lib/components/photos-page/actions/delete-assets.svelte';
|
||||
import DownloadAction from '$lib/components/photos-page/actions/download-action.svelte';
|
||||
import FavoriteAction from '$lib/components/photos-page/actions/favorite-action.svelte';
|
||||
import RemoveFromAlbum from '$lib/components/photos-page/actions/remove-from-album.svelte';
|
||||
import SelectAllAssets from '$lib/components/photos-page/actions/select-all-assets.svelte';
|
||||
import AssetGrid from '$lib/components/photos-page/asset-grid.svelte';
|
||||
import AssetSelectContextMenu from '$lib/components/photos-page/asset-select-context-menu.svelte';
|
||||
import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte';
|
||||
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte';
|
||||
import ContextMenu from '$lib/components/shared-components/context-menu/context-menu.svelte';
|
||||
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
|
||||
import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte';
|
||||
import CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte';
|
||||
import {afterNavigate, goto} from '$app/navigation'
|
||||
import AlbumDescription from '$lib/components/album-page/album-description.svelte'
|
||||
import AlbumOptions from '$lib/components/album-page/album-options.svelte'
|
||||
import AlbumSummary from '$lib/components/album-page/album-summary.svelte'
|
||||
import AlbumTitle from '$lib/components/album-page/album-title.svelte'
|
||||
import ShareInfoModal from '$lib/components/album-page/share-info-modal.svelte'
|
||||
import UserSelectionModal from '$lib/components/album-page/user-selection-modal.svelte'
|
||||
import ActivityStatus from '$lib/components/asset-viewer/activity-status.svelte'
|
||||
import ActivityViewer from '$lib/components/asset-viewer/activity-viewer.svelte'
|
||||
import Button from '$lib/components/elements/buttons/button.svelte'
|
||||
import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte'
|
||||
import Icon from '$lib/components/elements/icon.svelte'
|
||||
import AddToAlbum from '$lib/components/photos-page/actions/add-to-album.svelte'
|
||||
import ArchiveAction from '$lib/components/photos-page/actions/archive-action.svelte'
|
||||
import ChangeDate from '$lib/components/photos-page/actions/change-date-action.svelte'
|
||||
import ChangeLocation from '$lib/components/photos-page/actions/change-location-action.svelte'
|
||||
import CreateSharedLink from '$lib/components/photos-page/actions/create-shared-link.svelte'
|
||||
import DeleteAssets from '$lib/components/photos-page/actions/delete-assets.svelte'
|
||||
import DownloadAction from '$lib/components/photos-page/actions/download-action.svelte'
|
||||
import FavoriteAction from '$lib/components/photos-page/actions/favorite-action.svelte'
|
||||
import RemoveFromAlbum from '$lib/components/photos-page/actions/remove-from-album.svelte'
|
||||
import SelectAllAssets from '$lib/components/photos-page/actions/select-all-assets.svelte'
|
||||
import AssetGrid from '$lib/components/photos-page/asset-grid.svelte'
|
||||
import AssetSelectContextMenu from '$lib/components/photos-page/asset-select-context-menu.svelte'
|
||||
import AssetSelectControlBar from '$lib/components/photos-page/asset-select-control-bar.svelte'
|
||||
import ConfirmDialogue from '$lib/components/shared-components/confirm-dialogue.svelte'
|
||||
import ContextMenu from '$lib/components/shared-components/context-menu/context-menu.svelte'
|
||||
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte'
|
||||
import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte'
|
||||
import CreateSharedLinkModal
|
||||
from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte'
|
||||
import {notificationController, NotificationType,} from '$lib/components/shared-components/notification/notification'
|
||||
import UserAvatar from '$lib/components/shared-components/user-avatar.svelte'
|
||||
import {AppRoute} from '$lib/constants'
|
||||
import {numberOfComments, setNumberOfComments, updateNumberOfComments} from '$lib/stores/activity.store'
|
||||
import {createAssetInteractionStore} from '$lib/stores/asset-interaction.store'
|
||||
import {assetViewingStore} from '$lib/stores/asset-viewing.store'
|
||||
import {AssetStore} from '$lib/stores/assets.store'
|
||||
import {locale} from '$lib/stores/preferences.store'
|
||||
import {SlideshowNavigation, SlideshowState, slideshowStore} from '$lib/stores/slideshow.store'
|
||||
import {user} from '$lib/stores/user.store'
|
||||
import {handlePromiseError} from '$lib/utils'
|
||||
import {downloadAlbum} from '$lib/utils/asset-utils'
|
||||
import {clickOutside} from '$lib/utils/click-outside'
|
||||
import {getContextMenuPosition} from '$lib/utils/context-menu'
|
||||
import {openFileUploadDialog} from '$lib/utils/file-uploader'
|
||||
import {handleError} from '$lib/utils/handle-error'
|
||||
import {
|
||||
NotificationType,
|
||||
notificationController,
|
||||
} from '$lib/components/shared-components/notification/notification';
|
||||
import UserAvatar from '$lib/components/shared-components/user-avatar.svelte';
|
||||
import { AppRoute } from '$lib/constants';
|
||||
import { numberOfComments, setNumberOfComments, updateNumberOfComments } from '$lib/stores/activity.store';
|
||||
import { createAssetInteractionStore } from '$lib/stores/asset-interaction.store';
|
||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||
import { AssetStore } from '$lib/stores/assets.store';
|
||||
import { locale } from '$lib/stores/preferences.store';
|
||||
import { SlideshowNavigation, SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
|
||||
import { user } from '$lib/stores/user.store';
|
||||
import { downloadAlbum } from '$lib/utils/asset-utils';
|
||||
import { clickOutside } from '$lib/utils/click-outside';
|
||||
import { getContextMenuPosition } from '$lib/utils/context-menu';
|
||||
import { openFileUploadDialog } from '$lib/utils/file-uploader';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import {
|
||||
ReactionLevel,
|
||||
ReactionType,
|
||||
type ActivityResponseDto,
|
||||
addAssetsToAlbum,
|
||||
addUsersToAlbum,
|
||||
AlbumUserRole,
|
||||
AssetOrder,
|
||||
createActivity,
|
||||
deleteActivity,
|
||||
deleteAlbum,
|
||||
getActivities,
|
||||
getActivityStatistics,
|
||||
getAlbumInfo,
|
||||
ReactionLevel,
|
||||
ReactionType,
|
||||
updateAlbumInfo,
|
||||
type ActivityResponseDto,
|
||||
type UserResponseDto,
|
||||
AssetOrder,
|
||||
} from '@immich/sdk';
|
||||
} from '@immich/sdk'
|
||||
import {
|
||||
mdiArrowLeft,
|
||||
mdiCogOutline,
|
||||
mdiDeleteOutline,
|
||||
mdiDotsVertical,
|
||||
mdiFolderDownloadOutline,
|
||||
mdiLink,
|
||||
mdiPlus,
|
||||
mdiShareVariantOutline,
|
||||
mdiPresentationPlay,
|
||||
mdiCogOutline,
|
||||
mdiImageOutline,
|
||||
mdiImagePlusOutline,
|
||||
} from '@mdi/js';
|
||||
import { fly } from 'svelte/transition';
|
||||
import type { PageData } from './$types';
|
||||
import AlbumTitle from '$lib/components/album-page/album-title.svelte';
|
||||
import AlbumDescription from '$lib/components/album-page/album-description.svelte';
|
||||
import { handlePromiseError } from '$lib/utils';
|
||||
import AlbumSummary from '$lib/components/album-page/album-summary.svelte';
|
||||
mdiLink,
|
||||
mdiPlus,
|
||||
mdiPresentationPlay,
|
||||
mdiShareVariantOutline,
|
||||
} from '@mdi/js'
|
||||
import {fly} from 'svelte/transition'
|
||||
import type {PageData} from './$types'
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
@@ -136,8 +135,8 @@
|
||||
$: showActivityStatus =
|
||||
album.sharedUsers.length > 0 && !$showAssetViewer && (album.isActivityEnabled || $numberOfComments > 0);
|
||||
|
||||
$: userHasWriteAccess = !album.sharedUsersV2.find(({ user: { id } }) => id === $user.id)?.readonly;
|
||||
$: albumHasReadonlyUsers = album.sharedUsersV2.some(({ readonly }) => readonly);
|
||||
$: isEditor = album.sharedUsersV2.find(({ user: { id } }) => id === $user.id)?.role === AlbumUserRole.Editor;
|
||||
$: albumHasViewers = album.sharedUsersV2.some(({ role }) => role === AlbumUserRole.Viewer);
|
||||
|
||||
afterNavigate(({ from }) => {
|
||||
assetViewingStore.showAssetViewer(false);
|
||||
@@ -436,7 +435,7 @@
|
||||
{#if viewMode === ViewMode.VIEW || viewMode === ViewMode.ALBUM_OPTIONS}
|
||||
<ControlAppBar showBackButton backIcon={mdiArrowLeft} on:close={() => goto(backUrl)}>
|
||||
<svelte:fragment slot="trailing">
|
||||
{#if userHasWriteAccess}
|
||||
{#if isEditor}
|
||||
<CircleIconButton
|
||||
title="Add photos"
|
||||
on:click={() => (viewMode = ViewMode.SELECT_ASSETS)}
|
||||
@@ -584,14 +583,14 @@
|
||||
</button>
|
||||
|
||||
<!-- users with write access (collaborators) -->
|
||||
{#each album.sharedUsersV2.filter(({ readonly }) => !readonly) as { user } (user.id)}
|
||||
{#each album.sharedUsersV2.filter(({ role }) => role === AlbumUserRole.Editor) as { user } (user.id)}
|
||||
<button on:click={() => (viewMode = ViewMode.VIEW_USERS)}>
|
||||
<UserAvatar {user} size="md" />
|
||||
</button>
|
||||
{/each}
|
||||
|
||||
<!-- display ellipsis if there are readonly users too -->
|
||||
{#if albumHasReadonlyUsers}
|
||||
{#if albumHasViewers}
|
||||
<CircleIconButton
|
||||
title="View all users"
|
||||
backgroundColor="#d3d3d3"
|
||||
|
||||
Reference in New Issue
Block a user