Browse Source

Moire small refactorings.

pull/95/head
Sebastian Stehle 9 years ago
parent
commit
40acddfa74
  1. 9
      src/Squidex.Domain.Apps.Read/Contents/GraphQL/Types/AssetGraphType.cs
  2. 31
      src/Squidex.Infrastructure/FileExtensions.cs
  3. 4
      src/Squidex/Controllers/Api/Assets/AssetsController.cs
  4. 6
      src/Squidex/Controllers/Api/Assets/Models/AssetCreatedDto.cs
  5. 6
      src/Squidex/Controllers/Api/Assets/Models/AssetDto.cs
  6. 1
      src/Squidex/Controllers/Api/Assets/Models/AssetReplacedDto.cs
  7. 12
      src/Squidex/app/features/administration/pages/users/user-page.component.ts
  8. 2
      src/Squidex/app/features/assets/pages/assets-page.component.ts
  9. 16
      src/Squidex/app/features/content/pages/content/content-page.component.ts
  10. 18
      src/Squidex/app/features/content/pages/contents/contents-page.component.ts
  11. 2
      src/Squidex/app/features/content/shared/assets-editor.component.ts
  12. 8
      src/Squidex/app/features/dashboard/pages/dashboard-page.component.html
  13. 35
      src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts
  14. 4
      src/Squidex/app/features/schemas/pages/schema/field.component.ts
  15. 8
      src/Squidex/app/features/schemas/pages/schema/schema-edit-form.component.ts
  16. 22
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts
  17. 8
      src/Squidex/app/features/schemas/pages/schemas/schema-form.component.ts
  18. 2
      src/Squidex/app/features/settings/pages/clients/clients-page.component.ts
  19. 10
      src/Squidex/app/features/settings/pages/contributors/contributors-page.component.ts
  20. 4
      src/Squidex/app/features/settings/pages/languages/language.component.ts
  21. 2
      src/Squidex/app/features/settings/pages/languages/languages-page.component.ts
  22. 4
      src/Squidex/app/features/settings/pages/plans/plans-page.component.html
  23. 23
      src/Squidex/app/features/settings/pages/plans/plans-page.component.ts
  24. 30
      src/Squidex/app/framework/angular/numbers.pipes.spec.ts
  25. 50
      src/Squidex/app/framework/angular/numbers.pipes.ts
  26. 1
      src/Squidex/app/framework/declarations.ts
  27. 6
      src/Squidex/app/framework/module.ts
  28. 4
      src/Squidex/app/framework/services/message-bus.spec.ts
  29. 2
      src/Squidex/app/framework/services/message-bus.ts
  30. 8
      src/Squidex/app/shared/components/app-form.component.ts
  31. 20
      src/Squidex/app/shared/components/asset.component.html
  32. 37
      src/Squidex/app/shared/components/asset.component.ts
  33. 62
      src/Squidex/app/shared/components/pipes.ts
  34. 1
      src/Squidex/app/shared/declarations-base.ts
  35. 11
      src/Squidex/app/shared/module.ts
  36. 37
      src/Squidex/app/shared/services/assets.service.spec.ts
  37. 6
      src/Squidex/app/shared/services/assets.service.ts
  38. 75
      src/Squidex/app/shared/utils/file-helper.spec.ts
  39. 112
      src/Squidex/app/shared/utils/file-helper.ts
  40. 38
      tests/Squidex.Infrastructure.Tests/FileExtensionsTests.cs

9
src/Squidex.Domain.Apps.Read/Contents/GraphQL/Types/AssetGraphType.cs

