mirror of
https://github.com/immich-app/immich.git
synced 2025-03-01 15:11:21 +01:00
[WEB] Upload asset directly to album (#379)
* Added stores to get album assetId * Upload assets and add to album * Added comments * resolve conflict when add assets from upload directly * Filtered out duplicate asset before adding to the album
This commit is contained in:
parent
2336a6159c
commit
03457f5d32
15 changed files with 3823 additions and 4408 deletions
|
@ -9,6 +9,7 @@ import 'package:openapi/api.dart';
|
||||||
Name | Type | Description | Notes
|
Name | Type | Description | Notes
|
||||||
------------ | ------------- | ------------- | -------------
|
------------ | ------------- | ------------- | -------------
|
||||||
**isExist** | **bool** | |
|
**isExist** | **bool** | |
|
||||||
|
**id** | **String** | | [optional]
|
||||||
|
|
||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
|
||||||
|
|
||||||
|
|
|
@ -14,25 +14,41 @@ class CheckDuplicateAssetResponseDto {
|
||||||
/// Returns a new [CheckDuplicateAssetResponseDto] instance.
|
/// Returns a new [CheckDuplicateAssetResponseDto] instance.
|
||||||
CheckDuplicateAssetResponseDto({
|
CheckDuplicateAssetResponseDto({
|
||||||
required this.isExist,
|
required this.isExist,
|
||||||
|
this.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
bool isExist;
|
bool isExist;
|
||||||
|
|
||||||
|
///
|
||||||
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
|
/// does not include a default value (using the "default:" property), however, the generated
|
||||||
|
/// source code must fall back to having a nullable type.
|
||||||
|
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||||
|
///
|
||||||
|
String? id;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) => identical(this, other) || other is CheckDuplicateAssetResponseDto &&
|
bool operator ==(Object other) => identical(this, other) || other is CheckDuplicateAssetResponseDto &&
|
||||||
other.isExist == isExist;
|
other.isExist == isExist &&
|
||||||
|
other.id == id;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
// ignore: unnecessary_parenthesis
|
// ignore: unnecessary_parenthesis
|
||||||
(isExist.hashCode);
|
(isExist.hashCode) +
|
||||||
|
(id == null ? 0 : id!.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'CheckDuplicateAssetResponseDto[isExist=$isExist]';
|
String toString() => 'CheckDuplicateAssetResponseDto[isExist=$isExist, id=$id]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final _json = <String, dynamic>{};
|
final _json = <String, dynamic>{};
|
||||||
_json[r'isExist'] = isExist;
|
_json[r'isExist'] = isExist;
|
||||||
|
if (id != null) {
|
||||||
|
_json[r'id'] = id;
|
||||||
|
} else {
|
||||||
|
_json[r'id'] = null;
|
||||||
|
}
|
||||||
return _json;
|
return _json;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,6 +72,7 @@ class CheckDuplicateAssetResponseDto {
|
||||||
|
|
||||||
return CheckDuplicateAssetResponseDto(
|
return CheckDuplicateAssetResponseDto(
|
||||||
isExist: mapValueOfType<bool>(json, r'isExist')!,
|
isExist: mapValueOfType<bool>(json, r'isExist')!,
|
||||||
|
id: mapValueOfType<String>(json, r'id'),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -202,8 +202,6 @@ export class AssetController {
|
||||||
@GetAuthUser() authUser: AuthUserDto,
|
@GetAuthUser() authUser: AuthUserDto,
|
||||||
@Body(ValidationPipe) checkDuplicateAssetDto: CheckDuplicateAssetDto,
|
@Body(ValidationPipe) checkDuplicateAssetDto: CheckDuplicateAssetDto,
|
||||||
): Promise<CheckDuplicateAssetResponseDto> {
|
): Promise<CheckDuplicateAssetResponseDto> {
|
||||||
const res = await this.assetService.checkDuplicatedAsset(authUser, checkDuplicateAssetDto);
|
return await this.assetService.checkDuplicatedAsset(authUser, checkDuplicateAssetDto);
|
||||||
|
|
||||||
return new CheckDuplicateAssetResponseDto(res);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@ import { AssetFileUploadDto } from './dto/asset-file-upload.dto';
|
||||||
import { CreateAssetDto } from './dto/create-asset.dto';
|
import { CreateAssetDto } from './dto/create-asset.dto';
|
||||||
import { DeleteAssetResponseDto, DeleteAssetStatusEnum } from './response-dto/delete-asset-response.dto';
|
import { DeleteAssetResponseDto, DeleteAssetStatusEnum } from './response-dto/delete-asset-response.dto';
|
||||||
import { GetAssetThumbnailDto, GetAssetThumbnailFormatEnum } from './dto/get-asset-thumbnail.dto';
|
import { GetAssetThumbnailDto, GetAssetThumbnailFormatEnum } from './dto/get-asset-thumbnail.dto';
|
||||||
|
import { CheckDuplicateAssetResponseDto } from './response-dto/check-duplicate-asset-response.dto';
|
||||||
|
|
||||||
const fileInfo = promisify(stat);
|
const fileInfo = promisify(stat);
|
||||||
|
|
||||||
|
@ -487,7 +488,10 @@ export class AssetService {
|
||||||
return curatedObjects;
|
return curatedObjects;
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkDuplicatedAsset(authUser: AuthUserDto, checkDuplicateAssetDto: CheckDuplicateAssetDto): Promise<boolean> {
|
async checkDuplicatedAsset(
|
||||||
|
authUser: AuthUserDto,
|
||||||
|
checkDuplicateAssetDto: CheckDuplicateAssetDto,
|
||||||
|
): Promise<CheckDuplicateAssetResponseDto> {
|
||||||
const res = await this.assetRepository.findOne({
|
const res = await this.assetRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
deviceAssetId: checkDuplicateAssetDto.deviceAssetId,
|
deviceAssetId: checkDuplicateAssetDto.deviceAssetId,
|
||||||
|
@ -496,6 +500,8 @@ export class AssetService {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return res ? true : false;
|
const isDuplicated = res ? true : false;
|
||||||
|
|
||||||
|
return new CheckDuplicateAssetResponseDto(isDuplicated, res?.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
export class CheckDuplicateAssetResponseDto {
|
export class CheckDuplicateAssetResponseDto {
|
||||||
constructor(isExist: boolean) {
|
constructor(isExist: boolean, id?: string) {
|
||||||
this.isExist = isExist;
|
this.isExist = isExist;
|
||||||
|
this.id = id;
|
||||||
}
|
}
|
||||||
isExist: boolean;
|
isExist: boolean;
|
||||||
|
id?: string;
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load diff
|
@ -12,22 +12,23 @@
|
||||||
* Do not edit the class manually.
|
* Do not edit the class manually.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Configuration } from './configuration';
|
|
||||||
|
import { Configuration } from "./configuration";
|
||||||
// Some imports not used depending on template conditions
|
// Some imports not used depending on template conditions
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import globalAxios, { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios';
|
import globalAxios, { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios';
|
||||||
|
|
||||||
export const BASE_PATH = '/api'.replace(/\/+$/, '');
|
export const BASE_PATH = "/api".replace(/\/+$/, "");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
*/
|
*/
|
||||||
export const COLLECTION_FORMATS = {
|
export const COLLECTION_FORMATS = {
|
||||||
csv: ',',
|
csv: ",",
|
||||||
ssv: ' ',
|
ssv: " ",
|
||||||
tsv: '\t',
|
tsv: "\t",
|
||||||
pipes: '|'
|
pipes: "|",
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -48,17 +49,13 @@ export interface RequestArgs {
|
||||||
export class BaseAPI {
|
export class BaseAPI {
|
||||||
protected configuration: Configuration | undefined;
|
protected configuration: Configuration | undefined;
|
||||||
|
|
||||||
constructor(
|
constructor(configuration?: Configuration, protected basePath: string = BASE_PATH, protected axios: AxiosInstance = globalAxios) {
|
||||||
configuration?: Configuration,
|
|
||||||
protected basePath: string = BASE_PATH,
|
|
||||||
protected axios: AxiosInstance = globalAxios
|
|
||||||
) {
|
|
||||||
if (configuration) {
|
if (configuration) {
|
||||||
this.configuration = configuration;
|
this.configuration = configuration;
|
||||||
this.basePath = configuration.basePath || this.basePath;
|
this.basePath = configuration.basePath || this.basePath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -67,7 +64,7 @@ export class BaseAPI {
|
||||||
* @extends {Error}
|
* @extends {Error}
|
||||||
*/
|
*/
|
||||||
export class RequiredError extends Error {
|
export class RequiredError extends Error {
|
||||||
name: 'RequiredError' = 'RequiredError';
|
name: "RequiredError" = "RequiredError";
|
||||||
constructor(public field: string, msg?: string) {
|
constructor(public field: string, msg?: string) {
|
||||||
super(msg);
|
super(msg);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,51 +12,40 @@
|
||||||
* Do not edit the class manually.
|
* Do not edit the class manually.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Configuration } from './configuration';
|
|
||||||
import { RequiredError, RequestArgs } from './base';
|
import { Configuration } from "./configuration";
|
||||||
|
import { RequiredError, RequestArgs } from "./base";
|
||||||
import { AxiosInstance, AxiosResponse } from 'axios';
|
import { AxiosInstance, AxiosResponse } from 'axios';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
*/
|
*/
|
||||||
export const DUMMY_BASE_URL = 'https://example.com';
|
export const DUMMY_BASE_URL = 'https://example.com'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
* @export
|
* @export
|
||||||
*/
|
*/
|
||||||
export const assertParamExists = function (
|
export const assertParamExists = function (functionName: string, paramName: string, paramValue: unknown) {
|
||||||
functionName: string,
|
|
||||||
paramName: string,
|
|
||||||
paramValue: unknown
|
|
||||||
) {
|
|
||||||
if (paramValue === null || paramValue === undefined) {
|
if (paramValue === null || paramValue === undefined) {
|
||||||
throw new RequiredError(
|
throw new RequiredError(paramName, `Required parameter ${paramName} was null or undefined when calling ${functionName}.`);
|
||||||
paramName,
|
}
|
||||||
`Required parameter ${paramName} was null or undefined when calling ${functionName}.`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
*/
|
*/
|
||||||
export const setApiKeyToObject = async function (
|
export const setApiKeyToObject = async function (object: any, keyParamName: string, configuration?: Configuration) {
|
||||||
object: any,
|
|
||||||
keyParamName: string,
|
|
||||||
configuration?: Configuration
|
|
||||||
) {
|
|
||||||
if (configuration && configuration.apiKey) {
|
if (configuration && configuration.apiKey) {
|
||||||
const localVarApiKeyValue =
|
const localVarApiKeyValue = typeof configuration.apiKey === 'function'
|
||||||
typeof configuration.apiKey === 'function'
|
|
||||||
? await configuration.apiKey(keyParamName)
|
? await configuration.apiKey(keyParamName)
|
||||||
: await configuration.apiKey;
|
: await configuration.apiKey;
|
||||||
object[keyParamName] = localVarApiKeyValue;
|
object[keyParamName] = localVarApiKeyValue;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -64,9 +53,9 @@ export const setApiKeyToObject = async function (
|
||||||
*/
|
*/
|
||||||
export const setBasicAuthToObject = function (object: any, configuration?: Configuration) {
|
export const setBasicAuthToObject = function (object: any, configuration?: Configuration) {
|
||||||
if (configuration && (configuration.username || configuration.password)) {
|
if (configuration && (configuration.username || configuration.password)) {
|
||||||
object['auth'] = { username: configuration.username, password: configuration.password };
|
object["auth"] = { username: configuration.username, password: configuration.password };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -74,32 +63,25 @@ export const setBasicAuthToObject = function (object: any, configuration?: Confi
|
||||||
*/
|
*/
|
||||||
export const setBearerAuthToObject = async function (object: any, configuration?: Configuration) {
|
export const setBearerAuthToObject = async function (object: any, configuration?: Configuration) {
|
||||||
if (configuration && configuration.accessToken) {
|
if (configuration && configuration.accessToken) {
|
||||||
const accessToken =
|
const accessToken = typeof configuration.accessToken === 'function'
|
||||||
typeof configuration.accessToken === 'function'
|
|
||||||
? await configuration.accessToken()
|
? await configuration.accessToken()
|
||||||
: await configuration.accessToken;
|
: await configuration.accessToken;
|
||||||
object['Authorization'] = 'Bearer ' + accessToken;
|
object["Authorization"] = "Bearer " + accessToken;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
*/
|
*/
|
||||||
export const setOAuthToObject = async function (
|
export const setOAuthToObject = async function (object: any, name: string, scopes: string[], configuration?: Configuration) {
|
||||||
object: any,
|
|
||||||
name: string,
|
|
||||||
scopes: string[],
|
|
||||||
configuration?: Configuration
|
|
||||||
) {
|
|
||||||
if (configuration && configuration.accessToken) {
|
if (configuration && configuration.accessToken) {
|
||||||
const localVarAccessTokenValue =
|
const localVarAccessTokenValue = typeof configuration.accessToken === 'function'
|
||||||
typeof configuration.accessToken === 'function'
|
|
||||||
? await configuration.accessToken(name, scopes)
|
? await configuration.accessToken(name, scopes)
|
||||||
: await configuration.accessToken;
|
: await configuration.accessToken;
|
||||||
object['Authorization'] = 'Bearer ' + localVarAccessTokenValue;
|
object["Authorization"] = "Bearer " + localVarAccessTokenValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -120,51 +102,37 @@ export const setSearchParams = function (url: URL, ...objects: any[]) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
url.search = searchParams.toString();
|
url.search = searchParams.toString();
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
*/
|
*/
|
||||||
export const serializeDataIfNeeded = function (
|
export const serializeDataIfNeeded = function (value: any, requestOptions: any, configuration?: Configuration) {
|
||||||
value: any,
|
|
||||||
requestOptions: any,
|
|
||||||
configuration?: Configuration
|
|
||||||
) {
|
|
||||||
const nonString = typeof value !== 'string';
|
const nonString = typeof value !== 'string';
|
||||||
const needsSerialization =
|
const needsSerialization = nonString && configuration && configuration.isJsonMime
|
||||||
nonString && configuration && configuration.isJsonMime
|
|
||||||
? configuration.isJsonMime(requestOptions.headers['Content-Type'])
|
? configuration.isJsonMime(requestOptions.headers['Content-Type'])
|
||||||
: nonString;
|
: nonString;
|
||||||
return needsSerialization ? JSON.stringify(value !== undefined ? value : {}) : value || '';
|
return needsSerialization
|
||||||
};
|
? JSON.stringify(value !== undefined ? value : {})
|
||||||
|
: (value || "");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
*/
|
*/
|
||||||
export const toPathString = function (url: URL) {
|
export const toPathString = function (url: URL) {
|
||||||
return url.pathname + url.search + url.hash;
|
return url.pathname + url.search + url.hash
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @export
|
* @export
|
||||||
*/
|
*/
|
||||||
export const createRequestFunction = function (
|
export const createRequestFunction = function (axiosArgs: RequestArgs, globalAxios: AxiosInstance, BASE_PATH: string, configuration?: Configuration) {
|
||||||
axiosArgs: RequestArgs,
|
return <T = unknown, R = AxiosResponse<T>>(axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
|
||||||
globalAxios: AxiosInstance,
|
const axiosRequestArgs = {...axiosArgs.options, url: (configuration?.basePath || basePath) + axiosArgs.url};
|
||||||
BASE_PATH: string,
|
|
||||||
configuration?: Configuration
|
|
||||||
) {
|
|
||||||
return <T = unknown, R = AxiosResponse<T>>(
|
|
||||||
axios: AxiosInstance = globalAxios,
|
|
||||||
basePath: string = BASE_PATH
|
|
||||||
) => {
|
|
||||||
const axiosRequestArgs = {
|
|
||||||
...axiosArgs.options,
|
|
||||||
url: (configuration?.basePath || basePath) + axiosArgs.url
|
|
||||||
};
|
|
||||||
return axios.request<T, R>(axiosRequestArgs);
|
return axios.request<T, R>(axiosRequestArgs);
|
||||||
};
|
};
|
||||||
};
|
}
|
||||||
|
|
|
@ -12,19 +12,12 @@
|
||||||
* Do not edit the class manually.
|
* Do not edit the class manually.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
export interface ConfigurationParameters {
|
export interface ConfigurationParameters {
|
||||||
apiKey?:
|
apiKey?: string | Promise<string> | ((name: string) => string) | ((name: string) => Promise<string>);
|
||||||
| string
|
|
||||||
| Promise<string>
|
|
||||||
| ((name: string) => string)
|
|
||||||
| ((name: string) => Promise<string>);
|
|
||||||
username?: string;
|
username?: string;
|
||||||
password?: string;
|
password?: string;
|
||||||
accessToken?:
|
accessToken?: string | Promise<string> | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise<string>);
|
||||||
| string
|
|
||||||
| Promise<string>
|
|
||||||
| ((name?: string, scopes?: string[]) => string)
|
|
||||||
| ((name?: string, scopes?: string[]) => Promise<string>);
|
|
||||||
basePath?: string;
|
basePath?: string;
|
||||||
baseOptions?: any;
|
baseOptions?: any;
|
||||||
formDataCtor?: new () => any;
|
formDataCtor?: new () => any;
|
||||||
|
@ -36,11 +29,7 @@ export class Configuration {
|
||||||
* @param name security name
|
* @param name security name
|
||||||
* @memberof Configuration
|
* @memberof Configuration
|
||||||
*/
|
*/
|
||||||
apiKey?:
|
apiKey?: string | Promise<string> | ((name: string) => string) | ((name: string) => Promise<string>);
|
||||||
| string
|
|
||||||
| Promise<string>
|
|
||||||
| ((name: string) => string)
|
|
||||||
| ((name: string) => Promise<string>);
|
|
||||||
/**
|
/**
|
||||||
* parameter for basic security
|
* parameter for basic security
|
||||||
*
|
*
|
||||||
|
@ -61,11 +50,7 @@ export class Configuration {
|
||||||
* @param scopes oauth2 scope
|
* @param scopes oauth2 scope
|
||||||
* @memberof Configuration
|
* @memberof Configuration
|
||||||
*/
|
*/
|
||||||
accessToken?:
|
accessToken?: string | Promise<string> | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise<string>);
|
||||||
| string
|
|
||||||
| Promise<string>
|
|
||||||
| ((name?: string, scopes?: string[]) => string)
|
|
||||||
| ((name?: string, scopes?: string[]) => Promise<string>);
|
|
||||||
/**
|
/**
|
||||||
* override base path
|
* override base path
|
||||||
*
|
*
|
||||||
|
@ -110,12 +95,7 @@ export class Configuration {
|
||||||
* @return True if the given MIME is JSON, false otherwise.
|
* @return True if the given MIME is JSON, false otherwise.
|
||||||
*/
|
*/
|
||||||
public isJsonMime(mime: string): boolean {
|
public isJsonMime(mime: string): boolean {
|
||||||
const jsonMime: RegExp = new RegExp(
|
const jsonMime: RegExp = new RegExp('^(application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(;.*)?$', 'i');
|
||||||
'^(application/json|[^;/ \t]+/[^;/ \t]+[+]json)[ \t]*(;.*)?$',
|
return mime !== null && (jsonMime.test(mime) || mime.toLowerCase() === 'application/json-patch+json');
|
||||||
'i'
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
mime !== null && (jsonMime.test(mime) || mime.toLowerCase() === 'application/json-patch+json')
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,5 +12,7 @@
|
||||||
* Do not edit the class manually.
|
* Do not edit the class manually.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export * from './api';
|
|
||||||
export * from './configuration';
|
export * from "./api";
|
||||||
|
export * from "./configuration";
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,8 @@
|
||||||
import ImmichThumbnail from '../shared-components/immich-thumbnail.svelte';
|
import ImmichThumbnail from '../shared-components/immich-thumbnail.svelte';
|
||||||
import { AssetResponseDto } from '@api';
|
import { AssetResponseDto } from '@api';
|
||||||
import AlbumAppBar from './album-app-bar.svelte';
|
import AlbumAppBar from './album-app-bar.svelte';
|
||||||
|
import { openFileUploadDialog, UploadType } from '$lib/utils/file-uploader';
|
||||||
|
import { albumUploadAssetStore } from '$lib/stores/album-upload-asset';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
@ -19,7 +21,41 @@
|
||||||
let existingGroup: Set<number> = new Set();
|
let existingGroup: Set<number> = new Set();
|
||||||
let groupWithAssetsInAlbum: Record<number, Set<string>> = {};
|
let groupWithAssetsInAlbum: Record<number, Set<string>> = {};
|
||||||
|
|
||||||
onMount(() => scanForExistingSelectedGroup());
|
let uploadAssets: string[] = [];
|
||||||
|
let uploadAssetsCount = 9999;
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
scanForExistingSelectedGroup();
|
||||||
|
|
||||||
|
albumUploadAssetStore.asset.subscribe((uploadedAsset) => {
|
||||||
|
uploadAssets = uploadedAsset;
|
||||||
|
});
|
||||||
|
|
||||||
|
albumUploadAssetStore.count.subscribe((count) => {
|
||||||
|
uploadAssetsCount = count;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Watch for the uploading event - when the uploaded assets are the same number of the chosen asset
|
||||||
|
* navigate back and add them to the album
|
||||||
|
*/
|
||||||
|
$: {
|
||||||
|
if (uploadAssets.length == uploadAssetsCount) {
|
||||||
|
// Filter assets that are already in the album
|
||||||
|
const assetsToAdd = uploadAssets.filter(
|
||||||
|
(asset) => !assetsInAlbum.some((a) => a.id === asset)
|
||||||
|
);
|
||||||
|
// Add the just uploaded assets to the album
|
||||||
|
dispatch('create-album', {
|
||||||
|
assets: assetsToAdd
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clean up states.
|
||||||
|
albumUploadAssetStore.asset.set([]);
|
||||||
|
albumUploadAssetStore.count.set(9999);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const selectAssetHandler = (assetId: string, groupIndex: number) => {
|
const selectAssetHandler = (assetId: string, groupIndex: number) => {
|
||||||
const tempSelectedAsset = new Set(selectedAsset);
|
const tempSelectedAsset = new Set(selectedAsset);
|
||||||
|
@ -146,6 +182,12 @@
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
|
|
||||||
<svelte:fragment slot="trailing">
|
<svelte:fragment slot="trailing">
|
||||||
|
<button
|
||||||
|
on:click={() => openFileUploadDialog(UploadType.ALBUM)}
|
||||||
|
class="text-immich-primary text-sm hover:bg-immich-primary/10 transition-all px-6 py-2 rounded-lg font-medium"
|
||||||
|
>
|
||||||
|
Select from computer
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
disabled={selectedAsset.size === 0}
|
disabled={selectedAsset.size === 0}
|
||||||
on:click={addSelectedAssets}
|
on:click={addSelectedAssets}
|
||||||
|
|
13
web/src/lib/stores/album-upload-asset.ts
Normal file
13
web/src/lib/stores/album-upload-asset.ts
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { writable } from 'svelte/store';
|
||||||
|
|
||||||
|
function createAlbumUploadStore() {
|
||||||
|
const albumUploadAsset = writable<Array<string>>([]);
|
||||||
|
const albumUploadAssetCount = writable<number>(9999);
|
||||||
|
|
||||||
|
return {
|
||||||
|
asset: albumUploadAsset,
|
||||||
|
count: albumUploadAssetCount
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const albumUploadAssetStore = createAlbumUploadStore();
|
|
@ -3,9 +3,58 @@ import * as exifr from 'exifr';
|
||||||
import { serverEndpoint } from '../constants';
|
import { serverEndpoint } from '../constants';
|
||||||
import { uploadAssetsStore } from '$lib/stores/upload';
|
import { uploadAssetsStore } from '$lib/stores/upload';
|
||||||
import type { UploadAsset } from '../models/upload-asset';
|
import type { UploadAsset } from '../models/upload-asset';
|
||||||
import { api } from '@api';
|
import { api, AssetFileUploadResponseDto } from '@api';
|
||||||
|
import { albumUploadAssetStore } from '$lib/stores/album-upload-asset';
|
||||||
|
|
||||||
export async function fileUploader(asset: File) {
|
/**
|
||||||
|
* Determine if the upload is for album or for the user general backup
|
||||||
|
* @variant GENERAL - Upload assets to the server for general backup
|
||||||
|
* @variant ALBUM - Upload assets to the server for backup and add to the album
|
||||||
|
*/
|
||||||
|
export enum UploadType {
|
||||||
|
/**
|
||||||
|
* Upload assets to the server
|
||||||
|
*/
|
||||||
|
GENERAL = 'GENERAL',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload assets to the server and add to album
|
||||||
|
*/
|
||||||
|
ALBUM = 'ALBUM'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const openFileUploadDialog = (uploadType: UploadType) => {
|
||||||
|
try {
|
||||||
|
let fileSelector = document.createElement('input');
|
||||||
|
|
||||||
|
fileSelector.type = 'file';
|
||||||
|
fileSelector.multiple = true;
|
||||||
|
fileSelector.accept = 'image/*,video/*,.heic,.heif';
|
||||||
|
|
||||||
|
fileSelector.onchange = async (e: any) => {
|
||||||
|
const files = Array.from<File>(e.target.files);
|
||||||
|
|
||||||
|
const acceptedFile = files.filter(
|
||||||
|
(e) => e.type.split('/')[0] === 'video' || e.type.split('/')[0] === 'image'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (uploadType === UploadType.ALBUM) {
|
||||||
|
albumUploadAssetStore.asset.set([]);
|
||||||
|
albumUploadAssetStore.count.set(acceptedFile.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const asset of acceptedFile) {
|
||||||
|
await fileUploader(asset, uploadType);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fileSelector.click();
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Error seelcting file', e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async function fileUploader(asset: File, uploadType: UploadType) {
|
||||||
const assetType = asset.type.split('/')[0].toUpperCase();
|
const assetType = asset.type.split('/')[0].toUpperCase();
|
||||||
const temp = asset.name.split('.');
|
const temp = asset.name.split('.');
|
||||||
const fileExtension = temp[temp.length - 1];
|
const fileExtension = temp[temp.length - 1];
|
||||||
|
@ -61,6 +110,11 @@ export async function fileUploader(asset: File) {
|
||||||
|
|
||||||
if (status === 200) {
|
if (status === 200) {
|
||||||
if (data.isExist) {
|
if (data.isExist) {
|
||||||
|
if (uploadType === UploadType.ALBUM && data.id) {
|
||||||
|
albumUploadAssetStore.asset.update((a) => {
|
||||||
|
return [...a, data.id!];
|
||||||
|
});
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -78,12 +132,26 @@ export async function fileUploader(asset: File) {
|
||||||
uploadAssetsStore.addNewUploadAsset(newUploadAsset);
|
uploadAssetsStore.addNewUploadAsset(newUploadAsset);
|
||||||
};
|
};
|
||||||
|
|
||||||
request.upload.onload = () => {
|
request.upload.onload = (e) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
uploadAssetsStore.removeUploadAsset(deviceAssetId);
|
uploadAssetsStore.removeUploadAsset(deviceAssetId);
|
||||||
}, 1000);
|
}, 1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
request.onreadystatechange = () => {
|
||||||
|
try {
|
||||||
|
if (request.readyState === 4 && uploadType === UploadType.ALBUM) {
|
||||||
|
const res: AssetFileUploadResponseDto = JSON.parse(request.response);
|
||||||
|
|
||||||
|
albumUploadAssetStore.asset.update((assets) => {
|
||||||
|
return [...assets, res.id];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('ERROR parsing data JSON in upload onreadystatechange');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// listen for `error` event
|
// listen for `error` event
|
||||||
request.upload.onerror = () => {
|
request.upload.onerror = () => {
|
||||||
uploadAssetsStore.removeUploadAsset(deviceAssetId);
|
uploadAssetsStore.removeUploadAsset(deviceAssetId);
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
import ImmichThumbnail from '$lib/components/shared-components/immich-thumbnail.svelte';
|
import ImmichThumbnail from '$lib/components/shared-components/immich-thumbnail.svelte';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import AssetViewer from '$lib/components/asset-viewer/asset-viewer.svelte';
|
import AssetViewer from '$lib/components/asset-viewer/asset-viewer.svelte';
|
||||||
import { fileUploader } from '$lib/utils/file-uploader';
|
import { openFileUploadDialog, UploadType } from '$lib/utils/file-uploader';
|
||||||
import { api, AssetResponseDto, UserResponseDto } from '@api';
|
import { api, AssetResponseDto, UserResponseDto } from '@api';
|
||||||
import SideBar from '$lib/components/shared-components/side-bar/side-bar.svelte';
|
import SideBar from '$lib/components/shared-components/side-bar/side-bar.svelte';
|
||||||
|
|
||||||
|
@ -64,32 +64,6 @@
|
||||||
pushState(selectedAsset.id);
|
pushState(selectedAsset.id);
|
||||||
};
|
};
|
||||||
|
|
||||||
const uploadClickedHandler = async () => {
|
|
||||||
try {
|
|
||||||
let fileSelector = document.createElement('input');
|
|
||||||
|
|
||||||
fileSelector.type = 'file';
|
|
||||||
fileSelector.multiple = true;
|
|
||||||
fileSelector.accept = 'image/*,video/*,.heic,.heif';
|
|
||||||
|
|
||||||
fileSelector.onchange = async (e: any) => {
|
|
||||||
const files = Array.from<File>(e.target.files);
|
|
||||||
|
|
||||||
const acceptedFile = files.filter(
|
|
||||||
(e) => e.type.split('/')[0] === 'video' || e.type.split('/')[0] === 'image'
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const asset of acceptedFile) {
|
|
||||||
await fileUploader(asset);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fileSelector.click();
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Error seelcting file', e);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const navigateAssetForward = () => {
|
const navigateAssetForward = () => {
|
||||||
try {
|
try {
|
||||||
if (currentViewAssetIndex < $flattenAssetGroupByDate.length - 1) {
|
if (currentViewAssetIndex < $flattenAssetGroupByDate.length - 1) {
|
||||||
|
@ -131,7 +105,7 @@
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<NavigationBar {user} on:uploadClicked={uploadClickedHandler} />
|
<NavigationBar {user} on:uploadClicked={() => openFileUploadDialog(UploadType.GENERAL)} />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="grid grid-cols-[250px_auto] relative pt-[72px] h-screen bg-immich-bg">
|
<section class="grid grid-cols-[250px_auto] relative pt-[72px] h-screen bg-immich-bg">
|
||||||
|
|
Loading…
Add table
Reference in a new issue