mirror of
https://github.com/immich-app/immich.git
synced 2025-12-18 01:11:07 +03:00
pr feedback
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"get_people_error": "Error getting people",
|
||||||
"about": "About",
|
"about": "About",
|
||||||
"account": "Account",
|
"account": "Account",
|
||||||
"account_settings": "Account Settings",
|
"account_settings": "Account Settings",
|
||||||
|
|||||||
4
mobile/openapi/README.md
generated
4
mobile/openapi/README.md
generated
@@ -199,8 +199,8 @@ Class | Method | HTTP request | Description
|
|||||||
*PeopleApi* | [**updatePeople**](doc//PeopleApi.md#updatepeople) | **PUT** /people | Update people
|
*PeopleApi* | [**updatePeople**](doc//PeopleApi.md#updatepeople) | **PUT** /people | Update people
|
||||||
*PeopleApi* | [**updatePerson**](doc//PeopleApi.md#updateperson) | **PUT** /people/{id} | Update person
|
*PeopleApi* | [**updatePerson**](doc//PeopleApi.md#updateperson) | **PUT** /people/{id} | Update person
|
||||||
*PluginsApi* | [**getPlugin**](doc//PluginsApi.md#getplugin) | **GET** /plugins/{id} | Retrieve a plugin
|
*PluginsApi* | [**getPlugin**](doc//PluginsApi.md#getplugin) | **GET** /plugins/{id} | Retrieve a plugin
|
||||||
|
*PluginsApi* | [**getPluginTriggers**](doc//PluginsApi.md#getplugintriggers) | **GET** /plugins/triggers | List all plugin triggers
|
||||||
*PluginsApi* | [**getPlugins**](doc//PluginsApi.md#getplugins) | **GET** /plugins | List all plugins
|
*PluginsApi* | [**getPlugins**](doc//PluginsApi.md#getplugins) | **GET** /plugins | List all plugins
|
||||||
*PluginsApi* | [**getTriggers**](doc//PluginsApi.md#gettriggers) | **GET** /plugins/triggers | List all plugin triggers
|
|
||||||
*QueuesApi* | [**emptyQueue**](doc//QueuesApi.md#emptyqueue) | **DELETE** /queues/{name}/jobs | Empty a queue
|
*QueuesApi* | [**emptyQueue**](doc//QueuesApi.md#emptyqueue) | **DELETE** /queues/{name}/jobs | Empty a queue
|
||||||
*QueuesApi* | [**getQueue**](doc//QueuesApi.md#getqueue) | **GET** /queues/{name} | Retrieve a queue
|
*QueuesApi* | [**getQueue**](doc//QueuesApi.md#getqueue) | **GET** /queues/{name} | Retrieve a queue
|
||||||
*QueuesApi* | [**getQueueJobs**](doc//QueuesApi.md#getqueuejobs) | **GET** /queues/{name}/jobs | Retrieve queue jobs
|
*QueuesApi* | [**getQueueJobs**](doc//QueuesApi.md#getqueuejobs) | **GET** /queues/{name}/jobs | Retrieve queue jobs
|
||||||
@@ -466,7 +466,7 @@ Class | Method | HTTP request | Description
|
|||||||
- [PinCodeSetupDto](doc//PinCodeSetupDto.md)
|
- [PinCodeSetupDto](doc//PinCodeSetupDto.md)
|
||||||
- [PlacesResponseDto](doc//PlacesResponseDto.md)
|
- [PlacesResponseDto](doc//PlacesResponseDto.md)
|
||||||
- [PluginActionResponseDto](doc//PluginActionResponseDto.md)
|
- [PluginActionResponseDto](doc//PluginActionResponseDto.md)
|
||||||
- [PluginContext](doc//PluginContext.md)
|
- [PluginContextType](doc//PluginContextType.md)
|
||||||
- [PluginFilterResponseDto](doc//PluginFilterResponseDto.md)
|
- [PluginFilterResponseDto](doc//PluginFilterResponseDto.md)
|
||||||
- [PluginResponseDto](doc//PluginResponseDto.md)
|
- [PluginResponseDto](doc//PluginResponseDto.md)
|
||||||
- [PluginTriggerResponseDto](doc//PluginTriggerResponseDto.md)
|
- [PluginTriggerResponseDto](doc//PluginTriggerResponseDto.md)
|
||||||
|
|||||||
2
mobile/openapi/lib/api.dart
generated
2
mobile/openapi/lib/api.dart
generated
@@ -217,7 +217,7 @@ part 'model/pin_code_reset_dto.dart';
|
|||||||
part 'model/pin_code_setup_dto.dart';
|
part 'model/pin_code_setup_dto.dart';
|
||||||
part 'model/places_response_dto.dart';
|
part 'model/places_response_dto.dart';
|
||||||
part 'model/plugin_action_response_dto.dart';
|
part 'model/plugin_action_response_dto.dart';
|
||||||
part 'model/plugin_context.dart';
|
part 'model/plugin_context_type.dart';
|
||||||
part 'model/plugin_filter_response_dto.dart';
|
part 'model/plugin_filter_response_dto.dart';
|
||||||
part 'model/plugin_response_dto.dart';
|
part 'model/plugin_response_dto.dart';
|
||||||
part 'model/plugin_trigger_response_dto.dart';
|
part 'model/plugin_trigger_response_dto.dart';
|
||||||
|
|||||||
102
mobile/openapi/lib/api/plugins_api.dart
generated
102
mobile/openapi/lib/api/plugins_api.dart
generated
@@ -73,6 +73,57 @@ class PluginsApi {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// List all plugin triggers
|
||||||
|
///
|
||||||
|
/// Retrieve a list of all available plugin triggers.
|
||||||
|
///
|
||||||
|
/// Note: This method returns the HTTP [Response].
|
||||||
|
Future<Response> getPluginTriggersWithHttpInfo() async {
|
||||||
|
// ignore: prefer_const_declarations
|
||||||
|
final apiPath = r'/plugins/triggers';
|
||||||
|
|
||||||
|
// ignore: prefer_final_locals
|
||||||
|
Object? postBody;
|
||||||
|
|
||||||
|
final queryParams = <QueryParam>[];
|
||||||
|
final headerParams = <String, String>{};
|
||||||
|
final formParams = <String, String>{};
|
||||||
|
|
||||||
|
const contentTypes = <String>[];
|
||||||
|
|
||||||
|
|
||||||
|
return apiClient.invokeAPI(
|
||||||
|
apiPath,
|
||||||
|
'GET',
|
||||||
|
queryParams,
|
||||||
|
postBody,
|
||||||
|
headerParams,
|
||||||
|
formParams,
|
||||||
|
contentTypes.isEmpty ? null : contentTypes.first,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List all plugin triggers
|
||||||
|
///
|
||||||
|
/// Retrieve a list of all available plugin triggers.
|
||||||
|
Future<List<PluginTriggerResponseDto>?> getPluginTriggers() async {
|
||||||
|
final response = await getPluginTriggersWithHttpInfo();
|
||||||
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
|
}
|
||||||
|
// When a remote server returns no body with a status of 204, we shall not decode it.
|
||||||
|
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
|
||||||
|
// FormatException when trying to decode an empty string.
|
||||||
|
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
|
||||||
|
final responseBody = await _decodeBodyBytes(response);
|
||||||
|
return (await apiClient.deserializeAsync(responseBody, 'List<PluginTriggerResponseDto>') as List)
|
||||||
|
.cast<PluginTriggerResponseDto>()
|
||||||
|
.toList(growable: false);
|
||||||
|
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/// List all plugins
|
/// List all plugins
|
||||||
///
|
///
|
||||||
/// Retrieve a list of plugins available to the authenticated user.
|
/// Retrieve a list of plugins available to the authenticated user.
|
||||||
@@ -123,55 +174,4 @@ class PluginsApi {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// List all plugin triggers
|
|
||||||
///
|
|
||||||
/// Retrieve a list of all available plugin triggers.
|
|
||||||
///
|
|
||||||
/// Note: This method returns the HTTP [Response].
|
|
||||||
Future<Response> getTriggersWithHttpInfo() async {
|
|
||||||
// ignore: prefer_const_declarations
|
|
||||||
final apiPath = r'/plugins/triggers';
|
|
||||||
|
|
||||||
// ignore: prefer_final_locals
|
|
||||||
Object? postBody;
|
|
||||||
|
|
||||||
final queryParams = <QueryParam>[];
|
|
||||||
final headerParams = <String, String>{};
|
|
||||||
final formParams = <String, String>{};
|
|
||||||
|
|
||||||
const contentTypes = <String>[];
|
|
||||||
|
|
||||||
|
|
||||||
return apiClient.invokeAPI(
|
|
||||||
apiPath,
|
|
||||||
'GET',
|
|
||||||
queryParams,
|
|
||||||
postBody,
|
|
||||||
headerParams,
|
|
||||||
formParams,
|
|
||||||
contentTypes.isEmpty ? null : contentTypes.first,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// List all plugin triggers
|
|
||||||
///
|
|
||||||
/// Retrieve a list of all available plugin triggers.
|
|
||||||
Future<List<PluginTriggerResponseDto>?> getTriggers() async {
|
|
||||||
final response = await getTriggersWithHttpInfo();
|
|
||||||
if (response.statusCode >= HttpStatus.badRequest) {
|
|
||||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
|
||||||
}
|
|
||||||
// When a remote server returns no body with a status of 204, we shall not decode it.
|
|
||||||
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
|
|
||||||
// FormatException when trying to decode an empty string.
|
|
||||||
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
|
|
||||||
final responseBody = await _decodeBodyBytes(response);
|
|
||||||
return (await apiClient.deserializeAsync(responseBody, 'List<PluginTriggerResponseDto>') as List)
|
|
||||||
.cast<PluginTriggerResponseDto>()
|
|
||||||
.toList(growable: false);
|
|
||||||
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
4
mobile/openapi/lib/api_client.dart
generated
4
mobile/openapi/lib/api_client.dart
generated
@@ -482,8 +482,8 @@ class ApiClient {
|
|||||||
return PlacesResponseDto.fromJson(value);
|
return PlacesResponseDto.fromJson(value);
|
||||||
case 'PluginActionResponseDto':
|
case 'PluginActionResponseDto':
|
||||||
return PluginActionResponseDto.fromJson(value);
|
return PluginActionResponseDto.fromJson(value);
|
||||||
case 'PluginContext':
|
case 'PluginContextType':
|
||||||
return PluginContextTypeTransformer().decode(value);
|
return PluginContextTypeTypeTransformer().decode(value);
|
||||||
case 'PluginFilterResponseDto':
|
case 'PluginFilterResponseDto':
|
||||||
return PluginFilterResponseDto.fromJson(value);
|
return PluginFilterResponseDto.fromJson(value);
|
||||||
case 'PluginResponseDto':
|
case 'PluginResponseDto':
|
||||||
|
|||||||
4
mobile/openapi/lib/api_helper.dart
generated
4
mobile/openapi/lib/api_helper.dart
generated
@@ -127,8 +127,8 @@ String parameterToString(dynamic value) {
|
|||||||
if (value is Permission) {
|
if (value is Permission) {
|
||||||
return PermissionTypeTransformer().encode(value).toString();
|
return PermissionTypeTransformer().encode(value).toString();
|
||||||
}
|
}
|
||||||
if (value is PluginContext) {
|
if (value is PluginContextType) {
|
||||||
return PluginContextTypeTransformer().encode(value).toString();
|
return PluginContextTypeTypeTransformer().encode(value).toString();
|
||||||
}
|
}
|
||||||
if (value is PluginTriggerType) {
|
if (value is PluginTriggerType) {
|
||||||
return PluginTriggerTypeTypeTransformer().encode(value).toString();
|
return PluginTriggerTypeTypeTransformer().encode(value).toString();
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class PluginActionResponseDto {
|
|||||||
|
|
||||||
Object? schema;
|
Object? schema;
|
||||||
|
|
||||||
List<PluginContext> supportedContexts;
|
List<PluginContextType> supportedContexts;
|
||||||
|
|
||||||
String title;
|
String title;
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ class PluginActionResponseDto {
|
|||||||
methodName: mapValueOfType<String>(json, r'methodName')!,
|
methodName: mapValueOfType<String>(json, r'methodName')!,
|
||||||
pluginId: mapValueOfType<String>(json, r'pluginId')!,
|
pluginId: mapValueOfType<String>(json, r'pluginId')!,
|
||||||
schema: mapValueOfType<Object>(json, r'schema'),
|
schema: mapValueOfType<Object>(json, r'schema'),
|
||||||
supportedContexts: PluginContext.listFromJson(json[r'supportedContexts']),
|
supportedContexts: PluginContextType.listFromJson(json[r'supportedContexts']),
|
||||||
title: mapValueOfType<String>(json, r'title')!,
|
title: mapValueOfType<String>(json, r'title')!,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,9 +11,9 @@
|
|||||||
part of openapi.api;
|
part of openapi.api;
|
||||||
|
|
||||||
|
|
||||||
class PluginContext {
|
class PluginContextType {
|
||||||
/// Instantiate a new enum with the provided [value].
|
/// Instantiate a new enum with the provided [value].
|
||||||
const PluginContext._(this.value);
|
const PluginContextType._(this.value);
|
||||||
|
|
||||||
/// The underlying value of this enum member.
|
/// The underlying value of this enum member.
|
||||||
final String value;
|
final String value;
|
||||||
@@ -23,24 +23,24 @@ class PluginContext {
|
|||||||
|
|
||||||
String toJson() => value;
|
String toJson() => value;
|
||||||
|
|
||||||
static const asset = PluginContext._(r'asset');
|
static const asset = PluginContextType._(r'asset');
|
||||||
static const album = PluginContext._(r'album');
|
static const album = PluginContextType._(r'album');
|
||||||
static const person = PluginContext._(r'person');
|
static const person = PluginContextType._(r'person');
|
||||||
|
|
||||||
/// List of all possible values in this [enum][PluginContext].
|
/// List of all possible values in this [enum][PluginContextType].
|
||||||
static const values = <PluginContext>[
|
static const values = <PluginContextType>[
|
||||||
asset,
|
asset,
|
||||||
album,
|
album,
|
||||||
person,
|
person,
|
||||||
];
|
];
|
||||||
|
|
||||||
static PluginContext? fromJson(dynamic value) => PluginContextTypeTransformer().decode(value);
|
static PluginContextType? fromJson(dynamic value) => PluginContextTypeTypeTransformer().decode(value);
|
||||||
|
|
||||||
static List<PluginContext> listFromJson(dynamic json, {bool growable = false,}) {
|
static List<PluginContextType> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
final result = <PluginContext>[];
|
final result = <PluginContextType>[];
|
||||||
if (json is List && json.isNotEmpty) {
|
if (json is List && json.isNotEmpty) {
|
||||||
for (final row in json) {
|
for (final row in json) {
|
||||||
final value = PluginContext.fromJson(row);
|
final value = PluginContextType.fromJson(row);
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
result.add(value);
|
result.add(value);
|
||||||
}
|
}
|
||||||
@@ -50,16 +50,16 @@ class PluginContext {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Transformation class that can [encode] an instance of [PluginContext] to String,
|
/// Transformation class that can [encode] an instance of [PluginContextType] to String,
|
||||||
/// and [decode] dynamic data back to [PluginContext].
|
/// and [decode] dynamic data back to [PluginContextType].
|
||||||
class PluginContextTypeTransformer {
|
class PluginContextTypeTypeTransformer {
|
||||||
factory PluginContextTypeTransformer() => _instance ??= const PluginContextTypeTransformer._();
|
factory PluginContextTypeTypeTransformer() => _instance ??= const PluginContextTypeTypeTransformer._();
|
||||||
|
|
||||||
const PluginContextTypeTransformer._();
|
const PluginContextTypeTypeTransformer._();
|
||||||
|
|
||||||
String encode(PluginContext data) => data.value;
|
String encode(PluginContextType data) => data.value;
|
||||||
|
|
||||||
/// Decodes a [dynamic value][data] to a PluginContext.
|
/// Decodes a [dynamic value][data] to a PluginContextType.
|
||||||
///
|
///
|
||||||
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
|
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
|
||||||
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
|
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
|
||||||
@@ -67,12 +67,12 @@ class PluginContextTypeTransformer {
|
|||||||
///
|
///
|
||||||
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
|
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
|
||||||
/// and users are still using an old app with the old code.
|
/// and users are still using an old app with the old code.
|
||||||
PluginContext? decode(dynamic data, {bool allowNull = true}) {
|
PluginContextType? decode(dynamic data, {bool allowNull = true}) {
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
switch (data) {
|
switch (data) {
|
||||||
case r'asset': return PluginContext.asset;
|
case r'asset': return PluginContextType.asset;
|
||||||
case r'album': return PluginContext.album;
|
case r'album': return PluginContextType.album;
|
||||||
case r'person': return PluginContext.person;
|
case r'person': return PluginContextType.person;
|
||||||
default:
|
default:
|
||||||
if (!allowNull) {
|
if (!allowNull) {
|
||||||
throw ArgumentError('Unknown enum value to decode: $data');
|
throw ArgumentError('Unknown enum value to decode: $data');
|
||||||
@@ -82,7 +82,7 @@ class PluginContextTypeTransformer {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Singleton [PluginContextTypeTransformer] instance.
|
/// Singleton [PluginContextTypeTypeTransformer] instance.
|
||||||
static PluginContextTypeTransformer? _instance;
|
static PluginContextTypeTypeTransformer? _instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ class PluginFilterResponseDto {
|
|||||||
|
|
||||||
Object? schema;
|
Object? schema;
|
||||||
|
|
||||||
List<PluginContext> supportedContexts;
|
List<PluginContextType> supportedContexts;
|
||||||
|
|
||||||
String title;
|
String title;
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ class PluginFilterResponseDto {
|
|||||||
methodName: mapValueOfType<String>(json, r'methodName')!,
|
methodName: mapValueOfType<String>(json, r'methodName')!,
|
||||||
pluginId: mapValueOfType<String>(json, r'pluginId')!,
|
pluginId: mapValueOfType<String>(json, r'pluginId')!,
|
||||||
schema: mapValueOfType<Object>(json, r'schema'),
|
schema: mapValueOfType<Object>(json, r'schema'),
|
||||||
supportedContexts: PluginContext.listFromJson(json[r'supportedContexts']),
|
supportedContexts: PluginContextType.listFromJson(json[r'supportedContexts']),
|
||||||
title: mapValueOfType<String>(json, r'title')!,
|
title: mapValueOfType<String>(json, r'title')!,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,44 +13,44 @@ part of openapi.api;
|
|||||||
class PluginTriggerResponseDto {
|
class PluginTriggerResponseDto {
|
||||||
/// Returns a new [PluginTriggerResponseDto] instance.
|
/// Returns a new [PluginTriggerResponseDto] instance.
|
||||||
PluginTriggerResponseDto({
|
PluginTriggerResponseDto({
|
||||||
required this.context,
|
required this.contextType,
|
||||||
required this.description,
|
required this.description,
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.triggerType,
|
required this.type,
|
||||||
});
|
});
|
||||||
|
|
||||||
PluginContext context;
|
PluginContextType contextType;
|
||||||
|
|
||||||
String description;
|
String description;
|
||||||
|
|
||||||
String name;
|
String name;
|
||||||
|
|
||||||
PluginTriggerType triggerType;
|
PluginTriggerType type;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) => identical(this, other) || other is PluginTriggerResponseDto &&
|
bool operator ==(Object other) => identical(this, other) || other is PluginTriggerResponseDto &&
|
||||||
other.context == context &&
|
other.contextType == contextType &&
|
||||||
other.description == description &&
|
other.description == description &&
|
||||||
other.name == name &&
|
other.name == name &&
|
||||||
other.triggerType == triggerType;
|
other.type == type;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
// ignore: unnecessary_parenthesis
|
// ignore: unnecessary_parenthesis
|
||||||
(context.hashCode) +
|
(contextType.hashCode) +
|
||||||
(description.hashCode) +
|
(description.hashCode) +
|
||||||
(name.hashCode) +
|
(name.hashCode) +
|
||||||
(triggerType.hashCode);
|
(type.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'PluginTriggerResponseDto[context=$context, description=$description, name=$name, triggerType=$triggerType]';
|
String toString() => 'PluginTriggerResponseDto[contextType=$contextType, description=$description, name=$name, type=$type]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
json[r'context'] = this.context;
|
json[r'contextType'] = this.contextType;
|
||||||
json[r'description'] = this.description;
|
json[r'description'] = this.description;
|
||||||
json[r'name'] = this.name;
|
json[r'name'] = this.name;
|
||||||
json[r'triggerType'] = this.triggerType;
|
json[r'type'] = this.type;
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,10 +63,10 @@ class PluginTriggerResponseDto {
|
|||||||
final json = value.cast<String, dynamic>();
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
return PluginTriggerResponseDto(
|
return PluginTriggerResponseDto(
|
||||||
context: PluginContext.fromJson(json[r'context'])!,
|
contextType: PluginContextType.fromJson(json[r'contextType'])!,
|
||||||
description: mapValueOfType<String>(json, r'description')!,
|
description: mapValueOfType<String>(json, r'description')!,
|
||||||
name: mapValueOfType<String>(json, r'name')!,
|
name: mapValueOfType<String>(json, r'name')!,
|
||||||
triggerType: PluginTriggerType.fromJson(json[r'triggerType'])!,
|
type: PluginTriggerType.fromJson(json[r'type'])!,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -114,10 +114,10 @@ class PluginTriggerResponseDto {
|
|||||||
|
|
||||||
/// The list of required keys that must be present in a JSON.
|
/// The list of required keys that must be present in a JSON.
|
||||||
static const requiredKeys = <String>{
|
static const requiredKeys = <String>{
|
||||||
'context',
|
'contextType',
|
||||||
'description',
|
'description',
|
||||||
'name',
|
'name',
|
||||||
'triggerType',
|
'type',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8023,7 +8023,7 @@
|
|||||||
"/plugins/triggers": {
|
"/plugins/triggers": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Retrieve a list of all available plugin triggers.",
|
"description": "Retrieve a list of all available plugin triggers.",
|
||||||
"operationId": "getTriggers",
|
"operationId": "getPluginTriggers",
|
||||||
"parameters": [],
|
"parameters": [],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
@@ -18331,7 +18331,7 @@
|
|||||||
},
|
},
|
||||||
"supportedContexts": {
|
"supportedContexts": {
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/components/schemas/PluginContext"
|
"$ref": "#/components/schemas/PluginContextType"
|
||||||
},
|
},
|
||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
@@ -18350,7 +18350,7 @@
|
|||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"PluginContext": {
|
"PluginContextType": {
|
||||||
"enum": [
|
"enum": [
|
||||||
"asset",
|
"asset",
|
||||||
"album",
|
"album",
|
||||||
@@ -18378,7 +18378,7 @@
|
|||||||
},
|
},
|
||||||
"supportedContexts": {
|
"supportedContexts": {
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/components/schemas/PluginContext"
|
"$ref": "#/components/schemas/PluginContextType"
|
||||||
},
|
},
|
||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
@@ -18452,10 +18452,10 @@
|
|||||||
},
|
},
|
||||||
"PluginTriggerResponseDto": {
|
"PluginTriggerResponseDto": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"context": {
|
"contextType": {
|
||||||
"allOf": [
|
"allOf": [
|
||||||
{
|
{
|
||||||
"$ref": "#/components/schemas/PluginContext"
|
"$ref": "#/components/schemas/PluginContextType"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -18465,7 +18465,7 @@
|
|||||||
"name": {
|
"name": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"triggerType": {
|
"type": {
|
||||||
"allOf": [
|
"allOf": [
|
||||||
{
|
{
|
||||||
"$ref": "#/components/schemas/PluginTriggerType"
|
"$ref": "#/components/schemas/PluginTriggerType"
|
||||||
@@ -18474,10 +18474,10 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"context",
|
"contextType",
|
||||||
"description",
|
"description",
|
||||||
"name",
|
"name",
|
||||||
"triggerType"
|
"type"
|
||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -942,7 +942,7 @@ export type PluginActionResponseDto = {
|
|||||||
methodName: string;
|
methodName: string;
|
||||||
pluginId: string;
|
pluginId: string;
|
||||||
schema: object | null;
|
schema: object | null;
|
||||||
supportedContexts: PluginContext[];
|
supportedContexts: PluginContextType[];
|
||||||
title: string;
|
title: string;
|
||||||
};
|
};
|
||||||
export type PluginFilterResponseDto = {
|
export type PluginFilterResponseDto = {
|
||||||
@@ -951,7 +951,7 @@ export type PluginFilterResponseDto = {
|
|||||||
methodName: string;
|
methodName: string;
|
||||||
pluginId: string;
|
pluginId: string;
|
||||||
schema: object | null;
|
schema: object | null;
|
||||||
supportedContexts: PluginContext[];
|
supportedContexts: PluginContextType[];
|
||||||
title: string;
|
title: string;
|
||||||
};
|
};
|
||||||
export type PluginResponseDto = {
|
export type PluginResponseDto = {
|
||||||
@@ -967,10 +967,10 @@ export type PluginResponseDto = {
|
|||||||
version: string;
|
version: string;
|
||||||
};
|
};
|
||||||
export type PluginTriggerResponseDto = {
|
export type PluginTriggerResponseDto = {
|
||||||
context: PluginContext;
|
contextType: PluginContextType;
|
||||||
description: string;
|
description: string;
|
||||||
name: string;
|
name: string;
|
||||||
triggerType: PluginTriggerType;
|
"type": PluginTriggerType;
|
||||||
};
|
};
|
||||||
export type QueueResponseDto = {
|
export type QueueResponseDto = {
|
||||||
isPaused: boolean;
|
isPaused: boolean;
|
||||||
@@ -3666,7 +3666,7 @@ export function getPlugins(opts?: Oazapfts.RequestOpts) {
|
|||||||
/**
|
/**
|
||||||
* List all plugin triggers
|
* List all plugin triggers
|
||||||
*/
|
*/
|
||||||
export function getTriggers(opts?: Oazapfts.RequestOpts) {
|
export function getPluginTriggers(opts?: Oazapfts.RequestOpts) {
|
||||||
return oazapfts.ok(oazapfts.fetchJson<{
|
return oazapfts.ok(oazapfts.fetchJson<{
|
||||||
status: 200;
|
status: 200;
|
||||||
data: PluginTriggerResponseDto[];
|
data: PluginTriggerResponseDto[];
|
||||||
@@ -5436,7 +5436,7 @@ export enum PartnerDirection {
|
|||||||
SharedBy = "shared-by",
|
SharedBy = "shared-by",
|
||||||
SharedWith = "shared-with"
|
SharedWith = "shared-with"
|
||||||
}
|
}
|
||||||
export enum PluginContext {
|
export enum PluginContextType {
|
||||||
Asset = "asset",
|
Asset = "asset",
|
||||||
Album = "album",
|
Album = "album",
|
||||||
Person = "person"
|
Person = "person"
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export class PluginController {
|
|||||||
description: 'Retrieve a list of all available plugin triggers.',
|
description: 'Retrieve a list of all available plugin triggers.',
|
||||||
history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'),
|
history: new HistoryBuilder().added('v2.3.0').alpha('v2.3.0'),
|
||||||
})
|
})
|
||||||
getTriggers(): PluginTriggerResponseDto[] {
|
getPluginTriggers(): PluginTriggerResponseDto[] {
|
||||||
return this.service.getTriggers();
|
return this.service.getTriggers();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import { IsNotEmpty, IsString } from 'class-validator';
|
import { IsNotEmpty, IsString } from 'class-validator';
|
||||||
import { PluginAction, PluginFilter } from 'src/database';
|
import { PluginAction, PluginFilter } from 'src/database';
|
||||||
import { PluginContext, PluginTriggerType } from 'src/enum';
|
import { PluginContext as PluginContextType, PluginTriggerType } from 'src/enum';
|
||||||
import type { JSONSchema } from 'src/types/plugin-schema.types';
|
import type { JSONSchema } from 'src/types/plugin-schema.types';
|
||||||
import { ValidateEnum } from 'src/validation';
|
import { ValidateEnum } from 'src/validation';
|
||||||
|
|
||||||
export class PluginTriggerResponseDto {
|
export class PluginTriggerResponseDto {
|
||||||
name!: string;
|
name!: string;
|
||||||
@ValidateEnum({ enum: PluginTriggerType, name: 'PluginTriggerType' })
|
@ValidateEnum({ enum: PluginTriggerType, name: 'PluginTriggerType' })
|
||||||
triggerType!: PluginTriggerType;
|
type!: PluginTriggerType;
|
||||||
description!: string;
|
description!: string;
|
||||||
@ValidateEnum({ enum: PluginContext, name: 'PluginContext' })
|
@ValidateEnum({ enum: PluginContextType, name: 'PluginContextType' })
|
||||||
context!: PluginContext;
|
contextType!: PluginContextType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PluginResponseDto {
|
export class PluginResponseDto {
|
||||||
@@ -33,8 +33,8 @@ export class PluginFilterResponseDto {
|
|||||||
title!: string;
|
title!: string;
|
||||||
description!: string;
|
description!: string;
|
||||||
|
|
||||||
@ValidateEnum({ enum: PluginContext, name: 'PluginContext' })
|
@ValidateEnum({ enum: PluginContextType, name: 'PluginContextType' })
|
||||||
supportedContexts!: PluginContext[];
|
supportedContexts!: PluginContextType[];
|
||||||
schema!: JSONSchema | null;
|
schema!: JSONSchema | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,8 +45,8 @@ export class PluginActionResponseDto {
|
|||||||
title!: string;
|
title!: string;
|
||||||
description!: string;
|
description!: string;
|
||||||
|
|
||||||
@ValidateEnum({ enum: PluginContext, name: 'PluginContext' })
|
@ValidateEnum({ enum: PluginContextType, name: 'PluginContextType' })
|
||||||
supportedContexts!: PluginContext[];
|
supportedContexts!: PluginContextType[];
|
||||||
schema!: JSONSchema | null;
|
schema!: JSONSchema | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,22 +2,22 @@ import { PluginContext, PluginTriggerType } from 'src/enum';
|
|||||||
|
|
||||||
export type PluginTrigger = {
|
export type PluginTrigger = {
|
||||||
name: string;
|
name: string;
|
||||||
triggerType: PluginTriggerType;
|
type: PluginTriggerType;
|
||||||
description: string;
|
description: string;
|
||||||
context: PluginContext;
|
contextType: PluginContext;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const pluginTriggers: PluginTrigger[] = [
|
export const pluginTriggers: PluginTrigger[] = [
|
||||||
{
|
{
|
||||||
name: 'Asset Uploaded',
|
name: 'Asset Uploaded',
|
||||||
triggerType: PluginTriggerType.AssetCreate,
|
type: PluginTriggerType.AssetCreate,
|
||||||
description: 'Triggered when a new asset is uploaded',
|
description: 'Triggered when a new asset is uploaded',
|
||||||
context: PluginContext.Asset,
|
contextType: PluginContext.Asset,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Person Recognized',
|
name: 'Person Recognized',
|
||||||
triggerType: PluginTriggerType.PersonRecognized,
|
type: PluginTriggerType.PersonRecognized,
|
||||||
description: 'Triggered when a person is detected',
|
description: 'Triggered when a person is detected',
|
||||||
context: PluginContext.Person,
|
contextType: PluginContext.Person,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ from
|
|||||||
"workflow"
|
"workflow"
|
||||||
where
|
where
|
||||||
"id" = $1
|
"id" = $1
|
||||||
|
order by
|
||||||
|
"createdAt" desc
|
||||||
|
|
||||||
-- WorkflowRepository.getWorkflowsByOwner
|
-- WorkflowRepository.getWorkflowsByOwner
|
||||||
select
|
select
|
||||||
@@ -16,7 +18,7 @@ from
|
|||||||
where
|
where
|
||||||
"ownerId" = $1
|
"ownerId" = $1
|
||||||
order by
|
order by
|
||||||
"name"
|
"createdAt" desc
|
||||||
|
|
||||||
-- WorkflowRepository.getWorkflowsByTrigger
|
-- WorkflowRepository.getWorkflowsByTrigger
|
||||||
select
|
select
|
||||||
|
|||||||
@@ -12,12 +12,22 @@ export class WorkflowRepository {
|
|||||||
|
|
||||||
@GenerateSql({ params: [DummyValue.UUID] })
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
getWorkflow(id: string) {
|
getWorkflow(id: string) {
|
||||||
return this.db.selectFrom('workflow').selectAll().where('id', '=', id).executeTakeFirst();
|
return this.db
|
||||||
|
.selectFrom('workflow')
|
||||||
|
.selectAll()
|
||||||
|
.where('id', '=', id)
|
||||||
|
.orderBy('createdAt', 'desc')
|
||||||
|
.executeTakeFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GenerateSql({ params: [DummyValue.UUID] })
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
getWorkflowsByOwner(ownerId: string) {
|
getWorkflowsByOwner(ownerId: string) {
|
||||||
return this.db.selectFrom('workflow').selectAll().where('ownerId', '=', ownerId).orderBy('name').execute();
|
return this.db
|
||||||
|
.selectFrom('workflow')
|
||||||
|
.selectAll()
|
||||||
|
.where('ownerId', '=', ownerId)
|
||||||
|
.orderBy('createdAt', 'desc')
|
||||||
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GenerateSql({ params: [PluginTriggerType.AssetCreate] })
|
@GenerateSql({ params: [PluginTriggerType.AssetCreate] })
|
||||||
|
|||||||
@@ -116,12 +116,12 @@ export class PluginService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async loadPluginToDatabase(manifest: PluginManifestDto, basePath: string): Promise<void> {
|
private async loadPluginToDatabase(manifest: PluginManifestDto, basePath: string): Promise<void> {
|
||||||
// const currentPlugin = await this.pluginRepository.getPluginByName(manifest.name);
|
const currentPlugin = await this.pluginRepository.getPluginByName(manifest.name);
|
||||||
// if (currentPlugin != null && currentPlugin.version === manifest.version) {
|
if (currentPlugin != null && currentPlugin.version === manifest.version) {
|
||||||
// this.logger.log(`Plugin ${manifest.name} is up to date (version ${manifest.version}). Skipping`);
|
this.logger.log(`Plugin ${manifest.name} is up to date (version ${manifest.version}). Skipping`);
|
||||||
// return;
|
return;
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
const { plugin, filters, actions } = await this.pluginRepository.loadPlugin(manifest, basePath);
|
const { plugin, filters, actions } = await this.pluginRepository.loadPlugin(manifest, basePath);
|
||||||
|
|
||||||
this.logger.log(`Upserted plugin: ${plugin.name} (ID: ${plugin.id}, version: ${plugin.version})`);
|
this.logger.log(`Upserted plugin: ${plugin.name} (ID: ${plugin.id}, version: ${plugin.version})`);
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ import { BaseService } from 'src/services/base.service';
|
|||||||
@Injectable()
|
@Injectable()
|
||||||
export class WorkflowService extends BaseService {
|
export class WorkflowService extends BaseService {
|
||||||
async create(auth: AuthDto, dto: WorkflowCreateDto): Promise<WorkflowResponseDto> {
|
async create(auth: AuthDto, dto: WorkflowCreateDto): Promise<WorkflowResponseDto> {
|
||||||
const trigger = this.getTriggerOrFail(dto.triggerType);
|
const context = this.getContextForTrigger(dto.triggerType);
|
||||||
|
|
||||||
const filterInserts = await this.validateAndMapFilters(dto.filters, trigger.context);
|
const filterInserts = await this.validateAndMapFilters(dto.filters, context);
|
||||||
const actionInserts = await this.validateAndMapActions(dto.actions, trigger.context);
|
const actionInserts = await this.validateAndMapActions(dto.actions, context);
|
||||||
|
|
||||||
const workflow = await this.workflowRepository.createWorkflow(
|
const workflow = await this.workflowRepository.createWorkflow(
|
||||||
{
|
{
|
||||||
@@ -56,11 +56,11 @@ export class WorkflowService extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const workflow = await this.findOrFail(id);
|
const workflow = await this.findOrFail(id);
|
||||||
const trigger = this.getTriggerOrFail(dto.triggerType ?? workflow.triggerType);
|
const context = this.getContextForTrigger(dto.triggerType ?? workflow.triggerType);
|
||||||
|
|
||||||
const { filters, actions, ...workflowUpdate } = dto;
|
const { filters, actions, ...workflowUpdate } = dto;
|
||||||
const filterInserts = filters && (await this.validateAndMapFilters(filters, trigger.context));
|
const filterInserts = filters && (await this.validateAndMapFilters(filters, context));
|
||||||
const actionInserts = actions && (await this.validateAndMapActions(actions, trigger.context));
|
const actionInserts = actions && (await this.validateAndMapActions(actions, context));
|
||||||
|
|
||||||
const updatedWorkflow = await this.workflowRepository.updateWorkflow(
|
const updatedWorkflow = await this.workflowRepository.updateWorkflow(
|
||||||
id,
|
id,
|
||||||
@@ -124,12 +124,12 @@ export class WorkflowService extends BaseService {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
private getTriggerOrFail(triggerType: PluginTriggerType) {
|
private getContextForTrigger(type: PluginTriggerType) {
|
||||||
const trigger = pluginTriggers.find((t) => t.triggerType === triggerType);
|
const trigger = pluginTriggers.find((t) => t.type === type);
|
||||||
if (!trigger) {
|
if (!trigger) {
|
||||||
throw new BadRequestException(`Invalid trigger type: ${triggerType}`);
|
throw new BadRequestException(`Invalid trigger type: ${type}`);
|
||||||
}
|
}
|
||||||
return trigger;
|
return trigger.contextType;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async findOrFail(id: string) {
|
private async findOrFail(id: string) {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import AlbumPickerModal from '$lib/modals/AlbumPickerModal.svelte';
|
import AlbumPickerModal from '$lib/modals/AlbumPickerModal.svelte';
|
||||||
import PeoplePickerModal from '$lib/modals/PeoplePickerModal.svelte';
|
import PeoplePickerModal from '$lib/modals/PeoplePickerModal.svelte';
|
||||||
import { getAssetThumbnailUrl, getPeopleThumbnailUrl } from '$lib/utils';
|
import { getAssetThumbnailUrl, getPeopleThumbnailUrl } from '$lib/utils';
|
||||||
import { formatLabel, getComponentFromSchema } from '$lib/utils/workflow';
|
import { formatLabel, getComponentFromSchema, type ComponentConfig } from '$lib/utils/workflow';
|
||||||
import { getAlbumInfo, getPerson, type AlbumResponseDto, type PersonResponseDto } from '@immich/sdk';
|
import { getAlbumInfo, getPerson, type AlbumResponseDto, type PersonResponseDto } from '@immich/sdk';
|
||||||
import { Button, Field, Input, MultiSelect, Select, Switch, Text, modalManager, type SelectItem } from '@immich/ui';
|
import { Button, Field, Input, MultiSelect, Select, Switch, Text, modalManager, type SelectItem } from '@immich/ui';
|
||||||
import { mdiPlus } from '@mdi/js';
|
import { mdiPlus } from '@mdi/js';
|
||||||
@@ -43,56 +43,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchMetadata = async () => {
|
void fetchMetadata(components);
|
||||||
const metadataUpdates: Record<
|
|
||||||
string,
|
|
||||||
AlbumResponseDto | PersonResponseDto | AlbumResponseDto[] | PersonResponseDto[]
|
|
||||||
> = {};
|
|
||||||
|
|
||||||
for (const [key, component] of Object.entries(components)) {
|
|
||||||
const value = actualConfig[key];
|
|
||||||
if (!value || pickerMetadata[key]) {
|
|
||||||
continue; // Skip if no value or already loaded
|
|
||||||
}
|
|
||||||
|
|
||||||
const isAlbumPicker = component.subType === 'album-picker';
|
|
||||||
const isPeoplePicker = component.subType === 'people-picker';
|
|
||||||
|
|
||||||
if (!isAlbumPicker && !isPeoplePicker) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (Array.isArray(value) && value.length > 0) {
|
|
||||||
// Multiple selection
|
|
||||||
if (isAlbumPicker) {
|
|
||||||
const albums = await Promise.all(value.map((id) => getAlbumInfo({ id })));
|
|
||||||
metadataUpdates[key] = albums;
|
|
||||||
} else if (isPeoplePicker) {
|
|
||||||
const people = await Promise.all(value.map((id) => getPerson({ id })));
|
|
||||||
metadataUpdates[key] = people;
|
|
||||||
}
|
|
||||||
} else if (typeof value === 'string' && value) {
|
|
||||||
// Single selection
|
|
||||||
if (isAlbumPicker) {
|
|
||||||
const album = await getAlbumInfo({ id: value });
|
|
||||||
metadataUpdates[key] = album;
|
|
||||||
} else if (isPeoplePicker) {
|
|
||||||
const person = await getPerson({ id: value });
|
|
||||||
metadataUpdates[key] = person;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Failed to fetch metadata for ${key}:`, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Object.keys(metadataUpdates).length > 0) {
|
|
||||||
pickerMetadata = { ...pickerMetadata, ...metadataUpdates };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
void fetchMetadata();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
@@ -148,6 +99,55 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const fetchMetadata = async (components: Record<string, ComponentConfig>) => {
|
||||||
|
const metadataUpdates: Record<
|
||||||
|
string,
|
||||||
|
AlbumResponseDto | PersonResponseDto | AlbumResponseDto[] | PersonResponseDto[]
|
||||||
|
> = {};
|
||||||
|
|
||||||
|
for (const [key, component] of Object.entries(components)) {
|
||||||
|
const value = actualConfig[key];
|
||||||
|
if (!value || pickerMetadata[key]) {
|
||||||
|
continue; // Skip if no value or already loaded
|
||||||
|
}
|
||||||
|
|
||||||
|
const isAlbumPicker = component.subType === 'album-picker';
|
||||||
|
const isPeoplePicker = component.subType === 'people-picker';
|
||||||
|
|
||||||
|
if (!isAlbumPicker && !isPeoplePicker) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (Array.isArray(value) && value.length > 0) {
|
||||||
|
// Multiple selection
|
||||||
|
if (isAlbumPicker) {
|
||||||
|
const albums = await Promise.all(value.map((id) => getAlbumInfo({ id })));
|
||||||
|
metadataUpdates[key] = albums;
|
||||||
|
} else if (isPeoplePicker) {
|
||||||
|
const people = await Promise.all(value.map((id) => getPerson({ id })));
|
||||||
|
metadataUpdates[key] = people;
|
||||||
|
}
|
||||||
|
} else if (typeof value === 'string' && value) {
|
||||||
|
// Single selection
|
||||||
|
if (isAlbumPicker) {
|
||||||
|
const album = await getAlbumInfo({ id: value });
|
||||||
|
metadataUpdates[key] = album;
|
||||||
|
} else if (isPeoplePicker) {
|
||||||
|
const person = await getPerson({ id: value });
|
||||||
|
metadataUpdates[key] = person;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to fetch metadata for ${key}:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Object.keys(metadataUpdates).length > 0) {
|
||||||
|
pickerMetadata = { ...pickerMetadata, ...metadataUpdates };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleAlbumPicker = async (key: string, multiple: boolean) => {
|
const handleAlbumPicker = async (key: string, multiple: boolean) => {
|
||||||
const albums = await modalManager.show(AlbumPickerModal, { shared: false });
|
const albums = await modalManager.show(AlbumPickerModal, { shared: false });
|
||||||
if (albums && albums.length > 0) {
|
if (albums && albums.length > 0) {
|
||||||
@@ -161,7 +161,9 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handlePeoplePicker = async (key: string, multiple: boolean) => {
|
const handlePeoplePicker = async (key: string, multiple: boolean) => {
|
||||||
const people = await modalManager.show(PeoplePickerModal, { multiple });
|
const currentIds = (actualConfig[key] as string[] | undefined) ?? [];
|
||||||
|
const excludedIds = multiple ? currentIds : [];
|
||||||
|
const people = await modalManager.show(PeoplePickerModal, { multiple, excludedIds });
|
||||||
if (people && people.length > 0) {
|
if (people && people.length > 0) {
|
||||||
const value = multiple ? people.map((p) => p.id) : people[0].id;
|
const value = multiple ? people.map((p) => p.id) : people[0].id;
|
||||||
updateConfig(key, value);
|
updateConfig(key, value);
|
||||||
|
|||||||
@@ -63,7 +63,7 @@
|
|||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
<div
|
<div
|
||||||
bind:this={containerEl}
|
bind:this={containerEl}
|
||||||
class="hidden sm:block fixed w-64 z-50 hover:cursor-grab select-none"
|
class="hidden sm:block fixed w-64 hover:cursor-grab select-none"
|
||||||
style="left: {position.x}px; top: {position.y}px;"
|
style="left: {position.x}px; top: {position.y}px;"
|
||||||
class:cursor-grabbing={isDragging}
|
class:cursor-grabbing={isDragging}
|
||||||
onmousedown={handleMouseDown}
|
onmousedown={handleMouseDown}
|
||||||
@@ -112,7 +112,7 @@
|
|||||||
<span class="text-[10px] font-semibold uppercase tracking-wide">{$t('filters')}</span>
|
<span class="text-[10px] font-semibold uppercase tracking-wide">{$t('filters')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-1 pl-5">
|
<div class="space-y-1 pl-5">
|
||||||
{#each filters as filter, index (filter.id)}
|
{#each filters as filter, index (index)}
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span
|
<span
|
||||||
class="shrink-0 h-4 w-4 rounded-full bg-light-200 text-[10px] font-medium flex items-center justify-center"
|
class="shrink-0 h-4 w-4 rounded-full bg-light-200 text-[10px] font-medium flex items-center justify-center"
|
||||||
@@ -138,7 +138,7 @@
|
|||||||
<span class="text-[10px] font-semibold uppercase tracking-wide">{$t('actions')}</span>
|
<span class="text-[10px] font-semibold uppercase tracking-wide">{$t('actions')}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="space-y-1 pl-5">
|
<div class="space-y-1 pl-5">
|
||||||
{#each actions as action, index (action.id)}
|
{#each actions as action, index (index)}
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span
|
<span
|
||||||
class="shrink-0 h-4 w-4 rounded-full bg-light-200 text-[10px] font-medium flex items-center justify-center"
|
class="shrink-0 h-4 w-4 rounded-full bg-light-200 text-[10px] font-medium flex items-center justify-center"
|
||||||
@@ -156,7 +156,7 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="hidden sm:flex fixed right-6 bottom-6 z-50 h-14 w-14 items-center justify-center rounded-full bg-primary text-light shadow-lg hover:bg-primary/90 transition-colors"
|
class="hidden sm:flex fixed right-6 bottom-6 h-14 w-14 items-center justify-center rounded-full bg-primary text-light shadow-lg hover:bg-primary/90 transition-colors"
|
||||||
title={$t('workflow_summary')}
|
title={$t('workflow_summary')}
|
||||||
onclick={() => (isOpen = true)}
|
onclick={() => (isOpen = true)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -39,12 +39,12 @@
|
|||||||
? 'bg-primary text-light'
|
? 'bg-primary text-light'
|
||||||
: 'text-light-100 bg-light-300 group-hover:bg-light-500'}"
|
: 'text-light-100 bg-light-300 group-hover:bg-light-500'}"
|
||||||
>
|
>
|
||||||
<Icon icon={getTriggerIcon(trigger.triggerType)} size="24" />
|
<Icon icon={getTriggerIcon(trigger.type)} size="24" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<Text class="font-semibold mb-1">{trigger.name}</Text>
|
<Text class="font-semibold mb-1">{trigger.name}</Text>
|
||||||
{#if trigger.description}
|
{#if trigger.description}
|
||||||
<Text class="text-sm">{trigger.description}</Text>
|
<Text size="small">{trigger.description}</Text>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ export enum AppRoute {
|
|||||||
LARGE_FILES = '/utilities/large-files',
|
LARGE_FILES = '/utilities/large-files',
|
||||||
GEOLOCATION = '/utilities/geolocation',
|
GEOLOCATION = '/utilities/geolocation',
|
||||||
WORKFLOWS = '/utilities/workflows',
|
WORKFLOWS = '/utilities/workflows',
|
||||||
WORKFLOWS_EDIT = '/utilities/workflows/edit',
|
|
||||||
|
|
||||||
FOLDERS = '/folders',
|
FOLDERS = '/folders',
|
||||||
TAGS = '/tags',
|
TAGS = '/tags',
|
||||||
|
|||||||
@@ -1,23 +1,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PluginActionResponseDto, PluginFilterResponseDto } from '@immich/sdk';
|
import type { PluginActionResponseDto, PluginFilterResponseDto } from '@immich/sdk';
|
||||||
import { Icon, Modal, ModalBody } from '@immich/ui';
|
import { Icon, Modal, ModalBody, Text } from '@immich/ui';
|
||||||
import { mdiFilterOutline, mdiPlayCircleOutline } from '@mdi/js';
|
import { mdiFilterOutline, mdiPlayCircleOutline } from '@mdi/js';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
filters: PluginFilterResponseDto[];
|
filters: PluginFilterResponseDto[];
|
||||||
actions: PluginActionResponseDto[];
|
actions: PluginActionResponseDto[];
|
||||||
addedFilters?: PluginFilterResponseDto[];
|
|
||||||
addedActions?: PluginActionResponseDto[];
|
|
||||||
onClose: (result?: { type: 'filter' | 'action'; item: PluginFilterResponseDto | PluginActionResponseDto }) => void;
|
onClose: (result?: { type: 'filter' | 'action'; item: PluginFilterResponseDto | PluginActionResponseDto }) => void;
|
||||||
type?: 'filter' | 'action';
|
type?: 'filter' | 'action';
|
||||||
}
|
}
|
||||||
|
|
||||||
let { filters, actions, addedFilters = [], addedActions = [], onClose, type }: Props = $props();
|
let { filters, actions, onClose, type }: Props = $props();
|
||||||
|
|
||||||
// Filter out already-added items
|
|
||||||
const availableFilters = $derived(filters.filter((f) => !addedFilters.some((af) => af.id === f.id)));
|
|
||||||
const availableActions = $derived(actions.filter((a) => !addedActions.some((aa) => aa.id === a.id)));
|
|
||||||
|
|
||||||
type StepType = 'filter' | 'action';
|
type StepType = 'filter' | 'action';
|
||||||
|
|
||||||
@@ -30,7 +24,7 @@
|
|||||||
<ModalBody>
|
<ModalBody>
|
||||||
<div class="space-y-6">
|
<div class="space-y-6">
|
||||||
<!-- Filters Section -->
|
<!-- Filters Section -->
|
||||||
{#if availableFilters.length > 0 && (!type || type === 'filter')}
|
{#if filters.length > 0 && (!type || type === 'filter')}
|
||||||
<div class="flex items-center gap-2 mb-3">
|
<div class="flex items-center gap-2 mb-3">
|
||||||
<div class="h-6 w-6 rounded-md bg-warning-100 flex items-center justify-center">
|
<div class="h-6 w-6 rounded-md bg-warning-100 flex items-center justify-center">
|
||||||
<Icon icon={mdiFilterOutline} size="16" class="text-warning" />
|
<Icon icon={mdiFilterOutline} size="16" class="text-warning" />
|
||||||
@@ -38,7 +32,7 @@
|
|||||||
<h3 class="text-sm font-semibold">Filters</h3>
|
<h3 class="text-sm font-semibold">Filters</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-1 gap-2">
|
<div class="grid grid-cols-1 gap-2">
|
||||||
{#each availableFilters as filter (filter.id)}
|
{#each filters as filter (filter.id)}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={() => handleSelect('filter', filter)}
|
onclick={() => handleSelect('filter', filter)}
|
||||||
@@ -56,7 +50,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Actions Section -->
|
<!-- Actions Section -->
|
||||||
{#if availableActions.length > 0 && (!type || type === 'action')}
|
{#if actions.length > 0 && (!type || type === 'action')}
|
||||||
<div>
|
<div>
|
||||||
<div class="flex items-center gap-2 mb-3">
|
<div class="flex items-center gap-2 mb-3">
|
||||||
<div class="h-6 w-6 rounded-md bg-success-50 flex items-center justify-center">
|
<div class="h-6 w-6 rounded-md bg-success-50 flex items-center justify-center">
|
||||||
@@ -65,7 +59,7 @@
|
|||||||
<h3 class="text-sm font-semibold">Actions</h3>
|
<h3 class="text-sm font-semibold">Actions</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-1 gap-2">
|
<div class="grid grid-cols-1 gap-2">
|
||||||
{#each availableActions as action (action.id)}
|
{#each actions as action (action.id)}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={() => handleSelect('action', action)}
|
onclick={() => handleSelect('action', action)}
|
||||||
@@ -74,7 +68,7 @@
|
|||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<p class="font-medium text-sm">{action.title}</p>
|
<p class="font-medium text-sm">{action.title}</p>
|
||||||
{#if action.description}
|
{#if action.description}
|
||||||
<p class="text-xs text-light-500 mt-1">{action.description}</p>
|
<Text size="small" class="text-light-500 mt-1">{action.description}</Text>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import ImageThumbnail from '$lib/components/assets/thumbnail/image-thumbnail.svelte';
|
import ImageThumbnail from '$lib/components/assets/thumbnail/image-thumbnail.svelte';
|
||||||
import SearchBar from '$lib/elements/SearchBar.svelte';
|
import SearchBar from '$lib/elements/SearchBar.svelte';
|
||||||
import { getPeopleThumbnailUrl } from '$lib/utils';
|
import { getPeopleThumbnailUrl } from '$lib/utils';
|
||||||
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import { getAllPeople, type PersonResponseDto } from '@immich/sdk';
|
import { getAllPeople, type PersonResponseDto } from '@immich/sdk';
|
||||||
import { Button, HStack, LoadingSpinner, Modal, ModalBody, ModalFooter } from '@immich/ui';
|
import { Button, HStack, LoadingSpinner, Modal, ModalBody, ModalFooter } from '@immich/ui';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
@@ -9,10 +10,11 @@
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
multiple?: boolean;
|
multiple?: boolean;
|
||||||
|
excludedIds?: string[];
|
||||||
onClose: (people?: PersonResponseDto[]) => void;
|
onClose: (people?: PersonResponseDto[]) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { multiple = false, onClose }: Props = $props();
|
let { multiple = false, excludedIds = [], onClose }: Props = $props();
|
||||||
|
|
||||||
let people: PersonResponseDto[] = $state([]);
|
let people: PersonResponseDto[] = $state([]);
|
||||||
let loading = $state(true);
|
let loading = $state(true);
|
||||||
@@ -20,13 +22,20 @@
|
|||||||
let selectedPeople: PersonResponseDto[] = $state([]);
|
let selectedPeople: PersonResponseDto[] = $state([]);
|
||||||
|
|
||||||
const filteredPeople = $derived(
|
const filteredPeople = $derived(
|
||||||
searchName ? people.filter((person) => person.name.toLowerCase().includes(searchName.toLowerCase())) : people,
|
people
|
||||||
|
.filter((person) => !excludedIds.includes(person.id))
|
||||||
|
.filter((person) => !searchName || person.name.toLowerCase().includes(searchName.toLowerCase())),
|
||||||
);
|
);
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
|
try {
|
||||||
|
loading = true;
|
||||||
const result = await getAllPeople({ withHidden: false });
|
const result = await getAllPeople({ withHidden: false });
|
||||||
people = result.people;
|
people = result.people;
|
||||||
loading = false;
|
loading = false;
|
||||||
|
} catch (error) {
|
||||||
|
handleError(error, $t('get_people_error'));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const togglePerson = (person: PersonResponseDto) => {
|
const togglePerson = (person: PersonResponseDto) => {
|
||||||
@@ -86,11 +95,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
|
|
||||||
{#if multiple && selectedPeople.length > 0}
|
{#if multiple}
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
<HStack fullWidth gap={4}>
|
<HStack fullWidth gap={4}>
|
||||||
<Button shape="round" color="secondary" fullWidth onclick={() => onClose()}>{$t('cancel')}</Button>
|
<Button shape="round" color="secondary" fullWidth onclick={() => onClose()}>{$t('cancel')}</Button>
|
||||||
<Button shape="round" fullWidth onclick={handleSubmit}>
|
<Button shape="round" fullWidth onclick={handleSubmit} disabled={selectedPeople.length === 0}>
|
||||||
{$t('select_count', { values: { count: selectedPeople.length } })}
|
{$t('select_count', { values: { count: selectedPeople.length } })}
|
||||||
</Button>
|
</Button>
|
||||||
</HStack>
|
</HStack>
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ import {
|
|||||||
PluginTriggerType,
|
PluginTriggerType,
|
||||||
updateWorkflow as updateWorkflowApi,
|
updateWorkflow as updateWorkflowApi,
|
||||||
type PluginActionResponseDto,
|
type PluginActionResponseDto,
|
||||||
type PluginContext,
|
type PluginContextType,
|
||||||
type PluginFilterResponseDto,
|
type PluginFilterResponseDto,
|
||||||
type PluginTriggerResponseDto,
|
type PluginTriggerResponseDto,
|
||||||
|
type WorkflowActionItemDto,
|
||||||
|
type WorkflowFilterItemDto,
|
||||||
type WorkflowResponseDto,
|
type WorkflowResponseDto,
|
||||||
type WorkflowUpdateDto,
|
type WorkflowUpdateDto,
|
||||||
} from '@immich/sdk';
|
} from '@immich/sdk';
|
||||||
@@ -18,48 +20,38 @@ export interface WorkflowPayload {
|
|||||||
actions: Record<string, unknown>[];
|
actions: Record<string, unknown>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WorkflowService {
|
/**
|
||||||
private availableTriggers: PluginTriggerResponseDto[];
|
|
||||||
private availableFilters: PluginFilterResponseDto[];
|
|
||||||
private availableActions: PluginActionResponseDto[];
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
triggers: PluginTriggerResponseDto[],
|
|
||||||
filters: PluginFilterResponseDto[],
|
|
||||||
actions: PluginActionResponseDto[],
|
|
||||||
) {
|
|
||||||
this.availableTriggers = triggers;
|
|
||||||
this.availableFilters = filters;
|
|
||||||
this.availableActions = actions;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get filters that support the given context
|
* Get filters that support the given context
|
||||||
*/
|
*/
|
||||||
getFiltersByContext(context: PluginContext): PluginFilterResponseDto[] {
|
export const getFiltersByContext = (
|
||||||
return this.availableFilters.filter((filter) => filter.supportedContexts.includes(context));
|
availableFilters: PluginFilterResponseDto[],
|
||||||
}
|
context: PluginContextType,
|
||||||
|
): PluginFilterResponseDto[] => {
|
||||||
|
return availableFilters.filter((filter) => filter.supportedContexts.includes(context));
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get actions that support the given context
|
* Get actions that support the given context
|
||||||
*/
|
*/
|
||||||
getActionsByContext(context: PluginContext): PluginActionResponseDto[] {
|
export const getActionsByContext = (
|
||||||
return this.availableActions.filter((action) => action.supportedContexts.includes(context));
|
availableActions: PluginActionResponseDto[],
|
||||||
}
|
context: PluginContextType,
|
||||||
|
): PluginActionResponseDto[] => {
|
||||||
|
return availableActions.filter((action) => action.supportedContexts.includes(context));
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize filter configurations from existing workflow
|
* Initialize filter configurations from existing workflow
|
||||||
*/
|
*/
|
||||||
initializeFilterConfigs(
|
export const initializeFilterConfigs = (
|
||||||
workflow: WorkflowResponseDto,
|
workflow: WorkflowResponseDto,
|
||||||
contextFilters?: PluginFilterResponseDto[],
|
availableFilters: PluginFilterResponseDto[],
|
||||||
): Record<string, unknown> {
|
): Record<string, unknown> => {
|
||||||
const filters = contextFilters ?? this.availableFilters;
|
|
||||||
const configs: Record<string, unknown> = {};
|
const configs: Record<string, unknown> = {};
|
||||||
|
|
||||||
if (workflow.filters) {
|
if (workflow.filters) {
|
||||||
for (const workflowFilter of workflow.filters) {
|
for (const workflowFilter of workflow.filters) {
|
||||||
const filterDef = filters.find((f) => f.id === workflowFilter.filterId);
|
const filterDef = availableFilters.find((f) => f.id === workflowFilter.pluginFilterId);
|
||||||
if (filterDef) {
|
if (filterDef) {
|
||||||
configs[filterDef.methodName] = workflowFilter.filterConfig ?? {};
|
configs[filterDef.methodName] = workflowFilter.filterConfig ?? {};
|
||||||
}
|
}
|
||||||
@@ -67,21 +59,20 @@ export class WorkflowService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return configs;
|
return configs;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize action configurations from existing workflow
|
* Initialize action configurations from existing workflow
|
||||||
*/
|
*/
|
||||||
initializeActionConfigs(
|
export const initializeActionConfigs = (
|
||||||
workflow: WorkflowResponseDto,
|
workflow: WorkflowResponseDto,
|
||||||
contextActions?: PluginActionResponseDto[],
|
availableActions: PluginActionResponseDto[],
|
||||||
): Record<string, unknown> {
|
): Record<string, unknown> => {
|
||||||
const actions = contextActions ?? this.availableActions;
|
|
||||||
const configs: Record<string, unknown> = {};
|
const configs: Record<string, unknown> = {};
|
||||||
|
|
||||||
if (workflow.actions) {
|
if (workflow.actions) {
|
||||||
for (const workflowAction of workflow.actions) {
|
for (const workflowAction of workflow.actions) {
|
||||||
const actionDef = actions.find((a) => a.id === workflowAction.actionId);
|
const actionDef = availableActions.find((a) => a.id === workflowAction.pluginActionId);
|
||||||
if (actionDef) {
|
if (actionDef) {
|
||||||
configs[actionDef.methodName] = workflowAction.actionConfig ?? {};
|
configs[actionDef.methodName] = workflowAction.actionConfig ?? {};
|
||||||
}
|
}
|
||||||
@@ -89,46 +80,12 @@ export class WorkflowService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return configs;
|
return configs;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize ordered filters from existing workflow
|
|
||||||
*/
|
|
||||||
initializeOrderedFilters(
|
|
||||||
workflow: WorkflowResponseDto,
|
|
||||||
contextFilters?: PluginFilterResponseDto[],
|
|
||||||
): PluginFilterResponseDto[] {
|
|
||||||
if (!workflow.filters) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const filters = contextFilters ?? this.availableFilters;
|
|
||||||
return workflow.filters
|
|
||||||
.map((wf) => filters.find((f) => f.id === wf.filterId))
|
|
||||||
.filter(Boolean) as PluginFilterResponseDto[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize ordered actions from existing workflow
|
|
||||||
*/
|
|
||||||
initializeOrderedActions(
|
|
||||||
workflow: WorkflowResponseDto,
|
|
||||||
contextActions?: PluginActionResponseDto[],
|
|
||||||
): PluginActionResponseDto[] {
|
|
||||||
if (!workflow.actions) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const actions = contextActions ?? this.availableActions;
|
|
||||||
return workflow.actions
|
|
||||||
.map((wa) => actions.find((a) => a.id === wa.actionId))
|
|
||||||
.filter(Boolean) as PluginActionResponseDto[];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Build workflow payload from current state
|
* Build workflow payload from current state
|
||||||
*/
|
*/
|
||||||
buildWorkflowPayload(
|
export const buildWorkflowPayload = (
|
||||||
name: string,
|
name: string,
|
||||||
description: string,
|
description: string,
|
||||||
enabled: boolean,
|
enabled: boolean,
|
||||||
@@ -137,7 +94,7 @@ export class WorkflowService {
|
|||||||
orderedActions: PluginActionResponseDto[],
|
orderedActions: PluginActionResponseDto[],
|
||||||
filterConfigs: Record<string, unknown>,
|
filterConfigs: Record<string, unknown>,
|
||||||
actionConfigs: Record<string, unknown>,
|
actionConfigs: Record<string, unknown>,
|
||||||
): WorkflowPayload {
|
): WorkflowPayload => {
|
||||||
const filters = orderedFilters.map((filter) => ({
|
const filters = orderedFilters.map((filter) => ({
|
||||||
[filter.methodName]: filterConfigs[filter.methodName] ?? {},
|
[filter.methodName]: filterConfigs[filter.methodName] ?? {},
|
||||||
}));
|
}));
|
||||||
@@ -154,12 +111,17 @@ export class WorkflowService {
|
|||||||
filters,
|
filters,
|
||||||
actions,
|
actions,
|
||||||
};
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse JSON workflow and update state
|
* Parse JSON workflow and update state
|
||||||
*/
|
*/
|
||||||
parseWorkflowJson(jsonString: string): {
|
export const parseWorkflowJson = (
|
||||||
|
jsonString: string,
|
||||||
|
availableTriggers: PluginTriggerResponseDto[],
|
||||||
|
availableFilters: PluginFilterResponseDto[],
|
||||||
|
availableActions: PluginActionResponseDto[],
|
||||||
|
): {
|
||||||
success: boolean;
|
success: boolean;
|
||||||
error?: string;
|
error?: string;
|
||||||
data?: {
|
data?: {
|
||||||
@@ -172,12 +134,12 @@ export class WorkflowService {
|
|||||||
filterConfigs: Record<string, unknown>;
|
filterConfigs: Record<string, unknown>;
|
||||||
actionConfigs: Record<string, unknown>;
|
actionConfigs: Record<string, unknown>;
|
||||||
};
|
};
|
||||||
} {
|
} => {
|
||||||
try {
|
try {
|
||||||
const parsed = JSON.parse(jsonString);
|
const parsed = JSON.parse(jsonString);
|
||||||
|
|
||||||
// Find trigger
|
// Find trigger
|
||||||
const trigger = this.availableTriggers.find((t) => t.triggerType === parsed.triggerType);
|
const trigger = availableTriggers.find((t) => t.type === parsed.triggerType);
|
||||||
|
|
||||||
// Parse filters
|
// Parse filters
|
||||||
const filters: PluginFilterResponseDto[] = [];
|
const filters: PluginFilterResponseDto[] = [];
|
||||||
@@ -185,7 +147,7 @@ export class WorkflowService {
|
|||||||
if (Array.isArray(parsed.filters)) {
|
if (Array.isArray(parsed.filters)) {
|
||||||
for (const filterObj of parsed.filters) {
|
for (const filterObj of parsed.filters) {
|
||||||
const methodName = Object.keys(filterObj)[0];
|
const methodName = Object.keys(filterObj)[0];
|
||||||
const filter = this.availableFilters.find((f) => f.methodName === methodName);
|
const filter = availableFilters.find((f) => f.methodName === methodName);
|
||||||
if (filter) {
|
if (filter) {
|
||||||
filters.push(filter);
|
filters.push(filter);
|
||||||
filterConfigs[methodName] = (filterObj as Record<string, unknown>)[methodName];
|
filterConfigs[methodName] = (filterObj as Record<string, unknown>)[methodName];
|
||||||
@@ -199,7 +161,7 @@ export class WorkflowService {
|
|||||||
if (Array.isArray(parsed.actions)) {
|
if (Array.isArray(parsed.actions)) {
|
||||||
for (const actionObj of parsed.actions) {
|
for (const actionObj of parsed.actions) {
|
||||||
const methodName = Object.keys(actionObj)[0];
|
const methodName = Object.keys(actionObj)[0];
|
||||||
const action = this.availableActions.find((a) => a.methodName === methodName);
|
const action = availableActions.find((a) => a.methodName === methodName);
|
||||||
if (action) {
|
if (action) {
|
||||||
actions.push(action);
|
actions.push(action);
|
||||||
actionConfigs[methodName] = (actionObj as Record<string, unknown>)[methodName];
|
actionConfigs[methodName] = (actionObj as Record<string, unknown>)[methodName];
|
||||||
@@ -226,12 +188,12 @@ export class WorkflowService {
|
|||||||
error: error instanceof Error ? error.message : 'Invalid JSON',
|
error: error instanceof Error ? error.message : 'Invalid JSON',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if workflow has changes compared to previous version
|
* Check if workflow has changes compared to previous version
|
||||||
*/
|
*/
|
||||||
hasWorkflowChanged(
|
export const hasWorkflowChanged = (
|
||||||
previousWorkflow: WorkflowResponseDto,
|
previousWorkflow: WorkflowResponseDto,
|
||||||
enabled: boolean,
|
enabled: boolean,
|
||||||
name: string,
|
name: string,
|
||||||
@@ -241,7 +203,9 @@ export class WorkflowService {
|
|||||||
orderedActions: PluginActionResponseDto[],
|
orderedActions: PluginActionResponseDto[],
|
||||||
filterConfigs: Record<string, unknown>,
|
filterConfigs: Record<string, unknown>,
|
||||||
actionConfigs: Record<string, unknown>,
|
actionConfigs: Record<string, unknown>,
|
||||||
): boolean {
|
availableFilters: PluginFilterResponseDto[],
|
||||||
|
availableActions: PluginActionResponseDto[],
|
||||||
|
): boolean => {
|
||||||
// Check enabled state
|
// Check enabled state
|
||||||
if (enabled !== previousWorkflow.enabled) {
|
if (enabled !== previousWorkflow.enabled) {
|
||||||
return true;
|
return true;
|
||||||
@@ -258,14 +222,14 @@ export class WorkflowService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check filters order/items
|
// Check filters order/items
|
||||||
const previousFilterIds = previousWorkflow.filters?.map((f) => f.filterId) ?? [];
|
const previousFilterIds = previousWorkflow.filters?.map((f) => f.pluginFilterId) ?? [];
|
||||||
const currentFilterIds = orderedFilters.map((f) => f.id);
|
const currentFilterIds = orderedFilters.map((f) => f.id);
|
||||||
if (JSON.stringify(previousFilterIds) !== JSON.stringify(currentFilterIds)) {
|
if (JSON.stringify(previousFilterIds) !== JSON.stringify(currentFilterIds)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check actions order/items
|
// Check actions order/items
|
||||||
const previousActionIds = previousWorkflow.actions?.map((a) => a.actionId) ?? [];
|
const previousActionIds = previousWorkflow.actions?.map((a) => a.pluginActionId) ?? [];
|
||||||
const currentActionIds = orderedActions.map((a) => a.id);
|
const currentActionIds = orderedActions.map((a) => a.id);
|
||||||
if (JSON.stringify(previousActionIds) !== JSON.stringify(currentActionIds)) {
|
if (JSON.stringify(previousActionIds) !== JSON.stringify(currentActionIds)) {
|
||||||
return true;
|
return true;
|
||||||
@@ -274,7 +238,7 @@ export class WorkflowService {
|
|||||||
// Check filter configs
|
// Check filter configs
|
||||||
const previousFilterConfigs: Record<string, unknown> = {};
|
const previousFilterConfigs: Record<string, unknown> = {};
|
||||||
for (const wf of previousWorkflow.filters ?? []) {
|
for (const wf of previousWorkflow.filters ?? []) {
|
||||||
const filterDef = this.availableFilters.find((f) => f.id === wf.filterId);
|
const filterDef = availableFilters.find((f) => f.id === wf.pluginFilterId);
|
||||||
if (filterDef) {
|
if (filterDef) {
|
||||||
previousFilterConfigs[filterDef.methodName] = wf.filterConfig ?? {};
|
previousFilterConfigs[filterDef.methodName] = wf.filterConfig ?? {};
|
||||||
}
|
}
|
||||||
@@ -286,7 +250,7 @@ export class WorkflowService {
|
|||||||
// Check action configs
|
// Check action configs
|
||||||
const previousActionConfigs: Record<string, unknown> = {};
|
const previousActionConfigs: Record<string, unknown> = {};
|
||||||
for (const wa of previousWorkflow.actions ?? []) {
|
for (const wa of previousWorkflow.actions ?? []) {
|
||||||
const actionDef = this.availableActions.find((a) => a.id === wa.actionId);
|
const actionDef = availableActions.find((a) => a.id === wa.pluginActionId);
|
||||||
if (actionDef) {
|
if (actionDef) {
|
||||||
previousActionConfigs[actionDef.methodName] = wa.actionConfig ?? {};
|
previousActionConfigs[actionDef.methodName] = wa.actionConfig ?? {};
|
||||||
}
|
}
|
||||||
@@ -296,9 +260,12 @@ export class WorkflowService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
};
|
||||||
|
|
||||||
async updateWorkflow(
|
/**
|
||||||
|
* Update a workflow via API
|
||||||
|
*/
|
||||||
|
export const handleUpdateWorkflow = async (
|
||||||
workflowId: string,
|
workflowId: string,
|
||||||
name: string,
|
name: string,
|
||||||
description: string,
|
description: string,
|
||||||
@@ -308,16 +275,16 @@ export class WorkflowService {
|
|||||||
orderedActions: PluginActionResponseDto[],
|
orderedActions: PluginActionResponseDto[],
|
||||||
filterConfigs: Record<string, unknown>,
|
filterConfigs: Record<string, unknown>,
|
||||||
actionConfigs: Record<string, unknown>,
|
actionConfigs: Record<string, unknown>,
|
||||||
): Promise<WorkflowResponseDto> {
|
): Promise<WorkflowResponseDto> => {
|
||||||
const filters = orderedFilters.map((filter) => ({
|
const filters = orderedFilters.map((filter) => ({
|
||||||
filterId: filter.id,
|
pluginFilterId: filter.id,
|
||||||
filterConfig: filterConfigs[filter.methodName] ?? {},
|
filterConfig: filterConfigs[filter.methodName] ?? {},
|
||||||
}));
|
})) as WorkflowFilterItemDto[];
|
||||||
|
|
||||||
const actions = orderedActions.map((action) => ({
|
const actions = orderedActions.map((action) => ({
|
||||||
actionId: action.id,
|
pluginActionId: action.id,
|
||||||
actionConfig: actionConfigs[action.methodName] ?? {},
|
actionConfig: actionConfigs[action.methodName] ?? {},
|
||||||
}));
|
})) as WorkflowActionItemDto[];
|
||||||
|
|
||||||
const updateDto: WorkflowUpdateDto = {
|
const updateDto: WorkflowUpdateDto = {
|
||||||
name,
|
name,
|
||||||
@@ -329,5 +296,4 @@ export class WorkflowService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return updateWorkflowApi({ id: workflowId, workflowUpdateDto: updateDto });
|
return updateWorkflowApi({ id: workflowId, workflowUpdateDto: updateDto });
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|||||||
@@ -76,15 +76,15 @@
|
|||||||
enabled: workflow.enabled,
|
enabled: workflow.enabled,
|
||||||
triggerType: workflow.triggerType,
|
triggerType: workflow.triggerType,
|
||||||
filters: orderedFilters.map((filter) => {
|
filters: orderedFilters.map((filter) => {
|
||||||
const meta = pluginFilterLookup.get(filter.filterId);
|
const meta = pluginFilterLookup.get(filter.pluginFilterId);
|
||||||
const key = meta?.methodName ?? filter.filterId;
|
const key = meta?.methodName ?? filter.pluginFilterId;
|
||||||
return {
|
return {
|
||||||
[key]: filter.filterConfig ?? {},
|
[key]: filter.filterConfig ?? {},
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
actions: orderedActions.map((action) => {
|
actions: orderedActions.map((action) => {
|
||||||
const meta = pluginActionLookup.get(action.actionId);
|
const meta = pluginActionLookup.get(action.pluginActionId);
|
||||||
const key = meta?.methodName ?? action.actionId;
|
const key = meta?.methodName ?? action.pluginActionId;
|
||||||
return {
|
return {
|
||||||
[key]: action.actionConfig ?? {},
|
[key]: action.actionConfig ?? {},
|
||||||
};
|
};
|
||||||
@@ -123,7 +123,7 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleEditWorkflow = async (workflow: WorkflowResponseDto) => {
|
const handleEditWorkflow = async (workflow: WorkflowResponseDto) => {
|
||||||
await goto(`${AppRoute.WORKFLOWS_EDIT}/${workflow.id}?editMode=visual`);
|
await goto(`${AppRoute.WORKFLOWS}/${workflow.id}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCreateWorkflow = async () => {
|
const handleCreateWorkflow = async () => {
|
||||||
@@ -137,7 +137,7 @@
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await goto(`${AppRoute.WORKFLOWS_EDIT}/${workflow.id}?editMode=visual`);
|
await goto(`${AppRoute.WORKFLOWS}/${workflow.id}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFilterLabel = (filterId: string) => {
|
const getFilterLabel = (filterId: string) => {
|
||||||
@@ -289,7 +289,7 @@
|
|||||||
</span>
|
</span>
|
||||||
{:else}
|
{:else}
|
||||||
{#each workflow.filters as workflowFilter (workflowFilter.id)}
|
{#each workflow.filters as workflowFilter (workflowFilter.id)}
|
||||||
{@render chipItem(getFilterLabel(workflowFilter.filterId))}
|
{@render chipItem(getFilterLabel(workflowFilter.pluginFilterId))}
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
@@ -309,7 +309,7 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
{#each workflow.actions as workflowAction (workflowAction.id)}
|
{#each workflow.actions as workflowAction (workflowAction.id)}
|
||||||
{@render chipItem(getActionLabel(workflowAction.actionId))}
|
{@render chipItem(getActionLabel(workflowAction.pluginActionId))}
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { authenticate } from '$lib/utils/auth';
|
import { authenticate } from '$lib/utils/auth';
|
||||||
import { getFormatter } from '$lib/utils/i18n';
|
import { getFormatter } from '$lib/utils/i18n';
|
||||||
import { getPlugins, getWorkflows } from '@immich/sdk';
|
import { getPlugins, getWorkflows } from '@immich/sdk';
|
||||||
import type { PageLoad } from '../$types';
|
import type { PageLoad } from './$types';
|
||||||
|
|
||||||
export const load = (async ({ url }) => {
|
export const load = (async ({ url }) => {
|
||||||
await authenticate(url);
|
await authenticate(url);
|
||||||
|
|||||||
@@ -11,7 +11,17 @@
|
|||||||
import AddWorkflowStepModal from '$lib/modals/AddWorkflowStepModal.svelte';
|
import AddWorkflowStepModal from '$lib/modals/AddWorkflowStepModal.svelte';
|
||||||
import WorkflowNavigationConfirmModal from '$lib/modals/WorkflowNavigationConfirmModal.svelte';
|
import WorkflowNavigationConfirmModal from '$lib/modals/WorkflowNavigationConfirmModal.svelte';
|
||||||
import WorkflowTriggerUpdateConfirmModal from '$lib/modals/WorkflowTriggerUpdateConfirmModal.svelte';
|
import WorkflowTriggerUpdateConfirmModal from '$lib/modals/WorkflowTriggerUpdateConfirmModal.svelte';
|
||||||
import { WorkflowService, type WorkflowPayload } from '$lib/services/workflow.service';
|
import {
|
||||||
|
buildWorkflowPayload,
|
||||||
|
getActionsByContext,
|
||||||
|
getFiltersByContext,
|
||||||
|
handleUpdateWorkflow,
|
||||||
|
hasWorkflowChanged,
|
||||||
|
initializeActionConfigs,
|
||||||
|
initializeFilterConfigs,
|
||||||
|
parseWorkflowJson,
|
||||||
|
type WorkflowPayload,
|
||||||
|
} from '$lib/services/workflow.service';
|
||||||
import { handleError } from '$lib/utils/handle-error';
|
import { handleError } from '$lib/utils/handle-error';
|
||||||
import type { PluginActionResponseDto, PluginFilterResponseDto, PluginTriggerResponseDto } from '@immich/sdk';
|
import type { PluginActionResponseDto, PluginFilterResponseDto, PluginTriggerResponseDto } from '@immich/sdk';
|
||||||
import {
|
import {
|
||||||
@@ -57,7 +67,6 @@
|
|||||||
const triggers = data.triggers;
|
const triggers = data.triggers;
|
||||||
const filters = data.plugins.flatMap((plugin) => plugin.filters);
|
const filters = data.plugins.flatMap((plugin) => plugin.filters);
|
||||||
const actions = data.plugins.flatMap((plugin) => plugin.actions);
|
const actions = data.plugins.flatMap((plugin) => plugin.actions);
|
||||||
const workflowService = new WorkflowService(triggers, filters, actions);
|
|
||||||
|
|
||||||
let previousWorkflow = data.workflow;
|
let previousWorkflow = data.workflow;
|
||||||
let editWorkflow = $state(data.workflow);
|
let editWorkflow = $state(data.workflow);
|
||||||
@@ -67,26 +76,28 @@
|
|||||||
let name: string = $derived(editWorkflow.name ?? '');
|
let name: string = $derived(editWorkflow.name ?? '');
|
||||||
let description: string = $derived(editWorkflow.description ?? '');
|
let description: string = $derived(editWorkflow.description ?? '');
|
||||||
|
|
||||||
let selectedTrigger = $state(triggers.find((t) => t.triggerType === editWorkflow.triggerType) ?? triggers[0]);
|
let selectedTrigger = $state(triggers.find((t) => t.type === editWorkflow.triggerType) ?? triggers[0]);
|
||||||
|
|
||||||
let triggerType = $derived(selectedTrigger.triggerType);
|
let triggerType = $derived(selectedTrigger.type);
|
||||||
|
|
||||||
let supportFilters = $derived(workflowService.getFiltersByContext(selectedTrigger.context));
|
let supportFilters = $derived(getFiltersByContext(filters, selectedTrigger.contextType));
|
||||||
let supportActions = $derived(workflowService.getActionsByContext(selectedTrigger.context));
|
let supportActions = $derived(getActionsByContext(actions, selectedTrigger.contextType));
|
||||||
|
|
||||||
let orderedFilters: PluginFilterResponseDto[] = $derived(
|
let selectedFilters: PluginFilterResponseDto[] = $derived(
|
||||||
workflowService.initializeOrderedFilters(editWorkflow, supportFilters),
|
(editWorkflow.filters ?? []).flatMap((workflowFilter) =>
|
||||||
|
supportFilters.filter((supportedFilter) => supportedFilter.id === workflowFilter.pluginFilterId),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
let orderedActions: PluginActionResponseDto[] = $derived(
|
|
||||||
workflowService.initializeOrderedActions(editWorkflow, supportActions),
|
let selectedActions: PluginActionResponseDto[] = $derived(
|
||||||
);
|
(editWorkflow.actions ?? []).flatMap((workflowAction) =>
|
||||||
let filterConfigs: Record<string, unknown> = $derived(
|
supportActions.filter((supportedAction) => supportedAction.id === workflowAction.pluginActionId),
|
||||||
workflowService.initializeFilterConfigs(editWorkflow, supportFilters),
|
),
|
||||||
);
|
|
||||||
let actionConfigs: Record<string, unknown> = $derived(
|
|
||||||
workflowService.initializeActionConfigs(editWorkflow, supportActions),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let filterConfigs: Record<string, unknown> = $derived(initializeFilterConfigs(editWorkflow, supportFilters));
|
||||||
|
let actionConfigs: Record<string, unknown> = $derived(initializeActionConfigs(editWorkflow, supportActions));
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
editWorkflow.triggerType = triggerType;
|
editWorkflow.triggerType = triggerType;
|
||||||
});
|
});
|
||||||
@@ -94,10 +105,10 @@
|
|||||||
// Clear filters and actions when trigger changes (context changes)
|
// Clear filters and actions when trigger changes (context changes)
|
||||||
let previousContext = $state<string | undefined>(undefined);
|
let previousContext = $state<string | undefined>(undefined);
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const currentContext = selectedTrigger.context;
|
const currentContext = selectedTrigger.contextType;
|
||||||
if (previousContext !== undefined && previousContext !== currentContext) {
|
if (previousContext !== undefined && previousContext !== currentContext) {
|
||||||
orderedFilters = [];
|
selectedFilters = [];
|
||||||
orderedActions = [];
|
selectedActions = [];
|
||||||
filterConfigs = {};
|
filterConfigs = {};
|
||||||
actionConfigs = {};
|
actionConfigs = {};
|
||||||
}
|
}
|
||||||
@@ -106,14 +117,14 @@
|
|||||||
|
|
||||||
const updateWorkflow = async () => {
|
const updateWorkflow = async () => {
|
||||||
try {
|
try {
|
||||||
const updated = await workflowService.updateWorkflow(
|
const updated = await handleUpdateWorkflow(
|
||||||
editWorkflow.id,
|
editWorkflow.id,
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
editWorkflow.enabled,
|
editWorkflow.enabled,
|
||||||
triggerType,
|
triggerType,
|
||||||
orderedFilters,
|
selectedFilters,
|
||||||
orderedActions,
|
selectedActions,
|
||||||
filterConfigs,
|
filterConfigs,
|
||||||
actionConfigs,
|
actionConfigs,
|
||||||
);
|
);
|
||||||
@@ -131,13 +142,13 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const jsonContent = $derived(
|
const jsonContent = $derived(
|
||||||
workflowService.buildWorkflowPayload(
|
buildWorkflowPayload(
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
editWorkflow.enabled,
|
editWorkflow.enabled,
|
||||||
triggerType,
|
triggerType,
|
||||||
orderedFilters,
|
selectedFilters,
|
||||||
orderedActions,
|
selectedActions,
|
||||||
filterConfigs,
|
filterConfigs,
|
||||||
actionConfigs,
|
actionConfigs,
|
||||||
),
|
),
|
||||||
@@ -153,7 +164,7 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
const syncFromJson = () => {
|
const syncFromJson = () => {
|
||||||
const result = workflowService.parseWorkflowJson(JSON.stringify(jsonEditorContent));
|
const result = parseWorkflowJson(JSON.stringify(jsonEditorContent), triggers, filters, actions);
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
return;
|
return;
|
||||||
@@ -168,24 +179,26 @@
|
|||||||
selectedTrigger = result.data.trigger;
|
selectedTrigger = result.data.trigger;
|
||||||
}
|
}
|
||||||
|
|
||||||
orderedFilters = result.data.filters;
|
selectedFilters = result.data.filters;
|
||||||
orderedActions = result.data.actions;
|
selectedActions = result.data.actions;
|
||||||
filterConfigs = result.data.filterConfigs;
|
filterConfigs = result.data.filterConfigs;
|
||||||
actionConfigs = result.data.actionConfigs;
|
actionConfigs = result.data.actionConfigs;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let hasChanges: boolean = $derived(
|
let hasChanges: boolean = $derived(
|
||||||
workflowService.hasWorkflowChanged(
|
hasWorkflowChanged(
|
||||||
previousWorkflow,
|
previousWorkflow,
|
||||||
editWorkflow.enabled,
|
editWorkflow.enabled,
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
triggerType,
|
triggerType,
|
||||||
orderedFilters,
|
selectedFilters,
|
||||||
orderedActions,
|
selectedActions,
|
||||||
filterConfigs,
|
filterConfigs,
|
||||||
actionConfigs,
|
actionConfigs,
|
||||||
|
filters,
|
||||||
|
actions,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -211,10 +224,10 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newFilters = [...orderedFilters];
|
const newFilters = [...selectedFilters];
|
||||||
const [draggedItem] = newFilters.splice(draggedFilterIndex, 1);
|
const [draggedItem] = newFilters.splice(draggedFilterIndex, 1);
|
||||||
newFilters.splice(index, 0, draggedItem);
|
newFilters.splice(index, 0, draggedItem);
|
||||||
orderedFilters = newFilters;
|
selectedFilters = newFilters;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFilterDragEnd = () => {
|
const handleFilterDragEnd = () => {
|
||||||
@@ -238,10 +251,10 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const newActions = [...orderedActions];
|
const newActions = [...selectedActions];
|
||||||
const [draggedItem] = newActions.splice(draggedActionIndex, 1);
|
const [draggedItem] = newActions.splice(draggedActionIndex, 1);
|
||||||
newActions.splice(index, 0, draggedItem);
|
newActions.splice(index, 0, draggedItem);
|
||||||
orderedActions = newActions;
|
selectedActions = newActions;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleActionDragEnd = () => {
|
const handleActionDragEnd = () => {
|
||||||
@@ -253,26 +266,24 @@
|
|||||||
const result = (await modalManager.show(AddWorkflowStepModal, {
|
const result = (await modalManager.show(AddWorkflowStepModal, {
|
||||||
filters: supportFilters,
|
filters: supportFilters,
|
||||||
actions: supportActions,
|
actions: supportActions,
|
||||||
addedFilters: orderedFilters,
|
|
||||||
addedActions: orderedActions,
|
|
||||||
type,
|
type,
|
||||||
})) as { type: 'filter' | 'action'; item: PluginFilterResponseDto | PluginActionResponseDto } | undefined;
|
})) as { type: 'filter' | 'action'; item: PluginFilterResponseDto | PluginActionResponseDto } | undefined;
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
if (result.type === 'filter') {
|
if (result.type === 'filter') {
|
||||||
orderedFilters = [...orderedFilters, result.item as PluginFilterResponseDto];
|
selectedFilters = [...selectedFilters, result.item as PluginFilterResponseDto];
|
||||||
} else if (result.type === 'action') {
|
} else if (result.type === 'action') {
|
||||||
orderedActions = [...orderedActions, result.item as PluginActionResponseDto];
|
selectedActions = [...selectedActions, result.item as PluginActionResponseDto];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveFilter = (index: number) => {
|
const handleRemoveFilter = (index: number) => {
|
||||||
orderedFilters = orderedFilters.filter((_, i) => i !== index);
|
selectedFilters = selectedFilters.filter((_, i) => i !== index);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveAction = (index: number) => {
|
const handleRemoveAction = (index: number) => {
|
||||||
orderedActions = orderedActions.filter((_, i) => i !== index);
|
selectedActions = selectedActions.filter((_, i) => i !== index);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTriggerChange = async (newTrigger: PluginTriggerResponseDto) => {
|
const handleTriggerChange = async (newTrigger: PluginTriggerResponseDto) => {
|
||||||
@@ -340,8 +351,6 @@
|
|||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<main class="pt-24 immich-scrollbar">
|
<main class="pt-24 immich-scrollbar">
|
||||||
<WorkflowSummarySidebar trigger={selectedTrigger} filters={orderedFilters} actions={orderedActions} />
|
|
||||||
|
|
||||||
<Container size="medium" class="p-4" center>
|
<Container size="medium" class="p-4" center>
|
||||||
{#if viewMode === 'json'}
|
{#if viewMode === 'json'}
|
||||||
<WorkflowJsonEditor
|
<WorkflowJsonEditor
|
||||||
@@ -392,7 +401,7 @@
|
|||||||
{#each triggers as trigger (trigger.name)}
|
{#each triggers as trigger (trigger.name)}
|
||||||
<WorkflowTriggerCard
|
<WorkflowTriggerCard
|
||||||
{trigger}
|
{trigger}
|
||||||
selected={selectedTrigger.triggerType === trigger.triggerType}
|
selected={selectedTrigger.type === trigger.type}
|
||||||
onclick={() => handleTriggerChange(trigger)}
|
onclick={() => handleTriggerChange(trigger)}
|
||||||
/>
|
/>
|
||||||
{/each}
|
{/each}
|
||||||
@@ -414,10 +423,10 @@
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<CardBody>
|
<CardBody>
|
||||||
{#if orderedFilters.length === 0}
|
{#if selectedFilters.length === 0}
|
||||||
{@render emptyCreateButton($t('add_filter'), $t('add_filter_description'), () => handleAddStep('filter'))}
|
{@render emptyCreateButton($t('add_filter'), $t('add_filter_description'), () => handleAddStep('filter'))}
|
||||||
{:else}
|
{:else}
|
||||||
{#each orderedFilters as filter, index (filter.id)}
|
{#each selectedFilters as filter, index (index)}
|
||||||
{#if index > 0}
|
{#if index > 0}
|
||||||
{@render stepSeparator()}
|
{@render stepSeparator()}
|
||||||
{/if}
|
{/if}
|
||||||
@@ -483,10 +492,10 @@
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<CardBody>
|
<CardBody>
|
||||||
{#if orderedActions.length === 0}
|
{#if selectedActions.length === 0}
|
||||||
{@render emptyCreateButton($t('add_action'), $t('add_action_description'), () => handleAddStep('action'))}
|
{@render emptyCreateButton($t('add_action'), $t('add_action_description'), () => handleAddStep('action'))}
|
||||||
{:else}
|
{:else}
|
||||||
{#each orderedActions as action, index (action.id)}
|
{#each selectedActions as action, index (index)}
|
||||||
{#if index > 0}
|
{#if index > 0}
|
||||||
{@render stepSeparator()}
|
{@render stepSeparator()}
|
||||||
{/if}
|
{/if}
|
||||||
@@ -539,6 +548,8 @@
|
|||||||
</VStack>
|
</VStack>
|
||||||
{/if}
|
{/if}
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
|
<WorkflowSummarySidebar trigger={selectedTrigger} filters={selectedFilters} actions={selectedActions} />
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<ControlAppBar onClose={() => goto(AppRoute.WORKFLOWS)} backIcon={mdiArrowLeft} tailwindClasses="fixed! top-0! w-full">
|
<ControlAppBar onClose={() => goto(AppRoute.WORKFLOWS)} backIcon={mdiArrowLeft} tailwindClasses="fixed! top-0! w-full">
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { authenticate } from '$lib/utils/auth';
|
import { authenticate } from '$lib/utils/auth';
|
||||||
import { getFormatter } from '$lib/utils/i18n';
|
import { getFormatter } from '$lib/utils/i18n';
|
||||||
import { getPlugins, getTriggers, getWorkflow } from '@immich/sdk';
|
import { getPlugins, getPluginTriggers, getWorkflow } from '@immich/sdk';
|
||||||
import type { PageLoad } from './$types';
|
import type { PageLoad } from './$types';
|
||||||
|
|
||||||
export const load = (async ({ url, params }) => {
|
export const load = (async ({ url, params }) => {
|
||||||
@@ -8,7 +8,7 @@ export const load = (async ({ url, params }) => {
|
|||||||
const [plugins, workflow, triggers] = await Promise.all([
|
const [plugins, workflow, triggers] = await Promise.all([
|
||||||
getPlugins(),
|
getPlugins(),
|
||||||
getWorkflow({ id: params.workflowId }),
|
getWorkflow({ id: params.workflowId }),
|
||||||
getTriggers(),
|
getPluginTriggers(),
|
||||||
]);
|
]);
|
||||||
const $t = await getFormatter();
|
const $t = await getFormatter();
|
||||||
|
|
||||||
Reference in New Issue
Block a user