@ -10,6 +10,7 @@ using System;
using GraphQL.Resolvers;
using GraphQL.Types;
using Squidex.Domain.Apps.Read.Assets;
using Squidex.Infrastructure;
namespace Squidex.Domain.Apps.Read.Contents.GraphQL.Types
{
@ -99,6 +100,14 @@ namespace Squidex.Domain.Apps.Read.Contents.GraphQL.Types
Description = "The file name."
});
AddField(new FieldType
{
Name = "fileType",
Resolver = Resolver(x => x.FileName.FileType()),
ResolvedType = new NonNullGraphType(new StringGraphType()),
Description = "The file type."
});
AddField(new FieldType
{
Name = "fileSize",

31
src/Squidex.Infrastructure/FileExtensions.cs

@ -0,0 +1,31 @@
// ==========================================================================
// FileExtensions.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.IO;
// ReSharper disable InvertIf
namespace Squidex.Infrastructure
{
public static class FileExtensions
{
public static string FileType(this string fileName)
{
try
{
var fileInfo = new FileInfo(fileName);
return fileInfo.Extension.Substring(1).ToLowerInvariant();
}
catch
{
return "blob";
}
}
}
}

4
src/Squidex/Controllers/Api/Assets/AssetsController.cs

@ -108,7 +108,7 @@ namespace Squidex.Controllers.Api.Assets
var response = new AssetsDto
{
Total = taskForCount.Result,
Items = taskForItems.Result.Select(x => SimpleMapper.Map(x, new AssetDto())).ToArray()
Items = taskForItems.Result.Select(x => SimpleMapper.Map(x, new AssetDto { FileType = x.FileName.FileType() })).ToArray()
};
return Ok(response);
@ -137,7 +137,7 @@ namespace Squidex.Controllers.Api.Assets
return NotFound();
}
var response = SimpleMapper.Map(entity, new AssetDto());
var response = SimpleMapper.Map(entity, new AssetDto { FileType = entity.FileName.FileType() });
Response.Headers["ETag"] = new StringValues(entity.Version.ToString());

6
src/Squidex/Controllers/Api/Assets/Models/AssetCreatedDto.cs

@ -20,6 +20,12 @@ namespace Squidex.Controllers.Api.Assets.Models
/// </summary>
public Guid Id { get; set; }
/// <summary>
/// The file type.
/// </summary>
[Required]
public string FileType { get; set; }
/// <summary>
/// The file name.
/// </summary>

6
src/Squidex/Controllers/Api/Assets/Models/AssetDto.cs

@ -31,6 +31,12 @@ namespace Squidex.Controllers.Api.Assets.Models
/// </summary>
[Required]
public string MimeType { get; set; }
/// <summary>
/// The file type.
/// </summary>
[Required]
public string FileType { get; set; }
/// <summary>
/// The size of the file in bytes.

1
src/Squidex/Controllers/Api/Assets/Models/AssetReplacedDto.cs

@ -8,6 +8,7 @@
using System.ComponentModel.DataAnnotations;
using Squidex.Domain.Apps.Write.Assets.Commands;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Commands;
namespace Squidex.Controllers.Api.Assets.Models

12
src/Squidex/app/features/administration/pages/users/user-page.component.ts

@ -79,7 +79,7 @@ export class UserPageComponent extends ComponentBase implements OnInit {
created.pictureUrl!,
false);
this.sendUserCreated(this.user);
this.emitUserCreated(this.user);
this.notifyInfo('User created successfully.');
this.back();
}, error => {
@ -93,7 +93,7 @@ export class UserPageComponent extends ComponentBase implements OnInit {
requestDto.email,
requestDto.displayMessage);
this.sendUserUpdated(this.user);
this.emitUserUpdated(this.user);
this.notifyInfo('User saved successfully.');
this.resetUserForm();
}, error => {
@ -107,12 +107,12 @@ export class UserPageComponent extends ComponentBase implements OnInit {
this.router.navigate(['../'], { relativeTo: this.route, replaceUrl: true });
}
private sendUserCreated(user: UserDto) {
this.messageBus.publish(new UserCreated(user));
private emitUserCreated(user: UserDto) {
this.messageBus.emit(new UserCreated(user));
}
private sendUserUpdated(user: UserDto) {
this.messageBus.publish(new UserUpdated(user));
private emitUserUpdated(user: UserDto) {
this.messageBus.emit(new UserUpdated(user));
}
private setupAndPopulateForm() {

2
src/Squidex/app/features/assets/pages/assets-page.component.ts

@ -102,7 +102,7 @@ export class AssetsPageComponent extends AppComponentBase implements OnDestroy,
}
public onAssetUpdated(asset: AssetDto) {
this.messageBus.publish(new AssetUpdated(asset, this));
this.messageBus.emit(new AssetUpdated(asset, this));
}
public onAssetFailed(file: File) {

16
src/Squidex/app/features/content/pages/content/content-page.component.ts

@ -146,7 +146,7 @@ export class ContentPageComponent extends AppComponentBase implements CanCompone
.subscribe(dto => {
this.content = dto;
this.sendContentCreated(this.content);
this.emitContentCreated(this.content);
this.notifyInfo('Content created successfully.');
this.back();
}, error => {
@ -159,7 +159,7 @@ export class ContentPageComponent extends AppComponentBase implements CanCompone
.subscribe(() => {
this.content = this.content.update(requestDto, this.authService.user.token);
this.sendContentUpdated(this.content);
this.emitContentUpdated(this.content);
this.notifyInfo('Content saved successfully.');
this.enableContentForm();
}, error => {
@ -172,16 +172,16 @@ export class ContentPageComponent extends AppComponentBase implements CanCompone
}
}
private sendContentCreated(content: ContentDto) {
this.messageBus.publish(new ContentCreated(content));
private back() {
this.router.navigate(['../'], { relativeTo: this.route, replaceUrl: true });
}
private sendContentUpdated(content: ContentDto) {
this.messageBus.publish(new ContentUpdated(content));
private emitContentCreated(content: ContentDto) {
this.messageBus.emit(new ContentCreated(content));
}
private back() {
this.router.navigate(['../'], { relativeTo: this.route, replaceUrl: true });
private emitContentUpdated(content: ContentDto) {
this.messageBus.emit(new ContentUpdated(content));
}
private disableContentForm() {

18
src/Squidex/app/features/content/pages/contents/contents-page.component.ts

@ -105,6 +105,10 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy
this.isReadOnly = routeData['isReadOnly'];
}
public dropData(content: ContentDto) {
return { content, schemaId: this.schema.id };
}
public search() {
this.contentsQuery = this.contentsFilter.value;
this.contentsPager = new Pager(0);
@ -139,7 +143,7 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy
this.contentItems = this.contentItems.removeAll(x => x.id === content.id);
this.contentsPager = this.contentsPager.decrementCount();
this.sendContentDeleted(content);
this.emitContentDeleted(content);
}, error => {
this.notifyError(error);
});
@ -164,10 +168,6 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy
this.languageSelected = language;
}
public dropData(content: ContentDto) {
return { content, schemaId: this.schema.id };
}
public goNext() {
this.contentsPager = this.contentsPager.goNext();
@ -180,6 +180,10 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy
this.load();
}
private emitContentDeleted(content: ContentDto) {
this.messageBus.emit(new ContentDeleted(content));
}
private resetContents() {
this.contentItems = ImmutableArray.empty<ContentDto>();
this.contentsQuery = '';
@ -202,9 +206,5 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy
this.columnWidth = 100;
}
}
private sendContentDeleted(content: ContentDto) {
this.messageBus.publish(new ContentDeleted(content));
}
}

2
src/Squidex/app/features/content/shared/assets-editor.component.ts

@ -129,7 +129,7 @@ export class AssetsEditorComponent extends AppComponentBase implements ControlVa
}
public onAssetUpdated(asset: AssetDto) {
this.messageBus.publish(new AssetUpdated(asset, this));
this.messageBus.emit(new AssetUpdated(asset, this));
}
public onAssetFailed(file: File) {

8
src/Squidex/app/features/dashboard/pages/dashboard-page.component.html

@ -84,8 +84,8 @@
<div class="card-block">
<div class="aggregation" *ngIf="callsCurrent">
<div class="aggregation-label">API calls this month</div>
<div class="aggregation-value">{{callsCurrent}}</div>
<div class="aggregation-label" *ngIf="callsMax">Monthly limit: {{callsMax}}</div>
<div class="aggregation-value">{{callsCurrent | sqxKNumber}}</div>
<div class="aggregation-label" *ngIf="callsMax > 0">Monthly limit: {{callsMax | sqxKNumber}}</div>
</div>
</div>
</div>
@ -100,8 +100,8 @@
<div class="card-block">
<div class="aggregation" *ngIf="assetsCurrent">
<div class="aggregation-label">Asset size today</div>
<div class="aggregation-value">{{assetsCurrent}}</div>
<div class="aggregation-label" *ngIf="callsMax">Total limit: {{assetsMax}}</div>
<div class="aggregation-value">{{assetsCurrent | sqxFileSize}}</div>
<div class="aggregation-label" *ngIf="assetsMax > 0">Total limit: {{assetsMax | sqxFileSize}}</div>
</div>
</div>
</div>

35
src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts

@ -13,7 +13,6 @@ import {
AuthService,
DateTime,
fadeAnimation,
FileHelper,
NotificationService,
UsagesService
} from 'shared';
@ -55,11 +54,11 @@ export class DashboardPageComponent extends AppComponentBase implements OnInit {
maintainAspectRatio: false
};
public assetsCurrent: string | null = null;
public assetsMax: string | null = null;
public assetsCurrent = 0;
public assetsMax = 0;
public callsCurrent: string | null = null;
public callsMax: string | null = null;
public callsCurrent = 0;
public callsMax = 0;
constructor(apps: AppsStoreService, notifications: NotificationService,
private readonly authService: AuthService,
@ -72,15 +71,15 @@ export class DashboardPageComponent extends AppComponentBase implements OnInit {
this.appName()
.switchMap(app => this.usagesService.getTodayStorage(app))
.subscribe(dto => {
this.assetsCurrent = FileHelper.fileSize(dto.size);
this.assetsMax = FileHelper.fileSize(dto.maxAllowed);
this.assetsCurrent = dto.size;
this.assetsMax = dto.maxAllowed;
});
this.appName()
.switchMap(app => this.usagesService.getMonthCalls(app))
.subscribe(dto => {
this.callsCurrent = formatCalls(dto.count);
this.callsMax = formatCalls(dto.maxAllowed);
this.callsCurrent = dto.count;
this.callsMax = dto.maxAllowed;
});
this.appName()
@ -155,24 +154,6 @@ export class DashboardPageComponent extends AppComponentBase implements OnInit {
}
}
function formatCalls(count: number): string | null {
if (count > 1000) {
count = count / 1000;
if (count < 10) {
count = Math.round(count * 10) / 10;
} else {
count = Math.round(count);
}
return count + 'k';
} else if (count < 0) {
return null;
} else {
return count.toString();
}
}
function createLabels(dtos: { date: DateTime }[]): string[] {
return dtos.map(d => d.date.toStringFormat('M-DD'));
}

4
src/Squidex/app/features/schemas/pages/schema/field.component.ts

@ -109,11 +109,11 @@ export class FieldComponent implements OnInit {
this.field.partitioning,
properties);
this.sendSaving(field);
this.emitSaving(field);
}
}
private sendSaving(field: FieldDto) {
private emitSaving(field: FieldDto) {
this.saving.emit(field);
}

8
src/Squidex/app/features/schemas/pages/schema/schema-edit-form.component.ts

@ -65,7 +65,7 @@ export class SchemaEditFormComponent extends ComponentBase implements OnInit {
}
public cancel() {
this.sendCancelled();
this.emitCancelled();
this.resetEditForm();
}
@ -79,7 +79,7 @@ export class SchemaEditFormComponent extends ComponentBase implements OnInit {
this.schemas.putSchema(this.appName, this.name, requestDto, this.version)
.subscribe(dto => {
this.sendSaved(requestDto);
this.emitSaved(requestDto);
this.resetEditForm();
}, error => {
this.notifyError(error);
@ -88,11 +88,11 @@ export class SchemaEditFormComponent extends ComponentBase implements OnInit {
}
}
private sendCancelled() {
private emitCancelled() {
this.cancelled.emit();
}
private sendSaved(requestDto: any) {
private emitSaved(requestDto: any) {
this.saved.emit(new SchemaPropertiesDto(requestDto.label, requestDto.hints));
}

22
src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts

@ -201,7 +201,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
this.appNameOnce()
.switchMap(app => this.schemasService.deleteSchema(app, this.schema.name, this.schema.version)).retry(2)
.subscribe(() => {
this.sendSchemaDeleted(this.schema);
this.emitSchemaDeleted(this.schema);
this.hideDeleteDialog();
this.back();
}, error => {
@ -252,7 +252,7 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
private updateSchema(schema: SchemaDetailsDto) {
this.schema = schema;
this.sendSchemaUpdated(schema);
this.emitSchemaUpdated(schema);
this.notify();
this.export();
}
@ -288,24 +288,24 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
this.schemaExport = result;
}
private hideDeleteDialog() {
this.confirmDeleteDialog.hide();
}
private back() {
this.router.navigate(['../'], { relativeTo: this.route });
}
private sendSchemaDeleted(schema: SchemaDto) {
this.messageBus.publish(new SchemaDeleted(schema));
private emitSchemaDeleted(schema: SchemaDto) {
this.messageBus.emit(new SchemaDeleted(schema));
}
private sendSchemaUpdated(schema: SchemaDto) {
this.messageBus.publish(new SchemaUpdated(schema));
private emitSchemaUpdated(schema: SchemaDto) {
this.messageBus.emit(new SchemaUpdated(schema));
}
private hideDeleteDialog() {
this.confirmDeleteDialog.hide();
}
private notify() {
this.messageBus.publish(new HistoryChannelUpdated());
this.messageBus.emit(new HistoryChannelUpdated());
}
}

8
src/Squidex/app/features/schemas/pages/schemas/schema-form.component.ts

@ -72,7 +72,7 @@ export class SchemaFormComponent {
}
public cancel() {
this.sendCancelled();
this.emitCancelled();
this.resetCreateForm();
}
@ -91,7 +91,7 @@ export class SchemaFormComponent {
this.schemas.postSchema(this.appName, requestDto, me, undefined, schemaVersion)
.subscribe(dto => {
this.sendCreated(dto);
this.emitCreated(dto);
this.resetCreateForm();
}, error => {
this.enableCreateForm(error.displayMessage);
@ -99,11 +99,11 @@ export class SchemaFormComponent {
}
}
private sendCancelled() {
private emitCancelled() {
this.cancelled.emit();
}
private sendCreated(schema: SchemaDto) {
private emitCreated(schema: SchemaDto) {
this.created.emit(schema);
}

2
src/Squidex/app/features/settings/pages/clients/clients-page.component.ts

@ -136,6 +136,6 @@ export class ClientsPageComponent extends AppComponentBase implements OnInit {
private updateClients(clients: ImmutableArray<AppClientDto>) {
this.appClients = clients;
this.messageBus.publish(new HistoryChannelUpdated());
this.messageBus.emit(new HistoryChannelUpdated());
}
}

10
src/Squidex/app/features/settings/pages/contributors/contributors-page.component.ts

@ -137,13 +137,17 @@ export class ContributorsPageComponent extends AppComponentBase implements OnIni
.switchMap(app => this.appContributorsService.postContributor(app, requestDto, this.version))
.subscribe(() => {
this.updateContributors(this.appContributors.push(requestDto));
this.resetContributorForm();
}, error => {
this.notifyError(error);
}, () => {
this.addContributorForm.reset();
this.resetContributorForm();
});
}
private resetContributorForm() {
this.addContributorForm.reset();
}
private updateContributorsFromDto(dto: AppContributorsDto) {
this.updateContributors(ImmutableArray.of(dto.contributors));
@ -153,6 +157,6 @@ export class ContributorsPageComponent extends AppComponentBase implements OnIni
private updateContributors(contributors: ImmutableArray<AppContributorDto>) {
this.appContributors = contributors;
this.messageBus.publish(new HistoryChannelUpdated());
this.messageBus.emit(new HistoryChannelUpdated());
}
}

4
src/Squidex/app/features/settings/pages/languages/language.component.ts

@ -119,11 +119,11 @@ export class LanguageComponent implements OnInit, OnChanges, OnDestroy {
this.editForm.controls['isOptional'].value,
this.fallbackLanguages.map(l => l.iso2Code));
this.sendSaving(newLanguage);
this.emitSaving(newLanguage);
}
}
private sendSaving(language: AppLanguageDto) {
private emitSaving(language: AppLanguageDto) {
this.saving.emit(language);
}

2
src/Squidex/app/features/settings/pages/languages/languages-page.component.ts

@ -135,7 +135,7 @@ export class LanguagesPageComponent extends AppComponentBase implements OnInit {
this.updateNewLanguages();
this.messageBus.publish(new HistoryChannelUpdated());
this.messageBus.emit(new HistoryChannelUpdated());
}
private updateNewLanguages() {

4
src/Squidex/app/features/settings/pages/plans/plans-page.component.html

@ -45,10 +45,10 @@
</div>
<div class="card-block">
<div class="plan-fact">
{{formatCalls(plan.maxApiCalls)}} API Calls
{{plan.maxApiCalls | sqxKNumber}} API Calls
</div>
<div class="plan-fact">
{{formatSize(plan.maxAssetSize)}} Storage
{{plan.maxAssetSize | sqxFileSize}} Storage
</div>
<div class="plan-fact">
{{plan.maxContributors}} Contributors

23
src/Squidex/app/features/settings/pages/plans/plans-page.component.ts

@ -14,7 +14,6 @@ import {
AppsStoreService,
AuthService,
ChangePlanDto,
FileHelper,
NotificationService,
PlansService,
Version
@ -80,27 +79,5 @@ export class PlansPageComponent extends AppComponentBase implements OnInit {
this.isDisabled = false;
});
}
public formatSize(count: number): string {
return FileHelper.fileSize(count);
}
public formatCalls(count: number): string | null {
if (count > 1000) {
count = count / 1000;
if (count < 10) {
count = Math.round(count * 10) / 10;
} else {
count = Math.round(count);
}
return count + 'k';
} else if (count < 0) {
return null;
} else {
return count.toString();
}
}
}

30
src/Squidex/app/framework/angular/numbers.pipes.spec.ts

@ -0,0 +1,30 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { FileSizePipe, KNumberPipe } from './..';
describe('FileSizePipe', () => {
it('should calculate correct human file size', () => {
const pipe = new FileSizePipe();
expect(pipe.transform(50)).toBe('50 B');
expect(pipe.transform(1024)).toBe('1.0 kB');
expect(pipe.transform(1260000)).toBe('1.2 MB');
});
});
describe('KNumberPipe', () => {
it('should calculate correct human string', () => {
const pipe = new KNumberPipe();
expect(pipe.transform(0)).toBe('0');
expect(pipe.transform(-1)).toBe('');
expect(pipe.transform(50)).toBe('50');
expect(pipe.transform(1024)).toBe('1k');
expect(pipe.transform(1260000)).toBe('1000k');
});
});

50
src/Squidex/app/framework/angular/numbers.pipes.ts

@ -0,0 +1,50 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'sqxKNumber',
pure: true
})
export class KNumberPipe implements PipeTransform {
public transform(value: number) {
if (value > 1000) {
value = value / 1000;
if (value < 10) {
value = Math.round(value * 10) / 10;
} else {
value = Math.round(value);
}
return value + 'k';
} else if (value < 0) {
return '';
} else {
return value.toString();
}
}
}
@Pipe({
name: 'sqxFileSize',
pure: true
})
export class FileSizePipe implements PipeTransform {
public transform(value: number) {
let u = 0, s = 1024;
while (value >= s || -value >= s) {
value /= s;
u++;
}
return (u ? value.toFixed(1) + ' ' : value) + ' kMGTPEZY'[u] + 'B';
}
}

1
src/Squidex/app/framework/declarations.ts

@ -26,6 +26,7 @@ export * from './angular/modal-target.directive';
export * from './angular/modal-view.directive';
export * from './angular/money.pipe';
export * from './angular/name.pipe';
export * from './angular/numbers.pipes';
export * from './angular/panel.component';
export * from './angular/panel-container.directive';
export * from './angular/parent-link.directive';

6
src/Squidex/app/framework/module.ts

@ -24,12 +24,14 @@ import {
DropdownComponent,
DurationPipe,
FileDropDirective,
FileSizePipe,
FocusOnInitDirective,
FromNowPipe,
GeolocationEditorComponent,
ImageSourceDirective,
IndeterminateValueDirective,
JsonEditorComponent,
KNumberPipe,
LocalStoreService,
LowerCaseInputDirective,
MarkdownEditorComponent,
@ -83,12 +85,14 @@ import {
DropdownComponent,
DurationPipe,
FileDropDirective,
FileSizePipe,
FocusOnInitDirective,
FromNowPipe,
GeolocationEditorComponent,
ImageSourceDirective,
IndeterminateValueDirective,
JsonEditorComponent,
KNumberPipe,
LowerCaseInputDirective,
MarkdownEditorComponent,
ModalTargetDirective,
@ -126,12 +130,14 @@ import {
DropdownComponent,
DurationPipe,
FileDropDirective,
FileSizePipe,
FocusOnInitDirective,
FromNowPipe,
GeolocationEditorComponent,
ImageSourceDirective,
IndeterminateValueDirective,
JsonEditorComponent,
KNumberPipe,
LowerCaseInputDirective,
MarkdownEditorComponent,
ModalTargetDirective,

4
src/Squidex/app/framework/services/message-bus.spec.ts

@ -34,8 +34,8 @@ describe('MessageBus', () => {
lastEvent = event;
});
messageBus.publish(event1);
messageBus.publish(event2);
messageBus.emit(event1);
messageBus.emit(event2);
expect(lastEvent).toBe(event1);
});

2
src/Squidex/app/framework/services/message-bus.ts

@ -21,7 +21,7 @@ export const MessageBusFactory = () => {
export class MessageBus {
private message$ = new Subject<Message>();
public publish<T>(message: T): void {
public emit<T>(message: T): void {
const channel = (<any>message.constructor).name;
this.message$.next({ channel: channel, data: message });

8
src/Squidex/app/shared/components/app-form.component.ts

@ -54,7 +54,7 @@ export class AppFormComponent {
}
public cancel() {
this.sendCancelled();
this.emitCancelled();
this.resetCreateForm();
}
@ -69,18 +69,18 @@ export class AppFormComponent {
this.appsStore.createApp(request)
.subscribe(dto => {
this.resetCreateForm();
this.sendCreated(dto);
this.emitCreated(dto);
}, error => {
this.enableCreateForm(error.displayMessage);
});
}
}
private sendCancelled() {
private emitCancelled() {
this.cancelled.emit();
}
private sendCreated(app: AppDto) {
private emitCreated(app: AppDto) {
this.created.emit(app);
}

20
src/Squidex/app/shared/components/asset.component.html

@ -1,15 +1,15 @@
<div class="card" (sqxFileDrop)="updateFile($event)" dnd-draggable [dragEnabled]="!!asset" [dragData]="asset">
<div class="card-block">
<div class="file-preview" *ngIf="asset && progress == 0" [@fade]>
<span class="file-type" *ngIf="fileType">
{{fileType}}
<span class="file-type" *ngIf="asset.fileType">
{{asset.fileType}}
</span>
<div *ngIf="asset.isImage" class="file-image">
<img [sqxImageSource]="previewUrl">
<img [sqxImageSource]="asset | sqxAssetPreviewUrl">
</div>
<div *ngIf="!asset.isImage" class="file-icon-container">
<img class="file-icon" [attr.src]="fileIcon">
<img class="file-icon" [attr.src]="asset | sqxFileIcon">
</div>
<div class="file-overlay">
@ -18,7 +18,7 @@
<a class="file-edit" (click)="renameDialog.show()">
<i class="icon-pencil"></i>
</a>
<a class="file-download" [attr.href]="fileUrl" target="_blank">
<a class="file-download" [attr.href]="asset | sqxAssetUrl" target="_blank">
<i class="icon-download"></i>
</a>
@ -29,8 +29,8 @@
<i class="icon-close"></i>
</a>
<span class="file-overlay-type" *ngIf="fileType">
{{fileType}}
<span class="file-overlay-type" *ngIf="asset.fileType">
{{asset.fileType}}
</span>
<span class="file-user">
<i class="icon-user"></i> {{asset.lastModifiedBy | sqxUserNameRef}}
@ -42,11 +42,11 @@
</div>
</div>
<div class="card-footer" dnd-draggable-handle *ngIf="asset && progress == 0">
<div class="file-name" [attr.title]="fileName">
{{fileName}}
<div class="file-name" [attr.title]="asset.fileName">
{{asset.fileName}}
</div>
<div class="file-info">
{{fileInfo}}
<span *ngIf="asset.pixelWidth">{{asset.pixelWidth}}x{{asset.pixelHeight}}px, </span> {{asset.fileSize | sqxFileSize}}
</div>
</div>

37
src/Squidex/app/shared/components/asset.component.ts

@ -11,14 +11,12 @@ import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { AppComponentBase } from './app.component-base';
import {
ApiUrlConfig,
AppsStoreService,
AssetDto,
AssetReplacedDto,
AssetsService,
AuthService,
fadeAnimation,
FileHelper,
ModalView,
NotificationService,
UpdateAssetDto,
@ -34,7 +32,7 @@ import {
]
})
export class AssetComponent extends AppComponentBase implements OnInit {
private version: Version;
private assetVersion: Version;
@Input()
public initFile: File;
@ -71,18 +69,11 @@ export class AssetComponent extends AppComponentBase implements OnInit {
});
public progress = 0;
public previewUrl: string;
public fileUrl: string;
public fileName: string;
public fileType: string;
public fileIcon: string;
public fileInfo: string;
constructor(apps: AppsStoreService, notifications: NotificationService,
private readonly formBuilder: FormBuilder,
private readonly assetsService: AssetsService,
private readonly authService: AuthService,
private readonly apiUrl: ApiUrlConfig
private readonly authService: AuthService
) {
super(notifications, apps);
}
@ -95,7 +86,7 @@ export class AssetComponent extends AppComponentBase implements OnInit {
.switchMap(app => this.assetsService.uploadFile(app, initFile, this.authService.user.token))
.subscribe(dto => {
if (dto instanceof AssetDto) {
this.sendLoaded(dto);
this.emitLoaded(dto);
} else {
this.progress = dto;
}
@ -110,7 +101,7 @@ export class AssetComponent extends AppComponentBase implements OnInit {
public updateFile(files: FileList) {
if (files.length === 1) {
this.appNameOnce()
.switchMap(app => this.assetsService.replaceFile(app, this.asset.id, files[0], this.version))
.switchMap(app => this.assetsService.replaceFile(app, this.asset.id, files[0], this.assetVersion))
.subscribe(dto => {
if (dto instanceof AssetReplacedDto) {
this.updateAsset(this.asset.update(dto, this.authService.user.token), true);
@ -119,7 +110,7 @@ export class AssetComponent extends AppComponentBase implements OnInit {
}
}, error => {
this.setProgress();
this.sendFailed(error);
this.emitFailed(error);
});
}
}
@ -133,7 +124,7 @@ export class AssetComponent extends AppComponentBase implements OnInit {
const requestDto = new UpdateAssetDto(this.renameForm.controls['name'].value);
this.appNameOnce()
.switchMap(app => this.assetsService.putAsset(app, this.asset.id, requestDto, this.version))
.switchMap(app => this.assetsService.putAsset(app, this.asset.id, requestDto, this.assetVersion))
.subscribe(() => {
this.updateAsset(this.asset.rename(requestDto.fileName, this.authService.user.token), true);
this.resetRenameForm();
@ -152,15 +143,15 @@ export class AssetComponent extends AppComponentBase implements OnInit {
this.progress = progress;
}
private sendFailed(error: any) {
private emitFailed(error: any) {
this.failed.emit(error);
}
private sendLoaded(asset: AssetDto) {
private emitLoaded(asset: AssetDto) {
this.loaded.emit(asset);
}
private sendUpdated(asset: AssetDto) {
private emitUpdated(asset: AssetDto) {
this.updated.emit(asset);
}
@ -177,17 +168,11 @@ export class AssetComponent extends AppComponentBase implements OnInit {
private updateAsset(asset: AssetDto, emitEvent: boolean) {
this.asset = asset;
this.fileUrl = FileHelper.assetUrl(this.apiUrl, asset);
this.fileInfo = FileHelper.assetInfo(asset);
this.fileName = FileHelper.assetName(asset);
this.fileType = FileHelper.fileType(asset.mimeType, this.asset.fileName);
this.fileIcon = FileHelper.fileIcon(asset.mimeType);
this.assetVersion = asset.version;
this.progress = 0;
this.previewUrl = FileHelper.assetPreviewUrl(this.apiUrl, asset);
this.version = asset.version;
if (emitEvent) {
this.sendUpdated(asset);
this.emitUpdated(asset);
}
this.resetRenameForm();

62
src/Squidex/app/shared/components/pipes.ts

@ -8,7 +8,7 @@
import { ChangeDetectorRef, OnDestroy, Pipe, PipeTransform } from '@angular/core';
import { Observable, Subscription } from 'rxjs';
import { ApiUrlConfig } from 'framework';
import { ApiUrlConfig, MathHelper, Version } from 'framework';
import { UserDto, UsersProviderService } from './../declarations-base';
@ -192,4 +192,64 @@ export class UserPictureRefPipe extends UserAsyncPipe implements PipeTransform {
}
});
}
}
@Pipe({
name: 'sqxAssetUrl',
pure: false
})
export class AssetUrlPipe implements PipeTransform {
constructor(
private readonly apiUrl: ApiUrlConfig
) {
}
public transform(asset: { id: any }): string {
return this.apiUrl.buildUrl(`api/assets/${asset.id}?q=${MathHelper.guid()}`);
}
}
@Pipe({
name: 'sqxAssetPreviewUrl',
pure: false
})
export class AssetPreviewUrlPipe implements PipeTransform {
constructor(
private readonly apiUrl: ApiUrlConfig
) {
}
public transform(asset: { id: any, version: Version }): string {
return this.apiUrl.buildUrl(`api/assets/${asset.id}?version=${asset.version.value}`)
}
}
@Pipe({
name: 'sqxFileIcon',
pure: false
})
export class FileIconPipe implements PipeTransform {
public transform(asset: { mimeType: string, fileType: string }): string {
const knownTypes = [
'doc',
'docx',
'pdf',
'ppt',
'pptx',
'video',
'xls',
'xlsx'
];
let mimeIcon = '';
let mimeParts = asset.mimeType.split('/');
if (mimeParts.length === 2 && mimeParts[0].toLowerCase() === 'video') {
mimeIcon = 'video';
} else {
mimeIcon = knownTypes.indexOf(asset.fileType) >= 0 ? asset.fileType : 'generic'
}
return `/images/asset_${mimeIcon}.png`;
}
}

1
src/Squidex/app/shared/declarations-base.ts

@ -36,7 +36,6 @@ export * from './services/users-provider.service';
export * from './services/users.service';
export * from './services/webhooks.service';
export * from './utils/file-helper';
export * from './utils/messages';
export * from 'framework';

11
src/Squidex/app/shared/module.ts

@ -20,13 +20,16 @@ import {
AppsService,
AppMustExistGuard,
AssetComponent,
AssetPreviewUrlPipe,
AssetsService,
AssetUrlPipe,
AuthInterceptor,
AuthService,
ContentsService,
EventConsumersService,
HelpComponent,
FileIconPipe,
GraphQlService,
HelpComponent,
HelpService,
HistoryComponent,
HistoryService,
@ -64,6 +67,9 @@ import {
declarations: [
AppFormComponent,
AssetComponent,
AssetPreviewUrlPipe,
AssetUrlPipe,
FileIconPipe,
HelpComponent,
HistoryComponent,
LanguageSelectorComponent,
@ -79,6 +85,9 @@ import {
exports: [
AppFormComponent,
AssetComponent,
AssetPreviewUrlPipe,
AssetUrlPipe,
FileIconPipe,
HelpComponent,
HistoryComponent,
LanguageSelectorComponent,

37
src/Squidex/app/shared/services/assets.service.spec.ts

@ -23,10 +23,10 @@ describe('AssetDto', () => {
it('should update name property and user info when renaming', () => {
const now = DateTime.now();
const asset_1 = new AssetDto('1', 'other', 'other', DateTime.today(), DateTime.today(), 'name', 1, 1, 'image/png', false, 1, 1, null);
const asset_1 = new AssetDto('1', 'other', 'other', DateTime.today(), DateTime.today(), 'name.png', 'png', 1, 1, 'image/png', false, 1, 1, null);
const asset_2 = asset_1.rename('new-name', 'me', now);
expect(asset_2.fileName).toEqual('new-name');
expect(asset_2.fileName).toEqual('new-name.png');
expect(asset_2.lastModified).toEqual(now);
expect(asset_2.lastModifiedBy).toEqual('me');
});
@ -36,7 +36,7 @@ describe('AssetDto', () => {
const update = new AssetReplacedDto(2, 2, 'image/jpeg', true, 2, 2, null);
const asset_1 = new AssetDto('1', 'other', 'other', DateTime.today(), DateTime.today(), 'name', 1, 1, 'image/png', false, 1, 1, null);
const asset_1 = new AssetDto('1', 'other', 'other', DateTime.today(), DateTime.today(), 'name.png', 'png', 1, 1, 'image/png', false, 1, 1, null);
const asset_2 = asset_1.update(update, 'me', now);
expect(asset_2.fileSize).toEqual(2);
@ -95,9 +95,10 @@ describe('AssetsService', () => {
lastModified: '2017-12-12T10:10',
lastModifiedBy: 'LastModifiedBy1',
fileName: 'my-asset1.png',
fileType: 'png',
fileSize: 1024,
fileVersion: 2000,
mimeType: 'text/plain',
mimeType: 'image/png',
isImage: true,
pixelWidth: 1024,
pixelHeight: 2048,
@ -110,9 +111,10 @@ describe('AssetsService', () => {
lastModified: '2017-10-12T10:10',
lastModifiedBy: 'LastModifiedBy2',
fileName: 'my-asset2.png',
fileType: 'png',
fileSize: 1024,
fileVersion: 2000,
mimeType: 'text/plain',
mimeType: 'image/png',
isImage: true,
pixelWidth: 1024,
pixelHeight: 2048,
@ -128,9 +130,10 @@ describe('AssetsService', () => {
DateTime.parseISO_UTC('2016-12-12T10:10'),
DateTime.parseISO_UTC('2017-12-12T10:10'),
'my-asset1.png',
'png',
1024,
2000,
'text/plain',
'image/png',
true,
1024,
2048,
@ -139,9 +142,10 @@ describe('AssetsService', () => {
DateTime.parseISO_UTC('2016-10-12T10:10'),
DateTime.parseISO_UTC('2017-10-12T10:10'),
'my-asset2.png',
'png',
1024,
2000,
'text/plain',
'image/png',
true,
1024,
2048,
@ -170,9 +174,10 @@ describe('AssetsService', () => {
lastModified: '2017-12-12T10:10',
lastModifiedBy: 'LastModifiedBy1',
fileName: 'my-asset1.png',
fileType: 'png',
fileSize: 1024,
fileVersion: 2000,
mimeType: 'text/plain',
mimeType: 'image/png',
isImage: true,
pixelWidth: 1024,
pixelHeight: 2048,
@ -185,9 +190,10 @@ describe('AssetsService', () => {
DateTime.parseISO_UTC('2016-12-12T10:10'),
DateTime.parseISO_UTC('2017-12-12T10:10'),
'my-asset1.png',
'png',
1024,
2000,
'text/plain',
'image/png',
true,
1024,
2048,
@ -210,9 +216,9 @@ describe('AssetsService', () => {
it('should append mime types to find by types',
inject([AssetsService, HttpTestingController], (assetsService: AssetsService, httpMock: HttpTestingController) => {
assetsService.getAssets('my-app', 17, 13, undefined, ['text/plain', 'image/png']).subscribe();
assetsService.getAssets('my-app', 17, 13, undefined, ['image/png', 'image/png']).subscribe();
const req = httpMock.expectOne('http://service/p/api/apps/my-app/assets?mimeTypes=text/plain,image/png&take=17&skip=13');
const req = httpMock.expectOne('http://service/p/api/apps/my-app/assets?mimeTypes=image/png,image/png&take=17&skip=13');
expect(req.request.method).toEqual('GET');
expect(req.request.headers.get('If-Match')).toBeNull();
@ -252,7 +258,7 @@ describe('AssetsService', () => {
fileName: 'my-asset1.png',
fileSize: 1024,
fileVersion: 2,
mimeType: 'text/plain',
mimeType: 'image/png',
isImage: true,
pixelWidth: 1024,
pixelHeight: 2048,
@ -267,8 +273,9 @@ describe('AssetsService', () => {
now,
now,
'my-asset1.png',
'png',
1024, 2,
'text/plain',
'image/png',
true,
1024,
2048,
@ -292,7 +299,7 @@ describe('AssetsService', () => {
req.flush({
fileSize: 1024,
fileVersion: 2,
mimeType: 'text/plain',
mimeType: 'image/png',
isImage: true,
pixelWidth: 1024,
pixelHeight: 2048,
@ -302,7 +309,7 @@ describe('AssetsService', () => {
expect(asset).toEqual(
new AssetReplacedDto(
1024, 2,
'text/plain',
'image/png',
true,
1024,
2048,

6
src/Squidex/app/shared/services/assets.service.ts

@ -32,6 +32,7 @@ export class AssetDto {
public readonly created: DateTime,
public readonly lastModified: DateTime,
public readonly fileName: string,
public readonly fileType: string,
public readonly fileSize: number,
public readonly fileVersion: number,
public readonly mimeType: string,
@ -48,6 +49,7 @@ export class AssetDto {
this.createdBy, user,
this.created, now || DateTime.now(),
this.fileName,
this.fileType,
update.fileSize,
update.fileVersion,
update.mimeType,
@ -63,6 +65,7 @@ export class AssetDto {
this.createdBy, user,
this.created, now || DateTime.now(),
name,
this.fileType,
this.fileSize,
this.fileVersion,
this.mimeType,
@ -135,6 +138,7 @@ export class AssetsService {
DateTime.parseISO_UTC(item.created),
DateTime.parseISO_UTC(item.lastModified),
item.fileName,
item.fileType,
item.fileSize,
item.fileVersion,
item.mimeType,
@ -174,6 +178,7 @@ export class AssetsService {
now,
now,
response.fileName,
response.fileType,
response.fileSize,
response.fileVersion,
response.mimeType,
@ -204,6 +209,7 @@ export class AssetsService {
DateTime.parseISO_UTC(response.created),
DateTime.parseISO_UTC(response.lastModified),
response.fileName,
response.fileType,
response.fileSize,
response.fileVersion,
response.mimeType,

75
src/Squidex/app/shared/utils/file-helper.spec.ts

@ -1,75 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { ApiUrlConfig, DateTime, Version } from 'framework';
import { FileHelper } from './file-helper';
import { AssetDto } from './../services/assets.service';
describe('FileHelper', () => {
const now = DateTime.now();
const user = 'user';
it('should calculate correct human file size', () => {
expect(FileHelper.fileSize(50)).toBe('50 B');
expect(FileHelper.fileSize(1024)).toBe('1.0 kB');
expect(FileHelper.fileSize(1260000)).toBe('1.2 MB');
});
it('should calculate icon', () => {
expect(FileHelper.fileIcon('video/mp4')).toBe('/images/asset_video.png');
expect(FileHelper.fileIcon('application/text')).toBe('/images/asset_generic.png');
expect(FileHelper.fileIcon('application/msword')).toBe('/images/asset_doc.png');
});
it('should calculate file type', () => {
expect(FileHelper.fileType('video/mp4', 'test.mp4')).toBe('mp4');
expect(FileHelper.fileType('video/mp4')).toBe('mp4');
expect(FileHelper.fileType('application/text', 'test.txt')).toBe('txt');
expect(FileHelper.fileType('application/text')).toBe('text');
expect(FileHelper.fileType('invalid')).toBeUndefined();
expect(FileHelper.fileType(null!)).toBeUndefined();
});
it('should calculate asset info for image asset', () => {
const asset = new AssetDto('1', user, user, now, now, 'File.png', 50, 1, 'image/png', true, 100, 20, new Version('123'));
expect(FileHelper.assetInfo(asset)).toBe('100x20px, 50 B');
});
it('should calculate asset info for text asset', () => {
const asset = new AssetDto('1', user, user, now, now, 'File.txt', 50, 1, 'text/plain', false, 0, 0, null!);
expect(FileHelper.assetInfo(asset)).toBe('50 B');
});
it('should return asset name', () => {
const asset = new AssetDto('1', user, user, now, now, 'File.txt', 50, 1, 'text/plain', false, 0, 0, null!);
expect(FileHelper.assetName(asset)).toBe('File.txt');
});
it('should return empty string for invalid asset', () => {
expect(FileHelper.assetInfo(undefined!)).toBe('');
expect(FileHelper.assetInfo(null!)).toBe('');
});
it('should return preview url', () => {
const apiUrl = new ApiUrlConfig('my/');
const asset = new AssetDto('1', user, user, now, now, 'File.txt', 50, 1, 'text/plain', false, 0, 0, new Version('123'));
expect(FileHelper.assetPreviewUrl(apiUrl, asset)).toBe('my/api/assets/1?version=123');
});
it('should return download url', () => {
const apiUrl = new ApiUrlConfig('my/');
const asset = new AssetDto('1', user, user, now, now, 'File.txt', 50, 1, 'text/plain', false, 0, 0, new Version('123'));
expect(FileHelper.assetUrl(apiUrl, asset).startsWith('my/api/assets/1?q=')).toBeTruthy();
});
});

112
src/Squidex/app/shared/utils/file-helper.ts

@ -1,112 +0,0 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { ApiUrlConfig, MathHelper } from 'framework';
import { AssetDto } from './../services/assets.service';
const mimeMapping = {
'pdf': 'pdf',
'vnd.openxmlformats-officedocument.wordprocessingml.document': 'docx',
'vnd.openxmlformats-officedocument.wordprocessingml.template': 'docx',
'vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'xlsx',
'vnd.openxmlformats-officedocument.spreadsheetml.template': 'xlsx',
'vnd.openxmlformats-officedocument.presentationml.presentation': 'pptx',
'vnd.openxmlformats-officedocument.presentationml.template': 'pptx',
'vnd.openxmlformats-officedocument.presentationml.slideshow': 'pptx',
'msword': 'doc',
'vnd.ms-word': 'doc',
'vnd.ms-word.document.macroEnabled.12': 'docx',
'vnd.ms-word.template.macroEnabled.12': 'docx',
'vnd.ms-excel': 'xls',
'vnd.ms-excel.sheet.macroEnabled.12': 'xlsx',
'vnd.ms-excel.template.macroEnabled.12': 'xlsx',
'vnd.ms-excel.addin.macroEnabled.12': 'xlsx',
'vnd.ms-excel.sheet.binary.macroEnabled.12': 'xlsx',
'vnd.ms-powerpoint': 'ppt',
'vnd.ms-powerpoint.addin.macroEnabled.12': 'pptx',
'vnd.ms-powerpoint.presentation.macroEnabled.12': 'pptx',
'vnd.ms-powerpoint.template.macroEnabled.12': 'pptx',
'vnd.ms-powerpoint.slideshow.macroEnabled.12': 'pptx'
};
export module FileHelper {
export function assetUrl(apiUrl: ApiUrlConfig, asset: AssetDto): string {
return apiUrl.buildUrl(`api/assets/${asset.id}?q=${MathHelper.guid()}`);
}
export function assetName(asset: AssetDto): string {
return asset.fileName;
}
export function assetPreviewUrl(apiUrl: ApiUrlConfig, asset: AssetDto) {
return apiUrl.buildUrl(`api/assets/${asset.id}?version=${asset.version.value}`);
}
export function assetInfo(asset: AssetDto): string {
let result = '';
if (asset != null) {
if (asset.pixelWidth) {
result = `${asset.pixelWidth}x${asset.pixelHeight}px, `;
}
result += FileHelper.fileSize(asset.fileSize);
}
return result;
}
export function fileType(mimeType: string, fileName?: string) {
if (fileName) {
const parts = fileName.split('.');
if (parts.length > 1) {
return parts[parts.length - 1].toLowerCase();
}
}
if (mimeType) {
const parts = mimeType.split('/');
if (parts.length === 2) {
const mimeSuffix = parts[1].toLowerCase();
return mimeMapping[mimeSuffix] || mimeSuffix;
}
}
return undefined;
}
export function fileIcon(mimeType: string) {
const mimeParts = mimeType.split('/');
let mimeIcon = 'generic';
if (mimeParts.length === 2) {
const mimePrefix = mimeParts[0].toLowerCase();
const mimeSuffix = mimeParts[1].toLowerCase();
if (mimePrefix === 'video') {
mimeIcon = 'video';
} else {
mimeIcon = mimeMapping[mimeSuffix] || 'generic';
}
}
return `/images/asset_${mimeIcon}.png`;
}
export function fileSize(bytes: number) {
let u = 0, s = 1024;
while (bytes >= s || -bytes >= s) {
bytes /= s;
u++;
}
return (u ? bytes.toFixed(1) + ' ' : bytes) + ' kMGTPEZY'[u] + 'B';
}
}

38
tests/Squidex.Infrastructure.Tests/FileExtensionsTests.cs

@ -0,0 +1,38 @@
// ==========================================================================
// FileExtensionsTests.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Xunit;
namespace Squidex.Infrastructure
{
public class FileExtensionsTests
{
[Theory]
[InlineData("test.mp4", "mp4")]
[InlineData("test.MP4", "mp4")]
[InlineData("test.txt", "txt")]
[InlineData("test.TXT", "txt")]
public void Should_calculate_file_type(string fileName, string expected)
{
var actual = fileName.FileType();
Assert.Equal(expected, actual);
}
[Theory]
[InlineData("")]
[InlineData(" ")]
[InlineData(null)]
public void Should_blob_for_invalid_file_types(string fileName)
{
var actual = fileName.FileType();
Assert.Equal("blob", actual);
}
}
}
Loading…
Cancel
Save