Move to axios

This commit is contained in:
Rubikscraft 2022-12-26 16:55:54 +01:00
parent 9bf2dfd6fc
commit 2f4c74b8da
18 changed files with 349 additions and 178 deletions

View File

@ -38,6 +38,7 @@
"@types/resize-observer-browser": "^0.1.7",
"@types/validator": "^13.7.10",
"ackee-tracker": "^5.1.0",
"axios": "^1.2.1",
"bootstrap": "^5.2.3",
"caniuse-lite": "^1.0.30001441",
"fuse.js": "^6.6.2",

View File

@ -6,7 +6,7 @@ import {
Input,
OnChanges,
SimpleChanges,
ViewChild,
ViewChild
} from '@angular/core';
import { FileType, ImageFileType } from 'picsur-shared/dist/dto/mimes.dto';
import { AsyncFailable, HasFailed } from 'picsur-shared/dist/types';
@ -85,7 +85,7 @@ export class PicsurImgComponent implements OnChanges {
this.state = PicsurImgState.Canvas;
} else {
const result = await this.apiService.getBuffer(url);
const result = await this.apiService.getBuffer(url).result;
if (HasFailed(result)) return result;
const img = this.img.nativeElement;
@ -99,12 +99,12 @@ export class PicsurImgComponent implements OnChanges {
}
private async getFileType(url: string): AsyncFailable<FileType> {
const response = await this.apiService.head(url);
const response = await this.apiService.head(url).result;
if (HasFailed(response)) {
return response;
}
const mimeHeader = response.get('content-type') ?? '';
const mimeHeader = response['content-type'] ?? '';
const mime = mimeHeader.split(';')[0];
return ParseMime2FileType(mime);

View File

@ -0,0 +1,13 @@
import { MultiPartRequest } from './multi-part-request.dto';
export class ImagesUploadRequest implements MultiPartRequest {
constructor(private images: File[]) {}
public createFormData(): FormData {
const data = new FormData();
for (let i = 0; i < this.images.length; i++) {
data.append(`images[${i}]`, this.images[i]);
}
return data;
}
}

View File

@ -1,17 +1,23 @@
import { Inject, Injectable } from '@angular/core';
import { WINDOW } from '@ng-web-apis/common';
import axios, {
AxiosRequestConfig,
AxiosResponse,
AxiosResponseHeaders
} from 'axios';
import { ApiResponseSchema } from 'picsur-shared/dist/dto/api/api.dto';
import { FileType2Ext } from 'picsur-shared/dist/dto/mimes.dto';
import {
AsyncFailable,
Fail,
Failure,
FT,
HasFailed,
HasSuccess,
HasSuccess
} from 'picsur-shared/dist/types';
import { ZodDtoStatic } from 'picsur-shared/dist/util/create-zod-dto';
import { ParseMime2FileType } from 'picsur-shared/dist/util/parse-mime';
import { Subject } from 'rxjs';
import { BehaviorSubject, Observable, Subject } from 'rxjs';
import { ApiBuffer } from 'src/app/models/dto/api-buffer.dto';
import { ApiError } from 'src/app/models/dto/api-error.dto';
import { z } from 'zod';
@ -23,6 +29,37 @@ import { KeyStorageService } from '../storage/key-storage.service';
Proud of this, it works so smoooth
*/
interface RunningRequest<R> {
uploadProgress: Observable<number>;
downloadProgress: Observable<number>;
result: AsyncFailable<R>;
cancel: () => void;
}
function MapRunningRequest<R, T>(
runningRequest: RunningRequest<R>,
map: (r: R) => AsyncFailable<T>,
): RunningRequest<T> {
return {
...runningRequest,
result: runningRequest.result.then(async (result) => {
if (HasFailed(result)) return result;
return map(result);
}),
};
}
function CreateFailedRunningRequest<R>(failure: Failure) {
const subject = new Subject<number>();
subject.complete();
return {
uploadProgress: subject.asObservable(),
downloadProgress: subject.asObservable(),
result: Promise.resolve(failure),
cancel: () => {},
} as RunningRequest<R>;
}
@Injectable({
providedIn: 'root',
})
@ -40,177 +77,196 @@ export class ApiService {
@Inject(WINDOW) private readonly windowRef: Window,
) {}
public async get<T extends z.AnyZodObject>(
public get<T extends z.AnyZodObject>(
type: ZodDtoStatic<T>,
url: string,
): AsyncFailable<z.infer<T>> {
): RunningRequest<z.infer<T>> {
return this.fetchSafeJson(type, url, { method: 'GET' });
}
public async head(url: string): AsyncFailable<Headers> {
public head(url: string): RunningRequest<AxiosResponseHeaders> {
return this.fetchHead(url, { method: 'HEAD' });
}
public async getBuffer(url: string): AsyncFailable<ApiBuffer> {
public getBuffer(url: string): RunningRequest<ApiBuffer> {
return this.fetchBuffer(url, { method: 'GET' });
}
public async post<T extends z.AnyZodObject, W extends z.AnyZodObject>(
public post<T extends z.AnyZodObject, W extends z.AnyZodObject>(
sendType: ZodDtoStatic<T>,
receiveType: ZodDtoStatic<W>,
url: string,
data: z.infer<T>,
): AsyncFailable<z.infer<W>> {
): RunningRequest<z.infer<W>> {
const sendSchema = sendType.zodSchema;
const validateResult = sendSchema.safeParse(data);
if (!validateResult.success) {
return Fail(
FT.SysValidation,
'Something went wrong',
validateResult.error,
return CreateFailedRunningRequest(
Fail(FT.SysValidation, 'Something went wrong', validateResult.error),
);
}
return this.fetchSafeJson(receiveType, url, {
method: 'POST',
body: JSON.stringify(validateResult.data),
data: validateResult.data,
});
}
public async postEmpty<T extends z.AnyZodObject>(
public postEmpty<T extends z.AnyZodObject>(
type: ZodDtoStatic<T>,
url: string,
): AsyncFailable<z.infer<T>> {
): RunningRequest<z.infer<T>> {
return this.fetchSafeJson(type, url, { method: 'POST' });
}
public async postForm<T extends z.AnyZodObject>(
public postForm<T extends z.AnyZodObject>(
receiveType: ZodDtoStatic<T>,
url: string,
data: MultiPartRequest,
): AsyncFailable<z.infer<T>> {
): RunningRequest<z.infer<T>> {
return this.fetchSafeJson(receiveType, url, {
method: 'POST',
body: data.createFormData(),
data: data.createFormData(),
});
}
private async fetchSafeJson<T extends z.AnyZodObject>(
private fetchSafeJson<T extends z.AnyZodObject>(
type: ZodDtoStatic<T>,
url: RequestInfo,
options: RequestInit,
): AsyncFailable<z.infer<T>> {
url: string,
options: AxiosRequestConfig,
): RunningRequest<z.infer<T>> {
const resultSchema = ApiResponseSchema(type.zodSchema as z.AnyZodObject);
type resultType = z.infer<typeof resultSchema>;
let result = await this.fetchJsonAs<resultType>(url, options);
if (HasFailed(result)) return result;
let result = this.fetchJsonAs<resultType>(url, options);
const validateResult = resultSchema.safeParse(result);
if (!validateResult.success) {
return Fail(
FT.SysValidation,
'Something went wrong',
validateResult.error,
);
}
if (validateResult.data.success === false)
return Fail(FT.Unknown, result.data.message);
return validateResult.data.data;
}
private async fetchJsonAs<T>(
url: RequestInfo,
options: RequestInit,
): AsyncFailable<T> {
const response = await this.fetch(url, options);
if (HasFailed(response)) {
return response;
}
try {
return await response.json();
} catch (e) {
return Fail(FT.Internal, e);
}
}
private async fetchBuffer(
url: RequestInfo,
options: RequestInit,
): AsyncFailable<ApiBuffer> {
const response = await this.fetch(url, options);
if (HasFailed(response)) return response;
if (!response.ok) return Fail(FT.Network, 'Recieved a non-ok response');
const mimeType = response.headers.get('Content-Type') ?? 'other/unknown';
let name = response.headers.get('Content-Disposition');
if (!name) {
if (typeof url === 'string') {
name = url.split('/').pop() ?? 'unnamed';
} else {
name = url.url.split('/').pop() ?? 'unnamed';
return MapRunningRequest(result, async (r) => {
const validateResult = resultSchema.safeParse(r);
if (!validateResult.success) {
return Fail(
FT.SysValidation,
'Something went wrong',
validateResult.error,
);
}
}
const filetype = ParseMime2FileType(mimeType);
if (HasSuccess(filetype)) {
const ext = FileType2Ext(filetype.identifier);
if (HasSuccess(ext)) {
if (!name.endsWith(ext)) {
name += '.' + ext;
if (validateResult.data.success === false)
return Fail(FT.Unknown, r.data.message);
return validateResult.data.data;
});
}
private fetchJsonAs<T>(
url: string,
options: AxiosRequestConfig,
): RunningRequest<T> {
const response = this.fetch(url, {
...options,
responseType: 'json',
});
return MapRunningRequest(response, async (r) => r.data);
}
private fetchBuffer(
url: string,
options: AxiosRequestConfig,
): RunningRequest<ApiBuffer> {
const response = this.fetch(url, {
...options,
responseType: 'arraybuffer',
});
return MapRunningRequest(response, async (r) => {
const mimeType = r.headers['Content-Type'] ?? 'other/unknown';
let name = r.headers['Content-Disposition'];
if (!name) {
name = url.split('/').pop() ?? 'unnamed';
}
const filetype = ParseMime2FileType(mimeType);
if (HasSuccess(filetype)) {
const ext = FileType2Ext(filetype.identifier);
if (HasSuccess(ext)) {
if (!name.endsWith(ext)) {
name += '.' + ext;
}
}
}
}
try {
const arrayBuffer = await response.arrayBuffer();
return {
buffer: arrayBuffer,
buffer: r.data,
mimeType,
name,
};
} catch (e) {
return Fail(FT.Internal, e);
}
});
}
private async fetchHead(
url: RequestInfo,
options: RequestInit,
): AsyncFailable<Headers> {
const response = await this.fetch(url, options);
if (HasFailed(response)) return response;
private fetchHead(
url: string,
options: AxiosRequestConfig,
): RunningRequest<AxiosResponseHeaders> {
const response = this.fetch(url, options);
if (!response.ok) return Fail(FT.Network, 'Recieved a non-ok response');
return response.headers;
return MapRunningRequest(response, async (r) => {
return r.headers as AxiosResponseHeaders;
});
}
private async fetch(
url: RequestInfo,
options: RequestInit,
): AsyncFailable<Response> {
try {
const key = this.keyService.get();
const isJSON = typeof options.body === 'string';
private fetch(
url: string,
options: AxiosRequestConfig,
): RunningRequest<AxiosResponse> {
const key = this.keyService.get();
const isJSON = typeof options.data === 'string';
const headers: any = options.headers || {};
if (key !== null)
headers['Authorization'] = `Bearer ${this.keyService.get()}`;
if (isJSON) headers['Content-Type'] = 'application/json';
options.headers = headers;
const headers: any = options.headers || {};
if (key !== null)
headers['Authorization'] = `Bearer ${this.keyService.get()}`;
if (isJSON) headers['Content-Type'] = 'application/json';
options.headers = headers;
return await this.windowRef.fetch(url, options);
} catch (e) {
this.errorSubject.next({
error: e,
url,
});
return Fail(FT.Network, e);
}
const uploadProgress = new BehaviorSubject<number>(0);
const downloadProgress = new BehaviorSubject<number>(0);
const abortController = new AbortController();
const resultPromise: AsyncFailable<AxiosResponse> = (async () => {
try {
const result = await axios.request({
url,
onDownloadProgress: (e) => {
downloadProgress.next(e.loaded / (e.total ?? 1000000) * 100);
},
onUploadProgress: (e) => {
uploadProgress.next(e.loaded / (e.total ?? 1000000) * 100);
},
signal: abortController.signal,
...options,
});
uploadProgress.complete();
downloadProgress.complete();
if (result.status < 200 || result.status >= 300) {
return Fail(FT.Network, 'Recieved a non-ok response');
}
return result;
} catch (e) {
return Fail(FT.Network, e);
}
})();
return {
result: resultPromise,
uploadProgress,
downloadProgress,
cancel: () => {
abortController.abort();
uploadProgress.complete();
downloadProgress.complete();
},
};
}
}

View File

@ -8,7 +8,7 @@ import {
ApiKeyListRequest,
ApiKeyListResponse,
ApiKeyUpdateRequest,
ApiKeyUpdateResponse,
ApiKeyUpdateResponse
} from 'picsur-shared/dist/dto/api/apikeys.dto';
import { EApiKey } from 'picsur-shared/dist/entities/apikey.entity';
import { AsyncFailable } from 'picsur-shared/dist/types';
@ -26,7 +26,7 @@ export class ApiKeysService {
page: number,
userID?: string,
): AsyncFailable<FindResult<EApiKey>> {
const response = await this.api.post(
return await this.api.post(
ApiKeyListRequest,
ApiKeyListResponse,
'/api/apikeys/list',
@ -35,9 +35,7 @@ export class ApiKeysService {
page,
user_id: userID,
},
);
return response;
).result;
}
public async getApiKey(id: string): AsyncFailable<EApiKey> {
@ -48,14 +46,12 @@ export class ApiKeysService {
{
id,
},
);
).result;
}
public async createApiKey(): AsyncFailable<EApiKey> {
return await this.api.postEmpty(
ApiKeyCreateResponse,
'/api/apikeys/create',
);
return await this.api.postEmpty(ApiKeyCreateResponse, '/api/apikeys/create')
.result;
}
public async updateApiKey(id: string, name: string): AsyncFailable<EApiKey> {
@ -67,7 +63,7 @@ export class ApiKeysService {
id,
name,
},
);
).result;
}
public async deleteApiKey(id: string): AsyncFailable<Omit<EApiKey, 'id'>> {
@ -78,6 +74,6 @@ export class ApiKeysService {
{
id,
},
);
).result;
}
}

View File

@ -23,6 +23,7 @@ import {
HasSuccess,
Open
} from 'picsur-shared/dist/types/failable';
import { ImagesUploadRequest } from 'src/app/models/dto/images-upload-request.dto';
import { ImageUploadRequest } from '../../models/dto/image-upload-request.dto';
import { ApiService } from './api.service';
import { InfoService } from './info.service';
@ -43,13 +44,28 @@ export class ImageService {
ImageUploadResponse,
'/api/image/upload',
new ImageUploadRequest(image),
);
).result;
return Open(result, 'id');
}
public async UploadImages(images: File[]): AsyncFailable<string[]> {
console.log('Uploading images', images);
// Split into chunks of 20
const groups = this.chunks(images, 20);
const result = await this.api.postForm(
ImageUploadResponse,
'/api/image/upload/bulk',
new ImagesUploadRequest(images),
);
return [];
}
public async GetImageMeta(image: string): AsyncFailable<ImageMetaResponse> {
return await this.api.get(ImageMetaResponse, `/i/meta/${image}`);
return await this.api.get(ImageMetaResponse, `/i/meta/${image}`).result;
}
public async ListAllImages(
@ -66,7 +82,7 @@ export class ImageService {
page,
user_id: userID,
},
);
).result;
}
public async ListMyImages(
@ -93,7 +109,7 @@ export class ImageService {
id,
...settings,
},
);
).result;
}
public async DeleteImages(
@ -106,7 +122,7 @@ export class ImageService {
{
ids: images,
},
);
).result;
}
public async DeleteImage(image: string): AsyncFailable<EImage> {
@ -192,4 +208,13 @@ export class ImageService {
): ImageLinks {
return this.CreateImageLinks(this.GetImageURL(imageID, mime, true), name);
}
private chunks<T>(arr: T[], size: number): T[][] {
let result = [];
for (let i = 0; i < arr.length; i += size) {
result.push(arr.slice(i, size + i));
}
return result;
}
}

View File

@ -101,7 +101,7 @@ export class InfoService {
}
private async updateInfo(): AsyncFailable<ServerInfo> {
const response = await this.api.get(InfoResponse, '/api/info');
const response = await this.api.get(InfoResponse, '/api/info').result;
if (HasFailed(response)) return response;
this.infoSubject.next(response);

View File

@ -69,7 +69,7 @@ export class PermissionService {
const got = await this.api.get(
UserMePermissionsResponse,
'/api/user/me/permissions',
);
).result;
if (HasFailed(got)) return got;
this.permissionsSubject.next(got.permissions);

View File

@ -8,7 +8,7 @@ import {
RoleInfoResponse,
RoleListResponse,
RoleUpdateRequest,
RoleUpdateResponse,
RoleUpdateResponse
} from 'picsur-shared/dist/dto/api/roles.dto';
import { ERole } from 'picsur-shared/dist/entities/role.entity';
import { AsyncFailable, Open } from 'picsur-shared/dist/types';
@ -22,7 +22,8 @@ export class RolesService {
constructor(private readonly api: ApiService) {}
public async getRoles(): AsyncFailable<ERole[]> {
const response = await this.api.get(RoleListResponse, '/api/roles/list');
const response = await this.api.get(RoleListResponse, '/api/roles/list')
.result;
return Open(response, 'results');
}
@ -35,7 +36,7 @@ export class RolesService {
{
name,
},
);
).result;
}
public async createRole(role: RoleModel): AsyncFailable<ERole> {
@ -44,7 +45,7 @@ export class RolesService {
RoleCreateResponse,
'/api/roles/create',
role,
);
).result;
}
public async updateRole(role: RoleModel): AsyncFailable<ERole> {
@ -53,7 +54,7 @@ export class RolesService {
RoleUpdateResponse,
'/api/roles/update',
role,
);
).result;
}
public async deleteRole(name: string): AsyncFailable<ERole> {
@ -64,6 +65,6 @@ export class RolesService {
{
name,
},
);
).result;
}
}

