Browse Source

Fist asset field editor

pull/65/head
Sebastian Stehle 9 years ago
parent
commit
2d193ec75e
  1. 15
      src/Squidex.Read.MongoDb/Assets/MongoAssetRepository.cs
  2. 4
      src/Squidex.Read/Assets/Repositories/IAssetRepository.cs
  3. 2
      src/Squidex/Controllers/Api/Assets/AssetContentController.cs
  4. 50
      src/Squidex/Controllers/Api/Assets/AssetController.cs
  5. 2
      src/Squidex/Controllers/Api/Schemas/SchemasController.cs
  6. 7
      src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.ts
  7. 7
      src/Squidex/app/features/administration/pages/users/users-page.component.ts
  8. 1
      src/Squidex/app/features/assets/declarations.ts
  9. 7
      src/Squidex/app/features/assets/module.ts
  10. 2
      src/Squidex/app/features/assets/pages/assets-page.component.html
  11. 4
      src/Squidex/app/features/assets/pages/assets-page.component.scss
  12. 9
      src/Squidex/app/features/assets/pages/assets-page.component.ts
  13. 4
      src/Squidex/app/features/content/pages/content/content-field.component.html
  14. 5
      src/Squidex/app/features/content/pages/content/content-page.component.ts
  15. 2
      src/Squidex/app/features/content/pages/contents/content-item.component.html
  16. 7
      src/Squidex/app/features/content/pages/contents/content-item.component.ts
  17. 5
      src/Squidex/app/features/content/pages/contents/contents-page.component.ts
  18. 7
      src/Squidex/app/features/content/pages/schemas/schemas-page.component.ts
  19. 7
      src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts
  20. 5
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts
  21. 2
      src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html
  22. 5
      src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts
  23. 5
      src/Squidex/app/features/settings/pages/clients/clients-page.component.ts
  24. 6
      src/Squidex/app/features/settings/pages/contributors/contributors-page.component.html
  25. 5
      src/Squidex/app/features/settings/pages/contributors/contributors-page.component.ts
  26. 5
      src/Squidex/app/features/settings/pages/languages/languages-page.component.ts
  27. 5
      src/Squidex/app/framework/angular/cloak.directive.ts
  28. 21
      src/Squidex/app/framework/angular/date-time.pipes.ts
  29. 44
      src/Squidex/app/framework/angular/file-drop.directive.ts
  30. 6
      src/Squidex/app/framework/angular/focus-on-change.directive.ts
  31. 6
      src/Squidex/app/framework/angular/focus-on-init.directive.ts
  32. 30
      src/Squidex/app/framework/angular/image-source.directive.ts
  33. 8
      src/Squidex/app/framework/angular/indeterminate-value.directive.ts
  34. 10
      src/Squidex/app/framework/angular/lowercase-input.directive.ts
  35. 3
      src/Squidex/app/framework/angular/money.pipe.ts
  36. 3
      src/Squidex/app/framework/angular/name.pipe.ts
  37. 8
      src/Squidex/app/framework/angular/progress-bar.component.ts
  38. 35
      src/Squidex/app/framework/angular/square.directive.ts
  39. 1
      src/Squidex/app/framework/declarations.ts
  40. 3
      src/Squidex/app/framework/module.ts
  41. 7
      src/Squidex/app/shared/components/app-form.component.ts
  42. 10
      src/Squidex/app/shared/components/app.component-base.ts
  43. 86
      src/Squidex/app/shared/components/asset.component.html
  44. 16
      src/Squidex/app/shared/components/asset.component.scss
  45. 16
      src/Squidex/app/shared/components/asset.component.ts
  46. 12
      src/Squidex/app/shared/components/assets-editor.component.html
  47. 26
      src/Squidex/app/shared/components/assets-editor.component.scss
  48. 71
      src/Squidex/app/shared/components/assets-editor.component.ts
  49. 56
      src/Squidex/app/shared/components/component-base.ts
  50. 2
      src/Squidex/app/shared/components/dashboard-link.directive.ts
  51. 2
      src/Squidex/app/shared/components/help.component.ts
  52. 2
      src/Squidex/app/shared/components/history.component.html
  53. 39
      src/Squidex/app/shared/components/history.component.ts
  54. 159
      src/Squidex/app/shared/components/pipes.ts
  55. 35
      src/Squidex/app/shared/declarations-base.ts
  56. 33
      src/Squidex/app/shared/declarations.ts
  57. 27
      src/Squidex/app/shared/module.ts
  58. 87
      src/Squidex/app/shared/services/assets.service.spec.ts
  59. 30
      src/Squidex/app/shared/services/assets.service.ts

15
src/Squidex.Read.MongoDb/Assets/MongoAssetRepository.cs

