Compare commits
8 Commits
v1.84.0
...
dev/better
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b49d9660f6 | ||
|
|
b58edae134 | ||
|
|
2b9f20a1b5 | ||
|
|
d5f8199655 | ||
|
|
d8903de92e | ||
|
|
1d35965d03 | ||
|
|
309bf1ad22 | ||
|
|
0130591a0f |
23
cli/src/api/open-api/api.ts
generated
23
cli/src/api/open-api/api.ts
generated
@@ -5076,10 +5076,11 @@ export const ActivityApiAxiosParamCreator = function (configuration?: Configurat
|
|||||||
* @param {string} albumId
|
* @param {string} albumId
|
||||||
* @param {string} [assetId]
|
* @param {string} [assetId]
|
||||||
* @param {ReactionType} [type]
|
* @param {ReactionType} [type]
|
||||||
|
* @param {string} [userId]
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
getActivities: async (albumId: string, assetId?: string, type?: ReactionType, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
getActivities: async (albumId: string, assetId?: string, type?: ReactionType, userId?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
// verify required parameter 'albumId' is not null or undefined
|
// verify required parameter 'albumId' is not null or undefined
|
||||||
assertParamExists('getActivities', 'albumId', albumId)
|
assertParamExists('getActivities', 'albumId', albumId)
|
||||||
const localVarPath = `/activity`;
|
const localVarPath = `/activity`;
|
||||||
@@ -5115,6 +5116,10 @@ export const ActivityApiAxiosParamCreator = function (configuration?: Configurat
|
|||||||
localVarQueryParameter['type'] = type;
|
localVarQueryParameter['type'] = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (userId !== undefined) {
|
||||||
|
localVarQueryParameter['userId'] = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
@@ -5211,11 +5216,12 @@ export const ActivityApiFp = function(configuration?: Configuration) {
|
|||||||
* @param {string} albumId
|
* @param {string} albumId
|
||||||
* @param {string} [assetId]
|
* @param {string} [assetId]
|
||||||
* @param {ReactionType} [type]
|
* @param {ReactionType} [type]
|
||||||
|
* @param {string} [userId]
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
async getActivities(albumId: string, assetId?: string, type?: ReactionType, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<ActivityResponseDto>>> {
|
async getActivities(albumId: string, assetId?: string, type?: ReactionType, userId?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<ActivityResponseDto>>> {
|
||||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getActivities(albumId, assetId, type, options);
|
const localVarAxiosArgs = await localVarAxiosParamCreator.getActivities(albumId, assetId, type, userId, options);
|
||||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
@@ -5264,7 +5270,7 @@ export const ActivityApiFactory = function (configuration?: Configuration, baseP
|
|||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
getActivities(requestParameters: ActivityApiGetActivitiesRequest, options?: AxiosRequestConfig): AxiosPromise<Array<ActivityResponseDto>> {
|
getActivities(requestParameters: ActivityApiGetActivitiesRequest, options?: AxiosRequestConfig): AxiosPromise<Array<ActivityResponseDto>> {
|
||||||
return localVarFp.getActivities(requestParameters.albumId, requestParameters.assetId, requestParameters.type, options).then((request) => request(axios, basePath));
|
return localVarFp.getActivities(requestParameters.albumId, requestParameters.assetId, requestParameters.type, requestParameters.userId, options).then((request) => request(axios, basePath));
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -5332,6 +5338,13 @@ export interface ActivityApiGetActivitiesRequest {
|
|||||||
* @memberof ActivityApiGetActivities
|
* @memberof ActivityApiGetActivities
|
||||||
*/
|
*/
|
||||||
readonly type?: ReactionType
|
readonly type?: ReactionType
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof ActivityApiGetActivities
|
||||||
|
*/
|
||||||
|
readonly userId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -5392,7 +5405,7 @@ export class ActivityApi extends BaseAPI {
|
|||||||
* @memberof ActivityApi
|
* @memberof ActivityApi
|
||||||
*/
|
*/
|
||||||
public getActivities(requestParameters: ActivityApiGetActivitiesRequest, options?: AxiosRequestConfig) {
|
public getActivities(requestParameters: ActivityApiGetActivitiesRequest, options?: AxiosRequestConfig) {
|
||||||
return ActivityApiFp(this.configuration).getActivities(requestParameters.albumId, requestParameters.assetId, requestParameters.type, options).then((request) => request(this.axios, this.basePath));
|
return ActivityApiFp(this.configuration).getActivities(requestParameters.albumId, requestParameters.assetId, requestParameters.type, requestParameters.userId, options).then((request) => request(this.axios, this.basePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -33,8 +33,6 @@ To be concise, Immich can now read in the gallery files, register the path into
|
|||||||
- Only new files that are added to the gallery will be detected.
|
- Only new files that are added to the gallery will be detected.
|
||||||
- Deleted and moved files will not be detected.
|
- Deleted and moved files will not be detected.
|
||||||
|
|
||||||
You can find more information on how to use the feature by reading the documentation [here](/docs/features/read-only-gallery).
|
|
||||||
|
|
||||||
## Memory feature
|
## Memory feature
|
||||||
|
|
||||||
This is considered a fun feature that the team and I wanted to build for so long, but we had to put it off because of the refactoring of the code base. The code base is now in a good enough form to circle back and add more exciting features.
|
This is considered a fun feature that the team and I wanted to build for so long, but we had to put it off because of the refactoring of the code base. The code base is now in a good enough form to circle back and add more exciting features.
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ immich
|
|||||||
| --server / -s | Immich's server address |
|
| --server / -s | Immich's server address |
|
||||||
| --threads / -t | Number of threads to use (Default 5) |
|
| --threads / -t | Number of threads to use (Default 5) |
|
||||||
| --album/ -al | Create albums for assets based on the parent folder or a given name |
|
| --album/ -al | Create albums for assets based on the parent folder or a given name |
|
||||||
| --import/ -i | Import gallery (assets are not uploaded) |
|
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
@@ -108,70 +107,3 @@ npm run build
|
|||||||
```bash title="Run the command"
|
```bash title="Run the command"
|
||||||
node bin/index.js upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api --recursive your/asset/directory
|
node bin/index.js upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api --recursive your/asset/directory
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Importing existing libraries
|
|
||||||
|
|
||||||
If you do not wish to upload files into the server, existing files can be imported into the immich gallery through the use of the `--import` flag.
|
|
||||||
|
|
||||||
```
|
|
||||||
immich upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api --recursive directory/ --import
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
immich upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api file1.jpg file2.jpg --import
|
|
||||||
```
|
|
||||||
|
|
||||||
The `immich-server` and `immich-microservices` containers must be able to access the files, or directories at the path referenced in the command. The directories referenced must be set under a user's `External Path` setting. More detailed instructions can be found [here](/docs/features/read-only-gallery).
|
|
||||||
|
|
||||||
:::tip Matching volume references
|
|
||||||
The import command is most easily run on the machine running the immich service, as the path to the files on the machine running the command and the server much match identically.
|
|
||||||
|
|
||||||
If you are running immich within docker, the volume pointing to your existing library should be identical with your host machine.
|
|
||||||
|
|
||||||
```diff title="docker-compose.yml"
|
|
||||||
immich-server:
|
|
||||||
container_name: immich_server
|
|
||||||
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
|
|
||||||
command: [ "start.sh", "immich" ]
|
|
||||||
volumes:
|
|
||||||
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
|
||||||
+ - /path/to/media:/path/to/media
|
|
||||||
env_file:
|
|
||||||
- .env
|
|
||||||
depends_on:
|
|
||||||
- redis
|
|
||||||
- database
|
|
||||||
- typesense
|
|
||||||
restart: always
|
|
||||||
|
|
||||||
immich-microservices:
|
|
||||||
container_name: immich_microservices
|
|
||||||
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
|
|
||||||
command: [ "start.sh", "microservices" ]
|
|
||||||
volumes:
|
|
||||||
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
|
||||||
+ - /path/to/media:/path/to/media
|
|
||||||
env_file:
|
|
||||||
- .env
|
|
||||||
depends_on:
|
|
||||||
- redis
|
|
||||||
- database
|
|
||||||
- typesense
|
|
||||||
restart: always
|
|
||||||
```
|
|
||||||
|
|
||||||
The proper command for above would be as shown below. You should have access to `/path/to/media` exactly on the environment the CLI command is being run on
|
|
||||||
|
|
||||||
```
|
|
||||||
immich upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api --recursive /path/to/media --import
|
|
||||||
```
|
|
||||||
|
|
||||||
If you are running the import using the docker command, please note that the volumes should point to the `/path/to/media` exactly on the environment the CLI command is being run on
|
|
||||||
|
|
||||||
```
|
|
||||||
docker run -it --rm -v "/path/to/media:/path/to/media" ghcr.io/immich-app/immich-cli:latest upload --key HFEJ38DNSDUEG --server http://192.168.1.216:2283/api --recursive /path/to/media --import
|
|
||||||
```
|
|
||||||
|
|
||||||
:::
|
|
||||||
|
|||||||
@@ -1,97 +0,0 @@
|
|||||||
# Read-only Gallery [Deprecated]
|
|
||||||
|
|
||||||
:::caution
|
|
||||||
|
|
||||||
This feature is being deprecated in favor of [Libraries](/docs/features/libraries.md).
|
|
||||||
|
|
||||||
:::
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
This feature enables users to use an existing gallery without uploading the assets to Immich.
|
|
||||||
|
|
||||||
Upon syncing the file information, it will be read by Immich to generate supported files.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
:::tip Example scenario
|
|
||||||
|
|
||||||
On the VM/system that Immich is running, I have 2 galleries that I want to use with Immich.
|
|
||||||
|
|
||||||
- My gallery is stored at `/mnt/media/precious-memory`
|
|
||||||
- My wife's gallery is stored at `/mnt/media/childhood-memory`
|
|
||||||
|
|
||||||
We will use those values in the steps below.
|
|
||||||
|
|
||||||
:::
|
|
||||||
|
|
||||||
### Mount the gallery to the containers.
|
|
||||||
|
|
||||||
`immich-server` and `immich-microservices` containers will need access to the gallery. Mount the directory path as in the example below
|
|
||||||
|
|
||||||
```diff title="docker-compose.yml"
|
|
||||||
immich-server:
|
|
||||||
container_name: immich_server
|
|
||||||
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
|
|
||||||
command: [ "start.sh", "immich" ]
|
|
||||||
volumes:
|
|
||||||
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
|
||||||
+ - /mnt/media/precious-memory:/mnt/media/precious-memory:ro
|
|
||||||
+ - /mnt/media/childhood-memory:/mnt/media/childhood-memory:ro
|
|
||||||
env_file:
|
|
||||||
- .env
|
|
||||||
depends_on:
|
|
||||||
- redis
|
|
||||||
- database
|
|
||||||
- typesense
|
|
||||||
restart: always
|
|
||||||
|
|
||||||
immich-microservices:
|
|
||||||
container_name: immich_microservices
|
|
||||||
image: ghcr.io/immich-app/immich-server:${IMMICH_VERSION:-release}
|
|
||||||
command: [ "start.sh", "microservices" ]
|
|
||||||
volumes:
|
|
||||||
- ${UPLOAD_LOCATION}:/usr/src/app/upload
|
|
||||||
+ - /mnt/media/precious-memory:/mnt/media/precious-memory:ro
|
|
||||||
+ - /mnt/media/childhood-memory:/mnt/media/childhood-memory:ro
|
|
||||||
env_file:
|
|
||||||
- .env
|
|
||||||
depends_on:
|
|
||||||
- redis
|
|
||||||
- database
|
|
||||||
- typesense
|
|
||||||
restart: always
|
|
||||||
```
|
|
||||||
|
|
||||||
:::tip
|
|
||||||
Internal and external path have to be identical.
|
|
||||||
:::
|
|
||||||
|
|
||||||
_Remember to bring the container down/up to register the changes. Make sure you can see the mounted path in the container._
|
|
||||||
|
|
||||||
### Register the path for the user.
|
|
||||||
|
|
||||||
This action is done by the admin of the instance.
|
|
||||||
|
|
||||||
- Navigate to `Administration > Users` page on the web.
|
|
||||||
- Click on the user edit button.
|
|
||||||
- Add the gallery path to the `External Path` field for the corresponding user and confirm the changes.
|
|
||||||
|
|
||||||
<img src={require('./img/me.png').default} width='33%' title='My Account Storage Path' />
|
|
||||||
|
|
||||||
<img src={require('./img/my-wife.png').default} width='33%' title='My Wifes Account Storage Path' />
|
|
||||||
|
|
||||||
### Sync with the CLI tool.
|
|
||||||
|
|
||||||
- Install or update the [CLI Tool](/docs/features/bulk-upload.md). The import feature is supported from version `v0.39.0` of the CLI
|
|
||||||
- Run the command below to sync the gallery with Immich.
|
|
||||||
|
|
||||||
```bash title="Import my gallery"
|
|
||||||
immich upload --key <my-api-key> --server http://my-server-ip:2283/api /mnt/media/precious-memory --recursive --import
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash title="Import my wife gallery"
|
|
||||||
immich upload --key <my-wife-api-key> --server http://my-server-ip:2283/api /mnt/media/childhood-memory --recursive --import
|
|
||||||
```
|
|
||||||
|
|
||||||
The `--import` flag will tell Immich to import the files by path instead of uploading them.
|
|
||||||
@@ -5,17 +5,17 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000269">
|
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000625">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="81.160108">
|
<testcase classname="fastlane.lanes" name="1: bundleRelease" time="70.943413">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="39.176668">
|
<testcase classname="fastlane.lanes" name="2: upload_to_play_store" time="30.374484">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,12 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
- isar_flutter_libs (1.0.0):
|
- isar_flutter_libs (1.0.0):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- media_kit_libs_ios_video (1.0.4):
|
||||||
|
- Flutter
|
||||||
|
- media_kit_native_event_loop (1.0.0):
|
||||||
|
- Flutter
|
||||||
|
- media_kit_video (0.0.1):
|
||||||
|
- Flutter
|
||||||
- package_info_plus (0.4.5):
|
- package_info_plus (0.4.5):
|
||||||
- Flutter
|
- Flutter
|
||||||
- path_provider_foundation (0.0.1):
|
- path_provider_foundation (0.0.1):
|
||||||
@@ -42,6 +48,8 @@ PODS:
|
|||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- ReachabilitySwift (5.0.0)
|
- ReachabilitySwift (5.0.0)
|
||||||
- SAMKeychain (1.5.3)
|
- SAMKeychain (1.5.3)
|
||||||
|
- screen_brightness_ios (0.1.0):
|
||||||
|
- Flutter
|
||||||
- share_plus (0.0.1):
|
- share_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- shared_preferences_foundation (0.0.1):
|
- shared_preferences_foundation (0.0.1):
|
||||||
@@ -55,6 +63,8 @@ PODS:
|
|||||||
- Flutter
|
- Flutter
|
||||||
- video_player_avfoundation (0.0.1):
|
- video_player_avfoundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
- volume_controller (0.0.1):
|
||||||
|
- Flutter
|
||||||
- wakelock_plus (0.0.1):
|
- wakelock_plus (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
|
|
||||||
@@ -71,16 +81,21 @@ DEPENDENCIES:
|
|||||||
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
|
||||||
- integration_test (from `.symlinks/plugins/integration_test/ios`)
|
- integration_test (from `.symlinks/plugins/integration_test/ios`)
|
||||||
- isar_flutter_libs (from `.symlinks/plugins/isar_flutter_libs/ios`)
|
- isar_flutter_libs (from `.symlinks/plugins/isar_flutter_libs/ios`)
|
||||||
|
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
|
||||||
|
- media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`)
|
||||||
|
- media_kit_video (from `.symlinks/plugins/media_kit_video/ios`)
|
||||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||||
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
|
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
|
||||||
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
|
||||||
- photo_manager (from `.symlinks/plugins/photo_manager/ios`)
|
- photo_manager (from `.symlinks/plugins/photo_manager/ios`)
|
||||||
|
- screen_brightness_ios (from `.symlinks/plugins/screen_brightness_ios/ios`)
|
||||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||||
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||||
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
- sqflite (from `.symlinks/plugins/sqflite/ios`)
|
||||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||||
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/ios`)
|
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/ios`)
|
||||||
|
- volume_controller (from `.symlinks/plugins/volume_controller/ios`)
|
||||||
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
- wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`)
|
||||||
|
|
||||||
SPEC REPOS:
|
SPEC REPOS:
|
||||||
@@ -115,6 +130,12 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/integration_test/ios"
|
:path: ".symlinks/plugins/integration_test/ios"
|
||||||
isar_flutter_libs:
|
isar_flutter_libs:
|
||||||
:path: ".symlinks/plugins/isar_flutter_libs/ios"
|
:path: ".symlinks/plugins/isar_flutter_libs/ios"
|
||||||
|
media_kit_libs_ios_video:
|
||||||
|
:path: ".symlinks/plugins/media_kit_libs_ios_video/ios"
|
||||||
|
media_kit_native_event_loop:
|
||||||
|
:path: ".symlinks/plugins/media_kit_native_event_loop/ios"
|
||||||
|
media_kit_video:
|
||||||
|
:path: ".symlinks/plugins/media_kit_video/ios"
|
||||||
package_info_plus:
|
package_info_plus:
|
||||||
:path: ".symlinks/plugins/package_info_plus/ios"
|
:path: ".symlinks/plugins/package_info_plus/ios"
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
@@ -125,6 +146,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
:path: ".symlinks/plugins/permission_handler_apple/ios"
|
||||||
photo_manager:
|
photo_manager:
|
||||||
:path: ".symlinks/plugins/photo_manager/ios"
|
:path: ".symlinks/plugins/photo_manager/ios"
|
||||||
|
screen_brightness_ios:
|
||||||
|
:path: ".symlinks/plugins/screen_brightness_ios/ios"
|
||||||
share_plus:
|
share_plus:
|
||||||
:path: ".symlinks/plugins/share_plus/ios"
|
:path: ".symlinks/plugins/share_plus/ios"
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
@@ -135,6 +158,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||||
video_player_avfoundation:
|
video_player_avfoundation:
|
||||||
:path: ".symlinks/plugins/video_player_avfoundation/ios"
|
:path: ".symlinks/plugins/video_player_avfoundation/ios"
|
||||||
|
volume_controller:
|
||||||
|
:path: ".symlinks/plugins/volume_controller/ios"
|
||||||
wakelock_plus:
|
wakelock_plus:
|
||||||
:path: ".symlinks/plugins/wakelock_plus/ios"
|
:path: ".symlinks/plugins/wakelock_plus/ios"
|
||||||
|
|
||||||
@@ -152,6 +177,9 @@ SPEC CHECKSUMS:
|
|||||||
image_picker_ios: 4a8aadfbb6dc30ad5141a2ce3832af9214a705b5
|
image_picker_ios: 4a8aadfbb6dc30ad5141a2ce3832af9214a705b5
|
||||||
integration_test: 13825b8a9334a850581300559b8839134b124670
|
integration_test: 13825b8a9334a850581300559b8839134b124670
|
||||||
isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073
|
isar_flutter_libs: b69f437aeab9c521821c3f376198c4371fa21073
|
||||||
|
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
|
||||||
|
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
|
||||||
|
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
|
||||||
package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7
|
package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7
|
||||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
||||||
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
|
path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
|
||||||
@@ -159,14 +187,16 @@ SPEC CHECKSUMS:
|
|||||||
photo_manager: 4f6810b7dfc4feb03b461ac1a70dacf91fba7604
|
photo_manager: 4f6810b7dfc4feb03b461ac1a70dacf91fba7604
|
||||||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||||
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
|
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
|
||||||
|
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
|
||||||
share_plus: 599aa54e4ea31d4b4c0e9c911bcc26c55e791028
|
share_plus: 599aa54e4ea31d4b4c0e9c911bcc26c55e791028
|
||||||
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
|
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
|
||||||
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
|
sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a
|
||||||
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
|
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
|
||||||
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
|
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
|
||||||
video_player_avfoundation: 81e49bb3d9fb63dccf9fa0f6d877dc3ddbeac126
|
video_player_avfoundation: 81e49bb3d9fb63dccf9fa0f6d877dc3ddbeac126
|
||||||
|
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
|
||||||
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
|
wakelock_plus: 8b09852c8876491e4b6d179e17dfe2a0b5f60d47
|
||||||
|
|
||||||
PODFILE CHECKSUM: 599d8aeb73728400c15364e734525722250a5382
|
PODFILE CHECKSUM: 599d8aeb73728400c15364e734525722250a5382
|
||||||
|
|
||||||
COCOAPODS: 1.12.1
|
COCOAPODS: 1.11.3
|
||||||
|
|||||||
@@ -379,7 +379,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 118;
|
CURRENT_PROJECT_VERSION = 124;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
@@ -515,7 +515,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 118;
|
CURRENT_PROJECT_VERSION = 124;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
@@ -543,7 +543,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 118;
|
CURRENT_PROJECT_VERSION = 124;
|
||||||
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
DEVELOPMENT_TEAM = 2F67MQ8R79;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
|||||||
@@ -59,11 +59,11 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>1.78.1</string>
|
<string>1.84.0</string>
|
||||||
<key>CFBundleSignature</key>
|
<key>CFBundleSignature</key>
|
||||||
<string>????</string>
|
<string>????</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>118</string>
|
<string>124</string>
|
||||||
<key>FLTEnableImpeller</key>
|
<key>FLTEnableImpeller</key>
|
||||||
<true />
|
<true />
|
||||||
<key>ITSAppUsesNonExemptEncryption</key>
|
<key>ITSAppUsesNonExemptEncryption</key>
|
||||||
|
|||||||
@@ -5,32 +5,32 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000256">
|
<testcase classname="fastlane.lanes" name="0: default_platform" time="0.000253">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="7.645306">
|
<testcase classname="fastlane.lanes" name="1: increment_version_number" time="0.181977">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="4.669798">
|
<testcase classname="fastlane.lanes" name="2: latest_testflight_build_number" time="16.12614">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="2.218788">
|
<testcase classname="fastlane.lanes" name="3: increment_build_number" time="0.162663">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="4: build_app" time="97.596654">
|
<testcase classname="fastlane.lanes" name="4: build_app" time="145.399278">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|
||||||
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="89.490906">
|
<testcase classname="fastlane.lanes" name="5: upload_to_testflight" time="61.317235">
|
||||||
|
|
||||||
</testcase>
|
</testcase>
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import 'package:immich_mobile/utils/immich_app_theme.dart';
|
|||||||
import 'package:immich_mobile/utils/migration.dart';
|
import 'package:immich_mobile/utils/migration.dart';
|
||||||
import 'package:isar/isar.dart';
|
import 'package:isar/isar.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
import 'package:media_kit/media_kit.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
@@ -49,6 +50,7 @@ void main() async {
|
|||||||
|
|
||||||
Future<void> initApp() async {
|
Future<void> initApp() async {
|
||||||
await EasyLocalization.ensureInitialized();
|
await EasyLocalization.ensureInitialized();
|
||||||
|
MediaKit.ensureInitialized();
|
||||||
|
|
||||||
if (kReleaseMode && Platform.isAndroid) {
|
if (kReleaseMode && Platform.isAndroid) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -791,11 +791,6 @@ class GalleryViewerPage extends HookConsumerWidget {
|
|||||||
localPosition = details.localPosition,
|
localPosition = details.localPosition,
|
||||||
onDragUpdate: (_, details, __) =>
|
onDragUpdate: (_, details, __) =>
|
||||||
handleSwipeUpDown(details),
|
handleSwipeUpDown(details),
|
||||||
heroAttributes: PhotoViewHeroAttributes(
|
|
||||||
tag: isFromDto
|
|
||||||
? '${a.remoteId}-$heroOffset'
|
|
||||||
: a.id + heroOffset,
|
|
||||||
),
|
|
||||||
filterQuality: FilterQuality.high,
|
filterQuality: FilterQuality.high,
|
||||||
maxScale: 1.0,
|
maxScale: 1.0,
|
||||||
minScale: 1.0,
|
minScale: 1.0,
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import 'package:immich_mobile/modules/asset_viewer/ui/video_player_controls.dart
|
|||||||
import 'package:immich_mobile/shared/models/asset.dart';
|
import 'package:immich_mobile/shared/models/asset.dart';
|
||||||
import 'package:immich_mobile/shared/models/store.dart';
|
import 'package:immich_mobile/shared/models/store.dart';
|
||||||
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
|
import 'package:immich_mobile/shared/ui/immich_loading_indicator.dart';
|
||||||
|
import 'package:media_kit/media_kit.dart';
|
||||||
|
import 'package:media_kit_video/media_kit_video.dart';
|
||||||
import 'package:photo_manager/photo_manager.dart';
|
import 'package:photo_manager/photo_manager.dart';
|
||||||
import 'package:video_player/video_player.dart';
|
import 'package:video_player/video_player.dart';
|
||||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||||
@@ -125,99 +127,128 @@ class VideoPlayer extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _VideoPlayerState extends State<VideoPlayer> {
|
class _VideoPlayerState extends State<VideoPlayer> {
|
||||||
late VideoPlayerController videoPlayerController;
|
// late VideoPlayerController videoPlayerController;
|
||||||
ChewieController? chewieController;
|
// ChewieController? chewieController;
|
||||||
|
|
||||||
|
// Create a [Player] to control playback.
|
||||||
|
late final player = Player();
|
||||||
|
// Create a [VideoController] to handle video output from [Player].
|
||||||
|
late final controller = VideoController(player);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
initializePlayer();
|
// initializePlayer();
|
||||||
|
|
||||||
videoPlayerController.addListener(() {
|
// videoPlayerController.addListener(() {
|
||||||
if (videoPlayerController.value.isInitialized) {
|
// if (videoPlayerController.value.isInitialized) {
|
||||||
if (videoPlayerController.value.isPlaying) {
|
// if (videoPlayerController.value.isPlaying) {
|
||||||
WakelockPlus.enable();
|
// WakelockPlus.enable();
|
||||||
widget.onPlaying?.call();
|
// widget.onPlaying?.call();
|
||||||
} else if (!videoPlayerController.value.isPlaying) {
|
// } else if (!videoPlayerController.value.isPlaying) {
|
||||||
WakelockPlus.disable();
|
// WakelockPlus.disable();
|
||||||
widget.onPaused?.call();
|
// widget.onPaused?.call();
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (videoPlayerController.value.position ==
|
// if (videoPlayerController.value.position ==
|
||||||
videoPlayerController.value.duration) {
|
// videoPlayerController.value.duration) {
|
||||||
WakelockPlus.disable();
|
// WakelockPlus.disable();
|
||||||
widget.onVideoEnded();
|
// widget.onVideoEnded();
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> initializePlayer() async {
|
if (widget.file == null) {
|
||||||
try {
|
player.open(
|
||||||
videoPlayerController = widget.file == null
|
Media(
|
||||||
? VideoPlayerController.networkUrl(
|
Uri.parse(widget.url!).toString(),
|
||||||
Uri.parse(widget.url!),
|
httpHeaders: {
|
||||||
httpHeaders: {"Authorization": "Bearer ${widget.jwtToken}"},
|
"Authorization": "Bearer ${widget.jwtToken}",
|
||||||
)
|
},
|
||||||
: VideoPlayerController.file(widget.file!);
|
),
|
||||||
|
);
|
||||||
await videoPlayerController.initialize();
|
} else {
|
||||||
_createChewieController();
|
player.open(
|
||||||
setState(() {});
|
Media(
|
||||||
} catch (e) {
|
widget.file!.path,
|
||||||
debugPrint("ERROR initialize video player $e");
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_createChewieController() {
|
// Future<void> initializePlayer() async {
|
||||||
chewieController = ChewieController(
|
// try {
|
||||||
controlsSafeAreaMinimum: const EdgeInsets.only(
|
// videoPlayerController = widget.file == null
|
||||||
bottom: 100,
|
// ? VideoPlayerController.networkUrl(
|
||||||
),
|
// Uri.parse(widget.url!),
|
||||||
showOptions: true,
|
// httpHeaders: {"Authorization": "Bearer ${widget.jwtToken}"},
|
||||||
showControlsOnInitialize: false,
|
// )
|
||||||
videoPlayerController: videoPlayerController,
|
// : VideoPlayerController.file(widget.file!);
|
||||||
autoPlay: true,
|
|
||||||
autoInitialize: true,
|
// await videoPlayerController.initialize();
|
||||||
allowFullScreen: false,
|
// _createChewieController();
|
||||||
allowedScreenSleep: false,
|
// setState(() {});
|
||||||
showControls: !widget.isMotionVideo,
|
// } catch (e) {
|
||||||
customControls: const VideoPlayerControls(),
|
// debugPrint("ERROR initialize video player $e");
|
||||||
hideControlsTimer: const Duration(seconds: 5),
|
// }
|
||||||
);
|
// }
|
||||||
}
|
|
||||||
|
// _createChewieController() {
|
||||||
|
// chewieController = ChewieController(
|
||||||
|
// controlsSafeAreaMinimum: const EdgeInsets.only(
|
||||||
|
// bottom: 100,
|
||||||
|
// ),
|
||||||
|
// showOptions: true,
|
||||||
|
// showControlsOnInitialize: false,
|
||||||
|
// videoPlayerController: videoPlayerController,
|
||||||
|
// autoPlay: true,
|
||||||
|
// autoInitialize: true,
|
||||||
|
// allowFullScreen: false,
|
||||||
|
// allowedScreenSleep: false,
|
||||||
|
// showControls: !widget.isMotionVideo,
|
||||||
|
// customControls: const VideoPlayerControls(),
|
||||||
|
// hideControlsTimer: const Duration(seconds: 5),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
videoPlayerController.pause();
|
player.dispose();
|
||||||
videoPlayerController.dispose();
|
// videoPlayerController.pause();
|
||||||
chewieController?.dispose();
|
// videoPlayerController.dispose();
|
||||||
|
// chewieController?.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (chewieController?.videoPlayerController.value.isInitialized == true) {
|
return SizedBox(
|
||||||
return SizedBox(
|
width: MediaQuery.of(context).size.width,
|
||||||
child: Chewie(
|
height: MediaQuery.of(context).size.height,
|
||||||
controller: chewieController!,
|
child: Video(controller: controller),
|
||||||
),
|
);
|
||||||
);
|
|
||||||
} else {
|
// if (chewieController?.videoPlayerController.value.isInitialized == true) {
|
||||||
return SizedBox(
|
// return SizedBox(
|
||||||
height: MediaQuery.of(context).size.height,
|
// child: Chewie(
|
||||||
width: MediaQuery.of(context).size.width,
|
// controller: chewieController!,
|
||||||
child: Center(
|
// ),
|
||||||
child: Stack(
|
// );
|
||||||
children: [
|
// } else {
|
||||||
if (widget.placeholder != null) widget.placeholder!,
|
// return SizedBox(
|
||||||
const Center(
|
// height: MediaQuery.of(context).size.height,
|
||||||
child: ImmichLoadingIndicator(),
|
// width: MediaQuery.of(context).size.width,
|
||||||
),
|
// child: Center(
|
||||||
],
|
// child: Stack(
|
||||||
),
|
// children: [
|
||||||
),
|
// if (widget.placeholder != null) widget.placeholder!,
|
||||||
);
|
// const Center(
|
||||||
}
|
// child: ImmichLoadingIndicator(),
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
6
mobile/openapi/doc/ActivityApi.md
generated
6
mobile/openapi/doc/ActivityApi.md
generated
@@ -125,7 +125,7 @@ void (empty response body)
|
|||||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
|
||||||
|
|
||||||
# **getActivities**
|
# **getActivities**
|
||||||
> List<ActivityResponseDto> getActivities(albumId, assetId, type)
|
> List<ActivityResponseDto> getActivities(albumId, assetId, type, userId)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -151,9 +151,10 @@ final api_instance = ActivityApi();
|
|||||||
final albumId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
|
final albumId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
|
||||||
final assetId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
|
final assetId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
|
||||||
final type = ; // ReactionType |
|
final type = ; // ReactionType |
|
||||||
|
final userId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String |
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final result = api_instance.getActivities(albumId, assetId, type);
|
final result = api_instance.getActivities(albumId, assetId, type, userId);
|
||||||
print(result);
|
print(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Exception when calling ActivityApi->getActivities: $e\n');
|
print('Exception when calling ActivityApi->getActivities: $e\n');
|
||||||
@@ -167,6 +168,7 @@ Name | Type | Description | Notes
|
|||||||
**albumId** | **String**| |
|
**albumId** | **String**| |
|
||||||
**assetId** | **String**| | [optional]
|
**assetId** | **String**| | [optional]
|
||||||
**type** | [**ReactionType**](.md)| | [optional]
|
**type** | [**ReactionType**](.md)| | [optional]
|
||||||
|
**userId** | **String**| | [optional]
|
||||||
|
|
||||||
### Return type
|
### Return type
|
||||||
|
|
||||||
|
|||||||
13
mobile/openapi/lib/api/activity_api.dart
generated
13
mobile/openapi/lib/api/activity_api.dart
generated
@@ -111,7 +111,9 @@ class ActivityApi {
|
|||||||
/// * [String] assetId:
|
/// * [String] assetId:
|
||||||
///
|
///
|
||||||
/// * [ReactionType] type:
|
/// * [ReactionType] type:
|
||||||
Future<Response> getActivitiesWithHttpInfo(String albumId, { String? assetId, ReactionType? type, }) async {
|
///
|
||||||
|
/// * [String] userId:
|
||||||
|
Future<Response> getActivitiesWithHttpInfo(String albumId, { String? assetId, ReactionType? type, String? userId, }) async {
|
||||||
// ignore: prefer_const_declarations
|
// ignore: prefer_const_declarations
|
||||||
final path = r'/activity';
|
final path = r'/activity';
|
||||||
|
|
||||||
@@ -129,6 +131,9 @@ class ActivityApi {
|
|||||||
if (type != null) {
|
if (type != null) {
|
||||||
queryParams.addAll(_queryParams('', 'type', type));
|
queryParams.addAll(_queryParams('', 'type', type));
|
||||||
}
|
}
|
||||||
|
if (userId != null) {
|
||||||
|
queryParams.addAll(_queryParams('', 'userId', userId));
|
||||||
|
}
|
||||||
|
|
||||||
const contentTypes = <String>[];
|
const contentTypes = <String>[];
|
||||||
|
|
||||||
@@ -151,8 +156,10 @@ class ActivityApi {
|
|||||||
/// * [String] assetId:
|
/// * [String] assetId:
|
||||||
///
|
///
|
||||||
/// * [ReactionType] type:
|
/// * [ReactionType] type:
|
||||||
Future<List<ActivityResponseDto>?> getActivities(String albumId, { String? assetId, ReactionType? type, }) async {
|
///
|
||||||
final response = await getActivitiesWithHttpInfo(albumId, assetId: assetId, type: type, );
|
/// * [String] userId:
|
||||||
|
Future<List<ActivityResponseDto>?> getActivities(String albumId, { String? assetId, ReactionType? type, String? userId, }) async {
|
||||||
|
final response = await getActivitiesWithHttpInfo(albumId, assetId: assetId, type: type, userId: userId, );
|
||||||
if (response.statusCode >= HttpStatus.badRequest) {
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
}
|
}
|
||||||
|
|||||||
2
mobile/openapi/test/activity_api_test.dart
generated
2
mobile/openapi/test/activity_api_test.dart
generated
@@ -27,7 +27,7 @@ void main() {
|
|||||||
// TODO
|
// TODO
|
||||||
});
|
});
|
||||||
|
|
||||||
//Future<List<ActivityResponseDto>> getActivities(String albumId, { String assetId, ReactionType type }) async
|
//Future<List<ActivityResponseDto>> getActivities(String albumId, { String assetId, ReactionType type, String userId }) async
|
||||||
test('test getActivities', () async {
|
test('test getActivities', () async {
|
||||||
// TODO
|
// TODO
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: archive
|
name: archive
|
||||||
sha256: "0c8368c9b3f0abbc193b9d6133649a614204b528982bebc7026372d61677ce3a"
|
sha256: "7e0d52067d05f2e0324268097ba723b71cb41ac8a6a2b24d1edf9c536b987b03"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.3.7"
|
version: "3.4.6"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -201,6 +201,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.7.0"
|
version: "1.7.0"
|
||||||
|
cli_util:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cli_util
|
||||||
|
sha256: b8db3080e59b2503ca9e7922c3df2072cf13992354d5e944074ffa836fba43b7
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.4.0"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -455,10 +463,10 @@ packages:
|
|||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: flutter_launcher_icons
|
name: flutter_launcher_icons
|
||||||
sha256: "559c600f056e7c704bd843723c21e01b5fba47e8824bd02422165bcc02a5de1d"
|
sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.3"
|
version: "0.13.1"
|
||||||
flutter_lints:
|
flutter_lints:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@@ -516,10 +524,10 @@ packages:
|
|||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: flutter_native_splash
|
name: flutter_native_splash
|
||||||
sha256: "6777a3abb974021a39b5fdd2d46a03ca390e03903b6351f21d10e7ecc969f12d"
|
sha256: d93394f22f73e810bda59e11ebe83329c5511d6460b6b7509c4e1f3c92d6d625
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.16"
|
version: "2.3.5"
|
||||||
flutter_plugin_android_lifecycle:
|
flutter_plugin_android_lifecycle:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -691,10 +699,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image
|
name: image
|
||||||
sha256: "8e9d133755c3e84c73288363e6343157c383a0c6c56fc51afcc5d4d7180306d6"
|
sha256: "028f61960d56f26414eb616b48b04eb37d700cbe477b7fb09bf1d7ce57fd9271"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.3.0"
|
version: "4.1.3"
|
||||||
image_picker:
|
image_picker:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -868,6 +876,78 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.0"
|
version: "0.5.0"
|
||||||
|
media_kit:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: media_kit
|
||||||
|
sha256: "3289062540e3b8b9746e5c50d95bd78a9289826b7227e253dff806d002b9e67a"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.10+1"
|
||||||
|
media_kit_libs_android_video:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: media_kit_libs_android_video
|
||||||
|
sha256: "9dd8012572e4aff47516e55f2597998f0a378e3d588d0fad0ca1f11a53ae090c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.3.6"
|
||||||
|
media_kit_libs_ios_video:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: media_kit_libs_ios_video
|
||||||
|
sha256: b5382994eb37a4564c368386c154ad70ba0cc78dacdd3fb0cd9f30db6d837991
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.4"
|
||||||
|
media_kit_libs_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: media_kit_libs_linux
|
||||||
|
sha256: e186891c31daa6bedab4d74dcdb4e8adfccc7d786bfed6ad81fe24a3b3010310
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.3"
|
||||||
|
media_kit_libs_macos_video:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: media_kit_libs_macos_video
|
||||||
|
sha256: f26aa1452b665df288e360393758f84b911f70ffb3878032e1aabba23aa1032d
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.4"
|
||||||
|
media_kit_libs_video:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: media_kit_libs_video
|
||||||
|
sha256: "3688e0c31482074578652bf038ce6301a5d21e1eda6b54fc3117ffeb4bdba067"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.4"
|
||||||
|
media_kit_libs_windows_video:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: media_kit_libs_windows_video
|
||||||
|
sha256: "7bace5f35d9afcc7f9b5cdadb7541d2191a66bb3fc71bfa11c1395b3360f6122"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.9"
|
||||||
|
media_kit_native_event_loop:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: media_kit_native_event_loop
|
||||||
|
sha256: a605cf185499d14d58935b8784955a92a4bf0ff4e19a23de3d17a9106303930e
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.8"
|
||||||
|
media_kit_video:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: media_kit_video
|
||||||
|
sha256: c048d11a19e379aebbe810647636e3fc6d18374637e2ae12def4ff8a4b99a882
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.4"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -1171,6 +1251,62 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.27.7"
|
version: "0.27.7"
|
||||||
|
safe_local_storage:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: safe_local_storage
|
||||||
|
sha256: ede4eb6cb7d88a116b3d3bf1df70790b9e2038bc37cb19112e381217c74d9440
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.2"
|
||||||
|
screen_brightness:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: screen_brightness
|
||||||
|
sha256: ed8da4a4511e79422fc1aa88138e920e4008cd312b72cdaa15ccb426c0faaedd
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.2+1"
|
||||||
|
screen_brightness_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: screen_brightness_android
|
||||||
|
sha256: "3df10961e3a9e968a5e076fe27e7f4741fa8a1d3950bdeb48cf121ed529d0caf"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.0+2"
|
||||||
|
screen_brightness_ios:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: screen_brightness_ios
|
||||||
|
sha256: "99adc3ca5490b8294284aad5fcc87f061ad685050e03cf45d3d018fe398fd9a2"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.0"
|
||||||
|
screen_brightness_macos:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: screen_brightness_macos
|
||||||
|
sha256: "64b34e7e3f4900d7687c8e8fb514246845a73ecec05ab53483ed025bd4a899fd"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.0+1"
|
||||||
|
screen_brightness_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: screen_brightness_platform_interface
|
||||||
|
sha256: b211d07f0c96637a15fb06f6168617e18030d5d74ad03795dd8547a52717c171
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.0"
|
||||||
|
screen_brightness_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: screen_brightness_windows
|
||||||
|
sha256: "9261bf33d0fc2707d8cf16339ce25768100a65e70af0fcabaf032fc12408ba86"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.3"
|
||||||
scrollable_positioned_list:
|
scrollable_positioned_list:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1448,6 +1584,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.2"
|
version: "2.2.2"
|
||||||
|
universal_platform:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: universal_platform
|
||||||
|
sha256: d315be0f6641898b280ffa34e2ddb14f3d12b1a37882557869646e0cc363d0cc
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0+1"
|
||||||
|
uri_parser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: uri_parser
|
||||||
|
sha256: "6543c9fd86d2862fac55d800a43e67c0dcd1a41677cb69c2f8edfe73bbcf1835"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.2"
|
||||||
url_launcher:
|
url_launcher:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -1576,6 +1728,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "11.7.1"
|
version: "11.7.1"
|
||||||
|
volume_controller:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: volume_controller
|
||||||
|
sha256: "189bdc7a554f476b412e4c8b2f474562b09d74bc458c23667356bce3ca1d48c9"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.7"
|
||||||
wakelock_plus:
|
wakelock_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -53,6 +53,10 @@ dependencies:
|
|||||||
wakelock_plus: ^1.1.1
|
wakelock_plus: ^1.1.1
|
||||||
flutter_local_notifications: ^15.1.0+1
|
flutter_local_notifications: ^15.1.0+1
|
||||||
|
|
||||||
|
media_kit: ^1.1.10 # Primary package.
|
||||||
|
media_kit_video: ^1.2.4 # For video rendering.
|
||||||
|
media_kit_libs_video: ^1.0.4 # Native video dependencies.
|
||||||
|
|
||||||
openapi:
|
openapi:
|
||||||
path: openapi
|
path: openapi
|
||||||
|
|
||||||
@@ -75,7 +79,7 @@ dev_dependencies:
|
|||||||
flutter_lints: ^2.0.1
|
flutter_lints: ^2.0.1
|
||||||
build_runner: ^2.2.1
|
build_runner: ^2.2.1
|
||||||
auto_route_generator: ^5.0.2
|
auto_route_generator: ^5.0.2
|
||||||
flutter_launcher_icons: "^0.9.2"
|
flutter_launcher_icons: "^0.13.1"
|
||||||
flutter_native_splash: ^2.2.16
|
flutter_native_splash: ^2.2.16
|
||||||
isar_generator: *isar_version
|
isar_generator: *isar_version
|
||||||
mockito: ^5.3.2
|
mockito: ^5.3.2
|
||||||
|
|||||||
@@ -30,6 +30,15 @@
|
|||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/components/schemas/ReactionType"
|
"$ref": "#/components/schemas/ReactionType"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "userId",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"format": "uuid",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
|
|||||||
@@ -38,6 +38,9 @@ export class ActivitySearchDto extends ActivityDto {
|
|||||||
@Optional()
|
@Optional()
|
||||||
@ApiProperty({ enumName: 'ReactionType', enum: ReactionType })
|
@ApiProperty({ enumName: 'ReactionType', enum: ReactionType })
|
||||||
type?: ReactionType;
|
type?: ReactionType;
|
||||||
|
|
||||||
|
@ValidateUUID({ optional: true })
|
||||||
|
userId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isComment = (dto: ActivityCreateDto) => dto.type === 'comment';
|
const isComment = (dto: ActivityCreateDto) => dto.type === 'comment';
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export class ActivityService {
|
|||||||
async getAll(authUser: AuthUserDto, dto: ActivitySearchDto): Promise<ActivityResponseDto[]> {
|
async getAll(authUser: AuthUserDto, dto: ActivitySearchDto): Promise<ActivityResponseDto[]> {
|
||||||
await this.access.requirePermission(authUser, Permission.ALBUM_READ, dto.albumId);
|
await this.access.requirePermission(authUser, Permission.ALBUM_READ, dto.albumId);
|
||||||
const activities = await this.repository.search({
|
const activities = await this.repository.search({
|
||||||
|
userId: dto.userId,
|
||||||
albumId: dto.albumId,
|
albumId: dto.albumId,
|
||||||
assetId: dto.assetId,
|
assetId: dto.assetId,
|
||||||
isLiked: dto.type && dto.type === ReactionType.LIKE,
|
isLiked: dto.type && dto.type === ReactionType.LIKE,
|
||||||
|
|||||||
@@ -134,6 +134,29 @@ describe(`${ActivityController.name} (e2e)`, () => {
|
|||||||
expect(body[0]).toEqual(reaction);
|
expect(body[0]).toEqual(reaction);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should filter by userId', async () => {
|
||||||
|
const [reaction] = await Promise.all([
|
||||||
|
api.activityApi.create(server, admin.accessToken, { albumId: album.id, type: ReactionType.LIKE }),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const response1 = await request(server)
|
||||||
|
.get('/activity')
|
||||||
|
.query({ albumId: album.id, userId: uuidStub.notFound })
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
|
||||||
|
expect(response1.status).toEqual(200);
|
||||||
|
expect(response1.body.length).toBe(0);
|
||||||
|
|
||||||
|
const response2 = await request(server)
|
||||||
|
.get('/activity')
|
||||||
|
.query({ albumId: album.id, userId: admin.userId })
|
||||||
|
.set('Authorization', `Bearer ${admin.accessToken}`);
|
||||||
|
|
||||||
|
expect(response2.status).toEqual(200);
|
||||||
|
expect(response2.body.length).toBe(1);
|
||||||
|
expect(response2.body[0]).toEqual(reaction);
|
||||||
|
});
|
||||||
|
|
||||||
it('should filter by assetId', async () => {
|
it('should filter by assetId', async () => {
|
||||||
const [reaction] = await Promise.all([
|
const [reaction] = await Promise.all([
|
||||||
api.activityApi.create(server, admin.accessToken, {
|
api.activityApi.create(server, admin.accessToken, {
|
||||||
|
|||||||
23
web/src/api/open-api/api.ts
generated
23
web/src/api/open-api/api.ts
generated
@@ -5076,10 +5076,11 @@ export const ActivityApiAxiosParamCreator = function (configuration?: Configurat
|
|||||||
* @param {string} albumId
|
* @param {string} albumId
|
||||||
* @param {string} [assetId]
|
* @param {string} [assetId]
|
||||||
* @param {ReactionType} [type]
|
* @param {ReactionType} [type]
|
||||||
|
* @param {string} [userId]
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
getActivities: async (albumId: string, assetId?: string, type?: ReactionType, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
getActivities: async (albumId: string, assetId?: string, type?: ReactionType, userId?: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
// verify required parameter 'albumId' is not null or undefined
|
// verify required parameter 'albumId' is not null or undefined
|
||||||
assertParamExists('getActivities', 'albumId', albumId)
|
assertParamExists('getActivities', 'albumId', albumId)
|
||||||
const localVarPath = `/activity`;
|
const localVarPath = `/activity`;
|
||||||
@@ -5115,6 +5116,10 @@ export const ActivityApiAxiosParamCreator = function (configuration?: Configurat
|
|||||||
localVarQueryParameter['type'] = type;
|
localVarQueryParameter['type'] = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (userId !== undefined) {
|
||||||
|
localVarQueryParameter['userId'] = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
@@ -5211,11 +5216,12 @@ export const ActivityApiFp = function(configuration?: Configuration) {
|
|||||||
* @param {string} albumId
|
* @param {string} albumId
|
||||||
* @param {string} [assetId]
|
* @param {string} [assetId]
|
||||||
* @param {ReactionType} [type]
|
* @param {ReactionType} [type]
|
||||||
|
* @param {string} [userId]
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
async getActivities(albumId: string, assetId?: string, type?: ReactionType, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<ActivityResponseDto>>> {
|
async getActivities(albumId: string, assetId?: string, type?: ReactionType, userId?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Array<ActivityResponseDto>>> {
|
||||||
const localVarAxiosArgs = await localVarAxiosParamCreator.getActivities(albumId, assetId, type, options);
|
const localVarAxiosArgs = await localVarAxiosParamCreator.getActivities(albumId, assetId, type, userId, options);
|
||||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
@@ -5264,7 +5270,7 @@ export const ActivityApiFactory = function (configuration?: Configuration, baseP
|
|||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
getActivities(requestParameters: ActivityApiGetActivitiesRequest, options?: AxiosRequestConfig): AxiosPromise<Array<ActivityResponseDto>> {
|
getActivities(requestParameters: ActivityApiGetActivitiesRequest, options?: AxiosRequestConfig): AxiosPromise<Array<ActivityResponseDto>> {
|
||||||
return localVarFp.getActivities(requestParameters.albumId, requestParameters.assetId, requestParameters.type, options).then((request) => request(axios, basePath));
|
return localVarFp.getActivities(requestParameters.albumId, requestParameters.assetId, requestParameters.type, requestParameters.userId, options).then((request) => request(axios, basePath));
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -5332,6 +5338,13 @@ export interface ActivityApiGetActivitiesRequest {
|
|||||||
* @memberof ActivityApiGetActivities
|
* @memberof ActivityApiGetActivities
|
||||||
*/
|
*/
|
||||||
readonly type?: ReactionType
|
readonly type?: ReactionType
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @type {string}
|
||||||
|
* @memberof ActivityApiGetActivities
|
||||||
|
*/
|
||||||
|
readonly userId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -5392,7 +5405,7 @@ export class ActivityApi extends BaseAPI {
|
|||||||
* @memberof ActivityApi
|
* @memberof ActivityApi
|
||||||
*/
|
*/
|
||||||
public getActivities(requestParameters: ActivityApiGetActivitiesRequest, options?: AxiosRequestConfig) {
|
public getActivities(requestParameters: ActivityApiGetActivitiesRequest, options?: AxiosRequestConfig) {
|
||||||
return ActivityApiFp(this.configuration).getActivities(requestParameters.albumId, requestParameters.assetId, requestParameters.type, options).then((request) => request(this.axios, this.basePath));
|
return ActivityApiFp(this.configuration).getActivities(requestParameters.albumId, requestParameters.assetId, requestParameters.type, requestParameters.userId, options).then((request) => request(this.axios, this.basePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -29,25 +29,24 @@
|
|||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import type { AssetStore } from '$lib/stores/assets.store';
|
import type { AssetStore } from '$lib/stores/assets.store';
|
||||||
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
|
|
||||||
import ProgressBar, { ProgressBarStatus } from '../shared-components/progress-bar/progress-bar.svelte';
|
|
||||||
import { shouldIgnoreShortcut } from '$lib/utils/shortcut';
|
import { shouldIgnoreShortcut } from '$lib/utils/shortcut';
|
||||||
|
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||||
|
import { SlideshowHistory } from '$lib/utils/slideshow-history';
|
||||||
import { featureFlags } from '$lib/stores/server-config.store';
|
import { featureFlags } from '$lib/stores/server-config.store';
|
||||||
import {
|
import {
|
||||||
mdiChevronLeft,
|
|
||||||
mdiHeartOutline,
|
mdiHeartOutline,
|
||||||
mdiHeart,
|
mdiHeart,
|
||||||
mdiCommentOutline,
|
mdiCommentOutline,
|
||||||
|
mdiChevronLeft,
|
||||||
mdiChevronRight,
|
mdiChevronRight,
|
||||||
mdiClose,
|
|
||||||
mdiImageBrokenVariant,
|
mdiImageBrokenVariant,
|
||||||
mdiPause,
|
|
||||||
mdiPlay,
|
|
||||||
} from '@mdi/js';
|
} from '@mdi/js';
|
||||||
import Icon from '$lib/components/elements/icon.svelte';
|
import Icon from '$lib/components/elements/icon.svelte';
|
||||||
import Thumbnail from '../assets/thumbnail/thumbnail.svelte';
|
import Thumbnail from '../assets/thumbnail/thumbnail.svelte';
|
||||||
import { stackAssetsStore } from '$lib/stores/stacked-asset.store';
|
import { stackAssetsStore } from '$lib/stores/stacked-asset.store';
|
||||||
import ActivityViewer from './activity-viewer.svelte';
|
import ActivityViewer from './activity-viewer.svelte';
|
||||||
|
import { SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
|
||||||
|
import SlideshowBar from './slideshow-bar.svelte';
|
||||||
|
|
||||||
export let assetStore: AssetStore | null = null;
|
export let assetStore: AssetStore | null = null;
|
||||||
export let asset: AssetResponseDto;
|
export let asset: AssetResponseDto;
|
||||||
@@ -62,6 +61,14 @@
|
|||||||
|
|
||||||
let reactions: ActivityResponseDto[] = [];
|
let reactions: ActivityResponseDto[] = [];
|
||||||
|
|
||||||
|
const { setAssetId } = assetViewingStore;
|
||||||
|
const {
|
||||||
|
restartProgress: restartSlideshowProgress,
|
||||||
|
stopProgress: stopSlideshowProgress,
|
||||||
|
slideshowShuffle,
|
||||||
|
slideshowState,
|
||||||
|
} = slideshowStore;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher<{
|
const dispatch = createEventDispatcher<{
|
||||||
archived: AssetResponseDto;
|
archived: AssetResponseDto;
|
||||||
unarchived: AssetResponseDto;
|
unarchived: AssetResponseDto;
|
||||||
@@ -82,6 +89,8 @@
|
|||||||
let shouldShowDownloadButton = sharedLink ? sharedLink.allowDownload : !asset.isOffline;
|
let shouldShowDownloadButton = sharedLink ? sharedLink.allowDownload : !asset.isOffline;
|
||||||
let shouldShowDetailButton = asset.hasMetadata;
|
let shouldShowDetailButton = asset.hasMetadata;
|
||||||
let canCopyImagesToClipboard: boolean;
|
let canCopyImagesToClipboard: boolean;
|
||||||
|
let slideshowStateUnsubscribe: () => void;
|
||||||
|
let shuffleSlideshowUnsubscribe: () => void;
|
||||||
let previewStackedAsset: AssetResponseDto | undefined;
|
let previewStackedAsset: AssetResponseDto | undefined;
|
||||||
let isShowActivity = false;
|
let isShowActivity = false;
|
||||||
let isLiked: ActivityResponseDto | null = null;
|
let isLiked: ActivityResponseDto | null = null;
|
||||||
@@ -123,15 +132,18 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getFavorite = async () => {
|
const getFavorite = async () => {
|
||||||
if (album) {
|
if (album && user) {
|
||||||
try {
|
try {
|
||||||
const { data } = await api.activityApi.getActivities({
|
const { data } = await api.activityApi.getActivities({
|
||||||
|
userId: user.id,
|
||||||
assetId: asset.id,
|
assetId: asset.id,
|
||||||
albumId: album.id,
|
albumId: album.id,
|
||||||
type: ReactionType.Like,
|
type: ReactionType.Like,
|
||||||
});
|
});
|
||||||
if (data.length > 0) {
|
if (data.length > 0) {
|
||||||
isLiked = data[0];
|
isLiked = data[0];
|
||||||
|
} else {
|
||||||
|
isLiked = null;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
handleError(error, "Can't get Favorite");
|
handleError(error, "Can't get Favorite");
|
||||||
@@ -161,6 +173,23 @@
|
|||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
document.addEventListener('keydown', onKeyboardPress);
|
document.addEventListener('keydown', onKeyboardPress);
|
||||||
|
|
||||||
|
slideshowStateUnsubscribe = slideshowState.subscribe((value) => {
|
||||||
|
if (value === SlideshowState.PlaySlideshow) {
|
||||||
|
slideshowHistory.reset();
|
||||||
|
slideshowHistory.queue(asset.id);
|
||||||
|
handlePlaySlideshow();
|
||||||
|
} else if (value === SlideshowState.StopSlideshow) {
|
||||||
|
handleStopSlideshow();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
shuffleSlideshowUnsubscribe = slideshowShuffle.subscribe((value) => {
|
||||||
|
if (value) {
|
||||||
|
slideshowHistory.reset();
|
||||||
|
slideshowHistory.queue(asset.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (!sharedLink) {
|
if (!sharedLink) {
|
||||||
await getAllAlbums();
|
await getAllAlbums();
|
||||||
}
|
}
|
||||||
@@ -184,6 +213,14 @@
|
|||||||
if (browser) {
|
if (browser) {
|
||||||
document.removeEventListener('keydown', onKeyboardPress);
|
document.removeEventListener('keydown', onKeyboardPress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (slideshowStateUnsubscribe) {
|
||||||
|
slideshowStateUnsubscribe();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shuffleSlideshowUnsubscribe) {
|
||||||
|
shuffleSlideshowUnsubscribe();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$: asset.id && !sharedLink && getAllAlbums(); // Update the album information when the asset ID changes
|
$: asset.id && !sharedLink && getAllAlbums(); // Update the album information when the asset ID changes
|
||||||
@@ -262,11 +299,31 @@
|
|||||||
|
|
||||||
const closeViewer = () => dispatch('close');
|
const closeViewer = () => dispatch('close');
|
||||||
|
|
||||||
|
const navigateAssetRandom = async () => {
|
||||||
|
if (!assetStore) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const asset = await assetStore.getRandomAsset();
|
||||||
|
if (!asset) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
slideshowHistory.queue(asset.id);
|
||||||
|
|
||||||
|
setAssetId(asset.id);
|
||||||
|
$restartSlideshowProgress = true;
|
||||||
|
};
|
||||||
|
|
||||||
const navigateAssetForward = async (e?: Event) => {
|
const navigateAssetForward = async (e?: Event) => {
|
||||||
if (isSlideshowMode && assetStore && progressBar) {
|
if ($slideshowState === SlideshowState.PlaySlideshow && $slideshowShuffle) {
|
||||||
|
return slideshowHistory.next() || navigateAssetRandom();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($slideshowState === SlideshowState.PlaySlideshow && assetStore) {
|
||||||
const hasNext = await assetStore.getNextAssetId(asset.id);
|
const hasNext = await assetStore.getNextAssetId(asset.id);
|
||||||
if (hasNext) {
|
if (hasNext) {
|
||||||
progressBar.restart(true);
|
$restartSlideshowProgress = true;
|
||||||
} else {
|
} else {
|
||||||
await handleStopSlideshow();
|
await handleStopSlideshow();
|
||||||
}
|
}
|
||||||
@@ -277,8 +334,13 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const navigateAssetBackward = (e?: Event) => {
|
const navigateAssetBackward = (e?: Event) => {
|
||||||
if (isSlideshowMode && progressBar) {
|
if ($slideshowState === SlideshowState.PlaySlideshow && $slideshowShuffle) {
|
||||||
progressBar.restart(true);
|
slideshowHistory.previous();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($slideshowState === SlideshowState.PlaySlideshow) {
|
||||||
|
$restartSlideshowProgress = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
e?.stopPropagation();
|
e?.stopPropagation();
|
||||||
@@ -426,19 +488,21 @@
|
|||||||
* Slide show mode
|
* Slide show mode
|
||||||
*/
|
*/
|
||||||
|
|
||||||
let isSlideshowMode = false;
|
|
||||||
let assetViewerHtmlElement: HTMLElement;
|
let assetViewerHtmlElement: HTMLElement;
|
||||||
let progressBar: ProgressBar;
|
|
||||||
let progressBarStatus: ProgressBarStatus;
|
const slideshowHistory = new SlideshowHistory((assetId: string) => {
|
||||||
|
setAssetId(assetId);
|
||||||
|
$restartSlideshowProgress = true;
|
||||||
|
});
|
||||||
|
|
||||||
const handleVideoStarted = () => {
|
const handleVideoStarted = () => {
|
||||||
if (isSlideshowMode) {
|
if ($slideshowState === SlideshowState.PlaySlideshow) {
|
||||||
progressBar.restart(false);
|
$stopSlideshowProgress = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleVideoEnded = async () => {
|
const handleVideoEnded = async () => {
|
||||||
if (isSlideshowMode) {
|
if ($slideshowState === SlideshowState.PlaySlideshow) {
|
||||||
await navigateAssetForward();
|
await navigateAssetForward();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -448,19 +512,20 @@
|
|||||||
await assetViewerHtmlElement.requestFullscreen();
|
await assetViewerHtmlElement.requestFullscreen();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error entering fullscreen', error);
|
console.error('Error entering fullscreen', error);
|
||||||
} finally {
|
$slideshowState = SlideshowState.StopSlideshow;
|
||||||
isSlideshowMode = true;
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleStopSlideshow = async () => {
|
const handleStopSlideshow = async () => {
|
||||||
try {
|
try {
|
||||||
await document.exitFullscreen();
|
if (document.fullscreenElement) {
|
||||||
|
await document.exitFullscreen();
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error exiting fullscreen', error);
|
console.error('Error exiting fullscreen', error);
|
||||||
} finally {
|
} finally {
|
||||||
isSlideshowMode = false;
|
$stopSlideshowProgress = true;
|
||||||
progressBar.restart(false);
|
$slideshowState = SlideshowState.None;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -497,31 +562,10 @@
|
|||||||
<section
|
<section
|
||||||
id="immich-asset-viewer"
|
id="immich-asset-viewer"
|
||||||
class="fixed left-0 top-0 z-[1001] grid h-screen w-screen grid-cols-4 grid-rows-[64px_1fr] overflow-y-hidden bg-black"
|
class="fixed left-0 top-0 z-[1001] grid h-screen w-screen grid-cols-4 grid-rows-[64px_1fr] overflow-y-hidden bg-black"
|
||||||
bind:this={assetViewerHtmlElement}
|
|
||||||
>
|
>
|
||||||
<div class="z-[1000] col-span-4 col-start-1 row-span-1 row-start-1 transition-transform">
|
<!-- Top navigation bar -->
|
||||||
{#if isSlideshowMode}
|
{#if $slideshowState === SlideshowState.None}
|
||||||
<!-- SlideShowController -->
|
<div class="z-[1002] col-span-4 col-start-1 row-span-1 row-start-1 transition-transform">
|
||||||
<div class="flex">
|
|
||||||
<div class="m-4 flex gap-2">
|
|
||||||
<CircleIconButton icon={mdiClose} on:click={handleStopSlideshow} title="Exit Slideshow" />
|
|
||||||
<CircleIconButton
|
|
||||||
icon={progressBarStatus === ProgressBarStatus.Paused ? mdiPlay : mdiPause}
|
|
||||||
on:click={() => (progressBarStatus === ProgressBarStatus.Paused ? progressBar.play() : progressBar.pause())}
|
|
||||||
title={progressBarStatus === ProgressBarStatus.Paused ? 'Play' : 'Pause'}
|
|
||||||
/>
|
|
||||||
<CircleIconButton icon={mdiChevronLeft} on:click={navigateAssetBackward} title="Previous" />
|
|
||||||
<CircleIconButton icon={mdiChevronRight} on:click={navigateAssetForward} title="Next" />
|
|
||||||
</div>
|
|
||||||
<ProgressBar
|
|
||||||
autoplay
|
|
||||||
bind:this={progressBar}
|
|
||||||
bind:status={progressBarStatus}
|
|
||||||
on:done={navigateAssetForward}
|
|
||||||
duration={5000}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{:else}
|
|
||||||
<AssetViewerNavBar
|
<AssetViewerNavBar
|
||||||
{asset}
|
{asset}
|
||||||
isMotionPhotoPlaying={shouldPlayMotionPhoto}
|
isMotionPhotoPlaying={shouldPlayMotionPhoto}
|
||||||
@@ -544,19 +588,30 @@
|
|||||||
on:toggleArchive={toggleArchive}
|
on:toggleArchive={toggleArchive}
|
||||||
on:asProfileImage={() => (isShowProfileImageCrop = true)}
|
on:asProfileImage={() => (isShowProfileImageCrop = true)}
|
||||||
on:runJob={({ detail: job }) => handleRunJob(job)}
|
on:runJob={({ detail: job }) => handleRunJob(job)}
|
||||||
on:playSlideShow={handlePlaySlideshow}
|
on:playSlideShow={() => ($slideshowState = SlideshowState.PlaySlideshow)}
|
||||||
on:unstack={handleUnstack}
|
on:unstack={handleUnstack}
|
||||||
/>
|
/>
|
||||||
{/if}
|
</div>
|
||||||
</div>
|
{/if}
|
||||||
|
|
||||||
{#if !isSlideshowMode && showNavigation}
|
{#if $slideshowState === SlideshowState.None && showNavigation}
|
||||||
<div class="column-span-1 z-[999] col-start-1 row-span-1 row-start-2 mb-[60px] justify-self-start">
|
<div class="z-[1001] column-span-1 col-start-1 row-span-1 row-start-2 mb-[60px] justify-self-start">
|
||||||
<NavigationArea on:click={navigateAssetBackward}><Icon path={mdiChevronLeft} size="36" /></NavigationArea>
|
<NavigationArea on:click={navigateAssetBackward}><Icon path={mdiChevronLeft} size="36" /></NavigationArea>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Asset Viewer -->
|
<!-- Asset Viewer -->
|
||||||
<div class="relative col-span-4 col-start-1 row-span-full row-start-1">
|
<div class="z-[1000] relative col-start-1 col-span-4 row-start-1 row-span-full" bind:this={assetViewerHtmlElement}>
|
||||||
|
{#if $slideshowState != SlideshowState.None}
|
||||||
|
<div class="z-[1000] absolute w-full flex">
|
||||||
|
<SlideshowBar
|
||||||
|
on:prev={navigateAssetBackward}
|
||||||
|
on:next={navigateAssetForward}
|
||||||
|
on:close={() => ($slideshowState = SlideshowState.StopSlideshow)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if previewStackedAsset}
|
{#if previewStackedAsset}
|
||||||
{#key previewStackedAsset.id}
|
{#key previewStackedAsset.id}
|
||||||
{#if previewStackedAsset.type === AssetTypeEnum.Image}
|
{#if previewStackedAsset.type === AssetTypeEnum.Image}
|
||||||
@@ -602,7 +657,7 @@
|
|||||||
on:onVideoStarted={handleVideoStarted}
|
on:onVideoStarted={handleVideoStarted}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{#if isShared}
|
{#if $slideshowState === SlideshowState.None && isShared}
|
||||||
<div class="z-[9999] absolute bottom-0 right-0 mb-6 mr-6 justify-self-end">
|
<div class="z-[9999] absolute bottom-0 right-0 mb-6 mr-6 justify-self-end">
|
||||||
<div
|
<div
|
||||||
class="w-full h-14 flex p-4 text-white items-center justify-center rounded-full gap-4 bg-immich-dark-bg bg-opacity-60"
|
class="w-full h-14 flex p-4 text-white items-center justify-center rounded-full gap-4 bg-immich-dark-bg bg-opacity-60"
|
||||||
@@ -664,19 +719,17 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Stack & Stack Controller -->
|
{#if $slideshowState === SlideshowState.None && showNavigation}
|
||||||
|
<div class="z-[1001] col-span-1 col-start-4 row-span-1 row-start-2 mb-[60px] justify-self-end">
|
||||||
{#if !isSlideshowMode && showNavigation}
|
|
||||||
<div class="z-[999] col-span-1 col-start-4 row-span-1 row-start-2 mb-[60px] justify-self-end">
|
|
||||||
<NavigationArea on:click={navigateAssetForward}><Icon path={mdiChevronRight} size="36" /></NavigationArea>
|
<NavigationArea on:click={navigateAssetForward}><Icon path={mdiChevronRight} size="36" /></NavigationArea>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if !isSlideshowMode && $isShowDetail}
|
{#if $slideshowState === SlideshowState.None && $isShowDetail}
|
||||||
<div
|
<div
|
||||||
transition:fly={{ duration: 150 }}
|
transition:fly={{ duration: 150 }}
|
||||||
id="detail-panel"
|
id="detail-panel"
|
||||||
class="z-[1002] row-start-1 row-span-5 w-[360px] overflow-y-auto bg-immich-bg transition-all dark:border-l dark:border-l-immich-dark-gray dark:bg-immich-dark-bg"
|
class="z-[1002] row-start-1 row-span-4 w-[360px] overflow-y-auto bg-immich-bg transition-all dark:border-l dark:border-l-immich-dark-gray dark:bg-immich-dark-bg"
|
||||||
translate="yes"
|
translate="yes"
|
||||||
>
|
>
|
||||||
<DetailPanel
|
<DetailPanel
|
||||||
|
|||||||
78
web/src/lib/components/asset-viewer/slideshow-bar.svelte
Normal file
78
web/src/lib/components/asset-viewer/slideshow-bar.svelte
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import CircleIconButton from '../elements/buttons/circle-icon-button.svelte';
|
||||||
|
import ProgressBar, { ProgressBarStatus } from '../shared-components/progress-bar/progress-bar.svelte';
|
||||||
|
import { slideshowStore } from '$lib/stores/slideshow.store';
|
||||||
|
import { createEventDispatcher, onDestroy, onMount } from 'svelte';
|
||||||
|
import {
|
||||||
|
mdiChevronLeft,
|
||||||
|
mdiChevronRight,
|
||||||
|
mdiClose,
|
||||||
|
mdiPause,
|
||||||
|
mdiPlay,
|
||||||
|
mdiShuffle,
|
||||||
|
mdiShuffleDisabled,
|
||||||
|
} from '@mdi/js';
|
||||||
|
|
||||||
|
const { slideshowShuffle } = slideshowStore;
|
||||||
|
const { restartProgress, stopProgress } = slideshowStore;
|
||||||
|
|
||||||
|
let progressBarStatus: ProgressBarStatus;
|
||||||
|
let progressBar: ProgressBar;
|
||||||
|
|
||||||
|
let unsubscribeRestart: () => void;
|
||||||
|
let unsubscribeStop: () => void;
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher<{
|
||||||
|
next: void;
|
||||||
|
prev: void;
|
||||||
|
close: void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
unsubscribeRestart = restartProgress.subscribe((value) => {
|
||||||
|
if (value) {
|
||||||
|
progressBar.restart(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
unsubscribeStop = stopProgress.subscribe((value) => {
|
||||||
|
if (value) {
|
||||||
|
progressBar.restart(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
if (unsubscribeRestart) {
|
||||||
|
unsubscribeRestart();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unsubscribeStop) {
|
||||||
|
unsubscribeStop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="m-4 flex gap-2">
|
||||||
|
<CircleIconButton icon={mdiClose} on:click={() => dispatch('close')} title="Exit Slideshow" />
|
||||||
|
{#if $slideshowShuffle}
|
||||||
|
<CircleIconButton icon={mdiShuffle} on:click={() => ($slideshowShuffle = false)} title="Shuffle" />
|
||||||
|
{:else}
|
||||||
|
<CircleIconButton icon={mdiShuffleDisabled} on:click={() => ($slideshowShuffle = true)} title="No shuffle" />
|
||||||
|
{/if}
|
||||||
|
<CircleIconButton
|
||||||
|
icon={progressBarStatus === ProgressBarStatus.Paused ? mdiPlay : mdiPause}
|
||||||
|
on:click={() => (progressBarStatus === ProgressBarStatus.Paused ? progressBar.play() : progressBar.pause())}
|
||||||
|
title={progressBarStatus === ProgressBarStatus.Paused ? 'Play' : 'Pause'}
|
||||||
|
/>
|
||||||
|
<CircleIconButton icon={mdiChevronLeft} on:click={() => dispatch('prev')} title="Previous" />
|
||||||
|
<CircleIconButton icon={mdiChevronRight} on:click={() => dispatch('next')} title="Next" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
autoplay
|
||||||
|
bind:this={progressBar}
|
||||||
|
bind:status={progressBarStatus}
|
||||||
|
on:done={() => dispatch('next')}
|
||||||
|
duration={5000}
|
||||||
|
/>
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
export let scrollbar = true;
|
export let scrollbar = true;
|
||||||
export let admin = false;
|
export let admin = false;
|
||||||
|
|
||||||
$: scrollbarClass = scrollbar ? 'immich-scrollbar p-4 pb-8' : 'scrollbar-hidden pl-4';
|
$: scrollbarClass = scrollbar ? 'immich-scrollbar p-4 pb-8' : 'scrollbar-hidden';
|
||||||
$: hasTitleClass = title ? 'top-16 h-[calc(100%-theme(spacing.16))]' : 'top-0 h-full';
|
$: hasTitleClass = title ? 'top-16 h-[calc(100%-theme(spacing.16))]' : 'top-0 h-full';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -96,6 +96,19 @@
|
|||||||
{@const date = fromLocalDateTime(segment.timeGroup)}
|
{@const date = fromLocalDateTime(segment.timeGroup)}
|
||||||
{@const year = date.year}
|
{@const year = date.year}
|
||||||
{@const label = `${date.toLocaleString({ month: 'short' })} ${year}`}
|
{@const label = `${date.toLocaleString({ month: 'short' })} ${year}`}
|
||||||
|
{@const lastGroupYear = fromLocalDateTime(segments[index - 1]?.timeGroup).year}
|
||||||
|
|
||||||
|
<!-- Check if the next three segments are different years then don't render
|
||||||
|
to avoid overlapse -->
|
||||||
|
{@const canRenderYear = segments.slice(index + 1, index + 3).reduce((_, curr) => {
|
||||||
|
const nextGroupYear = fromLocalDateTime(curr.timeGroup).year;
|
||||||
|
|
||||||
|
if (nextGroupYear !== year || curr.height < 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}, true)}
|
||||||
|
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||||
<div
|
<div
|
||||||
@@ -105,10 +118,10 @@
|
|||||||
aria-label={segment.timeGroup + ' ' + segment.count}
|
aria-label={segment.timeGroup + ' ' + segment.count}
|
||||||
on:mousemove={() => (hoverLabel = label)}
|
on:mousemove={() => (hoverLabel = label)}
|
||||||
>
|
>
|
||||||
{#if new Date(segments[index - 1]?.timeGroup).getFullYear() !== year}
|
{#if lastGroupYear !== year && canRenderYear}
|
||||||
<div
|
<div
|
||||||
aria-label={segment.timeGroup + ' ' + segment.count}
|
aria-label={segment.timeGroup + ' ' + segment.count}
|
||||||
class="absolute right-0 z-10 pr-5 text-xs font-medium dark:text-immich-dark-fg"
|
class="absolute right-0 z-10 pr-5 text-xs font-medium dark:text-immich-dark-fg font-mono"
|
||||||
>
|
>
|
||||||
{year}
|
{year}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -304,6 +304,19 @@ export class AssetStore {
|
|||||||
return this.assetToBucket[assetId]?.bucketIndex ?? null;
|
return this.assetToBucket[assetId]?.bucketIndex ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getRandomAsset(): Promise<AssetResponseDto | null> {
|
||||||
|
const bucket = this.buckets[Math.floor(Math.random() * this.buckets.length)] || null;
|
||||||
|
if (!bucket) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bucket.assets.length === 0) {
|
||||||
|
await this.loadBucket(bucket.bucketDate, BucketPosition.Unknown);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bucket.assets[Math.floor(Math.random() * bucket.assets.length)] || null;
|
||||||
|
}
|
||||||
|
|
||||||
updateAsset(_asset: AssetResponseDto) {
|
updateAsset(_asset: AssetResponseDto) {
|
||||||
const asset = this.assets.find((asset) => asset.id === _asset.id);
|
const asset = this.assets.find((asset) => asset.id === _asset.id);
|
||||||
if (!asset) {
|
if (!asset) {
|
||||||
|
|||||||
45
web/src/lib/stores/slideshow.store.ts
Normal file
45
web/src/lib/stores/slideshow.store.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import { persisted } from 'svelte-local-storage-store';
|
||||||
|
import { writable } from 'svelte/store';
|
||||||
|
|
||||||
|
export enum SlideshowState {
|
||||||
|
PlaySlideshow = 'play-slideshow',
|
||||||
|
StopSlideshow = 'stop-slideshow',
|
||||||
|
None = 'none',
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSlideshowStore() {
|
||||||
|
const restartState = writable<boolean>(false);
|
||||||
|
const stopState = writable<boolean>(false);
|
||||||
|
|
||||||
|
const slideshowShuffle = persisted<boolean>('slideshow-shuffle', true);
|
||||||
|
const slideshowState = writable<SlideshowState>(SlideshowState.None);
|
||||||
|
|
||||||
|
return {
|
||||||
|
restartProgress: {
|
||||||
|
subscribe: restartState.subscribe,
|
||||||
|
set: (value: boolean) => {
|
||||||
|
// Trigger an action whenever the restartProgress is set to true. Automatically
|
||||||
|
// reset the restart state after that
|
||||||
|
if (value) {
|
||||||
|
restartState.set(true);
|
||||||
|
restartState.set(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
stopProgress: {
|
||||||
|
subscribe: stopState.subscribe,
|
||||||
|
set: (value: boolean) => {
|
||||||
|
// Trigger an action whenever the stopProgress is set to true. Automatically
|
||||||
|
// reset the stop state after that
|
||||||
|
if (value) {
|
||||||
|
stopState.set(true);
|
||||||
|
stopState.set(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
slideshowShuffle,
|
||||||
|
slideshowState,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const slideshowStore = createSlideshowStore();
|
||||||
40
web/src/lib/utils/slideshow-history.ts
Normal file
40
web/src/lib/utils/slideshow-history.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
export class SlideshowHistory {
|
||||||
|
private history: string[] = [];
|
||||||
|
private index = 0;
|
||||||
|
|
||||||
|
constructor(private onChange: (assetId: string) => void) {}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.history = [];
|
||||||
|
this.index = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
queue(assetId: string) {
|
||||||
|
this.history.push(assetId);
|
||||||
|
|
||||||
|
// If we were at the end of the slideshow history, move the index to the new end
|
||||||
|
if (this.index === this.history.length - 2) {
|
||||||
|
this.index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
next(): boolean {
|
||||||
|
if (this.index === this.history.length - 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.index++;
|
||||||
|
this.onChange(this.history[this.index]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
previous(): boolean {
|
||||||
|
if (this.index === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.index--;
|
||||||
|
this.onChange(this.history[this.index]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,6 +29,7 @@
|
|||||||
import { AppRoute, dateFormats } from '$lib/constants';
|
import { AppRoute, dateFormats } from '$lib/constants';
|
||||||
import { createAssetInteractionStore } from '$lib/stores/asset-interaction.store';
|
import { createAssetInteractionStore } from '$lib/stores/asset-interaction.store';
|
||||||
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
import { assetViewingStore } from '$lib/stores/asset-viewing.store';
|
||||||
|
import { SlideshowState, slideshowStore } from '$lib/stores/slideshow.store';
|
||||||
import { AssetStore } from '$lib/stores/assets.store';
|
import { AssetStore } from '$lib/stores/assets.store';
|
||||||
import { locale } from '$lib/stores/preferences.store';
|
import { locale } from '$lib/stores/preferences.store';
|
||||||
import { downloadArchive } from '$lib/utils/asset-utils';
|
import { downloadArchive } from '$lib/utils/asset-utils';
|
||||||
@@ -52,7 +53,8 @@
|
|||||||
|
|
||||||
export let data: PageData;
|
export let data: PageData;
|
||||||
|
|
||||||
let { isViewing: showAssetViewer } = assetViewingStore;
|
let { isViewing: showAssetViewer, setAssetId } = assetViewingStore;
|
||||||
|
let { slideshowState, slideshowShuffle } = slideshowStore;
|
||||||
|
|
||||||
let album = data.album;
|
let album = data.album;
|
||||||
$: album = data.album;
|
$: album = data.album;
|
||||||
@@ -108,6 +110,14 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleStartSlideshow = async () => {
|
||||||
|
const asset = $slideshowShuffle ? await assetStore.getRandomAsset() : assetStore.assets[0];
|
||||||
|
if (asset) {
|
||||||
|
setAssetId(asset.id);
|
||||||
|
$slideshowState = SlideshowState.PlaySlideshow;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleEscape = () => {
|
const handleEscape = () => {
|
||||||
if (viewMode === ViewMode.SELECT_USERS) {
|
if (viewMode === ViewMode.SELECT_USERS) {
|
||||||
viewMode = ViewMode.VIEW;
|
viewMode = ViewMode.VIEW;
|
||||||
@@ -365,6 +375,9 @@
|
|||||||
<CircleIconButton title="Album options" on:click={handleOpenAlbumOptions} icon={mdiDotsVertical}>
|
<CircleIconButton title="Album options" on:click={handleOpenAlbumOptions} icon={mdiDotsVertical}>
|
||||||
{#if viewMode === ViewMode.ALBUM_OPTIONS}
|
{#if viewMode === ViewMode.ALBUM_OPTIONS}
|
||||||
<ContextMenu {...contextMenuPosition}>
|
<ContextMenu {...contextMenuPosition}>
|
||||||
|
{#if album.assetCount !== 0}
|
||||||
|
<MenuOption on:click={handleStartSlideshow} text="Slideshow" />
|
||||||
|
{/if}
|
||||||
<MenuOption on:click={() => (viewMode = ViewMode.SELECT_THUMBNAIL)} text="Set album cover" />
|
<MenuOption on:click={() => (viewMode = ViewMode.SELECT_THUMBNAIL)} text="Set album cover" />
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -87,7 +87,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<AssetGrid forceDelete {assetStore} {assetInteractionStore}>
|
<AssetGrid forceDelete {assetStore} {assetInteractionStore}>
|
||||||
<p class="font-medium text-gray-500/60 dark:text-gray-300/60 py-4">
|
<p class="font-medium text-gray-500/60 dark:text-gray-300/60 p-4">
|
||||||
Trashed items will be permanently deleted after {$serverConfig.trashDays} days.
|
Trashed items will be permanently deleted after {$serverConfig.trashDays} days.
|
||||||
</p>
|
</p>
|
||||||
<EmptyPlaceholder
|
<EmptyPlaceholder
|
||||||
|
|||||||
Reference in New Issue
Block a user