View File

@ -29,7 +29,7 @@ export class StaticInfoService {
SoulBoundRoles: [],
UndeletableRoles: [],
},
() => this.api.get(SpecialRolesResponse, '/api/roles/special'),
() => this.api.get(SpecialRolesResponse, '/api/roles/special').result,
);
}
@ -41,7 +41,7 @@ export class StaticInfoService {
LockedLoginUsersList: [],
UndeletableUsersList: [],
},
() => this.api.get(GetSpecialUsersResponse, '/api/user/special'),
() => this.api.get(GetSpecialUsersResponse, '/api/user/special').result,
);
}
@ -54,7 +54,7 @@ export class StaticInfoService {
const res = await this.api.get(
AllPermissionsResponse,
'/api/info/permissions',
);
).result;
return Open(res, 'permissions');
},
);

View File

@ -4,19 +4,19 @@ import {
GetPreferenceResponse,
MultiplePreferencesResponse,
UpdatePreferenceRequest,
UpdatePreferenceResponse,
UpdatePreferenceResponse
} from 'picsur-shared/dist/dto/api/pref.dto';
import { Permission } from 'picsur-shared/dist/dto/permissions.enum';
import {
DecodedPref,
PrefValueType,
PrefValueType
} from 'picsur-shared/dist/dto/preferences.dto';
import {
AsyncFailable,
Fail,
FT,
HasFailed,
Map,
Map
} from 'picsur-shared/dist/types';
import { BehaviorSubject } from 'rxjs';
import { ErrorService } from 'src/app/util/error-manager/error.service';
@ -69,7 +69,7 @@ export class SysPrefService {
const response = await this.api.get(
MultiplePreferencesResponse,
'/api/pref/sys',
);
).result;
return Map(response, (pref) => {
this.sysprefObservable.next(pref.results);
@ -89,7 +89,7 @@ export class SysPrefService {
const response = await this.api.get(
GetPreferenceResponse,
`/api/pref/sys/${key}`,
);
).result;
if (!HasFailed(response)) this.updatePrefArray(response);
return response;
@ -110,7 +110,7 @@ export class SysPrefService {
UpdatePreferenceResponse,
`/api/pref/sys/${key}`,
{ value },
);
).result;
if (!HasFailed(response)) this.updatePrefArray(response);
return response;

View File

@ -9,7 +9,7 @@ import {
UserListRequest,
UserListResponse,
UserUpdateRequest,
UserUpdateResponse,
UserUpdateResponse
} from 'picsur-shared/dist/dto/api/user-manage.dto';
import { EUser } from 'picsur-shared/dist/entities/user.entity';
import { AsyncFailable } from 'picsur-shared/dist/types';
@ -27,7 +27,7 @@ export class UserAdminService {
UserInfoResponse,
'api/user/info',
{ id },
);
).result;
}
public async getUsers(
@ -42,7 +42,7 @@ export class UserAdminService {
count,
page,
},
);
).result;
}
public async createUser(user: UserCreateRequest): AsyncFailable<EUser> {
@ -51,7 +51,7 @@ export class UserAdminService {
UserCreateResponse,
'/api/user/create',
user,
);
).result;
}
public async updateUser(user: UserUpdateRequest): AsyncFailable<EUser> {
@ -60,7 +60,7 @@ export class UserAdminService {
UserUpdateResponse,
'/api/user/update',
user,
);
).result;
}
public async deleteUser(id: string): AsyncFailable<Omit<EUser, 'id'>> {
@ -69,6 +69,6 @@ export class UserAdminService {
UserDeleteResponse,
'/api/user/delete',
{ id },
);
).result;
}
}