@ -42,9 +42,9 @@ namespace Squidex.Read.MongoDb.Assets
return await Collection.Find(x => x.Id == assetId).CountAsync() == 1; return await Collection.Find(x => x.Id == assetId).CountAsync() == 1;
} }
public async Task<IReadOnlyList<IAssetEntity>> QueryAsync(Guid appId, HashSet<string> mimeTypes = null, string query = null, int take = 10, int skip = 0) public async Task<IReadOnlyList<IAssetEntity>> QueryAsync(Guid appId, HashSet<string> mimeTypes = null, HashSet<Guid> ids = null, string query = null, int take = 10, int skip = 0)
{ {
var filter = CreateFilter(appId, mimeTypes, query); var filter = CreateFilter(appId, mimeTypes, ids, query);
var assets = var assets =
await Collection.Find(filter).Skip(skip).Limit(take).SortByDescending(x => x.LastModified).ToListAsync(); await Collection.Find(filter).Skip(skip).Limit(take).SortByDescending(x => x.LastModified).ToListAsync();
@ -52,9 +52,9 @@ namespace Squidex.Read.MongoDb.Assets
return assets.OfType<IAssetEntity>().ToList(); return assets.OfType<IAssetEntity>().ToList();
} }
public async Task<long> CountAsync(Guid appId, HashSet<string> mimeTypes = null, string query = null) public async Task<long> CountAsync(Guid appId, HashSet<string> mimeTypes = null, HashSet<Guid> ids = null, string query = null)
{ {
var filter = CreateFilter(appId, mimeTypes, query); var filter = CreateFilter(appId, mimeTypes, ids, query);
var count = var count =
await Collection.Find(filter).CountAsync(); await Collection.Find(filter).CountAsync();
@ -70,13 +70,18 @@ namespace Squidex.Read.MongoDb.Assets
return entity; return entity;
} }
private static FilterDefinition<MongoAssetEntity> CreateFilter(Guid appId, ICollection<string> mimeTypes, string query) private static FilterDefinition<MongoAssetEntity> CreateFilter(Guid appId, ICollection<string> mimeTypes, ICollection<Guid> ids, string query)
{ {
var filters = new List<FilterDefinition<MongoAssetEntity>> var filters = new List<FilterDefinition<MongoAssetEntity>>
{ {
Filter.Eq(x => x.AppId, appId) Filter.Eq(x => x.AppId, appId)
}; };
if (ids != null && ids.Count > 0)
{
filters.Add(Filter.In(x => x.Id, ids));
}
if (mimeTypes != null && mimeTypes.Count > 0) if (mimeTypes != null && mimeTypes.Count > 0)
{ {
filters.Add(Filter.In(x => x.MimeType, mimeTypes)); filters.Add(Filter.In(x => x.MimeType, mimeTypes));

4
src/Squidex.Read/Assets/Repositories/IAssetRepository.cs

@ -14,9 +14,9 @@ namespace Squidex.Read.Assets.Repositories
{ {
public interface IAssetRepository public interface IAssetRepository
{ {
Task<IReadOnlyList<IAssetEntity>> QueryAsync(Guid appId, HashSet<string> mimeTypes = null, string query = null, int take = 10, int skip = 0); Task<IReadOnlyList<IAssetEntity>> QueryAsync(Guid appId, HashSet<string> mimeTypes = null, HashSet<Guid> ids = null, string query = null, int take = 10, int skip = 0);
Task<long> CountAsync(Guid appId, HashSet<string> mimeTypes = null, string query = null); Task<long> CountAsync(Guid appId, HashSet<string> mimeTypes = null, HashSet<Guid> ids = null, string query = null);
Task<IAssetEntity> FindAssetAsync(Guid id); Task<IAssetEntity> FindAssetAsync(Guid id);
} }

2
src/Squidex/Controllers/Api/Assets/AssetContentController.cs

@ -47,7 +47,7 @@ namespace Squidex.Controllers.Api.Assets
{ {
var asset = await assetRepository.FindAssetAsync(id); var asset = await assetRepository.FindAssetAsync(id);
if (asset == null || asset.FileVersion < version) if (asset == null || asset.FileVersion < version || width == 0 || height == 0)
{ {
return NotFound(); return NotFound();
} }

50
src/Squidex/Controllers/Api/Assets/AssetController.cs

@ -14,8 +14,10 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using NSwag.Annotations; using NSwag.Annotations;
using Squidex.Controllers.Api.Assets.Models; using Squidex.Controllers.Api.Assets.Models;
using Squidex.Controllers.Api.Schemas.Models;
using Squidex.Core.Identity; using Squidex.Core.Identity;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Commands; using Squidex.Infrastructure.CQRS.Commands;
@ -53,6 +55,7 @@ namespace Squidex.Controllers.Api.Assets
/// Get assets. /// Get assets.
/// </summary> /// </summary>
/// <param name="app">The app where the assets belong to.</param> /// <param name="app">The app where the assets belong to.</param>
/// <param name="ids">The optional asset ids.</param>
/// <param name="skip">The number of assets to skip.</param> /// <param name="skip">The number of assets to skip.</param>
/// <param name="take">The number of assets to take.</param> /// <param name="take">The number of assets to take.</param>
/// <param name="query">The query to limit the files by name.</param> /// <param name="query">The query to limit the files by name.</param>
@ -64,7 +67,7 @@ namespace Squidex.Controllers.Api.Assets
[HttpGet] [HttpGet]
[Route("apps/{app}/assets/")] [Route("apps/{app}/assets/")]
[ProducesResponseType(typeof(AssetsDto), 200)] [ProducesResponseType(typeof(AssetsDto), 200)]
public async Task<IActionResult> GetAssets(string app, [FromQuery] string query = null, [FromQuery] string mimeTypes = null, [FromQuery] int skip = 0, [FromQuery] int take = 10) public async Task<IActionResult> GetAssets(string app, [FromQuery] string query = null, [FromQuery] string mimeTypes = null, [FromQuery] string ids = null, [FromQuery] int skip = 0, [FromQuery] int take = 10)
{ {
var mimeTypeList = new HashSet<string>(); var mimeTypeList = new HashSet<string>();
@ -76,8 +79,21 @@ namespace Squidex.Controllers.Api.Assets
} }
} }
var taskForAssets = assetRepository.QueryAsync(AppId, mimeTypeList, query, take, skip); var idsList = new HashSet<Guid>();
var taskForCount = assetRepository.CountAsync(AppId, mimeTypeList, query);
if (!string.IsNullOrWhiteSpace(ids))
{
foreach (var id in ids.Split(','))
{
if (Guid.TryParse(id, out var guid))
{
idsList.Add(guid);
}
}
}
var taskForAssets = assetRepository.QueryAsync(AppId, mimeTypeList, idsList, query, take, skip);
var taskForCount = assetRepository.CountAsync(AppId, mimeTypeList, idsList, query);
await Task.WhenAll(taskForAssets, taskForCount); await Task.WhenAll(taskForAssets, taskForCount);
@ -90,6 +106,34 @@ namespace Squidex.Controllers.Api.Assets
return Ok(model); return Ok(model);
} }
/// <summary>
/// Get a asset by id.
/// </summary>
/// <param name="id">The id of the asset to retrieve.</param>
/// <param name="app">The name of the app to get the asset from.</param>
/// <returns>
/// 200 => Asset found.
/// 404 => Asset or app not found.
/// </returns>
[HttpGet]
[Route("apps/{app}/assets/{id}")]
[ProducesResponseType(typeof(AssetsDto), 200)]
public async Task<IActionResult> GetAsset(string app, Guid id)
{
var entity = await assetRepository.FindAssetAsync(id);
if (entity == null)
{
return NotFound();
}
var model = SimpleMapper.Map(entity, new AssetDto());
Response.Headers["ETag"] = new StringValues(entity.Version.ToString());
return Ok(model);
}
/// <summary> /// <summary>
/// Upload a new asset. /// Upload a new asset.
/// </summary> /// </summary>

2
src/Squidex/Controllers/Api/Schemas/SchemasController.cs

@ -65,7 +65,7 @@ namespace Squidex.Controllers.Api.Schemas
/// Get a schema by name. /// Get a schema by name.
/// </summary> /// </summary>
/// <param name="name">The name of the schema to retrieve.</param> /// <param name="name">The name of the schema to retrieve.</param>
/// <param name="app">The name of the app to create the schema to.</param> /// <param name="app">The name of the app to get the schema from.</param>
/// <returns> /// <returns>
/// 200 => Schema found. /// 200 => Schema found.
/// 404 => Schema or app not found. /// 404 => Schema or app not found.

7
src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.ts

@ -15,8 +15,7 @@ import {
fadeAnimation, fadeAnimation,
ImmutableArray, ImmutableArray,
ModalView, ModalView,
NotificationService, NotificationService
UsersProviderService
} from 'shared'; } from 'shared';
@Component({ @Component({
@ -34,10 +33,10 @@ export class EventConsumersPageComponent extends ComponentBase implements OnInit
public eventConsumerError = ''; public eventConsumerError = '';
public eventConsumers = ImmutableArray.empty<EventConsumerDto>(); public eventConsumers = ImmutableArray.empty<EventConsumerDto>();
constructor(notifications: NotificationService, users: UsersProviderService, constructor(notifications: NotificationService,
private readonly eventConsumersService: EventConsumersService private readonly eventConsumersService: EventConsumersService
) { ) {
super(notifications, users); super(notifications);
} }
public ngOnInit() { public ngOnInit() {

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

@ -15,8 +15,7 @@ import {
NotificationService, NotificationService,
Pager, Pager,
UserDto, UserDto,
UserManagementService, UserManagementService
UsersProviderService
} from 'shared'; } from 'shared';
@Component({ @Component({
@ -32,11 +31,11 @@ export class UsersPageComponent extends ComponentBase implements OnInit {
public usersFilter = new FormControl(); public usersFilter = new FormControl();
public usersQuery = ''; public usersQuery = '';
constructor(notifications: NotificationService, users: UsersProviderService, constructor(notifications: NotificationService,
private readonly userManagementService: UserManagementService, private readonly userManagementService: UserManagementService,
private readonly authService: AuthService private readonly authService: AuthService
) { ) {
super(notifications, users); super(notifications);
} }
public ngOnInit() { public ngOnInit() {

1
src/Squidex/app/features/assets/declarations.ts

@ -5,5 +5,4 @@
* Copyright (c) Sebastian Stehle. All rights reserved * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
export * from './pages/asset.component';
export * from './pages/assets-page.component'; export * from './pages/assets-page.component';

7
src/Squidex/app/features/assets/module.ts

@ -7,11 +7,11 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
import { DndModule } from 'ng2-dnd';
import { SqxFrameworkModule } from 'shared'; import { SqxFrameworkModule, SqxSharedModule } from 'shared';
import { import {
AssetComponent,
AssetsPageComponent AssetsPageComponent
} from './declarations'; } from './declarations';
@ -25,10 +25,11 @@ const routes: Routes = [
@NgModule({ @NgModule({
imports: [ imports: [
SqxFrameworkModule, SqxFrameworkModule,
SqxSharedModule,
DndModule,
RouterModule.forChild(routes) RouterModule.forChild(routes)
], ],
declarations: [ declarations: [
AssetComponent,
AssetsPageComponent AssetsPageComponent
] ]
}) })

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

@ -40,7 +40,7 @@
(failed)="onAssetFailed(file)" (failed)="onAssetFailed(file)"
(loaded)="onAssetLoaded(file, $event)"> (loaded)="onAssetLoaded(file, $event)">
</sqx-asset> </sqx-asset>
<sqx-asset class="col-3" *ngFor="let asset of assetsItems" [asset]="asset" <sqx-asset class="col-3" *ngFor="let asset of assetsItems" [asset]="asset"
(deleting)="onAssetDeleting($event)"> (deleting)="onAssetDeleting($event)">
</sqx-asset> </sqx-asset>
</div> </div>

4
src/Squidex/app/features/assets/pages/assets-page.component.scss

@ -52,4 +52,8 @@
.col-3 { .col-3 {
padding-left: 8px; padding-left: 8px;
padding-right: 8px; padding-right: 8px;
}
.dnd-sortable-drag {
border: 0;
} }

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

@ -18,8 +18,7 @@ import {
fadeAnimation, fadeAnimation,
ImmutableArray, ImmutableArray,
NotificationService, NotificationService,
Pager, Pager
UsersProviderService
} from 'shared'; } from 'shared';
@Component({ @Component({
@ -38,10 +37,10 @@ export class AssetsPageComponent extends AppComponentBase implements OnInit {
public assetsFilter = new FormControl(); public assetsFilter = new FormControl();
public assertQuery = ''; public assertQuery = '';
constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService, constructor(apps: AppsStoreService, notifications: NotificationService,
private readonly assetsService: AssetsService private readonly assetsService: AssetsService
) { ) {
super(notifications, users, apps); super(notifications, apps);
} }
public ngOnInit() { public ngOnInit() {
@ -57,7 +56,7 @@ export class AssetsPageComponent extends AppComponentBase implements OnInit {
private load() { private load() {
this.appName() this.appName()
.switchMap(app => this.assetsService.getAssets(app, this.assetsPager.pageSize, this.assetsPager.skip, this.assertQuery, null)) .switchMap(app => this.assetsService.getAssets(app, this.assetsPager.pageSize, this.assetsPager.skip, this.assertQuery, null, null))
.subscribe(dtos => { .subscribe(dtos => {
this.assetsItems = ImmutableArray.of(dtos.items); this.assetsItems = ImmutableArray.of(dtos.items);
this.assetsPager = this.assetsPager.setCount(dtos.total); this.assetsPager = this.assetsPager.setCount(dtos.total);

4
src/Squidex/app/features/content/pages/content/content-field.component.html

@ -91,9 +91,9 @@
</div> </div>
<div *ngSwitchCase="'Assets'"> <div *ngSwitchCase="'Assets'">
<div class="assets-container"> <div class="assets-container">
<a routerLink="assets" class="assets-link">Show Assets</a>
<sqx-assets-editor [formControlName]="language"></sqx-assets-editor> <sqx-assets-editor [formControlName]="language"></sqx-assets-editor>
<a routerLink="assets" class="assets-link">Show Assets</a>
</div> </div>
</div> </div>
</div> </div>

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

@ -28,7 +28,6 @@ import {
NumberFieldPropertiesDto, NumberFieldPropertiesDto,
SchemaDetailsDto, SchemaDetailsDto,
StringFieldPropertiesDto, StringFieldPropertiesDto,
UsersProviderService,
ValidatorsEx, ValidatorsEx,
Version Version
} from 'shared'; } from 'shared';
@ -53,14 +52,14 @@ export class ContentPageComponent extends AppComponentBase implements OnDestroy,
public languages: AppLanguageDto[] = []; public languages: AppLanguageDto[] = [];
constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService, constructor(apps: AppsStoreService, notifications: NotificationService,
private readonly contentsService: ContentsService, private readonly contentsService: ContentsService,
private readonly location: Location, private readonly location: Location,
private readonly route: ActivatedRoute, private readonly route: ActivatedRoute,
private readonly router: Router, private readonly router: Router,
private readonly messageBus: MessageBus private readonly messageBus: MessageBus
) { ) {
super(notifications, users, apps); super(notifications, apps);
} }
public ngOnDestroy() { public ngOnDestroy() {

2
src/Squidex/app/features/content/pages/contents/content-item.component.html

@ -8,7 +8,7 @@
<span class="item-modified">{{content.lastModified | fromNow}}</span> <span class="item-modified">{{content.lastModified | fromNow}}</span>
</td> </td>
<td> <td>
<img class="user-picture" [attr.title]="userName(content.lastModifiedBy, true) | async" [attr.src]="userPicture(content.lastModifiedBy, true) | async" /> <img class="user-picture" [attr.title]="content.lastModifiedBy | userNameRef" [attr.src]="content.lastModifiedBy | userPictureRef" />
</td> </td>
<td> <td>
<div class="dropdown dropdown-options" *ngIf="content"> <div class="dropdown dropdown-options" *ngIf="content">

7
src/Squidex/app/features/content/pages/contents/content-item.component.ts

@ -17,8 +17,7 @@ import {
FieldDto, FieldDto,
ModalView, ModalView,
NotificationService, NotificationService,
SchemaDto, SchemaDto
UsersProviderService
} from 'shared'; } from 'shared';
@Component({ @Component({
@ -55,8 +54,8 @@ export class ContentItemComponent extends AppComponentBase implements OnInit, On
public values: any[] = []; public values: any[] = [];
constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService) { constructor(apps: AppsStoreService, notifications: NotificationService) {
super(notifications, users, apps); super(notifications, apps);
} }
public ngOnChanges() { public ngOnChanges() {

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

@ -30,7 +30,6 @@ import {
NotificationService, NotificationService,
Pager, Pager,
SchemaDetailsDto, SchemaDetailsDto,
UsersProviderService,
Version Version
} from 'shared'; } from 'shared';
@ -58,13 +57,13 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy
return 100 / this.contentFields.length; return 100 / this.contentFields.length;
} }
constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService, constructor(apps: AppsStoreService, notifications: NotificationService,
private readonly authService: AuthService, private readonly authService: AuthService,
private readonly contentsService: ContentsService, private readonly contentsService: ContentsService,
private readonly route: ActivatedRoute, private readonly route: ActivatedRoute,
private readonly messageBus: MessageBus private readonly messageBus: MessageBus
) { ) {
super(notifications, users, apps); super(notifications, apps);
} }
public ngOnDestroy() { public ngOnDestroy() {

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

@ -14,8 +14,7 @@ import {
AppsStoreService, AppsStoreService,
NotificationService, NotificationService,
SchemaDto, SchemaDto,
SchemasService, SchemasService
UsersProviderService
} from 'shared'; } from 'shared';
@Component({ @Component({
@ -53,10 +52,10 @@ export class SchemasPageComponent extends AppComponentBase {
}); });
}); });
constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService, constructor(apps: AppsStoreService, notifications: NotificationService,
private readonly schemasService: SchemasService private readonly schemasService: SchemasService
) { ) {
super(notifications, users, apps); super(notifications, apps);
} }
private loadSchemas(): Observable<SchemaDto[]> { private loadSchemas(): Observable<SchemaDto[]> {

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

@ -13,8 +13,7 @@ import {
AppsStoreService, AppsStoreService,
AuthService, AuthService,
fadeAnimation, fadeAnimation,
NotificationService, NotificationService
UsersProviderService
} from 'shared'; } from 'shared';
declare var _urq: any; declare var _urq: any;
@ -32,10 +31,10 @@ export class DashboardPageComponent extends AppComponentBase implements OnInit,
public profileDisplayName = ''; public profileDisplayName = '';
constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService, constructor(apps: AppsStoreService, notifications: NotificationService,
private readonly auth: AuthService private readonly auth: AuthService
) { ) {
super(notifications, users, apps); super(notifications, apps);
} }
public ngOnDestroy() { public ngOnDestroy() {

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

@ -24,7 +24,6 @@ import {
SchemaDetailsDto, SchemaDetailsDto,
SchemasService, SchemasService,
UpdateFieldDto, UpdateFieldDto,
UsersProviderService,
ValidatorsEx, ValidatorsEx,
Version Version
} from 'shared'; } from 'shared';
@ -82,14 +81,14 @@ export class SchemaPageComponent extends AppComponentBase implements OnInit {
return this.addFieldForm.controls['name'].value && this.addFieldForm.controls['name'].value.length > 0; return this.addFieldForm.controls['name'].value && this.addFieldForm.controls['name'].value.length > 0;
} }
constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService, constructor(apps: AppsStoreService, notifications: NotificationService,
private readonly schemasService: SchemasService, private readonly schemasService: SchemasService,
private readonly messageBus: MessageBus, private readonly messageBus: MessageBus,
private readonly formBuilder: FormBuilder, private readonly formBuilder: FormBuilder,
private readonly route: ActivatedRoute, private readonly route: ActivatedRoute,
private readonly router: Router private readonly router: Router
) { ) {
super(notifications, users, apps); super(notifications, apps);
} }
public ngOnInit() { public ngOnInit() {

2
src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html

@ -34,7 +34,7 @@
</div> </div>
<div class="col col-4"> <div class="col col-4">
<span class="schema-user"> <span class="schema-user">
<i class="icon-user"></i> {{userName(schema.lastModifiedBy, true) | async}} <i class="icon-user"></i> {{schema.lastModifiedBy | userNameRef}}
</span> </span>
</div> </div>
<div class="col col-4 schema-modified"> <div class="col col-4 schema-modified">

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

@ -22,7 +22,6 @@ import {
NotificationService, NotificationService,
SchemaDto, SchemaDto,
SchemasService, SchemasService,
UsersProviderService,
Version Version
} from 'shared'; } from 'shared';
@ -47,13 +46,13 @@ export class SchemasPageComponent extends AppComponentBase implements OnDestroy,
public schemasFilter = new FormControl(); public schemasFilter = new FormControl();
public schemasFiltered = ImmutableArray.empty<SchemaDto>(); public schemasFiltered = ImmutableArray.empty<SchemaDto>();
constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService, constructor(apps: AppsStoreService, notifications: NotificationService,
private readonly schemasService: SchemasService, private readonly schemasService: SchemasService,
private readonly authService: AuthService, private readonly authService: AuthService,
private readonly messageBus: MessageBus, private readonly messageBus: MessageBus,
private readonly route: ActivatedRoute private readonly route: ActivatedRoute
) { ) {
super(notifications, users, apps); super(notifications, apps);
} }
public ngOnDestroy() { public ngOnDestroy() {

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

@ -19,7 +19,6 @@ import {
MessageBus, MessageBus,
NotificationService, NotificationService,
UpdateAppClientDto, UpdateAppClientDto,
UsersProviderService,
ValidatorsEx, ValidatorsEx,
Version Version
} from 'shared'; } from 'shared';
@ -48,12 +47,12 @@ export class ClientsPageComponent extends AppComponentBase implements OnInit {
return this.addClientForm.controls['name'].value && this.addClientForm.controls['name'].value.length > 0; return this.addClientForm.controls['name'].value && this.addClientForm.controls['name'].value.length > 0;
} }
constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService, constructor(apps: AppsStoreService, notifications: NotificationService,
private readonly appClientsService: AppClientsService, private readonly appClientsService: AppClientsService,
private readonly messageBus: MessageBus, private readonly messageBus: MessageBus,
private readonly formBuilder: FormBuilder private readonly formBuilder: FormBuilder
) { ) {
super(notifications, users, apps); super(notifications, apps);
} }
public ngOnInit() { public ngOnInit() {

6
src/Squidex/app/features/settings/pages/contributors/contributors-page.component.html

@ -43,13 +43,13 @@
<ng-template ngFor let-contributor [ngForOf]="appContributors"> <ng-template ngFor let-contributor [ngForOf]="appContributors">
<tr> <tr>
<td> <td>
<img class="user-picture" [attr.title]="userName(contributor.contributorId) | async" [attr.src]="userPicture(contributor.contributorId) | async" /> <img class="user-picture" [attr.title]="contributor.contributorId | userName" [attr.src]="contributor.contributorId | userPicture" />
</td> </td>
<td> <td>
<span class="user-name">{{userName(contributor.contributorId) | async}}</span> <span class="user-name">{{contributor.contributorId | userName}}</span>
</td> </td>
<td> <td>
<span class="user-email">{{userEmail(contributor.contributorId) | async}}</span> <span class="user-email">{{contributor.contributorId | userEmail}}</span>
</td> </td>
<td> <td>
<select class="form-control" [ngModel]="contributor.permission" (ngModelChange)="changePermission(contributor, $event)" [disabled]="currentUserId === contributor.contributorId"> <select class="form-control" [ngModel]="contributor.permission" (ngModelChange)="changePermission(contributor, $event)" [disabled]="currentUserId === contributor.contributorId">

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

@ -21,7 +21,6 @@ import {
ImmutableArray, ImmutableArray,
MessageBus, MessageBus,
NotificationService, NotificationService,
UsersProviderService,
UsersService, UsersService,
Version Version
} from 'shared'; } from 'shared';
@ -80,13 +79,13 @@ export class ContributorsPageComponent extends AppComponentBase implements OnIni
]] ]]
}); });
constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService, usersService: UsersService, constructor(apps: AppsStoreService, notifications: NotificationService, usersService: UsersService,
private readonly appContributorsService: AppContributorsService, private readonly appContributorsService: AppContributorsService,
private readonly messageBus: MessageBus, private readonly messageBus: MessageBus,
private readonly authService: AuthService, private readonly authService: AuthService,
private readonly formBuilder: FormBuilder private readonly formBuilder: FormBuilder
) { ) {
super(notifications, users, apps); super(notifications, apps);
this.usersDataSource = new UsersDataSource(usersService, this); this.usersDataSource = new UsersDataSource(usersService, this);
} }

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

@ -21,7 +21,6 @@ import {
LanguageService, LanguageService,
NotificationService, NotificationService,
UpdateAppLanguageDto, UpdateAppLanguageDto,
UsersProviderService,
Version Version
} from 'shared'; } from 'shared';
@ -47,13 +46,13 @@ export class LanguagesPageComponent extends AppComponentBase implements OnInit {
return this.allLanguages.filter(x => !this.appLanguages.find(l => l.iso2Code === x.iso2Code)); return this.allLanguages.filter(x => !this.appLanguages.find(l => l.iso2Code === x.iso2Code));
} }
constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService, constructor(apps: AppsStoreService, notifications: NotificationService,
private readonly appLanguagesService: AppLanguagesService, private readonly appLanguagesService: AppLanguagesService,
private readonly languagesService: LanguageService, private readonly languagesService: LanguageService,
private readonly messageBus: MessageBus, private readonly messageBus: MessageBus,
private readonly formBuilder: FormBuilder private readonly formBuilder: FormBuilder
) { ) {
super(notifications, users, apps); super(notifications, apps);
} }
public ngOnInit() { public ngOnInit() {

5
src/Squidex/app/framework/angular/cloak.directive.ts

@ -11,7 +11,10 @@ import { Directive, ElementRef, OnInit } from '@angular/core';
selector: '.sqx-cloak' selector: '.sqx-cloak'
}) })
export class CloakDirective implements OnInit { export class CloakDirective implements OnInit {
constructor(private readonly element: ElementRef) { } constructor(
private readonly element: ElementRef
) {
}
public ngOnInit() { public ngOnInit() {
this.element.nativeElement.classList.remove('sqx-cloak'); this.element.nativeElement.classList.remove('sqx-cloak');

21
src/Squidex/app/framework/angular/date-time.pipes.ts

@ -11,7 +11,8 @@ import { DateTime } from './../utils/date-time';
import { Duration } from './../utils/duration'; import { Duration } from './../utils/duration';
@Pipe({ @Pipe({
name: 'shortDate' name: 'shortDate',
pure: true
}) })
export class ShortDatePipe { export class ShortDatePipe {
public transform(value: DateTime): any { public transform(value: DateTime): any {
@ -20,7 +21,8 @@ export class ShortDatePipe {
} }
@Pipe({ @Pipe({
name: 'month' name: 'month',
pure: true
}) })
export class MonthPipe { export class MonthPipe {
public transform(value: DateTime): any { public transform(value: DateTime): any {
@ -29,7 +31,8 @@ export class MonthPipe {
} }
@Pipe({ @Pipe({
name: 'fromNow' name: 'fromNow',
pure: true
}) })
export class FromNowPipe { export class FromNowPipe {
public transform(value: DateTime): any { public transform(value: DateTime): any {
@ -38,7 +41,8 @@ export class FromNowPipe {
} }
@Pipe({ @Pipe({
name: 'dayOfWeek' name: 'dayOfWeek',
pure: true
}) })
export class DayOfWeekPipe { export class DayOfWeekPipe {
public transform(value: DateTime): any { public transform(value: DateTime): any {
@ -47,7 +51,8 @@ export class DayOfWeekPipe {
} }
@Pipe({ @Pipe({
name: 'day' name: 'day',
pure: true
}) })
export class DayPipe { export class DayPipe {
public transform(value: DateTime): any { public transform(value: DateTime): any {
@ -56,7 +61,8 @@ export class DayPipe {
} }
@Pipe({ @Pipe({
name: 'shortTime' name: 'shortTime',
pure: true
}) })
export class ShortTimePipe { export class ShortTimePipe {
public transform(value: DateTime): any { public transform(value: DateTime): any {
@ -65,7 +71,8 @@ export class ShortTimePipe {
} }
@Pipe({ @Pipe({
name: 'duration' name: 'duration',
pure: true
}) })
export class DurationPipe { export class DurationPipe {
public transform(value: Duration): any { public transform(value: Duration): any {

44
src/Squidex/app/framework/angular/file-drop.directive.ts

@ -17,32 +17,48 @@ export class FileDropDirective {
public drop = new EventEmitter<FileList>(); public drop = new EventEmitter<FileList>();
constructor( constructor(
private readonly elementRef: ElementRef, private readonly element: ElementRef,
private readonly renderer: Renderer private readonly renderer: Renderer
) { ) {
} }
@HostListener('dragleave', ['$event']) @HostListener('dragleave', ['$event'])
public onDragLeave(event: DragDropEvent) { public onDragLeave(event: DragDropEvent) {
this.dragEnd(); const hasFiles = this.hasFiles(event.dataTransfer.types);
if (hasFiles) {
this.dragEnd();
}
} }
@HostListener('dragenter', ['$event']) @HostListener('dragenter', ['$event'])
public onDragEnter(event: DragDropEvent) { public onDragEnter(event: DragDropEvent) {
this.dragStart(); const hasFiles = this.hasFiles(event.dataTransfer.types);
if (hasFiles) {
this.dragStart();
}
} }
@HostListener('dragover', ['$event']) @HostListener('dragover', ['$event'])
public onDragOver(event: DragDropEvent) { public onDragOver(event: DragDropEvent) {
this.tryStopEvent(event); const hasFiles = this.hasFiles(event.dataTransfer.types);
if (hasFiles) {
this.stopEvent(event);
}
} }
@HostListener('drop', ['$event']) @HostListener('drop', ['$event'])
public onDrop(event: DragDropEvent) { public onDrop(event: DragDropEvent) {
this.drop.emit(event.dataTransfer.files); const hasFiles = this.hasFiles(event.dataTransfer.types);
if (hasFiles) {
this.drop.emit(event.dataTransfer.files);
this.dragEnd(0); this.dragEnd(0);
this.stopEvent(event); this.stopEvent(event);
}
} }
private stopEvent(event: Event) { private stopEvent(event: Event) {
@ -54,7 +70,7 @@ export class FileDropDirective {
this.dragCounter++; this.dragCounter++;
if (this.dragCounter === 1) { if (this.dragCounter === 1) {
this.renderer.setElementClass(this.elementRef.nativeElement, 'drag', true); this.renderer.setElementClass(this.element.nativeElement, 'drag', true);
} }
} }
@ -62,20 +78,10 @@ export class FileDropDirective {
this.dragCounter = number || this.dragCounter - 1; this.dragCounter = number || this.dragCounter - 1;
if (this.dragCounter === 0) { if (this.dragCounter === 0) {
this.renderer.setElementClass(this.elementRef.nativeElement, 'drag', false); this.renderer.setElementClass(this.element.nativeElement, 'drag', false);
} }
} }
private tryStopEvent(event: DragDropEvent) {
const hasFiles = this.hasFiles(event.dataTransfer.types);
if (!hasFiles) {
return;
}
this.stopEvent(event);
}
private hasFiles(types: any): boolean { private hasFiles(types: any): boolean {
if (!types) { if (!types) {
return false; return false;

6
src/Squidex/app/framework/angular/focus-on-change.directive.ts

@ -18,17 +18,17 @@ export class FocusOnChangeDirective implements OnChanges {
public select: boolean; public select: boolean;
constructor( constructor(
private readonly elementRef: ElementRef, private readonly element: ElementRef,
private readonly renderer: Renderer private readonly renderer: Renderer
) { ) {
} }
public ngOnChanges() { public ngOnChanges() {
setTimeout(() => { setTimeout(() => {
this.renderer.invokeElementMethod(this.elementRef.nativeElement, 'focus', []); this.renderer.invokeElementMethod(this.element.nativeElement, 'focus', []);
if (this.select) { if (this.select) {
this.renderer.invokeElementMethod(this.elementRef.nativeElement, 'select', []); this.renderer.invokeElementMethod(this.element.nativeElement, 'select', []);
} }
}, 100); }, 100);
} }

6
src/Squidex/app/framework/angular/focus-on-init.directive.ts

@ -15,17 +15,17 @@ export class FocusOnInitDirective implements OnInit {
public select: boolean; public select: boolean;
constructor( constructor(
private readonly elementRef: ElementRef, private readonly element: ElementRef,
private readonly renderer: Renderer private readonly renderer: Renderer
) { ) {
} }
public ngOnInit() { public ngOnInit() {
setTimeout(() => { setTimeout(() => {
this.renderer.invokeElementMethod(this.elementRef.nativeElement, 'focus', []); this.renderer.invokeElementMethod(this.element.nativeElement, 'focus', []);
if (this.select) { if (this.select) {
this.renderer.invokeElementMethod(this.elementRef.nativeElement, 'select', []); this.renderer.invokeElementMethod(this.element.nativeElement, 'select', []);
} }
}); });
} }

30
src/Squidex/app/framework/angular/image-source.directive.ts

@ -5,14 +5,14 @@
* Copyright (c) Sebastian Stehle. All rights reserved * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
import { Directive, ElementRef, HostListener, Input, OnChanges, OnInit, Renderer } from '@angular/core'; import { AfterViewInit, Directive, ElementRef, HostListener, Input, OnChanges, OnInit, Renderer } from '@angular/core';
import { MathHelper } from './../utils/math-helper'; import { MathHelper } from './../utils/math-helper';
@Directive({ @Directive({
selector: '[sqxImageSource]' selector: '[sqxImageSource]'
}) })
export class ImageSourceComponent implements OnChanges, OnInit { export class ImageSourceComponent implements OnChanges, OnInit, AfterViewInit {
private retries = 0; private retries = 0;
private query = MathHelper.guid(); private query = MathHelper.guid();
@ -26,7 +26,7 @@ export class ImageSourceComponent implements OnChanges, OnInit {
public parent: any = null; public parent: any = null;
constructor( constructor(
private readonly elementRef: ElementRef, private readonly element: ElementRef,
private readonly renderer: Renderer private readonly renderer: Renderer
) { ) {
} }
@ -37,9 +37,13 @@ export class ImageSourceComponent implements OnChanges, OnInit {
this.setImageSource(); this.setImageSource();
} }
public ngAfterViewInit() {
this.resize(this.parent);
}
public ngOnInit() { public ngOnInit() {
if (this.parent === null) { if (this.parent === null) {
this.parent = this.elementRef.nativeElement.parentElement; this.parent = this.element.nativeElement.parentElement;
} }
this.resize(this.parent); this.resize(this.parent);
@ -51,7 +55,7 @@ export class ImageSourceComponent implements OnChanges, OnInit {
@HostListener('error') @HostListener('error')
public onError() { public onError() {
this.renderer.setElementStyle(this.elementRef.nativeElement, 'visibility', 'hidden'); this.renderer.setElementStyle(this.element.nativeElement, 'visibility', 'hidden');
this.retryLoadingImage(); this.retryLoadingImage();
} }
@ -63,25 +67,29 @@ export class ImageSourceComponent implements OnChanges, OnInit {
@HostListener('load') @HostListener('load')
public onLoad() { public onLoad() {
this.renderer.setElementStyle(this.elementRef.nativeElement, 'visibility', 'visible'); this.renderer.setElementStyle(this.element.nativeElement, 'visibility', 'visible');
} }
private resize(parent: any) { private resize(parent: any) {
const size = parent.getBoundingClientRect(); const size = parent.getBoundingClientRect();
this.renderer.setElementStyle(this.elementRef.nativeElement, 'width', size.width + 'px'); this.renderer.setElementStyle(this.element.nativeElement, 'width', size.width + 'px');
this.renderer.setElementStyle(this.elementRef.nativeElement, 'height', size.height + 'px'); this.renderer.setElementStyle(this.element.nativeElement, 'height', size.height + 'px');
this.setImageSource();
} }
private setImageSource() { private setImageSource() {
const size = this.elementRef.nativeElement.getBoundingClientRect(); const size = this.element.nativeElement.getBoundingClientRect();
const w = Math.round(size.width); const w = Math.round(size.width);
const h = Math.round(size.height); const h = Math.round(size.height);
const source = `${this.imageSource}&width=${w}&height=${h}&mode=Crop&q=${this.query}`; if (w > 0 && h > 0) {
const source = `${this.imageSource}&width=${w}&height=${h}&mode=Crop&q=${this.query}`;
this.renderer.setElementAttribute(this.elementRef.nativeElement, 'src', source); this.renderer.setElementAttribute(this.element.nativeElement, 'src', source);
}
} }
private retryLoadingImage() { private retryLoadingImage() {

8
src/Squidex/app/framework/angular/indeterminate-value.directive.ts

@ -27,20 +27,20 @@ export class IndeterminateValueDirective implements ControlValueAccessor {
constructor( constructor(
private readonly renderer: Renderer, private readonly renderer: Renderer,
private readonly elementRef: ElementRef private readonly element: ElementRef
) { ) {
} }
public writeValue(value: any) { public writeValue(value: any) {
if (value === undefined || value === null) { if (value === undefined || value === null) {
this.renderer.setElementProperty(this.elementRef.nativeElement, 'indeterminate', true); this.renderer.setElementProperty(this.element.nativeElement, 'indeterminate', true);
} else { } else {
this.renderer.setElementProperty(this.elementRef.nativeElement, 'checked', value); this.renderer.setElementProperty(this.element.nativeElement, 'checked', value);
} }
} }
public setDisabledState(isDisabled: boolean): void { public setDisabledState(isDisabled: boolean): void {
this.renderer.setElementProperty(this.elementRef.nativeElement, 'disabled', isDisabled); this.renderer.setElementProperty(this.element.nativeElement, 'disabled', isDisabled);
} }
public registerOnChange(fn: any) { public registerOnChange(fn: any) {

10
src/Squidex/app/framework/angular/lowercase-input.directive.ts

@ -26,19 +26,19 @@ export class LowerCaseInputDirective implements ControlValueAccessor {
private touchedCallback: () => void = NOOP; private touchedCallback: () => void = NOOP;
constructor( constructor(
private readonly renderer: Renderer, private readonly element: ElementRef,
private readonly elementRef: ElementRef private readonly renderer: Renderer
) { ) {
} }
public writeValue(value: any) { public writeValue(value: any) {
const normalizedValue = (value == null ? '' : value.toString()).toLowerCase(); const normalizedValue = (value == null ? '' : value.toString()).toLowerCase();
this.renderer.setElementProperty(this.elementRef.nativeElement, 'value', normalizedValue); this.renderer.setElementProperty(this.element.nativeElement, 'value', normalizedValue);
} }
public setDisabledState(isDisabled: boolean): void { public setDisabledState(isDisabled: boolean): void {
this.renderer.setElementProperty(this.elementRef.nativeElement, 'disabled', isDisabled); this.renderer.setElementProperty(this.element.nativeElement, 'disabled', isDisabled);
} }
public registerOnChange(fn: any) { public registerOnChange(fn: any) {
@ -52,7 +52,7 @@ export class LowerCaseInputDirective implements ControlValueAccessor {
public onChange(value: any) { public onChange(value: any) {
const normalizedValue = (value == null ? '' : value.toString()).toLowerCase(); const normalizedValue = (value == null ? '' : value.toString()).toLowerCase();
this.renderer.setElementProperty(this.elementRef.nativeElement, 'value', normalizedValue); this.renderer.setElementProperty(this.element.nativeElement, 'value', normalizedValue);
this.changeCallback(normalizedValue); this.changeCallback(normalizedValue);
} }

3
src/Squidex/app/framework/angular/money.pipe.ts

@ -10,7 +10,8 @@ import { Pipe } from '@angular/core';
import { CurrencyConfig, DecimalSeparatorConfig } from './../configurations'; import { CurrencyConfig, DecimalSeparatorConfig } from './../configurations';
@Pipe({ @Pipe({
name: 'money' name: 'money',
pure: true
}) })
export class MoneyPipe { export class MoneyPipe {
constructor( constructor(

3
src/Squidex/app/framework/angular/name.pipe.ts

@ -10,7 +10,8 @@ import { Pipe } from '@angular/core';
import { StringHelper } from './../utils/string-helper'; import { StringHelper } from './../utils/string-helper';
@Pipe({ @Pipe({
name: 'displayName' name: 'displayName',
pure: true
}) })
export class DisplayNamePipe { export class DisplayNamePipe {
public transform(value: any, field1 = 'label', field2 = 'name'): any { public transform(value: any, field1 = 'label', field2 = 'name'): any {

8
src/Squidex/app/framework/angular/progress-bar.component.ts

@ -35,7 +35,7 @@ export class ProgressBarComponent implements OnChanges, OnInit {
public value = 0; public value = 0;
constructor( constructor(
private readonly elementRef: ElementRef, private readonly element: ElementRef,
private readonly renderer: Renderer private readonly renderer: Renderer
) { ) {
} }
@ -48,12 +48,12 @@ export class ProgressBarComponent implements OnChanges, OnInit {
strokeWidth: this.strokeWidth strokeWidth: this.strokeWidth
}; };
this.renderer.setElementStyle(this.elementRef.nativeElement, 'display', 'block'); this.renderer.setElementStyle(this.element.nativeElement, 'display', 'block');
if (this.mode === 'Circle') { if (this.mode === 'Circle') {
this.progressBar = new ProgressBar.Circle(this.elementRef.nativeElement, options); this.progressBar = new ProgressBar.Circle(this.element.nativeElement, options);
} else { } else {
this.progressBar = new ProgressBar.Line(this.elementRef.nativeElement, options); this.progressBar = new ProgressBar.Line(this.element.nativeElement, options);
} }
this.updateValue(); this.updateValue();

35
src/Squidex/app/framework/angular/square.directive.ts

@ -0,0 +1,35 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Directive, ElementRef, HostListener, OnInit, Renderer } from '@angular/core';
@Directive({
selector: '[sqxSquare]'
})
export class SquareDirective implements OnInit {
constructor(
private readonly element: ElementRef,
private readonly renderer: Renderer
) {
}
public ngOnInit() {
this.resize();
}
@HostListener('resize')
public onResize() {
this.resize();
}
private resize() {
const size = this.element.nativeElement.getBoundingClientRect();
this.renderer.setElementStyle(this.element.nativeElement, 'height', size.width + 'px');
}
}

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

@ -33,6 +33,7 @@ export * from './angular/rich-editor.component';
export * from './angular/scroll-active.directive'; export * from './angular/scroll-active.directive';
export * from './angular/shortcut.component'; export * from './angular/shortcut.component';
export * from './angular/slider.component'; export * from './angular/slider.component';
export * from './angular/square.directive';
export * from './angular/stars.component'; export * from './angular/stars.component';
export * from './angular/tag-editor.component'; export * from './angular/tag-editor.component';
export * from './angular/title.component'; export * from './angular/title.component';

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

@ -50,6 +50,7 @@ import {
ShortDatePipe, ShortDatePipe,
ShortTimePipe, ShortTimePipe,
SliderComponent, SliderComponent,
SquareDirective,
StarsComponent, StarsComponent,
TagEditorComponent, TagEditorComponent,
TitleService, TitleService,
@ -98,6 +99,7 @@ import {
ShortDatePipe, ShortDatePipe,
ShortTimePipe, ShortTimePipe,
SliderComponent, SliderComponent,
SquareDirective,
StarsComponent, StarsComponent,
TagEditorComponent, TagEditorComponent,
TitleComponent, TitleComponent,
@ -136,6 +138,7 @@ import {
ShortDatePipe, ShortDatePipe,
ShortTimePipe, ShortTimePipe,
SliderComponent, SliderComponent,
SquareDirective,
StarsComponent, StarsComponent,
TagEditorComponent, TagEditorComponent,
TitleComponent, TitleComponent,

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

@ -10,8 +10,11 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ApiUrlConfig, ValidatorsEx } from 'framework'; import { ApiUrlConfig, ValidatorsEx } from 'framework';
import { AppsStoreService } from './../services/apps-store.service'; import {
import { AppDto, CreateAppDto } from './../services/apps.service'; AppDto,
AppsStoreService,
CreateAppDto
} from './../declarations-base';
const FALLBACK_NAME = 'my-app'; const FALLBACK_NAME = 'my-app';

10
src/Squidex/app/shared/components/app-component-base.ts → src/Squidex/app/shared/components/app.component-base.ts

@ -7,19 +7,15 @@
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { import { AppsStoreService, NotificationService } from './../declarations-base';
AppsStoreService,
NotificationService,
UsersProviderService
} from 'shared';
import { ComponentBase } from './component-base'; import { ComponentBase } from './component-base';
export abstract class AppComponentBase extends ComponentBase { export abstract class AppComponentBase extends ComponentBase {
constructor(notifications: NotificationService, users: UsersProviderService, constructor(notifications: NotificationService,
private readonly appsStore: AppsStoreService private readonly appsStore: AppsStoreService
) { ) {
super(notifications, users); super(notifications);
} }
public appName(): Observable<string> { public appName(): Observable<string> {

86
src/Squidex/app/features/assets/pages/asset.component.html → src/Squidex/app/shared/components/asset.component.html

@ -1,54 +1,60 @@
<div class="asset"> <div class="asset">
<div class="card" (sqxFileDrop)="updateFile($event)"> <div class="card" (sqxFileDrop)="updateFile($event)" sqxSquare dnd-draggable [dragEnabled]="!!asset" [dragData]="asset">
<div *ngIf="asset && progress == 0" [@fade]> <div class="card-block">
<div class="card-block"> <div class="file-preview" *ngIf="asset && progress == 0" [@fade]>
<div class="file-preview"> <span class="file-type" *ngIf="fileType">
<span class="file-type" *ngIf="fileType"> {{fileType}}
{{fileType}} </span>
</span>
<div *ngIf="asset.isImage" class="file-image">
<img [sqxImageSource]="previewUrl">
</div>
<div *ngIf="!asset.isImage" class="file-icon-container">
<img class="file-icon" [attr.src]="fileIcon">
</div>
<div class="file-overlay">
<div class="file-overlay-background"></div>
<div *ngIf="asset.isImage" class="file-image"> <a class="file-edit" (click)="renameDialog.show()">
<img [sqxImageSource]="previewUrl"> <i class="icon-pencil"></i>
</div> </a>
<div *ngIf="!asset.isImage" class="file-icon-container"> <a class="file-download" [attr.href]="fileUrl" target="_blank">
<img class="file-icon" [attr.src]="fileIcon"> <i class="icon-download"></i>
</div> </a>
<div class="file-overlay"> <span *ngIf="!closeMode">
<div class="file-overlay-background"></div>
<a class="file-edit" (click)="renameDialog.show()">
<i class="icon-pencil"></i>
</a>
<a class="file-download" [attr.href]="fileUrl" target="_blank">
<i class="icon-download"></i>
</a>
<a class="file-delete" (click)="deleting.emit(asset)"> <a class="file-delete" (click)="deleting.emit(asset)">
<i class="icon-delete"></i> <i class="icon-delete"></i>
</a> </a>
</span>
<span *ngIf="closeMode">
<a class="file-delete" (click)="closing.emit(asset)">
<i class="icon-close"></i>
</a>
</span>
<span class="file-overlay-type" *ngIf="fileType"> <span class="file-overlay-type" *ngIf="fileType">
{{fileType}} {{fileType}}
</span> </span>
<span class="file-user"> <span class="file-user">
<i class="icon-user"></i> {{userName(asset.lastModifiedBy, true) | async}} <i class="icon-user"></i> {{asset.lastModifiedBy | userNameRef}}
</span> </span>
<span class="file-modified"> <span class="file-modified">
{{asset.lastModified | fromNow}} {{asset.lastModified | fromNow}}
</span> </span>
</div>
</div> </div>
</div> </div>
<div class="card-footer"> </div>
<div class="file-name" [attr.title]="fileName"> <div class="card-footer">
{{fileName}} <div class="file-name" [attr.title]="fileName">
</div> {{fileName}}
<div class="file-info"> </div>
{{fileInfo}} <div class="file-info">
</div> {{fileInfo}}
</div> </div>
</div> </div>
<div *ngIf="progress > 0"> <div *ngIf="progress > 0">
<sqx-progress-bar class="upload-progress" style="width: 120px; height: 120px" mode="Circle" [value]="progress"></sqx-progress-bar> <sqx-progress-bar class="upload-progress" style="width: 120px; height: 120px" mode="Circle" [value]="progress"></sqx-progress-bar>
</div> </div>

16
src/Squidex/app/features/assets/pages/asset.component.scss → src/Squidex/app/shared/components/asset.component.scss

@ -1,8 +1,6 @@
@import '_vars'; @import '_vars';
@import '_mixins'; @import '_mixins';
$card-size: 240px;
@mixin overlay-container { @mixin overlay-container {
position: relative; position: relative;
padding: 0; padding: 0;
@ -72,7 +70,6 @@ $card-size: 240px;
.card { .card {
& { & {
@include overlay-container; @include overlay-container;
height: $card-size;
} }
&.drag { &.drag {
@ -82,7 +79,6 @@ $card-size: 240px;
} }
&-block { &-block {
padding: .8rem .8rem 0;
position: relative; position: relative;
} }
@ -91,6 +87,7 @@ $card-size: 240px;
background: transparent; background: transparent;
padding: .8rem; padding: .8rem;
padding-top: .4rem; padding-top: .4rem;
height: 70px;
} }
} }
@ -109,8 +106,7 @@ $card-size: 240px;
&-preview { &-preview {
& { & {
@include overlay-container; @include absolute(.8rem, .8rem, 0, .8rem);
height: 155px;
} }
&:hover { &:hover {
@ -162,11 +158,17 @@ $card-size: 240px;
line-height: 2rem; line-height: 2rem;
} }
&-icon {
margin-top: 10%;
margin-bottom: 0;
height: 70%;
}
&-icon-container { &-icon-container {
background: $color-border; background: $color-border;
border: 0; border: 0;
line-height: 155px;
text-align: center; text-align: center;
height: 100%;
} }
&-overlay { &-overlay {

16
src/Squidex/app/features/assets/pages/asset.component.ts → src/Squidex/app/shared/components/asset.component.ts

@ -8,9 +8,10 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms'; import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { AppComponentBase } from './app.component-base';
import { import {
ApiUrlConfig, ApiUrlConfig,
AppComponentBase,
AppsStoreService, AppsStoreService,
AssetCreatedDto, AssetCreatedDto,
AssetDto, AssetDto,
@ -23,9 +24,8 @@ import {
ModalView, ModalView,
NotificationService, NotificationService,
UpdateAssetDto, UpdateAssetDto,
UsersProviderService,
Version Version
} from 'shared'; } from './../declarations-base';
@Component({ @Component({
selector: 'sqx-asset', selector: 'sqx-asset',
@ -54,9 +54,15 @@ export class AssetComponent extends AppComponentBase implements OnInit {
@Input() @Input()
public asset: AssetDto; public asset: AssetDto;
@Input()
public closeMode = false;
@Output() @Output()
public loaded = new EventEmitter<AssetDto>(); public loaded = new EventEmitter<AssetDto>();
@Output()
public closing = new EventEmitter<AssetDto>();
@Output() @Output()
public deleting = new EventEmitter<AssetDto>(); public deleting = new EventEmitter<AssetDto>();
@ -71,13 +77,13 @@ export class AssetComponent extends AppComponentBase implements OnInit {
public fileIcon: string; public fileIcon: string;
public fileInfo: string; public fileInfo: string;
constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService, constructor(apps: AppsStoreService, notifications: NotificationService,
private readonly formBuilder: FormBuilder, private readonly formBuilder: FormBuilder,
private readonly assetsService: AssetsService, private readonly assetsService: AssetsService,
private readonly authService: AuthService, private readonly authService: AuthService,
private readonly apiUrl: ApiUrlConfig private readonly apiUrl: ApiUrlConfig
) { ) {
super(notifications, users, apps); super(notifications, apps);
} }
public ngOnInit() { public ngOnInit() {

12
src/Squidex/app/shared/components/assets-editor.component.html

@ -1,3 +1,11 @@
<div class="assets-container"> <div class="assets-container" (sqxFileDrop)="addFiles($event)" dnd-droppable (onDropSuccess)="onAssetDropped($event.dragData)">
<div class="row">
<sqx-asset class="col-4" *ngFor="let file of newAssets" [initFile]="file"
(failed)="onAssetFailed(file)"
(loaded)="onAssetLoaded(file, $event)">
</sqx-asset>
<sqx-asset class="col-4" *ngFor="let asset of oldAssets" [asset]="asset" [closeMode]="true"
(closing)="onAssetRemoving($event)">
</sqx-asset>
</div>
</div> </div>

26
src/Squidex/app/shared/components/assets-editor.component.scss

@ -3,6 +3,30 @@
.assets { .assets {
&-container { &-container {
border: 1px solid $color-input; & {
border: 2px solid $color-input;
background: $color-input;
overflow-x: hidden;
overflow-y: auto;
padding: 1rem;
padding-bottom: 0;
height: 240px;
}
&.drag {
border-color: darken($color-border, 10%);
border-style: dashed;
cursor: copy;
}
} }
}
.row {
margin-left: -8px;
margin-right: -8px;
}
.col-4 {
padding-left: 8px;
padding-right: 8px;
} }

71
src/Squidex/app/shared/components/assets-editor.component.ts

@ -5,9 +5,21 @@
* Copyright (c) Sebastian Stehle. All rights reserved * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
// tslint:disable:prefer-for-of
import { Component, forwardRef } from '@angular/core'; import { Component, forwardRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { AppComponentBase } from './app.component-base';
import {
AppsStoreService,
AssetDto,
AssetsService,
ImmutableArray,
NotificationService
} from './../declarations-base';
const NOOP = () => { /* NOOP */ }; const NOOP = () => { /* NOOP */ };
export const SQX_ASSETS_EDITOR_CONTROL_VALUE_ACCESSOR: any = { export const SQX_ASSETS_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
@ -20,14 +32,31 @@ export const SQX_ASSETS_EDITOR_CONTROL_VALUE_ACCESSOR: any = {
templateUrl: './assets-editor.component.html', templateUrl: './assets-editor.component.html',
providers: [SQX_ASSETS_EDITOR_CONTROL_VALUE_ACCESSOR] providers: [SQX_ASSETS_EDITOR_CONTROL_VALUE_ACCESSOR]
}) })
export class AssetsEditorComponent implements ControlValueAccessor { export class AssetsEditorComponent extends AppComponentBase implements ControlValueAccessor {
public newAssets = ImmutableArray.empty<File>();
public oldAssets = ImmutableArray.empty<AssetDto>();
private changeCallback: (value: any) => void = NOOP; private changeCallback: (value: any) => void = NOOP;
private touchedCallback: () => void = NOOP; private touchedCallback: () => void = NOOP;
public isDisabled = false; public isDisabled = false;
constructor(apps: AppsStoreService, notifications: NotificationService,
private readonly assetsService: AssetsService
) {
super(notifications, apps);
}
public writeValue(value: any) { public writeValue(value: any) {
// TODO if (!value) {
this.oldAssets = ImmutableArray.empty<AssetDto>();
} else {
this.appName()
.switchMap(app => this.assetsService.getAssets(app, 10000, 0, null, null, value))
.subscribe(dtos => {
this.oldAssets = ImmutableArray.of(dtos.items);
});
}
} }
public setDisabledState(isDisabled: boolean): void { public setDisabledState(isDisabled: boolean): void {
@ -41,4 +70,42 @@ export class AssetsEditorComponent implements ControlValueAccessor {
public registerOnTouched(fn: any) { public registerOnTouched(fn: any) {
this.touchedCallback = fn; this.touchedCallback = fn;
} }
public addFiles(files: FileList) {
for (let i = 0; i < files.length; i++) {
this.newAssets = this.newAssets.pushFront(files[i]);
}
}
public onAssetLoaded(file: File, asset: AssetDto) {
this.newAssets = this.newAssets.remove(file);
this.oldAssets = this.oldAssets.push(asset);
this.updateValue();
}
public onAssetDropped(asset: AssetDto) {
this.oldAssets = this.oldAssets.push(asset);
this.updateValue();
}
public onAssetRemoving(asset: AssetDto) {
this.oldAssets = this.oldAssets.remove(asset);
}
public onAssetFailed(file: File) {
this.newAssets = this.newAssets.remove(file);
}
private updateValue() {
let ids = this.oldAssets.values.map(x => x.id);
if (ids.length === 0) {
ids = null;
}
this.touchedCallback();
this.changeCallback(ids);
}
} }

56
src/Squidex/app/shared/components/component-base.ts

@ -5,68 +5,18 @@
* Copyright (c) Sebastian Stehle. All rights reserved * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
import { Observable } from 'rxjs';
import { import {
ErrorDto, ErrorDto,
Notification, Notification,
NotificationService, NotificationService
UsersProviderService } from './../declarations-base';
} from 'shared';
export abstract class ComponentBase { export abstract class ComponentBase {
constructor( constructor(
private readonly notifications: NotificationService, private readonly notifications: NotificationService
private readonly users: UsersProviderService
) { ) {
} }
public userEmail(userId: string, isRef: boolean = false): Observable<string> {
if (isRef) {
const parts = userId.split(':');
if (parts[0] === 'subject') {
return this.users.getUser(parts[1]).map(u => u.email);
} else {
return null;
}
} else {
return this.users.getUser(userId).map(u => u.email);
}
}
public userPicture(userId: string, isRef: boolean = false): Observable<string> {
if (isRef) {
const parts = userId.split(':');
if (parts[0] === 'subject') {
return this.users.getUser(parts[1]).map(u => u.pictureUrl);
} else {
return Observable.of('/images/client.png');
}
} else {
return this.users.getUser(userId).map(u => u.pictureUrl);
}
}
public userName(userId: string, isRef: boolean = false, placeholder = 'Me'): Observable<string> {
if (isRef) {
const parts = userId.split(':');
if (parts[0] === 'subject') {
return this.users.getUser(parts[1], placeholder).map(u => u.displayName);
} else {
if (parts[1].endsWith('client')) {
return Observable.of(parts[1]);
} else {
return Observable.of(`${parts[1]}-client`);
}
}
} else {
return this.users.getUser(userId, placeholder).map(u => u.displayName);
}
}
protected notifyError(error: string | ErrorDto) { protected notifyError(error: string | ErrorDto) {
if (error instanceof ErrorDto) { if (error instanceof ErrorDto) {
this.notifications.notify(Notification.error(error.displayMessage)); this.notifications.notify(Notification.error(error.displayMessage));

2
src/Squidex/app/shared/components/dashboard-link.directive.ts

@ -9,7 +9,7 @@ import { Directive, ElementRef, HostListener, OnDestroy, OnInit, Renderer } from
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { AppsStoreService } from './../services/apps-store.service'; import { AppsStoreService } from './../declarations-base';
@Directive({ @Directive({
selector: '[sqxDashboardLink]' selector: '[sqxDashboardLink]'

2
src/Squidex/app/shared/components/help.component.ts

@ -8,7 +8,7 @@
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { HelpService } from './../services/help.service'; import { HelpService } from './../declarations-base';
@Component({ @Component({
selector: 'sqx-help', selector: 'sqx-help',

2
src/Squidex/app/shared/components/history.component.html

@ -13,7 +13,7 @@
<div class="panel-content panel-content-blank"> <div class="panel-content panel-content-blank">
<div *ngFor="let event of events | async" class="event"> <div *ngFor="let event of events | async" class="event">
<div class="event-left"> <div class="event-left">
<img class="user-picture" [attr.title]="actorName(event.actor) | async" [attr.src]="actorPicture(event.actor) | async" /> <img class="user-picture" [attr.title]="event.actor | userName:'I'" [attr.src]="event.actor | userPicture" />
</div> </div>
<div class="event-main"> <div class="event-main">
<div class="event-message"> <div class="event-message">

39
src/Squidex/app/shared/components/history.component.ts

@ -9,13 +9,17 @@ import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { MessageBus, NotificationService } from 'framework'; import { AppComponentBase } from './app.component-base';
import { AppComponentBase } from './app-component-base'; import {
import { AppsStoreService } from './../services/apps-store.service'; AppsStoreService,
import { HistoryChannelUpdated } from './../utils/messages'; HistoryChannelUpdated,
import { HistoryEventDto, HistoryService } from './../services/history.service'; HistoryEventDto,
import { UsersProviderService } from './../services/users-provider.service'; HistoryService,
MessageBus,
NotificationService,
UsersProviderService
} from './../declarations-base';
const REPLACEMENT_TEMP = '$TEMP$'; const REPLACEMENT_TEMP = '$TEMP$';
@ -46,20 +50,27 @@ export class HistoryComponent extends AppComponentBase {
.switchMap(() => this.appName()) .switchMap(() => this.appName())
.switchMap(app => this.historyService.getHistory(app, this.channel).retry(2)); .switchMap(app => this.historyService.getHistory(app, this.channel).retry(2));
constructor(appsStore: AppsStoreService, notifications: NotificationService, users: UsersProviderService, constructor(appsStore: AppsStoreService, notifications: NotificationService,
private readonly users: UsersProviderService,
private readonly historyService: HistoryService, private readonly historyService: HistoryService,
private readonly messageBus: MessageBus, private readonly messageBus: MessageBus,
private readonly route: ActivatedRoute private readonly route: ActivatedRoute
) { ) {
super(notifications, users, appsStore); super(notifications, appsStore);
} }
public actorName(actor: string): Observable<string> { private userName(userId: string): Observable<string> {
return this.userName(actor, true, 'I'); const parts = userId.split(':');
}
public actorPicture(actor: string): Observable<string> { if (parts[0] === 'subject') {
return this.userPicture(actor, true); return this.users.getUser(parts[1], 'Me').map(u => u.displayName);
} else {
if (parts[1].endsWith('client')) {
return Observable.of(parts[1]);
} else {
return Observable.of(`${parts[1]}-client`);
}
}
} }
public format(message: string): Observable<string> { public format(message: string): Observable<string> {

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

@ -0,0 +1,159 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { ChangeDetectorRef, OnDestroy, Pipe, PipeTransform } from '@angular/core';
import { Observable, Subscription } from 'rxjs';
import { UsersProviderService } from './../declarations-base';
class UserAsyncPipe implements OnDestroy {
private lastUserId: string;
private lastValue: string;
private subscription: Subscription;
constructor(
private readonly users: UsersProviderService,
private readonly changeDetector: ChangeDetectorRef
) {
}
public ngOnDestroy() {
if (this.subscription) {
this.subscription.unsubscribe();
}
}
protected transformInternal(userId: string, transform: (users: UsersProviderService) => Observable<string>): string {
if (this.lastUserId !== userId) {
this.lastUserId = userId;
if (this.subscription) {
this.subscription.unsubscribe();
}
this.subscription = transform(this.users).subscribe(value => {
this.lastValue = value;
this.changeDetector.markForCheck();
});
}
return this.lastValue;
}
}
@Pipe({
name: 'userName',
pure: false
})
export class UserNamePipe extends UserAsyncPipe implements PipeTransform {
constructor(users: UsersProviderService, changeDetector: ChangeDetectorRef) {
super(users, changeDetector);
}
public transform(userId: string, placeholder = 'Me'): string {
return super.transformInternal(userId, users => users.getUser(userId, placeholder).map(u => u.displayName));
}
}
@Pipe({
name: 'userNameRef',
pure: false
})
export class UserNameRefPipe extends UserAsyncPipe implements PipeTransform {
constructor(users: UsersProviderService, changeDetector: ChangeDetectorRef) {
super(users, changeDetector);
}
public transform(userId: string, placeholder = 'Me'): string {
return super.transformInternal(userId, users => {
const parts = userId.split(':');
if (parts[0] === 'subject') {
return users.getUser(parts[1], placeholder).map(u => u.displayName);
} else {
if (parts[1].endsWith('client')) {
return Observable.of(parts[1]);
} else {
return Observable.of(`${parts[1]}-client`);
}
}
});
}
}
@Pipe({
name: 'userEmail',
pure: false
})
export class UserEmailPipe extends UserAsyncPipe implements PipeTransform {
constructor(users: UsersProviderService, changeDetector: ChangeDetectorRef) {
super(users, changeDetector);
}
public transform(userId: string): string {
return super.transformInternal(userId, users => users.getUser(userId).map(u => u.email));
}
}
@Pipe({
name: 'userEmailRef',
pure: false
})
export class UserEmailRefPipe extends UserAsyncPipe implements PipeTransform {
constructor(users: UsersProviderService, changeDetector: ChangeDetectorRef) {
super(users, changeDetector);
}
public transform(userId: string): string {
return super.transformInternal(userId, users => {
const parts = userId.split(':');
if (parts[0] === 'subject') {
return users.getUser(parts[1]).map(u => u.email);
} else {
return null;
}
});
}
}
@Pipe({
name: 'userPicture',
pure: false
})
export class UserPicturePipe extends UserAsyncPipe implements PipeTransform {
constructor(users: UsersProviderService, changeDetector: ChangeDetectorRef) {
super(users, changeDetector);
}
public transform(userId: string): string {
return super.transformInternal(userId, users => users.getUser(userId).map(u => u.pictureUrl));
}
}
@Pipe({
name: 'userPictureRef',
pure: false
})
export class UserPictureRefPipe extends UserAsyncPipe implements PipeTransform {
constructor(users: UsersProviderService, changeDetector: ChangeDetectorRef) {
super(users, changeDetector);
}
public transform(userId: string): string {
return super.transformInternal(userId, users => {
const parts = userId.split(':');
if (parts[0] === 'subject') {
return users.getUser(parts[1]).map(u => u.pictureUrl);
} else {
return Observable.of('/images/client.png');
}
});
}
}

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

@ -0,0 +1,35 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
export * from './guards/app-must-exist.guard';
export * from './guards/must-be-authenticated.guard';
export * from './guards/must-be-not-authenticated.guard';
export * from './guards/resolve-app-languages.guard';
export * from './guards/resolve-content.guard';
export * from './guards/resolve-published-schema.guard';
export * from './guards/resolve-schema.guard';
export * from './services/app-contributors.service';
export * from './services/app-clients.service';
export * from './services/app-languages.service';
export * from './services/apps-store.service';
export * from './services/apps.service';
export * from './services/assets.service';
export * from './services/auth.service';
export * from './services/contents.service';
export * from './services/event-consumers.service';
export * from './services/help.service';
export * from './services/history.service';
export * from './services/languages.service';
export * from './services/schemas.service';
export * from './services/users-provider.service';
export * from './services/users.service';
export * from './utils/file-helper';
export * from './utils/messages';
export * from 'framework';

33
src/Squidex/app/shared/declarations.ts

@ -5,40 +5,15 @@
* Copyright (c) Sebastian Stehle. All rights reserved * Copyright (c) Sebastian Stehle. All rights reserved
*/ */
export * from './components/app-component-base'; export * from './components/app.component-base';
export * from './components/app-form.component'; export * from './components/app-form.component';
export * from './components/asset.component';
export * from './components/assets-editor.component'; export * from './components/assets-editor.component';
export * from './components/component-base'; export * from './components/component-base';
export * from './components/dashboard-link.directive'; export * from './components/dashboard-link.directive';
export * from './components/help.component'; export * from './components/help.component';
export * from './components/history.component'; export * from './components/history.component';
export * from './components/language-selector.component'; export * from './components/language-selector.component';
export * from './components/pipes';
export * from './guards/app-must-exist.guard'; export * from './declarations-base';
export * from './guards/must-be-authenticated.guard';
export * from './guards/must-be-not-authenticated.guard';
export * from './guards/resolve-app-languages.guard';
export * from './guards/resolve-content.guard';
export * from './guards/resolve-published-schema.guard';
export * from './guards/resolve-schema.guard';
export * from './services/app-contributors.service';
export * from './services/app-clients.service';
export * from './services/app-languages.service';
export * from './services/apps-store.service';
export * from './services/apps.service';
export * from './services/assets.service';
export * from './services/auth.service';
export * from './services/contents.service';
export * from './services/event-consumers.service';
export * from './services/help.service';
export * from './services/history.service';
export * from './services/languages.service';
export * from './services/schemas.service';
export * from './services/users-provider.service';
export * from './services/users.service';
export * from './utils/file-helper';
export * from './utils/messages';
export * from 'framework';

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

@ -6,6 +6,7 @@
*/ */
import { ModuleWithProviders, NgModule } from '@angular/core'; import { ModuleWithProviders, NgModule } from '@angular/core';
import { DndModule } from 'ng2-dnd';
import { ProgressHttpModule } from 'angular-progress-http'; import { ProgressHttpModule } from 'angular-progress-http';
@ -19,6 +20,7 @@ import {
AppsStoreService, AppsStoreService,
AppsService, AppsService,
AppMustExistGuard, AppMustExistGuard,
AssetComponent,
AssetsEditorComponent, AssetsEditorComponent,
AssetsService, AssetsService,
AuthService, AuthService,
@ -38,6 +40,12 @@ import {
ResolvePublishedSchemaGuard, ResolvePublishedSchemaGuard,
ResolveSchemaGuard, ResolveSchemaGuard,
SchemasService, SchemasService,
UserEmailPipe,
UserEmailRefPipe,
UserNamePipe,
UserNameRefPipe,
UserPicturePipe,
UserPictureRefPipe,
UserManagementService, UserManagementService,
UsersProviderService, UsersProviderService,
UsersService UsersService
@ -46,23 +54,38 @@ import {
@NgModule({ @NgModule({
imports: [ imports: [
ProgressHttpModule, ProgressHttpModule,
DndModule,
SqxFrameworkModule SqxFrameworkModule
], ],
declarations: [ declarations: [
AppFormComponent, AppFormComponent,
AssetComponent,
AssetsEditorComponent, AssetsEditorComponent,
DashboardLinkDirective, DashboardLinkDirective,
HelpComponent, HelpComponent,
HistoryComponent, HistoryComponent,
LanguageSelectorComponent LanguageSelectorComponent,
UserEmailPipe,
UserEmailRefPipe,
UserNamePipe,
UserNameRefPipe,
UserPicturePipe,
UserPictureRefPipe
], ],
exports: [ exports: [
AppFormComponent, AppFormComponent,
AssetComponent,
AssetsEditorComponent, AssetsEditorComponent,
DashboardLinkDirective, DashboardLinkDirective,
HelpComponent, HelpComponent,
HistoryComponent, HistoryComponent,
LanguageSelectorComponent LanguageSelectorComponent,
UserEmailPipe,
UserEmailRefPipe,
UserNamePipe,
UserNameRefPipe,
UserPicturePipe,
UserPictureRefPipe
] ]
}) })
export class SqxSharedModule { export class SqxSharedModule {

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

@ -61,7 +61,7 @@ describe('AssetsService', () => {
lastModifiedBy: 'LastModifiedBy1', lastModifiedBy: 'LastModifiedBy1',
fileName: 'my-asset1.png', fileName: 'my-asset1.png',
fileSize: 1024, fileSize: 1024,
fileVersion: 2, fileVersion: 2000,
mimeType: 'text/plain', mimeType: 'text/plain',
isImage: true, isImage: true,
pixelWidth: 1024, pixelWidth: 1024,
@ -75,7 +75,7 @@ describe('AssetsService', () => {
lastModifiedBy: 'LastModifiedBy2', lastModifiedBy: 'LastModifiedBy2',
fileName: 'my-asset2.png', fileName: 'my-asset2.png',
fileSize: 1024, fileSize: 1024,
fileVersion: 2, fileVersion: 2000,
mimeType: 'text/plain', mimeType: 'text/plain',
isImage: true, isImage: true,
pixelWidth: 1024, pixelWidth: 1024,
@ -90,7 +90,7 @@ describe('AssetsService', () => {
let assets: AssetsDto | null = null; let assets: AssetsDto | null = null;
assetsService.getAssets('my-app', 17, 13, null, null).subscribe(result => { assetsService.getAssets('my-app', 17, 13, null, null, null).subscribe(result => {
assets = result; assets = result;
}).unsubscribe(); }).unsubscribe();
@ -101,7 +101,8 @@ describe('AssetsService', () => {
DateTime.parseISO_UTC('2016-12-12T10:10'), DateTime.parseISO_UTC('2016-12-12T10:10'),
DateTime.parseISO_UTC('2017-12-12T10:10'), DateTime.parseISO_UTC('2017-12-12T10:10'),
'my-asset1.png', 'my-asset1.png',
1024, 2, 1024,
2000,
'text/plain', 'text/plain',
true, true,
1024, 1024,
@ -111,7 +112,8 @@ describe('AssetsService', () => {
DateTime.parseISO_UTC('2016-10-12T10:10'), DateTime.parseISO_UTC('2016-10-12T10:10'),
DateTime.parseISO_UTC('2017-10-12T10:10'), DateTime.parseISO_UTC('2017-10-12T10:10'),
'my-asset2.png', 'my-asset2.png',
1024, 2, 1024,
2000,
'text/plain', 'text/plain',
true, true,
1024, 1024,
@ -122,6 +124,54 @@ describe('AssetsService', () => {
authService.verifyAll(); authService.verifyAll();
}); });
it('should make get request to get asset', () => {
authService.setup(x => x.authGet('http://service/p/api/apps/my-app/assets/123'))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: {
id: 'id1',
created: '2016-12-12T10:10',
createdBy: 'Created1',
lastModified: '2017-12-12T10:10',
lastModifiedBy: 'LastModifiedBy1',
fileName: 'my-asset1.png',
fileSize: 1024,
fileVersion: 2000,
mimeType: 'text/plain',
isImage: true,
pixelWidth: 1024,
pixelHeight: 2048,
version: 11
}
})
)
))
.verifiable(Times.once());
let assets: AssetDto | null = null;
assetsService.getAsset('my-app', '123', null).subscribe(result => {
assets = result;
}).unsubscribe();
expect(assets).toEqual(
new AssetDto(
'id1', 'Created1', 'LastModifiedBy1',
DateTime.parseISO_UTC('2016-12-12T10:10'),
DateTime.parseISO_UTC('2017-12-12T10:10'),
'my-asset1.png',
1024,
2000,
'text/plain',
true,
1024,
2048,
new Version('11')));
authService.verifyAll();
});
it('should append query to find by name', () => { it('should append query to find by name', () => {
authService.setup(x => x.authGet('http://service/p/api/apps/my-app/assets?query=my-query&take=17&skip=13')) authService.setup(x => x.authGet('http://service/p/api/apps/my-app/assets?query=my-query&take=17&skip=13'))
.returns(() => Observable.of( .returns(() => Observable.of(
@ -138,7 +188,7 @@ describe('AssetsService', () => {
let assets: AssetsDto | null = null; let assets: AssetsDto | null = null;
assetsService.getAssets('my-app', 17, 13, 'my-query', null).subscribe(result => { assetsService.getAssets('my-app', 17, 13, 'my-query', null, null).subscribe(result => {
assets = result; assets = result;
}).unsubscribe(); }).unsubscribe();
@ -161,7 +211,30 @@ describe('AssetsService', () => {
let assets: AssetsDto | null = null; let assets: AssetsDto | null = null;
assetsService.getAssets('my-app', 17, 13, null, ['text/plain', 'image/png']).subscribe(result => { assetsService.getAssets('my-app', 17, 13, null, ['text/plain', 'image/png'], null).subscribe(result => {
assets = result;
}).unsubscribe();
authService.verifyAll();
});
it('should append mime types to find by ids', () => {
authService.setup(x => x.authGet('http://service/p/api/apps/my-app/assets?ids=12,23&take=17&skip=13'))
.returns(() => Observable.of(
new Response(
new ResponseOptions({
body: {
total: 10,
items: []
}
})
)
))
.verifiable(Times.once());
let assets: AssetsDto | null = null;
assetsService.getAssets('my-app', 17, 13, null, null, ['12', '23']).subscribe(result => {
assets = result; assets = result;
}).unsubscribe(); }).unsubscribe();

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

@ -89,13 +89,17 @@ export class AssetsService {
) { ) {
} }
public getAssets(appName: string, take: number, skip: number, query: string, mimeTypes: string[]): Observable<AssetsDto> { public getAssets(appName: string, take: number, skip: number, query: string, mimeTypes: string[], ids: string[]): Observable<AssetsDto> {
let queries: string[] = []; let queries: string[] = [];
if (mimeTypes && mimeTypes.length > 0) { if (mimeTypes && mimeTypes.length > 0) {
queries.push(`mimeTypes=${mimeTypes.join(',')}`); queries.push(`mimeTypes=${mimeTypes.join(',')}`);
} }
if (ids && ids.length > 0) {
queries.push(`ids=${ids.join(',')}`);
}
if (query && query.length > 0) { if (query && query.length > 0) {
queries.push(`query=${query}`); queries.push(`query=${query}`);
} }
@ -169,6 +173,30 @@ export class AssetsService {
}); });
} }
public getAsset(appName: string, id: string, version: Version): Observable<AssetDto> {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets/${id}`);
return this.authService.authGet(url)
.map(response => response.json())
.map(response => {
return new AssetDto(
response.id,
response.createdBy,
response.lastModifiedBy,
DateTime.parseISO_UTC(response.created),
DateTime.parseISO_UTC(response.lastModified),
response.fileName,
response.fileSize,
response.fileVersion,
response.mimeType,
response.isImage,
response.pixelWidth,
response.pixelHeight,
new Version(response.version.toString()));
})
.catchError('Failed to load assets. Please reload.');
}
public replaceFile(appName: string, id: string, file: File, version?: Version): Observable<number | AssetReplacedDto> { public replaceFile(appName: string, id: string, file: File, version?: Version): Observable<number | AssetReplacedDto> {
return new Observable<number | AssetReplacedDto>(subscriber => { return new Observable<number | AssetReplacedDto>(subscriber => {
const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets/${id}/content`); const url = this.apiUrl.buildUrl(`api/apps/${appName}/assets/${id}/content`);

Loading…
Cancel
Save