mirror of
https://github.com/immich-app/immich.git
synced 2025-01-01 08:31:59 +00:00
feat(web) Individual assets shared mechanism (#1317)
* Create shared link modal for individual asset * Added API to create asset shared link * Added viewer for individual shared link * Added multiselection app bar * Refactor gallery viewer to its own component * Refactor * Refactor * Add and remove asset from shared link * Fixed test * Fixed notification card doesn't wrap * Add check asset access when created asset shared link * pr feedback
This commit is contained in:
parent
b9b2b559a1
commit
e9fda40b2b
66 changed files with 791 additions and 168 deletions
6
mobile/openapi/.openapi-generator/FILES
generated
6
mobile/openapi/.openapi-generator/FILES
generated
|
@ -31,6 +31,7 @@ doc/CheckExistingAssetsDto.md
|
||||||
doc/CheckExistingAssetsResponseDto.md
|
doc/CheckExistingAssetsResponseDto.md
|
||||||
doc/CreateAlbumDto.md
|
doc/CreateAlbumDto.md
|
||||||
doc/CreateAlbumShareLinkDto.md
|
doc/CreateAlbumShareLinkDto.md
|
||||||
|
doc/CreateAssetsShareLinkDto.md
|
||||||
doc/CreateProfileImageResponseDto.md
|
doc/CreateProfileImageResponseDto.md
|
||||||
doc/CreateTagDto.md
|
doc/CreateTagDto.md
|
||||||
doc/CreateUserDto.md
|
doc/CreateUserDto.md
|
||||||
|
@ -86,6 +87,7 @@ doc/ThumbnailFormat.md
|
||||||
doc/TimeGroupEnum.md
|
doc/TimeGroupEnum.md
|
||||||
doc/UpdateAlbumDto.md
|
doc/UpdateAlbumDto.md
|
||||||
doc/UpdateAssetDto.md
|
doc/UpdateAssetDto.md
|
||||||
|
doc/UpdateAssetsToSharedLinkDto.md
|
||||||
doc/UpdateTagDto.md
|
doc/UpdateTagDto.md
|
||||||
doc/UpdateUserDto.md
|
doc/UpdateUserDto.md
|
||||||
doc/UpsertDeviceInfoDto.md
|
doc/UpsertDeviceInfoDto.md
|
||||||
|
@ -140,6 +142,7 @@ lib/model/check_existing_assets_dto.dart
|
||||||
lib/model/check_existing_assets_response_dto.dart
|
lib/model/check_existing_assets_response_dto.dart
|
||||||
lib/model/create_album_dto.dart
|
lib/model/create_album_dto.dart
|
||||||
lib/model/create_album_share_link_dto.dart
|
lib/model/create_album_share_link_dto.dart
|
||||||
|
lib/model/create_assets_share_link_dto.dart
|
||||||
lib/model/create_profile_image_response_dto.dart
|
lib/model/create_profile_image_response_dto.dart
|
||||||
lib/model/create_tag_dto.dart
|
lib/model/create_tag_dto.dart
|
||||||
lib/model/create_user_dto.dart
|
lib/model/create_user_dto.dart
|
||||||
|
@ -188,6 +191,7 @@ lib/model/thumbnail_format.dart
|
||||||
lib/model/time_group_enum.dart
|
lib/model/time_group_enum.dart
|
||||||
lib/model/update_album_dto.dart
|
lib/model/update_album_dto.dart
|
||||||
lib/model/update_asset_dto.dart
|
lib/model/update_asset_dto.dart
|
||||||
|
lib/model/update_assets_to_shared_link_dto.dart
|
||||||
lib/model/update_tag_dto.dart
|
lib/model/update_tag_dto.dart
|
||||||
lib/model/update_user_dto.dart
|
lib/model/update_user_dto.dart
|
||||||
lib/model/upsert_device_info_dto.dart
|
lib/model/upsert_device_info_dto.dart
|
||||||
|
@ -224,6 +228,7 @@ test/check_existing_assets_dto_test.dart
|
||||||
test/check_existing_assets_response_dto_test.dart
|
test/check_existing_assets_response_dto_test.dart
|
||||||
test/create_album_dto_test.dart
|
test/create_album_dto_test.dart
|
||||||
test/create_album_share_link_dto_test.dart
|
test/create_album_share_link_dto_test.dart
|
||||||
|
test/create_assets_share_link_dto_test.dart
|
||||||
test/create_profile_image_response_dto_test.dart
|
test/create_profile_image_response_dto_test.dart
|
||||||
test/create_tag_dto_test.dart
|
test/create_tag_dto_test.dart
|
||||||
test/create_user_dto_test.dart
|
test/create_user_dto_test.dart
|
||||||
|
@ -279,6 +284,7 @@ test/thumbnail_format_test.dart
|
||||||
test/time_group_enum_test.dart
|
test/time_group_enum_test.dart
|
||||||
test/update_album_dto_test.dart
|
test/update_album_dto_test.dart
|
||||||
test/update_asset_dto_test.dart
|
test/update_asset_dto_test.dart
|
||||||
|
test/update_assets_to_shared_link_dto_test.dart
|
||||||
test/update_tag_dto_test.dart
|
test/update_tag_dto_test.dart
|
||||||
test/update_user_dto_test.dart
|
test/update_user_dto_test.dart
|
||||||
test/upsert_device_info_dto_test.dart
|
test/upsert_device_info_dto_test.dart
|
||||||
|
|
BIN
mobile/openapi/README.md
generated
BIN
mobile/openapi/README.md
generated
Binary file not shown.
BIN
mobile/openapi/doc/APIKeyApi.md
generated
BIN
mobile/openapi/doc/APIKeyApi.md
generated
Binary file not shown.
BIN
mobile/openapi/doc/AlbumApi.md
generated
BIN
mobile/openapi/doc/AlbumApi.md
generated
Binary file not shown.
BIN
mobile/openapi/doc/AssetApi.md
generated
BIN
mobile/openapi/doc/AssetApi.md
generated
Binary file not shown.
BIN
mobile/openapi/doc/AuthenticationApi.md
generated
BIN
mobile/openapi/doc/AuthenticationApi.md
generated
Binary file not shown.
BIN
mobile/openapi/doc/CreateAssetsShareLinkDto.md
generated
Normal file
BIN
mobile/openapi/doc/CreateAssetsShareLinkDto.md
generated
Normal file
Binary file not shown.
BIN
mobile/openapi/doc/DeviceInfoApi.md
generated
BIN
mobile/openapi/doc/DeviceInfoApi.md
generated
Binary file not shown.
BIN
mobile/openapi/doc/JobApi.md
generated
BIN
mobile/openapi/doc/JobApi.md
generated
Binary file not shown.
BIN
mobile/openapi/doc/OAuthApi.md
generated
BIN
mobile/openapi/doc/OAuthApi.md
generated
Binary file not shown.
BIN
mobile/openapi/doc/ServerInfoApi.md
generated
BIN
mobile/openapi/doc/ServerInfoApi.md
generated
Binary file not shown.
BIN
mobile/openapi/doc/ShareApi.md
generated
BIN
mobile/openapi/doc/ShareApi.md
generated
Binary file not shown.
BIN
mobile/openapi/doc/SystemConfigApi.md
generated
BIN
mobile/openapi/doc/SystemConfigApi.md
generated
Binary file not shown.
BIN
mobile/openapi/doc/TagApi.md
generated
BIN
mobile/openapi/doc/TagApi.md
generated
Binary file not shown.
BIN
mobile/openapi/doc/UpdateAssetsToSharedLinkDto.md
generated
Normal file
BIN
mobile/openapi/doc/UpdateAssetsToSharedLinkDto.md
generated
Normal file
Binary file not shown.
BIN
mobile/openapi/doc/UserApi.md
generated
BIN
mobile/openapi/doc/UserApi.md
generated
Binary file not shown.
BIN
mobile/openapi/lib/api.dart
generated
BIN
mobile/openapi/lib/api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/album_api.dart
generated
BIN
mobile/openapi/lib/api/album_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/api_key_api.dart
generated
BIN
mobile/openapi/lib/api/api_key_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/asset_api.dart
generated
BIN
mobile/openapi/lib/api/asset_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/authentication_api.dart
generated
BIN
mobile/openapi/lib/api/authentication_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/device_info_api.dart
generated
BIN
mobile/openapi/lib/api/device_info_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/job_api.dart
generated
BIN
mobile/openapi/lib/api/job_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/o_auth_api.dart
generated
BIN
mobile/openapi/lib/api/o_auth_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/server_info_api.dart
generated
BIN
mobile/openapi/lib/api/server_info_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/share_api.dart
generated
BIN
mobile/openapi/lib/api/share_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/system_config_api.dart
generated
BIN
mobile/openapi/lib/api/system_config_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/tag_api.dart
generated
BIN
mobile/openapi/lib/api/tag_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api/user_api.dart
generated
BIN
mobile/openapi/lib/api/user_api.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/api_client.dart
generated
BIN
mobile/openapi/lib/api_client.dart
generated
Binary file not shown.
BIN
mobile/openapi/lib/model/create_assets_share_link_dto.dart
generated
Normal file
BIN
mobile/openapi/lib/model/create_assets_share_link_dto.dart
generated
Normal file
Binary file not shown.
BIN
mobile/openapi/lib/model/update_assets_to_shared_link_dto.dart
generated
Normal file
BIN
mobile/openapi/lib/model/update_assets_to_shared_link_dto.dart
generated
Normal file
Binary file not shown.
BIN
mobile/openapi/test/album_api_test.dart
generated
BIN
mobile/openapi/test/album_api_test.dart
generated
Binary file not shown.
BIN
mobile/openapi/test/api_key_api_test.dart
generated
BIN
mobile/openapi/test/api_key_api_test.dart
generated
Binary file not shown.
BIN
mobile/openapi/test/asset_api_test.dart
generated
BIN
mobile/openapi/test/asset_api_test.dart
generated
Binary file not shown.
BIN
mobile/openapi/test/authentication_api_test.dart
generated
BIN
mobile/openapi/test/authentication_api_test.dart
generated
Binary file not shown.
BIN
mobile/openapi/test/create_assets_share_link_dto_test.dart
generated
Normal file
BIN
mobile/openapi/test/create_assets_share_link_dto_test.dart
generated
Normal file
Binary file not shown.
BIN
mobile/openapi/test/device_info_api_test.dart
generated
BIN
mobile/openapi/test/device_info_api_test.dart
generated
Binary file not shown.
BIN
mobile/openapi/test/job_api_test.dart
generated
BIN
mobile/openapi/test/job_api_test.dart
generated
Binary file not shown.
BIN
mobile/openapi/test/o_auth_api_test.dart
generated
BIN
mobile/openapi/test/o_auth_api_test.dart
generated
Binary file not shown.
BIN
mobile/openapi/test/server_info_api_test.dart
generated
BIN
mobile/openapi/test/server_info_api_test.dart
generated
Binary file not shown.
BIN
mobile/openapi/test/share_api_test.dart
generated
BIN
mobile/openapi/test/share_api_test.dart
generated
Binary file not shown.
BIN
mobile/openapi/test/system_config_api_test.dart
generated
BIN
mobile/openapi/test/system_config_api_test.dart
generated
Binary file not shown.
BIN
mobile/openapi/test/tag_api_test.dart
generated
BIN
mobile/openapi/test/tag_api_test.dart
generated
Binary file not shown.
BIN
mobile/openapi/test/update_assets_to_shared_link_dto_test.dart
generated
Normal file
BIN
mobile/openapi/test/update_assets_to_shared_link_dto_test.dart
generated
Normal file
Binary file not shown.
BIN
mobile/openapi/test/user_api_test.dart
generated
BIN
mobile/openapi/test/user_api_test.dart
generated
Binary file not shown.
|
@ -14,6 +14,7 @@ import {
|
||||||
Header,
|
Header,
|
||||||
Put,
|
Put,
|
||||||
UploadedFiles,
|
UploadedFiles,
|
||||||
|
Patch,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { Authenticated } from '../../decorators/authenticated.decorator';
|
import { Authenticated } from '../../decorators/authenticated.decorator';
|
||||||
import { AssetService } from './asset.service';
|
import { AssetService } from './asset.service';
|
||||||
|
@ -50,6 +51,9 @@ import {
|
||||||
IMMICH_CONTENT_LENGTH_HINT,
|
IMMICH_CONTENT_LENGTH_HINT,
|
||||||
} from '../../constants/download.constant';
|
} from '../../constants/download.constant';
|
||||||
import { DownloadFilesDto } from './dto/download-files.dto';
|
import { DownloadFilesDto } from './dto/download-files.dto';
|
||||||
|
import { CreateAssetsShareLinkDto } from './dto/create-asset-shared-link.dto';
|
||||||
|
import { SharedLinkResponseDto } from '../share/response-dto/shared-link-response.dto';
|
||||||
|
import { UpdateAssetsToSharedLinkDto } from './dto/add-assets-to-shared-link.dto';
|
||||||
|
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@ApiTags('Asset')
|
@ApiTags('Asset')
|
||||||
|
@ -321,4 +325,22 @@ export class AssetController {
|
||||||
): Promise<CheckExistingAssetsResponseDto> {
|
): Promise<CheckExistingAssetsResponseDto> {
|
||||||
return await this.assetService.checkExistingAssets(authUser, checkExistingAssetsDto);
|
return await this.assetService.checkExistingAssets(authUser, checkExistingAssetsDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Authenticated()
|
||||||
|
@Post('/shared-link')
|
||||||
|
async createAssetsSharedLink(
|
||||||
|
@GetAuthUser() authUser: AuthUserDto,
|
||||||
|
@Body(ValidationPipe) dto: CreateAssetsShareLinkDto,
|
||||||
|
): Promise<SharedLinkResponseDto> {
|
||||||
|
return await this.assetService.createAssetsSharedLink(authUser, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Authenticated({ isShared: true })
|
||||||
|
@Patch('/shared-link')
|
||||||
|
async updateAssetsInSharedLink(
|
||||||
|
@GetAuthUser() authUser: AuthUserDto,
|
||||||
|
@Body(ValidationPipe) dto: UpdateAssetsToSharedLinkDto,
|
||||||
|
): Promise<SharedLinkResponseDto> {
|
||||||
|
return await this.assetService.updateAssetsInSharedLink(authUser, dto);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { createHash, randomUUID } from 'node:crypto';
|
import { createHash, randomUUID } from 'node:crypto';
|
||||||
import { QueryFailedError, Repository } from 'typeorm';
|
import { QueryFailedError, Repository } from 'typeorm';
|
||||||
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
||||||
import { AssetEntity, AssetType } from '@app/infra';
|
import { AssetEntity, AssetType, SharedLinkType } from '@app/infra';
|
||||||
import { constants, createReadStream, ReadStream, stat } from 'fs';
|
import { constants, createReadStream, ReadStream, stat } from 'fs';
|
||||||
import { ServeFileDto } from './dto/serve-file.dto';
|
import { ServeFileDto } from './dto/serve-file.dto';
|
||||||
import { Response as Res } from 'express';
|
import { Response as Res } from 'express';
|
||||||
|
@ -59,6 +59,9 @@ import { StorageService } from '@app/storage';
|
||||||
import { ShareCore } from '../share/share.core';
|
import { ShareCore } from '../share/share.core';
|
||||||
import { ISharedLinkRepository } from '../share/shared-link.repository';
|
import { ISharedLinkRepository } from '../share/shared-link.repository';
|
||||||
import { DownloadFilesDto } from './dto/download-files.dto';
|
import { DownloadFilesDto } from './dto/download-files.dto';
|
||||||
|
import { CreateAssetsShareLinkDto } from './dto/create-asset-shared-link.dto';
|
||||||
|
import { mapSharedLinkToResponseDto, SharedLinkResponseDto } from '../share/response-dto/shared-link-response.dto';
|
||||||
|
import { UpdateAssetsToSharedLinkDto } from './dto/add-assets-to-shared-link.dto';
|
||||||
|
|
||||||
const fileInfo = promisify(stat);
|
const fileInfo = promisify(stat);
|
||||||
|
|
||||||
|
@ -699,6 +702,42 @@ export class AssetService {
|
||||||
throw new ForbiddenException();
|
throw new ForbiddenException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createAssetsSharedLink(authUser: AuthUserDto, dto: CreateAssetsShareLinkDto): Promise<SharedLinkResponseDto> {
|
||||||
|
const assets = [];
|
||||||
|
|
||||||
|
await this.checkAssetsAccess(authUser, dto.assetIds);
|
||||||
|
for (const assetId of dto.assetIds) {
|
||||||
|
const asset = await this._assetRepository.getById(assetId);
|
||||||
|
assets.push(asset);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sharedLink = await this.shareCore.createSharedLink(authUser.id, {
|
||||||
|
sharedType: SharedLinkType.INDIVIDUAL,
|
||||||
|
expiredAt: dto.expiredAt,
|
||||||
|
allowUpload: dto.allowUpload,
|
||||||
|
assets: assets,
|
||||||
|
description: dto.description,
|
||||||
|
});
|
||||||
|
|
||||||
|
return mapSharedLinkToResponseDto(sharedLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateAssetsInSharedLink(
|
||||||
|
authUser: AuthUserDto,
|
||||||
|
dto: UpdateAssetsToSharedLinkDto,
|
||||||
|
): Promise<SharedLinkResponseDto> {
|
||||||
|
if (!authUser.sharedLinkId) throw new ForbiddenException();
|
||||||
|
const assets = [];
|
||||||
|
|
||||||
|
for (const assetId of dto.assetIds) {
|
||||||
|
const asset = await this._assetRepository.getById(assetId);
|
||||||
|
assets.push(asset);
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedLink = await this.shareCore.updateAssetsInSharedLink(authUser.sharedLinkId, assets);
|
||||||
|
return mapSharedLinkToResponseDto(updatedLink);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function processETag(path: string, res: Res, headers: Record<string, string>): Promise<boolean> {
|
async function processETag(path: string, res: Res, headers: Record<string, string>): Promise<boolean> {
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
import { IsNotEmpty } from 'class-validator';
|
||||||
|
|
||||||
|
export class UpdateAssetsToSharedLinkDto {
|
||||||
|
@IsNotEmpty()
|
||||||
|
assetIds!: string[];
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsArray, IsBoolean, IsNotEmpty, IsOptional, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class CreateAssetsShareLinkDto {
|
||||||
|
@IsArray()
|
||||||
|
@IsString({ each: true })
|
||||||
|
@IsNotEmpty({ each: true })
|
||||||
|
@ApiProperty({
|
||||||
|
isArray: true,
|
||||||
|
type: String,
|
||||||
|
title: 'Array asset IDs to be shared',
|
||||||
|
example: [
|
||||||
|
'bf973405-3f2a-48d2-a687-2ed4167164be',
|
||||||
|
'dd41870b-5d00-46d2-924e-1d8489a0aa0f',
|
||||||
|
'fad77c3f-deef-4e7e-9608-14c1aa4e559a',
|
||||||
|
],
|
||||||
|
})
|
||||||
|
assetIds!: string[];
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
expiredAt?: string;
|
||||||
|
|
||||||
|
@IsBoolean()
|
||||||
|
@IsOptional()
|
||||||
|
allowUpload?: boolean;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@IsOptional()
|
||||||
|
description?: string;
|
||||||
|
}
|
|
@ -1258,6 +1258,78 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/asset/shared-link": {
|
||||||
|
"post": {
|
||||||
|
"operationId": "createAssetsSharedLink",
|
||||||
|
"description": "",
|
||||||
|
"parameters": [],
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/CreateAssetsShareLinkDto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/SharedLinkResponseDto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Asset"
|
||||||
|
],
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearer": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"patch": {
|
||||||
|
"operationId": "updateAssetsInSharedLink",
|
||||||
|
"description": "",
|
||||||
|
"parameters": [],
|
||||||
|
"requestBody": {
|
||||||
|
"required": true,
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/UpdateAssetsToSharedLinkDto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/SharedLinkResponseDto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Asset"
|
||||||
|
],
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"bearer": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/share": {
|
"/share": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "getAllSharedLinks",
|
"operationId": "getAllSharedLinks",
|
||||||
|
@ -3548,6 +3620,35 @@
|
||||||
"existingIds"
|
"existingIds"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"CreateAssetsShareLinkDto": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"assetIds": {
|
||||||
|
"title": "Array asset IDs to be shared",
|
||||||
|
"example": [
|
||||||
|
"bf973405-3f2a-48d2-a687-2ed4167164be",
|
||||||
|
"dd41870b-5d00-46d2-924e-1d8489a0aa0f",
|
||||||
|
"fad77c3f-deef-4e7e-9608-14c1aa4e559a"
|
||||||
|
],
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"expiredAt": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"allowUpload": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"assetIds"
|
||||||
|
]
|
||||||
|
},
|
||||||
"SharedLinkType": {
|
"SharedLinkType": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
|
@ -3654,6 +3755,20 @@
|
||||||
"allowUpload"
|
"allowUpload"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"UpdateAssetsToSharedLinkDto": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"assetIds": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"assetIds"
|
||||||
|
]
|
||||||
|
},
|
||||||
"EditSharedLinkDto": {
|
"EditSharedLinkDto": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
192
web/src/api/open-api/api.ts
generated
192
web/src/api/open-api/api.ts
generated
|
@ -4,7 +4,7 @@
|
||||||
* Immich
|
* Immich
|
||||||
* Immich API
|
* Immich API
|
||||||
*
|
*
|
||||||
* The version of the OpenAPI document: 1.40.0
|
* The version of the OpenAPI document: 1.41.1
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
|
@ -702,6 +702,37 @@ export interface CreateAlbumShareLinkDto {
|
||||||
*/
|
*/
|
||||||
'description'?: string;
|
'description'?: string;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface CreateAssetsShareLinkDto
|
||||||
|
*/
|
||||||
|
export interface CreateAssetsShareLinkDto {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {Array<string>}
|
||||||
|
* @memberof CreateAssetsShareLinkDto
|
||||||
|
*/
|
||||||
|
'assetIds': Array<string>;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof CreateAssetsShareLinkDto
|
||||||
|
*/
|
||||||
|
'expiredAt'?: string;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {boolean}
|
||||||
|
* @memberof CreateAssetsShareLinkDto
|
||||||
|
*/
|
||||||
|
'allowUpload'?: boolean;
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof CreateAssetsShareLinkDto
|
||||||
|
*/
|
||||||
|
'description'?: string;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
|
@ -2029,6 +2060,19 @@ export interface UpdateAssetDto {
|
||||||
*/
|
*/
|
||||||
'isFavorite'?: boolean;
|
'isFavorite'?: boolean;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface UpdateAssetsToSharedLinkDto
|
||||||
|
*/
|
||||||
|
export interface UpdateAssetsToSharedLinkDto {
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {Array<string>}
|
||||||
|
* @memberof UpdateAssetsToSharedLinkDto
|
||||||
|
*/
|
||||||
|
'assetIds': Array<string>;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
|
@ -3599,6 +3643,45 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
||||||
options: localVarRequestOptions,
|
options: localVarRequestOptions,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {CreateAssetsShareLinkDto} createAssetsShareLinkDto
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
createAssetsSharedLink: async (createAssetsShareLinkDto: CreateAssetsShareLinkDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
|
// verify required parameter 'createAssetsShareLinkDto' is not null or undefined
|
||||||
|
assertParamExists('createAssetsSharedLink', 'createAssetsShareLinkDto', createAssetsShareLinkDto)
|
||||||
|
const localVarPath = `/asset/shared-link`;
|
||||||
|
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||||
|
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||||
|
let baseOptions;
|
||||||
|
if (configuration) {
|
||||||
|
baseOptions = configuration.baseOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
|
||||||
|
const localVarHeaderParameter = {} as any;
|
||||||
|
const localVarQueryParameter = {} as any;
|
||||||
|
|
||||||
|
// authentication bearer required
|
||||||
|
// http bearer authentication required
|
||||||
|
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
localVarHeaderParameter['Content-Type'] = 'application/json';
|
||||||
|
|
||||||
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
|
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||||
|
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||||
|
localVarRequestOptions.data = serializeDataIfNeeded(createAssetsShareLinkDto, localVarRequestOptions, configuration)
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: toPathString(localVarUrlObj),
|
||||||
|
options: localVarRequestOptions,
|
||||||
|
};
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {DeleteAssetDto} deleteAssetDto
|
* @param {DeleteAssetDto} deleteAssetDto
|
||||||
|
@ -4255,6 +4338,45 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
||||||
options: localVarRequestOptions,
|
options: localVarRequestOptions,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {UpdateAssetsToSharedLinkDto} updateAssetsToSharedLinkDto
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
updateAssetsInSharedLink: async (updateAssetsToSharedLinkDto: UpdateAssetsToSharedLinkDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
|
// verify required parameter 'updateAssetsToSharedLinkDto' is not null or undefined
|
||||||
|
assertParamExists('updateAssetsInSharedLink', 'updateAssetsToSharedLinkDto', updateAssetsToSharedLinkDto)
|
||||||
|
const localVarPath = `/asset/shared-link`;
|
||||||
|
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||||
|
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||||
|
let baseOptions;
|
||||||
|
if (configuration) {
|
||||||
|
baseOptions = configuration.baseOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
const localVarRequestOptions = { method: 'PATCH', ...baseOptions, ...options};
|
||||||
|
const localVarHeaderParameter = {} as any;
|
||||||
|
const localVarQueryParameter = {} as any;
|
||||||
|
|
||||||
|
// authentication bearer required
|
||||||
|
// http bearer authentication required
|
||||||
|
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
localVarHeaderParameter['Content-Type'] = 'application/json';
|
||||||
|
|
||||||
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
|
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||||
|
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||||
|
localVarRequestOptions.data = serializeDataIfNeeded(updateAssetsToSharedLinkDto, localVarRequestOptions, configuration)
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: toPathString(localVarUrlObj),
|
||||||
|
options: localVarRequestOptions,
|
||||||
|
};
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {any} assetData
|
* @param {any} assetData
|
||||||
|
@ -4329,6 +4451,16 @@ export const AssetApiFp = function(configuration?: Configuration) {
|
||||||
const localVarAxiosArgs = await localVarAxiosParamCreator.checkExistingAssets(checkExistingAssetsDto, options);
|
const localVarAxiosArgs = await localVarAxiosParamCreator.checkExistingAssets(checkExistingAssetsDto, options);
|
||||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {CreateAssetsShareLinkDto} createAssetsShareLinkDto
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
async createAssetsSharedLink(createAssetsShareLinkDto: CreateAssetsShareLinkDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<SharedLinkResponseDto>> {
|
||||||
|
const localVarAxiosArgs = await localVarAxiosParamCreator.createAssetsSharedLink(createAssetsShareLinkDto, options);
|
||||||
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {DeleteAssetDto} deleteAssetDto
|
* @param {DeleteAssetDto} deleteAssetDto
|
||||||
|
@ -4501,6 +4633,16 @@ export const AssetApiFp = function(configuration?: Configuration) {
|
||||||
const localVarAxiosArgs = await localVarAxiosParamCreator.updateAsset(assetId, updateAssetDto, options);
|
const localVarAxiosArgs = await localVarAxiosParamCreator.updateAsset(assetId, updateAssetDto, options);
|
||||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {UpdateAssetsToSharedLinkDto} updateAssetsToSharedLinkDto
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
async updateAssetsInSharedLink(updateAssetsToSharedLinkDto: UpdateAssetsToSharedLinkDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<SharedLinkResponseDto>> {
|
||||||
|
const localVarAxiosArgs = await localVarAxiosParamCreator.updateAssetsInSharedLink(updateAssetsToSharedLinkDto, options);
|
||||||
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {any} assetData
|
* @param {any} assetData
|
||||||
|
@ -4539,6 +4681,15 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
|
||||||
checkExistingAssets(checkExistingAssetsDto: CheckExistingAssetsDto, options?: any): AxiosPromise<CheckExistingAssetsResponseDto> {
|
checkExistingAssets(checkExistingAssetsDto: CheckExistingAssetsDto, options?: any): AxiosPromise<CheckExistingAssetsResponseDto> {
|
||||||
return localVarFp.checkExistingAssets(checkExistingAssetsDto, options).then((request) => request(axios, basePath));
|
return localVarFp.checkExistingAssets(checkExistingAssetsDto, options).then((request) => request(axios, basePath));
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {CreateAssetsShareLinkDto} createAssetsShareLinkDto
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
createAssetsSharedLink(createAssetsShareLinkDto: CreateAssetsShareLinkDto, options?: any): AxiosPromise<SharedLinkResponseDto> {
|
||||||
|
return localVarFp.createAssetsSharedLink(createAssetsShareLinkDto, options).then((request) => request(axios, basePath));
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {DeleteAssetDto} deleteAssetDto
|
* @param {DeleteAssetDto} deleteAssetDto
|
||||||
|
@ -4694,6 +4845,15 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
|
||||||
updateAsset(assetId: string, updateAssetDto: UpdateAssetDto, options?: any): AxiosPromise<AssetResponseDto> {
|
updateAsset(assetId: string, updateAssetDto: UpdateAssetDto, options?: any): AxiosPromise<AssetResponseDto> {
|
||||||
return localVarFp.updateAsset(assetId, updateAssetDto, options).then((request) => request(axios, basePath));
|
return localVarFp.updateAsset(assetId, updateAssetDto, options).then((request) => request(axios, basePath));
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {UpdateAssetsToSharedLinkDto} updateAssetsToSharedLinkDto
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
updateAssetsInSharedLink(updateAssetsToSharedLinkDto: UpdateAssetsToSharedLinkDto, options?: any): AxiosPromise<SharedLinkResponseDto> {
|
||||||
|
return localVarFp.updateAssetsInSharedLink(updateAssetsToSharedLinkDto, options).then((request) => request(axios, basePath));
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {any} assetData
|
* @param {any} assetData
|
||||||
|
@ -4735,6 +4895,17 @@ export class AssetApi extends BaseAPI {
|
||||||
return AssetApiFp(this.configuration).checkExistingAssets(checkExistingAssetsDto, options).then((request) => request(this.axios, this.basePath));
|
return AssetApiFp(this.configuration).checkExistingAssets(checkExistingAssetsDto, options).then((request) => request(this.axios, this.basePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {CreateAssetsShareLinkDto} createAssetsShareLinkDto
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
* @memberof AssetApi
|
||||||
|
*/
|
||||||
|
public createAssetsSharedLink(createAssetsShareLinkDto: CreateAssetsShareLinkDto, options?: AxiosRequestConfig) {
|
||||||
|
return AssetApiFp(this.configuration).createAssetsSharedLink(createAssetsShareLinkDto, options).then((request) => request(this.axios, this.basePath));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {DeleteAssetDto} deleteAssetDto
|
* @param {DeleteAssetDto} deleteAssetDto
|
||||||
|
@ -4924,6 +5095,17 @@ export class AssetApi extends BaseAPI {
|
||||||
return AssetApiFp(this.configuration).updateAsset(assetId, updateAssetDto, options).then((request) => request(this.axios, this.basePath));
|
return AssetApiFp(this.configuration).updateAsset(assetId, updateAssetDto, options).then((request) => request(this.axios, this.basePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {UpdateAssetsToSharedLinkDto} updateAssetsToSharedLinkDto
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
* @memberof AssetApi
|
||||||
|
*/
|
||||||
|
public updateAssetsInSharedLink(updateAssetsToSharedLinkDto: UpdateAssetsToSharedLinkDto, options?: AxiosRequestConfig) {
|
||||||
|
return AssetApiFp(this.configuration).updateAssetsInSharedLink(updateAssetsToSharedLinkDto, options).then((request) => request(this.axios, this.basePath));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {any} assetData
|
* @param {any} assetData
|
||||||
|
@ -5300,6 +5482,7 @@ export const DeviceInfoApiAxiosParamCreator = function (configuration?: Configur
|
||||||
* @deprecated
|
* @deprecated
|
||||||
* @param {UpsertDeviceInfoDto} upsertDeviceInfoDto
|
* @param {UpsertDeviceInfoDto} upsertDeviceInfoDto
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
|
* @deprecated
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
createDeviceInfo: async (upsertDeviceInfoDto: UpsertDeviceInfoDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
createDeviceInfo: async (upsertDeviceInfoDto: UpsertDeviceInfoDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
|
@ -5339,6 +5522,7 @@ export const DeviceInfoApiAxiosParamCreator = function (configuration?: Configur
|
||||||
* @deprecated
|
* @deprecated
|
||||||
* @param {UpsertDeviceInfoDto} upsertDeviceInfoDto
|
* @param {UpsertDeviceInfoDto} upsertDeviceInfoDto
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
|
* @deprecated
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
updateDeviceInfo: async (upsertDeviceInfoDto: UpsertDeviceInfoDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
updateDeviceInfo: async (upsertDeviceInfoDto: UpsertDeviceInfoDto, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
|
@ -5427,6 +5611,7 @@ export const DeviceInfoApiFp = function(configuration?: Configuration) {
|
||||||
* @deprecated
|
* @deprecated
|
||||||
* @param {UpsertDeviceInfoDto} upsertDeviceInfoDto
|
* @param {UpsertDeviceInfoDto} upsertDeviceInfoDto
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
|
* @deprecated
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
async createDeviceInfo(upsertDeviceInfoDto: UpsertDeviceInfoDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<DeviceInfoResponseDto>> {
|
async createDeviceInfo(upsertDeviceInfoDto: UpsertDeviceInfoDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<DeviceInfoResponseDto>> {
|
||||||
|
@ -5437,6 +5622,7 @@ export const DeviceInfoApiFp = function(configuration?: Configuration) {
|
||||||
* @deprecated
|
* @deprecated
|
||||||
* @param {UpsertDeviceInfoDto} upsertDeviceInfoDto
|
* @param {UpsertDeviceInfoDto} upsertDeviceInfoDto
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
|
* @deprecated
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
async updateDeviceInfo(upsertDeviceInfoDto: UpsertDeviceInfoDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<DeviceInfoResponseDto>> {
|
async updateDeviceInfo(upsertDeviceInfoDto: UpsertDeviceInfoDto, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<DeviceInfoResponseDto>> {
|
||||||
|
@ -5467,6 +5653,7 @@ export const DeviceInfoApiFactory = function (configuration?: Configuration, bas
|
||||||
* @deprecated
|
* @deprecated
|
||||||
* @param {UpsertDeviceInfoDto} upsertDeviceInfoDto
|
* @param {UpsertDeviceInfoDto} upsertDeviceInfoDto
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
|
* @deprecated
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
createDeviceInfo(upsertDeviceInfoDto: UpsertDeviceInfoDto, options?: any): AxiosPromise<DeviceInfoResponseDto> {
|
createDeviceInfo(upsertDeviceInfoDto: UpsertDeviceInfoDto, options?: any): AxiosPromise<DeviceInfoResponseDto> {
|
||||||
|
@ -5476,6 +5663,7 @@ export const DeviceInfoApiFactory = function (configuration?: Configuration, bas
|
||||||
* @deprecated
|
* @deprecated
|
||||||
* @param {UpsertDeviceInfoDto} upsertDeviceInfoDto
|
* @param {UpsertDeviceInfoDto} upsertDeviceInfoDto
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
|
* @deprecated
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
updateDeviceInfo(upsertDeviceInfoDto: UpsertDeviceInfoDto, options?: any): AxiosPromise<DeviceInfoResponseDto> {
|
updateDeviceInfo(upsertDeviceInfoDto: UpsertDeviceInfoDto, options?: any): AxiosPromise<DeviceInfoResponseDto> {
|
||||||
|
@ -5504,6 +5692,7 @@ export class DeviceInfoApi extends BaseAPI {
|
||||||
* @deprecated
|
* @deprecated
|
||||||
* @param {UpsertDeviceInfoDto} upsertDeviceInfoDto
|
* @param {UpsertDeviceInfoDto} upsertDeviceInfoDto
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
|
* @deprecated
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
* @memberof DeviceInfoApi
|
* @memberof DeviceInfoApi
|
||||||
*/
|
*/
|
||||||
|
@ -5515,6 +5704,7 @@ export class DeviceInfoApi extends BaseAPI {
|
||||||
* @deprecated
|
* @deprecated
|
||||||
* @param {UpsertDeviceInfoDto} upsertDeviceInfoDto
|
* @param {UpsertDeviceInfoDto} upsertDeviceInfoDto
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
|
* @deprecated
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
* @memberof DeviceInfoApi
|
* @memberof DeviceInfoApi
|
||||||
*/
|
*/
|
||||||
|
|
2
web/src/api/open-api/base.ts
generated
2
web/src/api/open-api/base.ts
generated
|
@ -4,7 +4,7 @@
|
||||||
* Immich
|
* Immich
|
||||||
* Immich API
|
* Immich API
|
||||||
*
|
*
|
||||||
* The version of the OpenAPI document: 1.40.0
|
* The version of the OpenAPI document: 1.41.1
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
|
|
2
web/src/api/open-api/common.ts
generated
2
web/src/api/open-api/common.ts
generated
|
@ -4,7 +4,7 @@
|
||||||
* Immich
|
* Immich
|
||||||
* Immich API
|
* Immich API
|
||||||
*
|
*
|
||||||
* The version of the OpenAPI document: 1.40.0
|
* The version of the OpenAPI document: 1.41.1
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
|
|
2
web/src/api/open-api/configuration.ts
generated
2
web/src/api/open-api/configuration.ts
generated
|
@ -4,7 +4,7 @@
|
||||||
* Immich
|
* Immich
|
||||||
* Immich API
|
* Immich API
|
||||||
*
|
*
|
||||||
* The version of the OpenAPI document: 1.40.0
|
* The version of the OpenAPI document: 1.41.1
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
|
|
2
web/src/api/open-api/index.ts
generated
2
web/src/api/open-api/index.ts
generated
|
@ -4,7 +4,7 @@
|
||||||
* Immich
|
* Immich
|
||||||
* Immich API
|
* Immich API
|
||||||
*
|
*
|
||||||
* The version of the OpenAPI document: 1.40.0
|
* The version of the OpenAPI document: 1.41.1
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
|
|
|
@ -1,13 +1,11 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { afterNavigate, goto } from '$app/navigation';
|
import { afterNavigate, goto } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
|
||||||
import {
|
import {
|
||||||
AlbumResponseDto,
|
AlbumResponseDto,
|
||||||
api,
|
api,
|
||||||
AssetResponseDto,
|
AssetResponseDto,
|
||||||
SharedLinkResponseDto,
|
SharedLinkResponseDto,
|
||||||
SharedLinkType,
|
SharedLinkType,
|
||||||
ThumbnailFormat,
|
|
||||||
UserResponseDto
|
UserResponseDto
|
||||||
} from '@api';
|
} from '@api';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
@ -15,9 +13,7 @@
|
||||||
import Plus from 'svelte-material-icons/Plus.svelte';
|
import Plus from 'svelte-material-icons/Plus.svelte';
|
||||||
import FileImagePlusOutline from 'svelte-material-icons/FileImagePlusOutline.svelte';
|
import FileImagePlusOutline from 'svelte-material-icons/FileImagePlusOutline.svelte';
|
||||||
import ShareVariantOutline from 'svelte-material-icons/ShareVariantOutline.svelte';
|
import ShareVariantOutline from 'svelte-material-icons/ShareVariantOutline.svelte';
|
||||||
import AssetViewer from '../asset-viewer/asset-viewer.svelte';
|
|
||||||
import CircleAvatar from '../shared-components/circle-avatar.svelte';
|
import CircleAvatar from '../shared-components/circle-avatar.svelte';
|
||||||
import ImmichThumbnail from '../shared-components/immich-thumbnail.svelte';
|
|
||||||
import AssetSelection from './asset-selection.svelte';
|
import AssetSelection from './asset-selection.svelte';
|
||||||
import UserSelectionModal from './user-selection-modal.svelte';
|
import UserSelectionModal from './user-selection-modal.svelte';
|
||||||
import ShareInfoModal from './share-info-modal.svelte';
|
import ShareInfoModal from './share-info-modal.svelte';
|
||||||
|
@ -43,14 +39,13 @@
|
||||||
import ThemeButton from '../shared-components/theme-button.svelte';
|
import ThemeButton from '../shared-components/theme-button.svelte';
|
||||||
import { openFileUploadDialog } from '$lib/utils/file-uploader';
|
import { openFileUploadDialog } from '$lib/utils/file-uploader';
|
||||||
import { bulkDownload } from '$lib/utils/asset-utils';
|
import { bulkDownload } from '$lib/utils/asset-utils';
|
||||||
|
import GalleryViewer from '../shared-components/gallery-viewer/gallery-viewer.svelte';
|
||||||
|
|
||||||
export let album: AlbumResponseDto;
|
export let album: AlbumResponseDto;
|
||||||
export let sharedLink: SharedLinkResponseDto | undefined = undefined;
|
export let sharedLink: SharedLinkResponseDto | undefined = undefined;
|
||||||
|
|
||||||
const { isAlbumAssetSelectionOpen } = albumAssetSelectionStore;
|
const { isAlbumAssetSelectionOpen } = albumAssetSelectionStore;
|
||||||
|
|
||||||
let isShowAssetViewer = false;
|
|
||||||
|
|
||||||
let isShowAssetSelection = false;
|
let isShowAssetSelection = false;
|
||||||
|
|
||||||
let isShowShareLinkModal = false;
|
let isShowShareLinkModal = false;
|
||||||
|
@ -72,11 +67,6 @@
|
||||||
let isShowAlbumOptions = false;
|
let isShowAlbumOptions = false;
|
||||||
let isShowThumbnailSelection = false;
|
let isShowThumbnailSelection = false;
|
||||||
|
|
||||||
let selectedAsset: AssetResponseDto;
|
|
||||||
let currentViewAssetIndex = 0;
|
|
||||||
|
|
||||||
let viewWidth: number;
|
|
||||||
let thumbnailSize = 300;
|
|
||||||
let backUrl = '/albums';
|
let backUrl = '/albums';
|
||||||
let currentAlbumName = '';
|
let currentAlbumName = '';
|
||||||
let currentUser: UserResponseDto;
|
let currentUser: UserResponseDto;
|
||||||
|
@ -97,18 +87,6 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$: {
|
|
||||||
if (album.assets?.length < 6) {
|
|
||||||
thumbnailSize = Math.floor(viewWidth / album.assetCount - album.assetCount);
|
|
||||||
} else {
|
|
||||||
if (viewWidth > 600) thumbnailSize = Math.floor(viewWidth / 6 - 6);
|
|
||||||
else if (viewWidth > 400) thumbnailSize = Math.floor(viewWidth / 4 - 6);
|
|
||||||
else if (viewWidth > 300) thumbnailSize = Math.floor(viewWidth / 2 - 6);
|
|
||||||
else if (viewWidth > 200) thumbnailSize = Math.floor(viewWidth / 2 - 6);
|
|
||||||
else if (viewWidth > 100) thumbnailSize = Math.floor(viewWidth / 1 - 6);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const locale = navigator.language;
|
const locale = navigator.language;
|
||||||
const albumDateFormat: Intl.DateTimeFormatOptions = {
|
const albumDateFormat: Intl.DateTimeFormatOptions = {
|
||||||
month: 'short',
|
month: 'short',
|
||||||
|
@ -140,28 +118,6 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const viewAssetHandler = (event: CustomEvent) => {
|
|
||||||
const { asset }: { asset: AssetResponseDto } = event.detail;
|
|
||||||
|
|
||||||
currentViewAssetIndex = album.assets.findIndex((a) => a.id == asset.id);
|
|
||||||
selectedAsset = album.assets[currentViewAssetIndex];
|
|
||||||
isShowAssetViewer = true;
|
|
||||||
pushState(selectedAsset.id);
|
|
||||||
};
|
|
||||||
|
|
||||||
const selectAssetHandler = (event: CustomEvent) => {
|
|
||||||
const { asset }: { asset: AssetResponseDto } = event.detail;
|
|
||||||
let temp = new Set(multiSelectAsset);
|
|
||||||
|
|
||||||
if (multiSelectAsset.has(asset)) {
|
|
||||||
temp.delete(asset);
|
|
||||||
} else {
|
|
||||||
temp.add(asset);
|
|
||||||
}
|
|
||||||
|
|
||||||
multiSelectAsset = temp;
|
|
||||||
};
|
|
||||||
|
|
||||||
const clearMultiSelectAssetAssetHandler = () => {
|
const clearMultiSelectAssetAssetHandler = () => {
|
||||||
multiSelectAsset = new Set();
|
multiSelectAsset = new Set();
|
||||||
};
|
};
|
||||||
|
@ -184,40 +140,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const navigateAssetForward = () => {
|
|
||||||
try {
|
|
||||||
if (currentViewAssetIndex < album.assetCount - 1) {
|
|
||||||
currentViewAssetIndex++;
|
|
||||||
selectedAsset = album.assets[currentViewAssetIndex];
|
|
||||||
pushState(selectedAsset.id);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const navigateAssetBackward = () => {
|
|
||||||
try {
|
|
||||||
if (currentViewAssetIndex > 0) {
|
|
||||||
currentViewAssetIndex--;
|
|
||||||
selectedAsset = album.assets[currentViewAssetIndex];
|
|
||||||
pushState(selectedAsset.id);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const pushState = (assetId: string) => {
|
|
||||||
// add a URL to the browser's history
|
|
||||||
// changes the current URL in the address bar but doesn't perform any SvelteKit navigation
|
|
||||||
history.pushState(null, '', `${$page.url.pathname}/photos/${assetId}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeViewer = () => {
|
|
||||||
isShowAssetViewer = false;
|
|
||||||
history.pushState(null, '', `${$page.url.pathname}`);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Update Album Name
|
// Update Album Name
|
||||||
$: {
|
$: {
|
||||||
|
@ -606,34 +528,11 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if album.assetCount > 0}
|
{#if album.assetCount > 0}
|
||||||
<div class="flex flex-wrap gap-1 w-full pb-20" bind:clientWidth={viewWidth}>
|
<GalleryViewer
|
||||||
{#each album.assets as asset}
|
assets={album.assets}
|
||||||
{#key asset.id}
|
key={sharedLink?.key ?? ''}
|
||||||
{#if album.assetCount < 7}
|
bind:selectedAssets={multiSelectAsset}
|
||||||
<ImmichThumbnail
|
/>
|
||||||
{asset}
|
|
||||||
{thumbnailSize}
|
|
||||||
publicSharedKey={sharedLink?.key}
|
|
||||||
format={ThumbnailFormat.Jpeg}
|
|
||||||
on:click={(e) =>
|
|
||||||
isMultiSelectionMode ? selectAssetHandler(e) : viewAssetHandler(e)}
|
|
||||||
on:select={selectAssetHandler}
|
|
||||||
selected={multiSelectAsset.has(asset)}
|
|
||||||
/>
|
|
||||||
{:else}
|
|
||||||
<ImmichThumbnail
|
|
||||||
{asset}
|
|
||||||
{thumbnailSize}
|
|
||||||
publicSharedKey={sharedLink?.key}
|
|
||||||
on:click={(e) =>
|
|
||||||
isMultiSelectionMode ? selectAssetHandler(e) : viewAssetHandler(e)}
|
|
||||||
on:select={selectAssetHandler}
|
|
||||||
selected={multiSelectAsset.has(asset)}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
{/key}
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{:else}
|
{:else}
|
||||||
<!-- Album is empty - Show asset selectection buttons -->
|
<!-- Album is empty - Show asset selectection buttons -->
|
||||||
<section id="empty-album" class=" mt-[200px] flex place-content-center place-items-center">
|
<section id="empty-album" class=" mt-[200px] flex place-content-center place-items-center">
|
||||||
|
@ -654,17 +553,6 @@
|
||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Overlay Asset Viewer -->
|
|
||||||
{#if isShowAssetViewer}
|
|
||||||
<AssetViewer
|
|
||||||
asset={selectedAsset}
|
|
||||||
publicSharedKey={sharedLink?.key}
|
|
||||||
on:navigate-previous={navigateAssetBackward}
|
|
||||||
on:navigate-next={navigateAssetForward}
|
|
||||||
on:close={closeViewer}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if isShowAssetSelection}
|
{#if isShowAssetSelection}
|
||||||
<AssetSelection
|
<AssetSelection
|
||||||
albumId={album.id}
|
albumId={album.id}
|
||||||
|
|
|
@ -233,7 +233,7 @@
|
||||||
|
|
||||||
<section
|
<section
|
||||||
id="immich-asset-viewer"
|
id="immich-asset-viewer"
|
||||||
class="fixed h-screen w-screen top-0 overflow-y-hidden bg-black z-[999] grid grid-rows-[64px_1fr] grid-cols-4"
|
class="fixed h-screen w-screen left-0 top-0 overflow-y-hidden bg-black z-[999] grid grid-rows-[64px_1fr] grid-cols-4"
|
||||||
>
|
>
|
||||||
<div class="col-start-1 col-span-4 row-start-1 row-span-1 z-[1000] transition-transform">
|
<div class="col-start-1 col-span-4 row-start-1 row-span-1 z-[1000] transition-transform">
|
||||||
<AssetViewerNavBar
|
<AssetViewerNavBar
|
||||||
|
|
|
@ -0,0 +1,150 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import ArrowLeft from 'svelte-material-icons/ArrowLeft.svelte';
|
||||||
|
|
||||||
|
import { api, AssetResponseDto, SharedLinkResponseDto } from '@api';
|
||||||
|
import ControlAppBar from '../shared-components/control-app-bar.svelte';
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
import CircleIconButton from '../shared-components/circle-icon-button.svelte';
|
||||||
|
import FileImagePlusOutline from 'svelte-material-icons/FileImagePlusOutline.svelte';
|
||||||
|
import FolderDownloadOutline from 'svelte-material-icons/FolderDownloadOutline.svelte';
|
||||||
|
import { openFileUploadDialog } from '$lib/utils/file-uploader';
|
||||||
|
import { bulkDownload } from '$lib/utils/asset-utils';
|
||||||
|
import Close from 'svelte-material-icons/Close.svelte';
|
||||||
|
import CloudDownloadOutline from 'svelte-material-icons/CloudDownloadOutline.svelte';
|
||||||
|
import GalleryViewer from '../shared-components/gallery-viewer/gallery-viewer.svelte';
|
||||||
|
import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte';
|
||||||
|
import {
|
||||||
|
notificationController,
|
||||||
|
NotificationType
|
||||||
|
} from '../shared-components/notification/notification';
|
||||||
|
|
||||||
|
export let sharedLink: SharedLinkResponseDto;
|
||||||
|
export let isOwned: boolean;
|
||||||
|
|
||||||
|
let assets = sharedLink.assets;
|
||||||
|
let selectedAssets: Set<AssetResponseDto> = new Set();
|
||||||
|
|
||||||
|
$: isMultiSelectionMode = selectedAssets.size > 0;
|
||||||
|
|
||||||
|
const clearMultiSelectAssetAssetHandler = () => {
|
||||||
|
selectedAssets = new Set();
|
||||||
|
};
|
||||||
|
|
||||||
|
const downloadAssets = async (isAll: boolean) => {
|
||||||
|
await bulkDownload(
|
||||||
|
'immich-shared',
|
||||||
|
isAll ? assets : Array.from(selectedAssets),
|
||||||
|
() => {
|
||||||
|
isMultiSelectionMode = false;
|
||||||
|
clearMultiSelectAssetAssetHandler();
|
||||||
|
},
|
||||||
|
sharedLink?.key
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUploadAssets = () => {
|
||||||
|
openFileUploadDialog(undefined, sharedLink?.key, async (assetId) => {
|
||||||
|
await api.assetApi.updateAssetsInSharedLink(
|
||||||
|
{
|
||||||
|
assetIds: [...assets.map((a) => a.id), assetId]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
key: sharedLink?.key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
notificationController.show({
|
||||||
|
message: 'Add asset to shared link successfully',
|
||||||
|
type: NotificationType.Info
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRemoveAssetsFromSharedLink = async () => {
|
||||||
|
if (window.confirm('Do you want to remove selected assets from the shared link?')) {
|
||||||
|
await api.assetApi.updateAssetsInSharedLink(
|
||||||
|
{
|
||||||
|
assetIds: assets.filter((a) => !selectedAssets.has(a)).map((a) => a.id)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
key: sharedLink?.key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
assets = assets.filter((a) => !selectedAssets.has(a));
|
||||||
|
clearMultiSelectAssetAssetHandler();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section class="bg-immich-bg dark:bg-immich-dark-bg">
|
||||||
|
{#if isMultiSelectionMode}
|
||||||
|
<ControlAppBar
|
||||||
|
on:close-button-click={clearMultiSelectAssetAssetHandler}
|
||||||
|
backIcon={Close}
|
||||||
|
tailwindClasses={'bg-white shadow-md'}
|
||||||
|
>
|
||||||
|
<svelte:fragment slot="leading">
|
||||||
|
<p class="font-medium text-immich-primary dark:text-immich-dark-primary">
|
||||||
|
Selected {selectedAssets.size}
|
||||||
|
</p>
|
||||||
|
</svelte:fragment>
|
||||||
|
<svelte:fragment slot="trailing">
|
||||||
|
<CircleIconButton
|
||||||
|
title="Download"
|
||||||
|
on:click={() => downloadAssets(false)}
|
||||||
|
logo={CloudDownloadOutline}
|
||||||
|
/>
|
||||||
|
{#if isOwned}
|
||||||
|
<CircleIconButton
|
||||||
|
title="Remove from album"
|
||||||
|
on:click={handleRemoveAssetsFromSharedLink}
|
||||||
|
logo={DeleteOutline}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</svelte:fragment>
|
||||||
|
</ControlAppBar>
|
||||||
|
{:else}
|
||||||
|
<ControlAppBar
|
||||||
|
on:close-button-click={() => goto('/photos')}
|
||||||
|
backIcon={ArrowLeft}
|
||||||
|
showBackButton={false}
|
||||||
|
>
|
||||||
|
<svelte:fragment slot="leading">
|
||||||
|
<a
|
||||||
|
data-sveltekit-preload-data="hover"
|
||||||
|
class="flex gap-2 place-items-center hover:cursor-pointer ml-6"
|
||||||
|
href="https://immich.app"
|
||||||
|
>
|
||||||
|
<img src="/immich-logo.svg" alt="immich logo" height="30" width="30" />
|
||||||
|
<h1 class="font-immich-title text-lg text-immich-primary dark:text-immich-dark-primary">
|
||||||
|
IMMICH
|
||||||
|
</h1>
|
||||||
|
</a>
|
||||||
|
</svelte:fragment>
|
||||||
|
|
||||||
|
<svelte:fragment slot="trailing">
|
||||||
|
{#if sharedLink?.allowUpload}
|
||||||
|
<CircleIconButton
|
||||||
|
title="Add Photos"
|
||||||
|
on:click={handleUploadAssets}
|
||||||
|
logo={FileImagePlusOutline}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<CircleIconButton
|
||||||
|
title="Download"
|
||||||
|
on:click={() => downloadAssets(true)}
|
||||||
|
logo={FolderDownloadOutline}
|
||||||
|
/>
|
||||||
|
</svelte:fragment>
|
||||||
|
</ControlAppBar>
|
||||||
|
{/if}
|
||||||
|
<section class="flex flex-col my-[160px] px-6 sm:px-12 md:px-24 lg:px-40">
|
||||||
|
<GalleryViewer {assets} key={sharedLink.key} bind:selectedAssets />
|
||||||
|
</section>
|
||||||
|
</section>
|
|
@ -2,7 +2,13 @@
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { createEventDispatcher, onMount } from 'svelte';
|
||||||
import BaseModal from '../base-modal.svelte';
|
import BaseModal from '../base-modal.svelte';
|
||||||
import Link from 'svelte-material-icons/Link.svelte';
|
import Link from 'svelte-material-icons/Link.svelte';
|
||||||
import { AlbumResponseDto, api, SharedLinkResponseDto, SharedLinkType } from '@api';
|
import {
|
||||||
|
AlbumResponseDto,
|
||||||
|
api,
|
||||||
|
AssetResponseDto,
|
||||||
|
SharedLinkResponseDto,
|
||||||
|
SharedLinkType
|
||||||
|
} from '@api';
|
||||||
import { notificationController, NotificationType } from '../notification/notification';
|
import { notificationController, NotificationType } from '../notification/notification';
|
||||||
import { ImmichDropDownOption } from '../dropdown-button.svelte';
|
import { ImmichDropDownOption } from '../dropdown-button.svelte';
|
||||||
import SettingSwitch from '$lib/components/admin-page/settings/setting-switch.svelte';
|
import SettingSwitch from '$lib/components/admin-page/settings/setting-switch.svelte';
|
||||||
|
@ -10,9 +16,11 @@
|
||||||
import SettingInputField, {
|
import SettingInputField, {
|
||||||
SettingInputFieldType
|
SettingInputFieldType
|
||||||
} from '$lib/components/admin-page/settings/setting-input-field.svelte';
|
} from '$lib/components/admin-page/settings/setting-input-field.svelte';
|
||||||
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
|
|
||||||
export let shareType: SharedLinkType;
|
export let shareType: SharedLinkType;
|
||||||
export let album: AlbumResponseDto | undefined;
|
export let sharedAssets: AssetResponseDto[] = [];
|
||||||
|
export let album: AlbumResponseDto | undefined = undefined;
|
||||||
export let editingLink: SharedLinkResponseDto | undefined = undefined;
|
export let editingLink: SharedLinkResponseDto | undefined = undefined;
|
||||||
|
|
||||||
let isShowSharedLink = false;
|
let isShowSharedLink = false;
|
||||||
|
@ -37,32 +45,36 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const createAlbumSharedLink = async () => {
|
const handleCreateSharedLink = async () => {
|
||||||
if (album) {
|
const expirationTime = getExpirationTimeInMillisecond();
|
||||||
try {
|
const currentTime = new Date().getTime();
|
||||||
const expirationTime = getExpirationTimeInMillisecond();
|
const expirationDate = expirationTime
|
||||||
const currentTime = new Date().getTime();
|
? new Date(currentTime + expirationTime).toISOString()
|
||||||
const expirationDate = expirationTime
|
: undefined;
|
||||||
? new Date(currentTime + expirationTime).toISOString()
|
|
||||||
: undefined;
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (shareType === SharedLinkType.Album && album) {
|
||||||
const { data } = await api.albumApi.createAlbumSharedLink({
|
const { data } = await api.albumApi.createAlbumSharedLink({
|
||||||
albumId: album.id,
|
albumId: album.id,
|
||||||
expiredAt: expirationDate,
|
expiredAt: expirationDate,
|
||||||
allowUpload: isAllowUpload,
|
allowUpload: isAllowUpload,
|
||||||
description: description
|
description: description
|
||||||
});
|
});
|
||||||
|
|
||||||
buildSharedLink(data);
|
buildSharedLink(data);
|
||||||
isShowSharedLink = true;
|
} else {
|
||||||
} catch (e) {
|
const { data } = await api.assetApi.createAssetsSharedLink({
|
||||||
console.error('[createAlbumSharedLink] Error: ', e);
|
assetIds: sharedAssets.map((a) => a.id),
|
||||||
notificationController.show({
|
expiredAt: expirationDate,
|
||||||
type: NotificationType.Error,
|
allowUpload: isAllowUpload,
|
||||||
message: 'Failed to create shared link'
|
description: description
|
||||||
});
|
});
|
||||||
|
buildSharedLink(data);
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
handleError(e, 'Failed to create shared link');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isShowSharedLink = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const buildSharedLink = (createdLink: SharedLinkResponseDto) => {
|
const buildSharedLink = (createdLink: SharedLinkResponseDto) => {
|
||||||
|
@ -76,8 +88,11 @@
|
||||||
message: 'Copied to clipboard!',
|
message: 'Copied to clipboard!',
|
||||||
type: NotificationType.Info
|
type: NotificationType.Info
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (e) {
|
||||||
console.error('Error', error);
|
handleError(
|
||||||
|
e,
|
||||||
|
'Cannot copy to clipboard, make sure you are accessing the page through https'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -127,11 +142,7 @@
|
||||||
|
|
||||||
dispatch('close');
|
dispatch('close');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[handleEditLink]', e);
|
handleError(e, 'Failed to edit shared link');
|
||||||
notificationController.show({
|
|
||||||
type: NotificationType.Error,
|
|
||||||
message: 'Failed to edit shared link'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -162,6 +173,18 @@
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if shareType == SharedLinkType.Individual}
|
||||||
|
{#if !editingLink}
|
||||||
|
<div>Let anyone with the link see the selected photo(s)</div>
|
||||||
|
{:else}
|
||||||
|
<div class="text-sm">
|
||||||
|
Individual shared | <span class="text-immich-primary dark:text-immich-dark-primary"
|
||||||
|
>{editingLink.description}</span
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
{/if}
|
||||||
|
|
||||||
<div class="mt-6 mb-2">
|
<div class="mt-6 mb-2">
|
||||||
<p class="text-xs">LINK OPTIONS</p>
|
<p class="text-xs">LINK OPTIONS</p>
|
||||||
</div>
|
</div>
|
||||||
|
@ -215,7 +238,7 @@
|
||||||
{:else}
|
{:else}
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
<button
|
<button
|
||||||
on:click={createAlbumSharedLink}
|
on:click={handleCreateSharedLink}
|
||||||
class="text-white dark:text-black bg-immich-primary px-4 py-2 rounded-lg text-sm transition-colors hover:bg-immich-primary/75 dark:bg-immich-dark-primary dark:hover:bg-immich-dark-primary/75"
|
class="text-white dark:text-black bg-immich-primary px-4 py-2 rounded-lg text-sm transition-colors hover:bg-immich-primary/75 dark:bg-immich-dark-primary dark:hover:bg-immich-dark-primary/75"
|
||||||
>
|
>
|
||||||
Create Link
|
Create Link
|
||||||
|
|
|
@ -0,0 +1,118 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import { page } from '$app/stores';
|
||||||
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
|
import { AssetResponseDto, ThumbnailFormat } from '@api';
|
||||||
|
|
||||||
|
import AssetViewer from '../../asset-viewer/asset-viewer.svelte';
|
||||||
|
import ImmichThumbnail from '../../shared-components/immich-thumbnail.svelte';
|
||||||
|
|
||||||
|
export let assets: AssetResponseDto[];
|
||||||
|
export let key: string;
|
||||||
|
export let selectedAssets: Set<AssetResponseDto> = new Set();
|
||||||
|
|
||||||
|
let isShowAssetViewer = false;
|
||||||
|
|
||||||
|
let selectedAsset: AssetResponseDto;
|
||||||
|
let currentViewAssetIndex = 0;
|
||||||
|
|
||||||
|
let viewWidth: number;
|
||||||
|
let thumbnailSize = 300;
|
||||||
|
|
||||||
|
$: isMultiSelectionMode = selectedAssets.size > 0;
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (assets.length < 6) {
|
||||||
|
thumbnailSize = Math.floor(viewWidth / assets.length - assets.length);
|
||||||
|
} else {
|
||||||
|
if (viewWidth > 600) thumbnailSize = Math.floor(viewWidth / 6 - 6);
|
||||||
|
else if (viewWidth > 400) thumbnailSize = Math.floor(viewWidth / 4 - 6);
|
||||||
|
else if (viewWidth > 300) thumbnailSize = Math.floor(viewWidth / 2 - 6);
|
||||||
|
else if (viewWidth > 200) thumbnailSize = Math.floor(viewWidth / 2 - 6);
|
||||||
|
else if (viewWidth > 100) thumbnailSize = Math.floor(viewWidth / 1 - 6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const viewAssetHandler = (event: CustomEvent) => {
|
||||||
|
const { asset }: { asset: AssetResponseDto } = event.detail;
|
||||||
|
|
||||||
|
currentViewAssetIndex = assets.findIndex((a) => a.id == asset.id);
|
||||||
|
selectedAsset = assets[currentViewAssetIndex];
|
||||||
|
isShowAssetViewer = true;
|
||||||
|
pushState(selectedAsset.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectAssetHandler = (event: CustomEvent) => {
|
||||||
|
const { asset }: { asset: AssetResponseDto } = event.detail;
|
||||||
|
let temp = new Set(selectedAssets);
|
||||||
|
|
||||||
|
if (selectedAssets.has(asset)) {
|
||||||
|
temp.delete(asset);
|
||||||
|
} else {
|
||||||
|
temp.add(asset);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedAssets = temp;
|
||||||
|
};
|
||||||
|
|
||||||
|
const navigateAssetForward = () => {
|
||||||
|
try {
|
||||||
|
if (currentViewAssetIndex < assets.length - 1) {
|
||||||
|
currentViewAssetIndex++;
|
||||||
|
selectedAsset = assets[currentViewAssetIndex];
|
||||||
|
pushState(selectedAsset.id);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
handleError(e, 'Cannot navigate to the next asset');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const navigateAssetBackward = () => {
|
||||||
|
try {
|
||||||
|
if (currentViewAssetIndex > 0) {
|
||||||
|
currentViewAssetIndex--;
|
||||||
|
selectedAsset = assets[currentViewAssetIndex];
|
||||||
|
pushState(selectedAsset.id);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
handleError(e, 'Cannot navigate to previous asset');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const pushState = (assetId: string) => {
|
||||||
|
// add a URL to the browser's history
|
||||||
|
// changes the current URL in the address bar but doesn't perform any SvelteKit navigation
|
||||||
|
history.pushState(null, '', `${$page.url.pathname}/photos/${assetId}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeViewer = () => {
|
||||||
|
isShowAssetViewer = false;
|
||||||
|
history.pushState(null, '', `${$page.url.pathname}`);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if assets.length > 0}
|
||||||
|
<div class="flex flex-wrap gap-1 w-full pb-20" bind:clientWidth={viewWidth}>
|
||||||
|
{#each assets as asset (asset.id)}
|
||||||
|
<ImmichThumbnail
|
||||||
|
{asset}
|
||||||
|
{thumbnailSize}
|
||||||
|
publicSharedKey={key}
|
||||||
|
format={assets.length < 7 ? ThumbnailFormat.Jpeg : ThumbnailFormat.Webp}
|
||||||
|
on:click={(e) => (isMultiSelectionMode ? selectAssetHandler(e) : viewAssetHandler(e))}
|
||||||
|
on:select={selectAssetHandler}
|
||||||
|
selected={selectedAssets.has(asset)}
|
||||||
|
/>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<!-- Overlay Asset Viewer -->
|
||||||
|
{#if isShowAssetViewer}
|
||||||
|
<AssetViewer
|
||||||
|
asset={selectedAsset}
|
||||||
|
publicSharedKey={key}
|
||||||
|
on:navigate-previous={navigateAssetBackward}
|
||||||
|
on:navigate-next={navigateAssetForward}
|
||||||
|
on:close={closeViewer}
|
||||||
|
/>
|
||||||
|
{/if}
|
|
@ -90,7 +90,7 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="whitespace-pre text-sm pl-[28px] pr-[16px]" data-testid="message">
|
<p class="whitespace-pre-wrap text-sm pl-[28px] pr-[16px]" data-testid="message">
|
||||||
{@html notificationInfo.message}
|
{@html notificationInfo.message}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { addAssetsToAlbum } from '$lib/utils/asset-utils';
|
||||||
export const openFileUploadDialog = (
|
export const openFileUploadDialog = (
|
||||||
albumId: string | undefined = undefined,
|
albumId: string | undefined = undefined,
|
||||||
sharedKey: string | undefined = undefined,
|
sharedKey: string | undefined = undefined,
|
||||||
callback?: () => void
|
onDone?: (id: string) => void
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const fileSelector = document.createElement('input');
|
const fileSelector = document.createElement('input');
|
||||||
|
@ -28,8 +28,7 @@ export const openFileUploadDialog = (
|
||||||
}
|
}
|
||||||
const files = Array.from<File>(target.files);
|
const files = Array.from<File>(target.files);
|
||||||
|
|
||||||
await fileUploadHandler(files, albumId, sharedKey);
|
await fileUploadHandler(files, albumId, sharedKey, onDone);
|
||||||
callback && callback();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
fileSelector.click();
|
fileSelector.click();
|
||||||
|
@ -41,7 +40,8 @@ export const openFileUploadDialog = (
|
||||||
export const fileUploadHandler = async (
|
export const fileUploadHandler = async (
|
||||||
files: File[],
|
files: File[],
|
||||||
albumId: string | undefined = undefined,
|
albumId: string | undefined = undefined,
|
||||||
sharedKey: string | undefined = undefined
|
sharedKey: string | undefined = undefined,
|
||||||
|
onDone?: (id: string) => void
|
||||||
) => {
|
) => {
|
||||||
if (files.length > 50) {
|
if (files.length > 50) {
|
||||||
notificationController.show({
|
notificationController.show({
|
||||||
|
@ -54,13 +54,13 @@ export const fileUploadHandler = async (
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log('fileUploadHandler');
|
|
||||||
const acceptedFile = files.filter(
|
const acceptedFile = files.filter(
|
||||||
(e) => e.type.split('/')[0] === 'video' || e.type.split('/')[0] === 'image'
|
(e) => e.type.split('/')[0] === 'video' || e.type.split('/')[0] === 'image'
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const asset of acceptedFile) {
|
for (const asset of acceptedFile) {
|
||||||
await fileUploader(asset, albumId, sharedKey);
|
await fileUploader(asset, albumId, sharedKey, onDone);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -68,7 +68,8 @@ export const fileUploadHandler = async (
|
||||||
async function fileUploader(
|
async function fileUploader(
|
||||||
asset: File,
|
asset: File,
|
||||||
albumId: string | undefined = undefined,
|
albumId: string | undefined = undefined,
|
||||||
sharedKey: string | undefined = undefined
|
sharedKey: string | undefined = undefined,
|
||||||
|
onDone?: (id: string) => void
|
||||||
) {
|
) {
|
||||||
const assetType = asset.type.split('/')[0].toUpperCase();
|
const assetType = asset.type.split('/')[0].toUpperCase();
|
||||||
const temp = asset.name.split('.');
|
const temp = asset.name.split('.');
|
||||||
|
@ -135,6 +136,7 @@ async function fileUploader(
|
||||||
if (albumId && dataId) {
|
if (albumId && dataId) {
|
||||||
addAssetsToAlbum(albumId, [dataId]);
|
addAssetsToAlbum(albumId, [dataId]);
|
||||||
}
|
}
|
||||||
|
onDone && dataId && onDone(dataId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -154,10 +156,9 @@ async function fileUploader(
|
||||||
request.upload.onload = () => {
|
request.upload.onload = () => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
uploadAssetsStore.removeUploadAsset(deviceAssetId);
|
uploadAssetsStore.removeUploadAsset(deviceAssetId);
|
||||||
|
const res: AssetFileUploadResponseDto = JSON.parse(request.response || '{}');
|
||||||
if (albumId) {
|
if (albumId) {
|
||||||
try {
|
try {
|
||||||
const res: AssetFileUploadResponseDto = JSON.parse(request.response || '{}');
|
|
||||||
if (res.id) {
|
if (res.id) {
|
||||||
addAssetsToAlbum(albumId, [res.id], sharedKey);
|
addAssetsToAlbum(albumId, [res.id], sharedKey);
|
||||||
}
|
}
|
||||||
|
@ -165,6 +166,7 @@ async function fileUploader(
|
||||||
console.error('ERROR parsing data JSON in upload onload');
|
console.error('ERROR parsing data JSON in upload onload');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
onDone && onDone(res.id);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,8 @@
|
||||||
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
|
import MenuOption from '$lib/components/shared-components/context-menu/menu-option.svelte';
|
||||||
import AlbumSelectionModal from '$lib/components/shared-components/album-selection-modal.svelte';
|
import AlbumSelectionModal from '$lib/components/shared-components/album-selection-modal.svelte';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
|
import ShareVariantOutline from 'svelte-material-icons/ShareVariantOutline.svelte';
|
||||||
import { openFileUploadDialog } from '$lib/utils/file-uploader';
|
import { openFileUploadDialog } from '$lib/utils/file-uploader';
|
||||||
import {
|
import {
|
||||||
assetInteractionStore,
|
assetInteractionStore,
|
||||||
|
@ -21,16 +20,17 @@
|
||||||
import CircleIconButton from '$lib/components/shared-components/circle-icon-button.svelte';
|
import CircleIconButton from '$lib/components/shared-components/circle-icon-button.svelte';
|
||||||
import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte';
|
import DeleteOutline from 'svelte-material-icons/DeleteOutline.svelte';
|
||||||
import Plus from 'svelte-material-icons/Plus.svelte';
|
import Plus from 'svelte-material-icons/Plus.svelte';
|
||||||
import { AlbumResponseDto, api } from '@api';
|
import { AlbumResponseDto, api, SharedLinkType } from '@api';
|
||||||
import {
|
import {
|
||||||
notificationController,
|
notificationController,
|
||||||
NotificationType
|
NotificationType
|
||||||
} from '$lib/components/shared-components/notification/notification';
|
} from '$lib/components/shared-components/notification/notification';
|
||||||
import { assetStore } from '$lib/stores/assets.store';
|
import { assetStore } from '$lib/stores/assets.store';
|
||||||
import { addAssetsToAlbum, bulkDownload } from '$lib/utils/asset-utils';
|
import { addAssetsToAlbum, bulkDownload } from '$lib/utils/asset-utils';
|
||||||
|
import CreateSharedLinkModal from '$lib/components/shared-components/create-share-link-modal/create-shared-link-modal.svelte';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
let isShowCreateSharedLinkModal = false;
|
||||||
const deleteSelectedAssetHandler = async () => {
|
const deleteSelectedAssetHandler = async () => {
|
||||||
try {
|
try {
|
||||||
if (
|
if (
|
||||||
|
@ -114,6 +114,15 @@
|
||||||
assetInteractionStore.clearMultiselect();
|
assetInteractionStore.clearMultiselect();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCreateSharedLink = async () => {
|
||||||
|
isShowCreateSharedLinkModal = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCloseSharedLinkModal = () => {
|
||||||
|
assetInteractionStore.clearMultiselect();
|
||||||
|
isShowCreateSharedLinkModal = false;
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
|
@ -129,6 +138,11 @@
|
||||||
</p>
|
</p>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
<svelte:fragment slot="trailing">
|
<svelte:fragment slot="trailing">
|
||||||
|
<CircleIconButton
|
||||||
|
title="Share"
|
||||||
|
logo={ShareVariantOutline}
|
||||||
|
on:click={handleCreateSharedLink}
|
||||||
|
/>
|
||||||
<CircleIconButton
|
<CircleIconButton
|
||||||
title="Download"
|
title="Download"
|
||||||
logo={CloudDownloadOutline}
|
logo={CloudDownloadOutline}
|
||||||
|
@ -164,6 +178,14 @@
|
||||||
on:close={() => (isShowAlbumPicker = false)}
|
on:close={() => (isShowAlbumPicker = false)}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if isShowCreateSharedLinkModal}
|
||||||
|
<CreateSharedLinkModal
|
||||||
|
sharedAssets={Array.from($selectedAssets)}
|
||||||
|
shareType={SharedLinkType.Individual}
|
||||||
|
on:close={handleCloseSharedLinkModal}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section
|
<section
|
||||||
|
|
|
@ -5,7 +5,9 @@ import { getThumbnailUrl } from '$lib/utils/asset-utils';
|
||||||
import { serverApi, ThumbnailFormat } from '@api';
|
import { serverApi, ThumbnailFormat } from '@api';
|
||||||
import type { PageServerLoad } from './$types';
|
import type { PageServerLoad } from './$types';
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ params }) => {
|
export const load: PageServerLoad = async ({ params, parent }) => {
|
||||||
|
const { user } = await parent();
|
||||||
|
|
||||||
const { key } = params;
|
const { key } = params;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -22,7 +24,8 @@ export const load: PageServerLoad = async ({ params }) => {
|
||||||
imageUrl: assetId
|
imageUrl: assetId
|
||||||
? getThumbnailUrl(assetId, ThumbnailFormat.Webp, sharedLink.key)
|
? getThumbnailUrl(assetId, ThumbnailFormat.Webp, sharedLink.key)
|
||||||
: 'feature-panel.png'
|
: 'feature-panel.png'
|
||||||
}
|
},
|
||||||
|
user
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw error(404, {
|
throw error(404, {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import AlbumViewer from '$lib/components/album-page/album-viewer.svelte';
|
import AlbumViewer from '$lib/components/album-page/album-viewer.svelte';
|
||||||
import { AlbumResponseDto } from '@api';
|
import IndividualSharedViewer from '$lib/components/share-page/individual-shared-viewer.svelte';
|
||||||
|
import { AlbumResponseDto, SharedLinkType } from '@api';
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
@ -8,13 +9,20 @@
|
||||||
const { sharedLink } = data;
|
const { sharedLink } = data;
|
||||||
|
|
||||||
let album: AlbumResponseDto | null = null;
|
let album: AlbumResponseDto | null = null;
|
||||||
|
let isOwned = data.user ? data.user.id === sharedLink.userId : false;
|
||||||
if (sharedLink.album) {
|
if (sharedLink.album) {
|
||||||
album = { ...sharedLink.album, assets: sharedLink.assets };
|
album = { ...sharedLink.album, assets: sharedLink.assets };
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if album}
|
{#if sharedLink.type == SharedLinkType.Album && album}
|
||||||
<div class="immich-scrollbar">
|
<div class="immich-scrollbar">
|
||||||
<AlbumViewer {album} {sharedLink} />
|
<AlbumViewer {album} {sharedLink} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if sharedLink.type == SharedLinkType.Individual}
|
||||||
|
<div class="immich-scrollbar">
|
||||||
|
<IndividualSharedViewer {sharedLink} {isOwned} />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
Loading…
Reference in a new issue