View File

@ -7,7 +7,7 @@ import {
UserLoginResponse,
UserMeResponse,
UserRegisterRequest,
UserRegisterResponse,
UserRegisterResponse
} from 'picsur-shared/dist/dto/api/user.dto';
import { JwtDataSchema } from 'picsur-shared/dist/dto/jwt.dto';
import { EUser } from 'picsur-shared/dist/entities/user.entity';
@ -16,7 +16,7 @@ import {
Fail,
FT,
HasFailed,
Open,
Open
} from 'picsur-shared/dist/types';
import { BehaviorSubject } from 'rxjs';
import { Logger } from '../logger/logger.service';
@ -72,7 +72,7 @@ export class UserService {
username,
password,
},
);
).result;
if (HasFailed(response)) return response;
// Set the key so the apiservice can use it
@ -94,7 +94,7 @@ export class UserService {
{
username,
},
),
).result,
'available',
);
}
@ -111,7 +111,7 @@ export class UserService {
username,
password,
},
);
).result;
}
public async logout(): AsyncFailable<EUser> {
@ -147,7 +147,7 @@ export class UserService {
// This actually fetches up to date information from the server
private async fetchUser(): AsyncFailable<EUser> {
const got = await this.api.get(UserMeResponse, '/api/user/me');
const got = await this.api.get(UserMeResponse, '/api/user/me').result;
if (HasFailed(got)) return got;
this.key.set(got.token);

View File

@ -4,19 +4,19 @@ import {
GetPreferenceResponse,
MultiplePreferencesResponse,
UpdatePreferenceRequest,
UpdatePreferenceResponse,
UpdatePreferenceResponse
} from 'picsur-shared/dist/dto/api/pref.dto';
import { Permission } from 'picsur-shared/dist/dto/permissions.enum';
import {
DecodedPref,
PrefValueType,
PrefValueType
} from 'picsur-shared/dist/dto/preferences.dto';
import {
AsyncFailable,
Fail,
FT,
HasFailed,
Map,
Map
} from 'picsur-shared/dist/types';
import { BehaviorSubject } from 'rxjs';
import { ErrorService } from 'src/app/util/error-manager/error.service';
@ -69,7 +69,7 @@ export class UsrPrefService {
const response = await this.api.get(
MultiplePreferencesResponse,
'/api/pref/usr',
);
).result;
return Map(response, (pref) => {
this.usrprefObservable.next(pref.results);
@ -89,7 +89,7 @@ export class UsrPrefService {
const response = await this.api.get(
GetPreferenceResponse,
`/api/pref/usr/${key}`,
);
).result;
if (!HasFailed(response)) this.updatePrefArray(response);
return response;
@ -110,7 +110,7 @@ export class UsrPrefService {
UpdatePreferenceResponse,
`/api/pref/usr/${key}`,
{ value },
);
).result;
if (!HasFailed(response)) this.updatePrefArray(response);
return response;

View File

@ -1,5 +1,5 @@
<div class="dialog-text">
<h2>Downloading {{ data.name }}...</h2>
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
<mat-progress-bar mode="determinate" [value]="progress | async"></mat-progress-bar>
</div>
<div class="dialog-buttons"></div>

View File

@ -1,8 +1,10 @@
import { Component, Inject } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { Observable } from 'rxjs';
export interface DownloadDialogData {
name: string;
progress?: Observable<number>;
}
@Component({
@ -10,8 +12,12 @@ export interface DownloadDialogData {
templateUrl: './download-dialog.component.html',
})
export class DownloadDialogComponent {
public progress: Observable<number>;
constructor(
public readonly dialogRef: MatDialogRef<DownloadDialogComponent>,
@Inject(MAT_DIALOG_DATA) public readonly data: DownloadDialogData,
) {}
) {
this.progress = data.progress ?? new Observable<number>();
}
}

View File

@ -1,6 +1,7 @@
import { Injectable } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { Fail, FT, HasFailed } from 'picsur-shared/dist/types';
import { Observable } from 'rxjs';
import { ApiService } from 'src/app/services/api/api.service';
import { Logger } from 'src/app/services/logger/logger.service';
import { DownloadDialogComponent } from '../dialog-manager/download-dialog/download-dialog.component';
@ -20,9 +21,12 @@ export class DownloadService {
private readonly errorService: ErrorService,
) {}
public showDownloadDialog(filename: string): () => void {
public showDownloadDialog(
filename: string,
progress?: Observable<number>,
): () => void {
const ref = this.dialog.open(DownloadDialogComponent, {
data: { name: filename },
data: { name: filename, progress: progress },
disableClose: true,
closeOnNavigation: false,
});
@ -31,11 +35,17 @@ export class DownloadService {
}
public async downloadFile(url: string) {
const closeDialog = this.showDownloadDialog('image');
const file = await this.api.getBuffer(url);
if (HasFailed(file))
const request = this.api.getBuffer(url);
const closeDialog = this.showDownloadDialog('image', request.downloadProgress);
const file = await request.result;
if (HasFailed(file)){
closeDialog();
return this.errorService.showFailure(file, this.logger);
}
this.util.downloadBuffer(file.buffer, file.name, file.mimeType);
@ -80,7 +90,7 @@ export class DownloadService {
url,
};
} else {
const image = await this.api.getBuffer(url);
const image = await this.api.getBuffer(url).result;
if (HasFailed(image))
return this.errorService.showFailure(image, this.logger);

View File

@ -4354,6 +4354,13 @@ __metadata:
languageName: node
linkType: hard
"asynckit@npm:^0.4.0":
version: 0.4.0
resolution: "asynckit@npm:0.4.0"
checksum: 7b78c451df768adba04e2d02e63e2d0bf3b07adcd6e42b4cf665cb7ce899bedd344c69a1dcbce355b5f972d597b25aaa1c1742b52cffd9caccb22f348114f6be
languageName: node
linkType: hard
"atomic-sleep@npm:^1.0.0":
version: 1.0.0
resolution: "atomic-sleep@npm:1.0.0"
@ -4390,6 +4397,17 @@ __metadata:
languageName: node
linkType: hard
"axios@npm:^1.2.1":
version: 1.2.1
resolution: "axios@npm:1.2.1"
dependencies:
follow-redirects: ^1.15.0
form-data: ^4.0.0
proxy-from-env: ^1.1.0
checksum: c4dc4e119064c9aed09a3de309bedb797a139a6fb372223aafe3e0c10a7d4a14e4d3e9c9d309467fadb9d2b490b891ee3df96ef5b55716bb971910466ff9f0c5
languageName: node
linkType: hard
"babel-loader@npm:9.1.0":
version: 9.1.0
resolution: "babel-loader@npm:9.1.0"
@ -5041,6 +5059,15 @@ __metadata:
languageName: node
linkType: hard
"combined-stream@npm:^1.0.8":
version: 1.0.8
resolution: "combined-stream@npm:1.0.8"
dependencies:
delayed-stream: ~1.0.0
checksum: 49fa4aeb4916567e33ea81d088f6584749fc90c7abec76fd516bf1c5aa5c79f3584b5ba3de6b86d26ddd64bae5329c4c7479343250cfe71c75bb366eae53bb7c
languageName: node
linkType: hard
"commander@npm:4.1.1":
version: 4.1.1
resolution: "commander@npm:4.1.1"
@ -5411,6 +5438,13 @@ __metadata:
languageName: node
linkType: hard
"delayed-stream@npm:~1.0.0":
version: 1.0.0
resolution: "delayed-stream@npm:1.0.0"
checksum: 46fe6e83e2cb1d85ba50bd52803c68be9bd953282fa7096f51fc29edd5d67ff84ff753c51966061e5ba7cb5e47ef6d36a91924eddb7f3f3483b1c560f77a0020
languageName: node
linkType: hard
"delegates@npm:^1.0.0":
version: 1.0.0
resolution: "delegates@npm:1.0.0"
@ -6546,6 +6580,16 @@ __metadata:
languageName: node
linkType: hard
"follow-redirects@npm:^1.15.0":
version: 1.15.2
resolution: "follow-redirects@npm:1.15.2"
peerDependenciesMeta:
debug:
optional: true
checksum: faa66059b66358ba65c234c2f2a37fcec029dc22775f35d9ad6abac56003268baf41e55f9ee645957b32c7d9f62baf1f0b906e68267276f54ec4b4c597c2b190
languageName: node
linkType: hard
"fork-ts-checker-webpack-plugin@npm:7.2.13":
version: 7.2.13
resolution: "fork-ts-checker-webpack-plugin@npm:7.2.13"
@ -6573,6 +6617,17 @@ __metadata:
languageName: node
linkType: hard
"form-data@npm:^4.0.0":
version: 4.0.0
resolution: "form-data@npm:4.0.0"
dependencies:
asynckit: ^0.4.0
combined-stream: ^1.0.8
mime-types: ^2.1.12
checksum: 01135bf8675f9d5c61ff18e2e2932f719ca4de964e3be90ef4c36aacfc7b9cb2fceb5eca0b7e0190e3383fe51c5b37f4cb80b62ca06a99aaabfcfd6ac7c9328c
languageName: node
linkType: hard
"formdata-polyfill@npm:^4.0.10":
version: 4.0.10
resolution: "formdata-polyfill@npm:4.0.10"
@ -8199,7 +8254,7 @@ __metadata:
languageName: node
linkType: hard
"mime-types@npm:^2.1.27, mime-types@npm:^2.1.31, mime-types@npm:~2.1.17, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34":
"mime-types@npm:^2.1.12, mime-types@npm:^2.1.27, mime-types@npm:^2.1.31, mime-types@npm:~2.1.17, mime-types@npm:~2.1.24, mime-types@npm:~2.1.34":
version: 2.1.35
resolution: "mime-types@npm:2.1.35"
dependencies:
@ -9532,6 +9587,7 @@ __metadata:
"@types/resize-observer-browser": ^0.1.7
"@types/validator": ^13.7.10
ackee-tracker: ^5.1.0
axios: ^1.2.1
bootstrap: ^5.2.3
caniuse-lite: ^1.0.30001441
fuse.js: ^6.6.2
@ -9905,6 +9961,13 @@ __metadata:
languageName: node
linkType: hard
"proxy-from-env@npm:^1.1.0":
version: 1.1.0
resolution: "proxy-from-env@npm:1.1.0"
checksum: ed7fcc2ba0a33404958e34d95d18638249a68c430e30fcb6c478497d72739ba64ce9810a24f53a7d921d0c065e5b78e3822759800698167256b04659366ca4d4
languageName: node
linkType: hard
"prr@npm:~1.0.1":
version: 1.0.1
resolution: "prr@npm:1.0.1"