From a7c0b87d88294a6049c4f32b46788e06134d577c Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sun, 2 Jul 2017 21:48:22 +0200 Subject: [PATCH] References editor and drag drop refactoring. --- .../Contents/MongoContentRepository.cs | 32 +- .../Contents/Visitors/FindExtensions.cs | 11 +- .../Repositories/IContentRepository.cs | 4 +- .../Api/Schemas/SchemasController.cs | 17 +- .../ContentApi/ContentsController.cs | 42 +- .../EnrichWithSchemaIdHandler.cs | 12 +- .../app/features/content/declarations.ts | 6 +- src/Squidex/app/features/content/module.ts | 26 + .../content/content-field.component.html | 3 + .../pages/content/content-page.component.ts | 15 +- .../contents/contents-page.component.html | 30 +- .../pages/contents/contents-page.component.ts | 22 +- .../shared}/assets-editor.component.html | 6 +- .../shared}/assets-editor.component.scss | 9 + .../shared}/assets-editor.component.ts | 34 +- .../content-item.component.html | 7 +- .../content-item.component.scss | 0 .../content-item.component.ts | 3 + .../shared/references-editor.component.html | 31 + .../shared/references-editor.component.scss | 63 + .../shared/references-editor.component.ts | 154 ++ src/Squidex/app/features/schemas/module.ts | 3 - .../schemas/pages/schema/field.component.html | 4 +- .../pages/schema/schema-page.component.ts | 12 +- .../schema/types/references-ui.component.ts | 5 +- .../references-validation.component.html | 12 +- .../types/references-validation.component.ts | 18 +- .../angular/geolocation-editor.component.ts | 5 +- .../angular/parent-link.directive.ts | 4 +- .../angular/progress-bar.component.ts | 6 +- .../app/framework/angular/router-utils.ts | 48 + .../angular}/sorted.directive.ts | 0 .../angular/template-wrapper.directive.ts | 11 +- src/Squidex/app/framework/declarations.ts | 2 + src/Squidex/app/framework/module.ts | 3 + .../shared/components/history.component.ts | 18 +- src/Squidex/app/shared/declarations.ts | 1 - .../guards/resolve-app-languages.guard.ts | 22 +- .../shared/guards/resolve-content.guard.ts | 40 +- .../guards/resolve-published-schema.guard.ts | 33 +- .../app/shared/guards/resolve-schema.guard.ts | 33 +- .../app/shared/guards/resolve-user.guard.ts | 22 +- src/Squidex/app/shared/module.ts | 3 - .../app/shared/services/contents.service.ts | 6 +- .../app/shared/services/schemas.service.ts | 11 + .../app/theme/icomoon/demo-files/demo.css | 6 +- src/Squidex/app/theme/icomoon/demo.html | 742 +++++----- .../app/theme/icomoon/fonts/icomoon.eot | Bin 18216 -> 17676 bytes .../app/theme/icomoon/fonts/icomoon.svg | 4 +- .../app/theme/icomoon/fonts/icomoon.ttf | Bin 18052 -> 17512 bytes .../app/theme/icomoon/fonts/icomoon.woff | Bin 18128 -> 17588 bytes src/Squidex/app/theme/icomoon/selection.json | 1306 ++++++++--------- src/Squidex/app/theme/icomoon/style.css | 97 +- 53 files changed, 1673 insertions(+), 1331 deletions(-) rename src/Squidex/app/{shared/components => features/content/shared}/assets-editor.component.html (70%) rename src/Squidex/app/{shared/components => features/content/shared}/assets-editor.component.scss (88%) rename src/Squidex/app/{shared/components => features/content/shared}/assets-editor.component.ts (89%) rename src/Squidex/app/features/content/{pages/contents => shared}/content-item.component.html (86%) rename src/Squidex/app/features/content/{pages/contents => shared}/content-item.component.scss (100%) rename src/Squidex/app/features/content/{pages/contents => shared}/content-item.component.ts (98%) create mode 100644 src/Squidex/app/features/content/shared/references-editor.component.html create mode 100644 src/Squidex/app/features/content/shared/references-editor.component.scss create mode 100644 src/Squidex/app/features/content/shared/references-editor.component.ts create mode 100644 src/Squidex/app/framework/angular/router-utils.ts rename src/Squidex/app/{features/schemas/utils => framework/angular}/sorted.directive.ts (100%) diff --git a/src/Squidex.Read.MongoDb/Contents/MongoContentRepository.cs b/src/Squidex.Read.MongoDb/Contents/MongoContentRepository.cs index b00e39ebc..c9c31b009 100644 --- a/src/Squidex.Read.MongoDb/Contents/MongoContentRepository.cs +++ b/src/Squidex.Read.MongoDb/Contents/MongoContentRepository.cs @@ -66,7 +66,7 @@ namespace Squidex.Read.MongoDb.Contents this.modelBuilder = modelBuilder; } - public async Task> QueryAsync(Guid schemaId, bool nonPublished, string odataQuery, IAppEntity appEntity) + public async Task> QueryAsync(Guid schemaId, bool nonPublished, HashSet ids, string odataQuery, IAppEntity appEntity) { List result = null; @@ -81,7 +81,7 @@ namespace Squidex.Read.MongoDb.Contents cursor = collection - .Find(parser, schemaEntity.Id, schemaEntity.Schema, nonPublished) + .Find(parser, ids, schemaEntity.Id, schemaEntity.Schema, nonPublished) .Take(parser) .Skip(parser) .Sort(parser, schemaEntity.Schema); @@ -112,19 +112,7 @@ namespace Squidex.Read.MongoDb.Contents return result; } - public async Task ExistsAsync(Guid appId, Guid schemaId, Guid contentId) - { - var result = false; - - await ForAppIdAsync(appId, async collection => - { - result = await collection.Find(x => x.Id == contentId && x.SchemaId == schemaId).CountAsync() == 1; - }); - - return result; - } - - public async Task CountAsync(Guid schemaId, bool nonPublished, string odataQuery, IAppEntity appEntity) + public async Task CountAsync(Guid schemaId, bool nonPublished, HashSet ids, string odataQuery, IAppEntity appEntity) { var result = 0L; @@ -137,7 +125,7 @@ namespace Squidex.Read.MongoDb.Contents var parser = model.ParseQuery(odataQuery); - cursor = collection.Find(parser, schemaEntity.Id, schemaEntity.Schema, nonPublished); + cursor = collection.Find(parser, ids, schemaEntity.Id, schemaEntity.Schema, nonPublished); } catch (NotSupportedException) { @@ -158,6 +146,18 @@ namespace Squidex.Read.MongoDb.Contents return result; } + public async Task ExistsAsync(Guid appId, Guid schemaId, Guid contentId) + { + var result = false; + + await ForAppIdAsync(appId, async collection => + { + result = await collection.Find(x => x.Id == contentId && x.SchemaId == schemaId).CountAsync() == 1; + }); + + return result; + } + public async Task FindContentAsync(Guid schemaId, Guid id, IAppEntity appEntity) { MongoContentEntity result = null; diff --git a/src/Squidex.Read.MongoDb/Contents/Visitors/FindExtensions.cs b/src/Squidex.Read.MongoDb/Contents/Visitors/FindExtensions.cs index 924c5e382..d8ca104b7 100644 --- a/src/Squidex.Read.MongoDb/Contents/Visitors/FindExtensions.cs +++ b/src/Squidex.Read.MongoDb/Contents/Visitors/FindExtensions.cs @@ -59,14 +59,14 @@ namespace Squidex.Read.MongoDb.Contents.Visitors return cursor; } - public static IFindFluent Find(this IMongoCollection cursor, ODataUriParser query, Guid schemaId, Schema schema, bool nonPublished) + public static IFindFluent Find(this IMongoCollection cursor, ODataUriParser query, HashSet ids, Guid schemaId, Schema schema, bool nonPublished) { - var filter = BuildQuery(query, schemaId, schema, nonPublished); + var filter = BuildQuery(query, ids, schemaId, schema, nonPublished); return cursor.Find(filter); } - public static FilterDefinition BuildQuery(ODataUriParser query, Guid schemaId, Schema schema, bool nonPublished) + public static FilterDefinition BuildQuery(ODataUriParser query, HashSet ids, Guid schemaId, Schema schema, bool nonPublished) { var filters = new List> { @@ -78,6 +78,11 @@ namespace Squidex.Read.MongoDb.Contents.Visitors filters.Add(Filter.Eq(x => x.IsPublished, true)); } + if (ids != null && ids.Count > 0) + { + Filter.In(x => x.Id, ids); + } + var filter = FilterBuilder.Build(query, schema); if (filter != null) diff --git a/src/Squidex.Read/Contents/Repositories/IContentRepository.cs b/src/Squidex.Read/Contents/Repositories/IContentRepository.cs index e650395d9..06babf145 100644 --- a/src/Squidex.Read/Contents/Repositories/IContentRepository.cs +++ b/src/Squidex.Read/Contents/Repositories/IContentRepository.cs @@ -15,9 +15,9 @@ namespace Squidex.Read.Contents.Repositories { public interface IContentRepository { - Task> QueryAsync(Guid schemaId, bool nonPublished, string odataQuery, IAppEntity appEntity); + Task> QueryAsync(Guid schemaId, bool nonPublished, HashSet ids, string odataQuery, IAppEntity appEntity); - Task CountAsync(Guid schemaId, bool nonPublished, string odataQuery, IAppEntity appEntity); + Task CountAsync(Guid schemaId, bool nonPublished, HashSet ids, string odataQuery, IAppEntity appEntity); Task ExistsAsync(Guid appId, Guid schemaId, Guid contentId); diff --git a/src/Squidex/Controllers/Api/Schemas/SchemasController.cs b/src/Squidex/Controllers/Api/Schemas/SchemasController.cs index 315764c2d..a4f85cb3f 100644 --- a/src/Squidex/Controllers/Api/Schemas/SchemasController.cs +++ b/src/Squidex/Controllers/Api/Schemas/SchemasController.cs @@ -6,6 +6,7 @@ // All rights reserved. // ========================================================================== +using System; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; @@ -17,6 +18,7 @@ using Squidex.Controllers.Api.Schemas.Models; using Squidex.Controllers.Api.Schemas.Models.Converters; using Squidex.Core.Schemas; using Squidex.Pipeline; +using Squidex.Read.Schemas; using Squidex.Read.Schemas.Repositories; using Squidex.Write.Schemas.Commands; @@ -50,7 +52,7 @@ namespace Squidex.Controllers.Api.Schemas [HttpGet] [Route("apps/{app}/schemas/")] [ProducesResponseType(typeof(SchemaDto[]), 200)] - [ApiCosts(1)] + [ApiCosts(0)] public async Task GetSchemas(string app) { var schemas = await schemaRepository.QueryAllAsync(AppId); @@ -73,10 +75,19 @@ namespace Squidex.Controllers.Api.Schemas [HttpGet] [Route("apps/{app}/schemas/{name}/")] [ProducesResponseType(typeof(SchemaDetailsDto[]), 200)] - [ApiCosts(1)] + [ApiCosts(0)] public async Task GetSchema(string app, string name) { - var entity = await schemaRepository.FindSchemaAsync(AppId, name); + ISchemaEntity entity; + + if (Guid.TryParse(name, out var id)) + { + entity = await schemaRepository.FindSchemaAsync(id); + } + else + { + entity = await schemaRepository.FindSchemaAsync(AppId, name); + } if (entity == null) { diff --git a/src/Squidex/Controllers/ContentApi/ContentsController.cs b/src/Squidex/Controllers/ContentApi/ContentsController.cs index ac532898b..2b2efa398 100644 --- a/src/Squidex/Controllers/ContentApi/ContentsController.cs +++ b/src/Squidex/Controllers/ContentApi/ContentsController.cs @@ -7,6 +7,7 @@ // ========================================================================== using System; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; @@ -18,6 +19,7 @@ using Squidex.Infrastructure.CQRS.Commands; using Squidex.Infrastructure.Reflection; using Squidex.Pipeline; using Squidex.Read.Contents.Repositories; +using Squidex.Read.Schemas; using Squidex.Read.Schemas.Services; using Squidex.Write.Contents.Commands; @@ -43,19 +45,27 @@ namespace Squidex.Controllers.ContentApi [HttpGet] [Route("content/{app}/{name}")] [ApiCosts(2)] - public async Task GetContents(string name, [FromQuery] bool nonPublished = false, [FromQuery] bool hidden = false) + public async Task GetContents(string name, [FromQuery] bool nonPublished = false, [FromQuery] bool hidden = false, [FromQuery] string ids = null) { - var schemaEntity = await schemas.FindSchemaByNameAsync(AppId, name); + var schemaEntity = await FindSchemaAsync(name); - if (schemaEntity == null) + var idsList = new HashSet(); + + if (!string.IsNullOrWhiteSpace(ids)) { - return NotFound(); + foreach (var id in ids.Split(',')) + { + if (Guid.TryParse(id, out var guid)) + { + idsList.Add(guid); + } + } } - + var query = Request.QueryString.ToString(); - var taskForItems = contentRepository.QueryAsync(schemaEntity.Id, nonPublished, query, App); - var taskForCount = contentRepository.CountAsync(schemaEntity.Id, nonPublished, query, App); + var taskForItems = contentRepository.QueryAsync(schemaEntity.Id, nonPublished, idsList, query, App); + var taskForCount = contentRepository.CountAsync(schemaEntity.Id, nonPublished, idsList, query, App); await Task.WhenAll(taskForItems, taskForCount); @@ -83,7 +93,7 @@ namespace Squidex.Controllers.ContentApi [ApiCosts(1)] public async Task GetContent(string name, Guid id, bool hidden = false) { - var schemaEntity = await schemas.FindSchemaByNameAsync(AppId, name); + var schemaEntity = await FindSchemaAsync(name); if (schemaEntity == null) { @@ -183,5 +193,21 @@ namespace Squidex.Controllers.ContentApi return NoContent(); } + + private async Task FindSchemaAsync(string name) + { + ISchemaEntity schemaEntity; + + if (Guid.TryParse(name, out var schemaId)) + { + schemaEntity = await schemas.FindSchemaByIdAsync(schemaId); + } + else + { + schemaEntity = await schemas.FindSchemaByNameAsync(AppId, name); + } + + return schemaEntity; + } } } diff --git a/src/Squidex/Pipeline/CommandHandlers/EnrichWithSchemaIdHandler.cs b/src/Squidex/Pipeline/CommandHandlers/EnrichWithSchemaIdHandler.cs index a4be18e38..6d2b61472 100644 --- a/src/Squidex/Pipeline/CommandHandlers/EnrichWithSchemaIdHandler.cs +++ b/src/Squidex/Pipeline/CommandHandlers/EnrichWithSchemaIdHandler.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.Infrastructure; using Squidex.Infrastructure; using Squidex.Infrastructure.CQRS.Commands; +using Squidex.Read.Schemas; using Squidex.Read.Schemas.Services; using Squidex.Write; using Squidex.Write.Schemas; @@ -41,7 +42,16 @@ namespace Squidex.Pipeline.CommandHandlers { var schemaName = routeValues["name"].ToString(); - var schema = await schemas.FindSchemaByNameAsync(schemaCommand.AppId.Id, schemaName); + ISchemaEntity schema; + + if (Guid.TryParse(schemaName, out var id)) + { + schema = await schemas.FindSchemaByIdAsync(id); + } + else + { + schema = await schemas.FindSchemaByNameAsync(schemaCommand.AppId.Id, schemaName); + } if (schema == null) { diff --git a/src/Squidex/app/features/content/declarations.ts b/src/Squidex/app/features/content/declarations.ts index be71ab636..9bb9b3c3e 100644 --- a/src/Squidex/app/features/content/declarations.ts +++ b/src/Squidex/app/features/content/declarations.ts @@ -7,6 +7,8 @@ export * from './pages/content/content-field.component'; export * from './pages/content/content-page.component'; -export * from './pages/contents/content-item.component'; export * from './pages/contents/contents-page.component'; -export * from './pages/schemas/schemas-page.component'; \ No newline at end of file +export * from './pages/schemas/schemas-page.component'; +export * from './shared/assets-editor.component'; +export * from './shared/content-item.component'; +export * from './shared/references-editor.component'; \ No newline at end of file diff --git a/src/Squidex/app/features/content/module.ts b/src/Squidex/app/features/content/module.ts index 1a852e1d2..0b619c2dd 100644 --- a/src/Squidex/app/features/content/module.ts +++ b/src/Squidex/app/features/content/module.ts @@ -7,6 +7,7 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; +import { DndModule } from 'ng2-dnd'; import { CanDeactivateGuard, @@ -19,10 +20,12 @@ import { } from 'shared'; import { + AssetsEditorComponent, ContentFieldComponent, ContentPageComponent, ContentItemComponent, ContentsPageComponent, + ReferencesEditorComponent, SchemasPageComponent } from './declarations'; @@ -49,6 +52,16 @@ const routes: Routes = [ { path: 'assets', loadChildren: './../assets/module#SqxFeatureAssetsModule' + }, + { + path: 'references/:schemaName/:language', + component: ContentsPageComponent, + data: { + isReadOnly: true + }, + resolve: { + schemaOverride: ResolvePublishedSchemaGuard + } } ] }, @@ -67,6 +80,16 @@ const routes: Routes = [ channel: 'contents.{contentId}' } }, + { + path: 'references/:schemaName/:language', + component: ContentsPageComponent, + data: { + isReadOnly: true + }, + resolve: { + schema: ResolvePublishedSchemaGuard + } + }, { path: 'assets', loadChildren: './../assets/module#SqxFeatureAssetsModule' @@ -82,13 +105,16 @@ const routes: Routes = [ imports: [ SqxFrameworkModule, SqxSharedModule, + DndModule, RouterModule.forChild(routes) ], declarations: [ + AssetsEditorComponent, ContentFieldComponent, ContentItemComponent, ContentPageComponent, ContentsPageComponent, + ReferencesEditorComponent, SchemasPageComponent ] }) diff --git a/src/Squidex/app/features/content/pages/content/content-field.component.html b/src/Squidex/app/features/content/pages/content/content-field.component.html index 82ce4062b..8ea262a6b 100644 --- a/src/Squidex/app/features/content/pages/content/content-field.component.html +++ b/src/Squidex/app/features/content/pages/content/content-field.component.html @@ -92,6 +92,9 @@
+
+ +
diff --git a/src/Squidex/app/features/content/pages/content/content-page.component.ts b/src/Squidex/app/features/content/pages/content/content-page.component.ts index 40a2f9c9c..b898963af 100644 --- a/src/Squidex/app/features/content/pages/content/content-page.component.ts +++ b/src/Squidex/app/features/content/pages/content/content-page.component.ts @@ -20,6 +20,7 @@ import { AppComponentBase, AppLanguageDto, AppsStoreService, + allDataFromRoute, CanComponentDeactivate, ContentDto, ContentsService, @@ -73,6 +74,10 @@ export class ContentPageComponent extends AppComponentBase implements CanCompone } public ngOnInit() { + const routeData = allDataFromRoute(this.route); + + this.languages = routeData['appLanguages']; + this.contentDeletedSubscription = this.messageBus.of(ContentDeleted) .subscribe(message => { @@ -81,15 +86,7 @@ export class ContentPageComponent extends AppComponentBase implements CanCompone } }); - this.route.parent!.data.map(p => p['appLanguages']) - .subscribe((languages: AppLanguageDto[]) => { - this.languages = languages; - }); - - this.route.parent!.data.map(p => p['schema']) - .subscribe((schema: SchemaDetailsDto) => { - this.setupForm(schema); - }); + this.setupForm(routeData['schema']); this.route.data.map(p => p['content']) .subscribe((content: ContentDto) => { diff --git a/src/Squidex/app/features/content/pages/contents/contents-page.component.html b/src/Squidex/app/features/content/pages/contents/contents-page.component.html index 2676778d6..d793d3134 100644 --- a/src/Squidex/app/features/content/pages/contents/contents-page.component.html +++ b/src/Squidex/app/features/content/pages/contents/contents-page.component.html @@ -1,6 +1,6 @@ - +
@@ -10,17 +10,17 @@ - +
- + -
@@ -38,9 +38,9 @@ - - - + + + @@ -54,15 +54,15 @@ - - + - + + + + + + +
By + Options
diff --git a/src/Squidex/app/features/content/pages/contents/contents-page.component.ts b/src/Squidex/app/features/content/pages/contents/contents-page.component.ts index 96fd3480b..9a04d6ba3 100644 --- a/src/Squidex/app/features/content/pages/contents/contents-page.component.ts +++ b/src/Squidex/app/features/content/pages/contents/contents-page.component.ts @@ -17,6 +17,7 @@ import { } from './../messages'; import { + allDataFromRoute, AppComponentBase, AppLanguageDto, AppsStoreService, @@ -52,6 +53,9 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy public languages: AppLanguageDto[] = []; public languageSelected: AppLanguageDto; + public languageParameter: string; + + public isReadOnly = false; public columnWidth: number; @@ -70,6 +74,10 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy } public ngOnInit() { + const routeData = allDataFromRoute(this.route); + + this.languages = routeData['appLanguages']; + this.contentCreatedSubscription = this.messageBus.of(ContentCreated) .subscribe(message => { @@ -83,18 +91,20 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy this.updateContents(message.id, undefined, message.data, message.version); }); - this.route.data.map(p => p['appLanguages']) - .subscribe((languages: AppLanguageDto[]) => { - this.languages = languages; + this.route.params.map(p => p['language']) + .subscribe(language => { + this.languageSelected = this.languages.find(l => l.iso2Code === language) || this.languages.find(l => l.isMaster) || this.languages[0]; }); - this.route.data.map(p => p['schema']) + this.route.data.map(p => p['schemaOverride'] || p['schema']) .subscribe(schema => { this.schema = schema; this.reset(); this.load(); }); + + this.isReadOnly = routeData['isReadOnly']; } public search() { @@ -175,6 +185,10 @@ export class ContentsPageComponent extends AppComponentBase implements OnDestroy }); } + public dropData(content: ContentDto) { + return { content, schemaId: this.schema.id }; + } + public goNext() { this.contentsPager = this.contentsPager.goNext(); diff --git a/src/Squidex/app/shared/components/assets-editor.component.html b/src/Squidex/app/features/content/shared/assets-editor.component.html similarity index 70% rename from src/Squidex/app/shared/components/assets-editor.component.html rename to src/Squidex/app/features/content/shared/assets-editor.component.html index c523ea304..d0f581d2a 100644 --- a/src/Squidex/app/shared/components/assets-editor.component.html +++ b/src/Squidex/app/features/content/shared/assets-editor.component.html @@ -1,7 +1,7 @@ -
+
-
-
+
+
Drop files or assets here to add them.
diff --git a/src/Squidex/app/shared/components/assets-editor.component.scss b/src/Squidex/app/features/content/shared/assets-editor.component.scss similarity index 88% rename from src/Squidex/app/shared/components/assets-editor.component.scss rename to src/Squidex/app/features/content/shared/assets-editor.component.scss index 203cfd79c..6dc418e55 100644 --- a/src/Squidex/app/shared/components/assets-editor.component.scss +++ b/src/Squidex/app/features/content/shared/assets-editor.component.scss @@ -1,6 +1,10 @@ @import '_vars'; @import '_mixins'; +.disabled { + pointer-events: none; +} + .assets { &-container { & { @@ -27,6 +31,10 @@ color: darken($color-border, 30%); } + &:hover { + text-decoration: underline; + } + &-container { padding-bottom: 1rem; } @@ -37,6 +45,7 @@ border-color: darken($color-border, 10%); cursor: copy; color: darken($color-border, 40%); + text-decoration: none; } } diff --git a/src/Squidex/app/shared/components/assets-editor.component.ts b/src/Squidex/app/features/content/shared/assets-editor.component.ts similarity index 89% rename from src/Squidex/app/shared/components/assets-editor.component.ts rename to src/Squidex/app/features/content/shared/assets-editor.component.ts index 998e413bd..ea873b862 100644 --- a/src/Squidex/app/shared/components/assets-editor.component.ts +++ b/src/Squidex/app/features/content/shared/assets-editor.component.ts @@ -8,12 +8,11 @@ // tslint:disable:prefer-for-of import { Component, forwardRef, OnDestroy, OnInit } from '@angular/core'; -import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { Subscription } from 'rxjs'; -import { AppComponentBase } from './app.component-base'; - import { + AppComponentBase, AppsStoreService, AssetDto, AssetsService, @@ -21,7 +20,7 @@ import { ImmutableArray, MessageBus, NotificationService -} from './../declarations-base'; +} from 'shared'; const NOOP = () => { /* NOOP */ }; @@ -37,13 +36,12 @@ export const SQX_ASSETS_EDITOR_CONTROL_VALUE_ACCESSOR: any = { }) export class AssetsEditorComponent extends AppComponentBase implements ControlValueAccessor, OnDestroy, OnInit { private assetUpdatedSubscription: Subscription; + private changeCallback: (value: any) => void = NOOP; + private touchedCallback: () => void = NOOP; public newAssets = ImmutableArray.empty(); public oldAssets = ImmutableArray.empty(); - private changeCallback: (value: any) => void = NOOP; - private touchedCallback: () => void = NOOP; - public isDisabled = false; constructor(apps: AppsStoreService, notifications: NotificationService, @@ -102,6 +100,14 @@ export class AssetsEditorComponent extends AppComponentBase implements ControlVa } } + public canDrop() { + const component = this; + + return (dragData: any) => { + return dragData instanceof AssetDto && !component.oldAssets.find(a => a.id === dragData.id); + }; + } + public onAssetDropped(asset: AssetDto) { if (asset) { this.oldAssets = this.oldAssets.pushFront(asset); @@ -110,15 +116,17 @@ export class AssetsEditorComponent extends AppComponentBase implements ControlVa } } - public onAssetLoaded(file: File, asset: AssetDto) { - this.newAssets = this.newAssets.remove(file); - this.oldAssets = this.oldAssets.pushFront(asset); + public onAssetRemoving(asset: AssetDto) { + if (asset) { + this.oldAssets = this.oldAssets.remove(asset); - this.updateValue(); + this.updateValue(); + } } - public onAssetRemoving(asset: AssetDto) { - this.oldAssets = this.oldAssets.remove(asset); + public onAssetLoaded(file: File, asset: AssetDto) { + this.newAssets = this.newAssets.remove(file); + this.oldAssets = this.oldAssets.pushFront(asset); this.updateValue(); } diff --git a/src/Squidex/app/features/content/pages/contents/content-item.component.html b/src/Squidex/app/features/content/shared/content-item.component.html similarity index 86% rename from src/Squidex/app/features/content/pages/contents/content-item.component.html rename to src/Squidex/app/features/content/shared/content-item.component.html index a45c7e429..749689e98 100644 --- a/src/Squidex/app/features/content/pages/contents/content-item.component.html +++ b/src/Squidex/app/features/content/shared/content-item.component.html @@ -11,7 +11,7 @@ - +
+ + + \ No newline at end of file diff --git a/src/Squidex/app/features/content/pages/contents/content-item.component.scss b/src/Squidex/app/features/content/shared/content-item.component.scss similarity index 100% rename from src/Squidex/app/features/content/pages/contents/content-item.component.scss rename to src/Squidex/app/features/content/shared/content-item.component.scss diff --git a/src/Squidex/app/features/content/pages/contents/content-item.component.ts b/src/Squidex/app/features/content/shared/content-item.component.ts similarity index 98% rename from src/Squidex/app/features/content/pages/contents/content-item.component.ts rename to src/Squidex/app/features/content/shared/content-item.component.ts index 6dc6e477c..28cc13843 100644 --- a/src/Squidex/app/features/content/pages/contents/content-item.component.ts +++ b/src/Squidex/app/features/content/shared/content-item.component.ts @@ -49,6 +49,9 @@ export class ContentItemComponent extends AppComponentBase implements OnInit, On @Input() public schema: SchemaDto; + @Input() + public isReadOnly = false; + @Input('sqxContent') public content: ContentDto; diff --git a/src/Squidex/app/features/content/shared/references-editor.component.html b/src/Squidex/app/features/content/shared/references-editor.component.html new file mode 100644 index 000000000..11ba6829b --- /dev/null +++ b/src/Squidex/app/features/content/shared/references-editor.component.html @@ -0,0 +1,31 @@ +
+
+
+ Drop content here to add them. +
+
+ +
+ Schema not found or not configured yet. +
+ + + + + + + + + + + + + + + +
+
\ No newline at end of file diff --git a/src/Squidex/app/features/content/shared/references-editor.component.scss b/src/Squidex/app/features/content/shared/references-editor.component.scss new file mode 100644 index 000000000..c7bc71e73 --- /dev/null +++ b/src/Squidex/app/features/content/shared/references-editor.component.scss @@ -0,0 +1,63 @@ +@import '_vars'; +@import '_mixins'; + +.disabled { + pointer-events: none; +} + +.references { + &-container { + & { + background: $color-background; + overflow-x: hidden; + overflow-y: scroll; + padding: 1rem; + } + } +} + +.invalid { + padding: 2rem; + font-size: 1.2rem; + font-weight: normal; + text-align: center; + color: darken($color-border, 30%); +} + +.drop-area { + & { + @include transition(border-color .4s ease); + @include border-radius; + border: 2px dashed $color-border; + font-size: 1.2rem; + font-weight: normal; + text-align: center; + padding: 2rem; + cursor: pointer; + color: darken($color-border, 30%); + } + + &:hover { + text-decoration: underline; + } + + &.drag, + &.dnd-drag-over, + &.dnd-drag-enter { + border-color: darken($color-border, 10%); + cursor: copy; + color: darken($color-border, 40%); + text-decoration: none; + } +} + +.table { + & { + margin-bottom: -.3rem; + margin-top: 1rem; + } + + &.disabled { + margin-top: 0; + } +} \ No newline at end of file diff --git a/src/Squidex/app/features/content/shared/references-editor.component.ts b/src/Squidex/app/features/content/shared/references-editor.component.ts new file mode 100644 index 000000000..70d9c4205 --- /dev/null +++ b/src/Squidex/app/features/content/shared/references-editor.component.ts @@ -0,0 +1,154 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Sebastian Stehle. All rights reserved + */ + +// tslint:disable:prefer-for-of + +import { Component, forwardRef, Input } from '@angular/core'; +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; + +import { + AppComponentBase, + AppsStoreService, + ContentDto, + ContentsService, + FieldDto, + ImmutableArray, + NotificationService, + SchemaDetailsDto, + SchemasService +} from 'shared'; + +const NOOP = () => { /* NOOP */ }; + +export const SQX_REFERENCES_EDITOR_CONTROL_VALUE_ACCESSOR: any = { + provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => ReferencesEditorComponent), multi: true +}; + +@Component({ + selector: 'sqx-references-editor', + styleUrls: ['./references-editor.component.scss'], + templateUrl: './references-editor.component.html', + providers: [SQX_REFERENCES_EDITOR_CONTROL_VALUE_ACCESSOR] +}) +export class ReferencesEditorComponent extends AppComponentBase implements ControlValueAccessor { + private changeCallback: (value: any) => void = NOOP; + private touchedCallback: () => void = NOOP; + + @Input() + public schemaId: string; + + @Input() + public languageCode: string; + + public schema: SchemaDetailsDto; + + public contentItems = ImmutableArray.empty(); + public contentFields: FieldDto[]; + + public columnWidth: number; + + public isDisabled = false; + public isInvalidSchema = false; + + constructor(apps: AppsStoreService, notifications: NotificationService, + private readonly contentsService: ContentsService, + private readonly schemasService: SchemasService + ) { + super(notifications, apps); + } + + public ngOnInit() { + this.appNameOnce() + .switchMap(app => this.schemasService.getSchema(app, this.schemaId)) + .subscribe(dto => { + this.schema = dto; + + this.loadFields(); + }, error => { + this.isInvalidSchema = true; + }); + } + + public writeValue(value: any) { + this.contentItems = ImmutableArray.empty(); + + if (value && value.length > 0) { + const contentIds: string[] = value; + + this.appNameOnce() + .switchMap(app => this.contentsService.getContents(app, this.schemaId, 10000, 0, undefined, contentIds)) + .subscribe(dtos => { + this.contentItems = ImmutableArray.of(dtos.items); + }); + } + } + + public setDisabledState(isDisabled: boolean): void { + this.isDisabled = isDisabled; + } + + public registerOnChange(fn: any) { + this.changeCallback = fn; + } + + public registerOnTouched(fn: any) { + this.touchedCallback = fn; + } + + public canDrop() { + const component = this; + + return (dragData: any) => { + return dragData.content instanceof ContentDto && dragData.schemaId === component.schemaId && !component.contentItems.find(c => c.id === dragData.content.id); + }; + } + + public onContentDropped(content: ContentDto) { + if (content) { + this.contentItems = this.contentItems.pushFront(content); + + this.updateValue(); + } + } + + public onContentRemoving(content: ContentDto) { + if (content) { + this.contentItems = this.contentItems.remove(content); + + this.updateValue(); + } + } + + public onContentsSorted(contents: ContentDto[]) { + if (contents) { + this.contentItems = ImmutableArray.of(contents); + + this.updateValue(); + } + } + + private updateValue() { + let ids: string[] | null = this.contentItems.values.map(x => x.id); + + if (ids.length === 0) { + ids = null; + } + + this.touchedCallback(); + this.changeCallback(ids); + } + + private loadFields() { + this.contentFields = this.schema.fields.filter(x => x.properties.isListField); + + this.columnWidth = 100 / this.contentFields.length; + + if (this.contentFields.length === 0 && this.schema.fields.length > 0) { + this.contentFields = [this.schema.fields[0]]; + } + } +} \ No newline at end of file diff --git a/src/Squidex/app/features/schemas/module.ts b/src/Squidex/app/features/schemas/module.ts index e8741b3f5..e4e7011ae 100644 --- a/src/Squidex/app/features/schemas/module.ts +++ b/src/Squidex/app/features/schemas/module.ts @@ -17,8 +17,6 @@ import { SqxSharedModule } from 'shared'; -import { SortedDirective } from './utils/sorted.directive'; - import { FieldComponent, AssetsUIComponent, @@ -104,7 +102,6 @@ const routes: Routes = [ SchemaFormComponent, SchemaPageComponent, SchemasPageComponent, - SortedDirective, StringUIComponent, StringValidationComponent ] diff --git a/src/Squidex/app/features/schemas/pages/schema/field.component.html b/src/Squidex/app/features/schemas/pages/schema/field.component.html index 748981f18..973176cea 100644 --- a/src/Squidex/app/features/schemas/pages/schema/field.component.html +++ b/src/Squidex/app/features/schemas/pages/schema/field.component.html @@ -147,7 +147,7 @@
- +
@@ -176,7 +176,7 @@
- +
diff --git a/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts b/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts index 655f21dd5..9237aafa7 100644 --- a/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts +++ b/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts @@ -16,6 +16,7 @@ import { createProperties, fadeAnimation, FieldDto, + fieldTypes, HistoryChannelUpdated, ImmutableArray, MessageBus, @@ -41,16 +42,7 @@ import { SchemaDeleted, SchemaUpdated } from './../messages'; ] }) export class SchemaPageComponent extends AppComponentBase implements OnInit { - public fieldTypes: string[] = [ - 'Assets', - 'Boolean', - 'DateTime', - 'Geolocation', - 'Json', - 'Number', - 'References', - 'String' - ]; + public fieldTypes = fieldTypes; public schemaExport: any; public schemaName: string; diff --git a/src/Squidex/app/features/schemas/pages/schema/types/references-ui.component.ts b/src/Squidex/app/features/schemas/pages/schema/types/references-ui.component.ts index 04cb10f96..02738d004 100644 --- a/src/Squidex/app/features/schemas/pages/schema/types/references-ui.component.ts +++ b/src/Squidex/app/features/schemas/pages/schema/types/references-ui.component.ts @@ -8,7 +8,7 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; import { FormGroup } from '@angular/forms'; -import { ReferencesFieldPropertiesDto, SchemaDto } from 'shared'; +import { ReferencesFieldPropertiesDto } from 'shared'; @Component({ selector: 'sqx-references-ui', @@ -22,7 +22,4 @@ export class ReferencesUIComponent { @Input() public properties: ReferencesFieldPropertiesDto; - - @Input() - public schems: SchemaDto[]; } \ No newline at end of file diff --git a/src/Squidex/app/features/schemas/pages/schema/types/references-validation.component.html b/src/Squidex/app/features/schemas/pages/schema/types/references-validation.component.html index 54887deda..c4fd37c92 100644 --- a/src/Squidex/app/features/schemas/pages/schema/types/references-validation.component.html +++ b/src/Squidex/app/features/schemas/pages/schema/types/references-validation.component.html @@ -1,4 +1,13 @@
+
+ + +
+ +
+
@@ -6,4 +15,5 @@
-
\ No newline at end of file + + diff --git a/src/Squidex/app/features/schemas/pages/schema/types/references-validation.component.ts b/src/Squidex/app/features/schemas/pages/schema/types/references-validation.component.ts index 9ff95d3fe..4da4ba98e 100644 --- a/src/Squidex/app/features/schemas/pages/schema/types/references-validation.component.ts +++ b/src/Squidex/app/features/schemas/pages/schema/types/references-validation.component.ts @@ -5,10 +5,10 @@ * Copyright (c) Sebastian Stehle. All rights reserved */ -import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; -import { FormGroup } from '@angular/forms'; +import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core'; +import { FormControl, FormGroup, Validators } from '@angular/forms'; -import { ReferencesFieldPropertiesDto } from 'shared'; +import { ReferencesFieldPropertiesDto, SchemaDto } from 'shared'; @Component({ selector: 'sqx-references-validation', @@ -16,10 +16,20 @@ import { ReferencesFieldPropertiesDto } from 'shared'; templateUrl: 'references-validation.component.html', changeDetection: ChangeDetectionStrategy.OnPush }) -export class ReferencesValidationComponent { +export class ReferencesValidationComponent implements OnInit { @Input() public editForm: FormGroup; @Input() public properties: ReferencesFieldPropertiesDto; + + @Input() + public schemas: SchemaDto[]; + + public ngOnInit() { + this.editForm.setControl('schemaId', + new FormControl(this.properties.schemaId, [ + Validators.required + ])); + } } \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/geolocation-editor.component.ts b/src/Squidex/app/framework/angular/geolocation-editor.component.ts index a01e39ea9..76e803261 100644 --- a/src/Squidex/app/framework/angular/geolocation-editor.component.ts +++ b/src/Squidex/app/framework/angular/geolocation-editor.component.ts @@ -73,6 +73,7 @@ export class GeolocationEditorComponent implements ControlValueAccessor, AfterVi if (isDisabled) { if (this.map) { this.map.zoomControl.disable(); + this.map._handlers.forEach((handler: any) => { handler.disable(); }); @@ -86,6 +87,7 @@ export class GeolocationEditorComponent implements ControlValueAccessor, AfterVi } else { if (this.map) { this.map.zoomControl.enable(); + this.map._handlers.forEach((handler: any) => { handler.enable(); }); @@ -129,7 +131,7 @@ export class GeolocationEditorComponent implements ControlValueAccessor, AfterVi }).addTo(this.map); this.map.on('click', (event: any) => { - if (!this.marker) { + if (!this.marker && !this.isDisabled) { const latlng = event.latlng.wrap(); this.value = { latitude: latlng.lat, longitude: latlng.lng }; @@ -142,6 +144,7 @@ export class GeolocationEditorComponent implements ControlValueAccessor, AfterVi if (this.isDisabled) { this.map.zoomControl.disable(); + this.map._handlers.forEach((handler: any) => { handler.disable(); }); diff --git a/src/Squidex/app/framework/angular/parent-link.directive.ts b/src/Squidex/app/framework/angular/parent-link.directive.ts index 13b294f74..bdd3ef643 100644 --- a/src/Squidex/app/framework/angular/parent-link.directive.ts +++ b/src/Squidex/app/framework/angular/parent-link.directive.ts @@ -28,7 +28,9 @@ export class ParentLinkDirective implements OnInit, OnDestroy { } public ngOnDestroy() { - this.urlSubscription.unsubscribe(); + if (this.urlSubscription) { + this.urlSubscription.unsubscribe(); + } } public ngOnInit() { diff --git a/src/Squidex/app/framework/angular/progress-bar.component.ts b/src/Squidex/app/framework/angular/progress-bar.component.ts index 7a0f0c34f..4e1b1f6ab 100644 --- a/src/Squidex/app/framework/angular/progress-bar.component.ts +++ b/src/Squidex/app/framework/angular/progress-bar.component.ts @@ -5,7 +5,7 @@ * Copyright (c) Sebastian Stehle. All rights reserved */ -import { Component, ElementRef, Input, OnChanges, OnInit, Renderer } from '@angular/core'; +import { Component, ElementRef, Input, OnChanges, OnInit, Renderer, SimpleChanges } from '@angular/core'; const ProgressBar = require('progressbar.js'); @@ -59,8 +59,8 @@ export class ProgressBarComponent implements OnChanges, OnInit { this.updateValue(); } - public ngOnChanges() { - if (this.progressBar) { + public ngOnChanges(changes: SimpleChanges) { + if (this.progressBar && changes.value) { this.updateValue(); } } diff --git a/src/Squidex/app/framework/angular/router-utils.ts b/src/Squidex/app/framework/angular/router-utils.ts new file mode 100644 index 000000000..ca49ef802 --- /dev/null +++ b/src/Squidex/app/framework/angular/router-utils.ts @@ -0,0 +1,48 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Sebastian Stehle. All rights reserved + */ + +import { ActivatedRoute, ActivatedRouteSnapshot, Data, Params } from '@angular/router'; + +export function allDataFromRoute(route: ActivatedRoute): Data { + return allData(route.snapshot); +} + +export function allData(route: ActivatedRouteSnapshot): Data { + let result: { [key: string]: any } = { }; + + while (route) { + for (let key in route.data) { + if (route.data.hasOwnProperty(key) && !result[key]) { + result[key] = route.data[key]; + } + } + + route = route.parent; + } + + return result; +} + +export function allParametersFromRoute(route: ActivatedRoute): Params { + return allParameters(route.snapshot); +} + +export function allParameters(route: ActivatedRouteSnapshot): Params { + let result: { [key: string]: any } = { }; + + while (route) { + for (let key of route.paramMap.keys) { + if (!result[key]) { + result[key] = route.paramMap.get(key); + } + } + + route = route.parent; + } + + return result; +} \ No newline at end of file diff --git a/src/Squidex/app/features/schemas/utils/sorted.directive.ts b/src/Squidex/app/framework/angular/sorted.directive.ts similarity index 100% rename from src/Squidex/app/features/schemas/utils/sorted.directive.ts rename to src/Squidex/app/framework/angular/sorted.directive.ts diff --git a/src/Squidex/app/framework/angular/template-wrapper.directive.ts b/src/Squidex/app/framework/angular/template-wrapper.directive.ts index e7f65da9c..0785f29a8 100644 --- a/src/Squidex/app/framework/angular/template-wrapper.directive.ts +++ b/src/Squidex/app/framework/angular/template-wrapper.directive.ts @@ -5,7 +5,7 @@ * Copyright (c) Sebastian Stehle. All rights reserved */ -import { Directive, Input, OnDestroy, OnInit, OnChanges, TemplateRef, ViewContainerRef, EmbeddedViewRef } from '@angular/core'; +import { Directive, EmbeddedViewRef, Input, OnChanges, OnDestroy, OnInit, SimpleChanges, TemplateRef, ViewContainerRef } from '@angular/core'; @Directive({ selector: '[sqxTemplateWrapper]' @@ -34,10 +34,13 @@ export class TemplateWrapper implements OnInit, OnDestroy, OnChanges { }); } - public ngOnChanges() { + public ngOnChanges(changes: SimpleChanges) { if (this.view) { - this.view.context.$implicit = this.item; - this.view.context.index = this.index; + if (changes.item) { + this.view.context.$implicit = this.item; + } else if (changes.index) { + this.view.context.index = this.index; + } } } diff --git a/src/Squidex/app/framework/declarations.ts b/src/Squidex/app/framework/declarations.ts index f988ad4a2..6ca5cb097 100644 --- a/src/Squidex/app/framework/declarations.ts +++ b/src/Squidex/app/framework/declarations.ts @@ -34,9 +34,11 @@ export * from './angular/parent-link.directive'; export * from './angular/popup-link.directive'; export * from './angular/progress-bar.component'; export * from './angular/rich-editor.component'; +export * from './angular/router-utils'; export * from './angular/scroll-active.directive'; export * from './angular/shortcut.component'; export * from './angular/slider.component'; +export * from './angular/sorted.directive'; export * from './angular/stars.component'; export * from './angular/tag-editor.component'; export * from './angular/template-wrapper.directive'; diff --git a/src/Squidex/app/framework/module.ts b/src/Squidex/app/framework/module.ts index 3277bcc27..3fff587a2 100644 --- a/src/Squidex/app/framework/module.ts +++ b/src/Squidex/app/framework/module.ts @@ -55,6 +55,7 @@ import { ShortDatePipe, ShortTimePipe, SliderComponent, + SortedDirective, StarsComponent, TagEditorComponent, TemplateWrapper, @@ -108,6 +109,7 @@ import { ShortDatePipe, ShortTimePipe, SliderComponent, + SortedDirective, StarsComponent, TagEditorComponent, TemplateWrapper, @@ -151,6 +153,7 @@ import { ShortDatePipe, ShortTimePipe, SliderComponent, + SortedDirective, StarsComponent, TagEditorComponent, TemplateWrapper, diff --git a/src/Squidex/app/shared/components/history.component.ts b/src/Squidex/app/shared/components/history.component.ts index 59d03a9a3..9edef92e1 100644 --- a/src/Squidex/app/shared/components/history.component.ts +++ b/src/Squidex/app/shared/components/history.component.ts @@ -12,6 +12,7 @@ import { Observable } from 'rxjs'; import { AppComponentBase } from './app.component-base'; import { + allParametersFromRoute, AppsStoreService, HistoryChannelUpdated, HistoryEventDto, @@ -30,18 +31,21 @@ const REPLACEMENT_TEMP = '$TEMP$'; }) export class HistoryComponent extends AppComponentBase { public get channel(): string { - let result = this.route.snapshot.data['channel']; - let params = this.route.parent!.snapshot.params; + let channelPath = this.route.snapshot.data['channel']; - for (let key in params) { - if (params.hasOwnProperty(key)) { - const value = params[key]; + if (channelPath) { + let params = allParametersFromRoute(this.route); - result = result.replace('{' + key + '}', value); + for (let key in params) { + if (params.hasOwnProperty(key)) { + const value = params[key]; + + channelPath = channelPath.replace('{' + key + '}', value); + } } } - return result; + return channelPath; } public events: Observable = diff --git a/src/Squidex/app/shared/declarations.ts b/src/Squidex/app/shared/declarations.ts index b1cb1dba5..3e4313916 100644 --- a/src/Squidex/app/shared/declarations.ts +++ b/src/Squidex/app/shared/declarations.ts @@ -8,7 +8,6 @@ export * from './components/app.component-base'; export * from './components/app-form.component'; export * from './components/asset.component'; -export * from './components/assets-editor.component'; export * from './components/component-base'; export * from './components/help.component'; export * from './components/history.component'; diff --git a/src/Squidex/app/shared/guards/resolve-app-languages.guard.ts b/src/Squidex/app/shared/guards/resolve-app-languages.guard.ts index 9a52abb39..9f28bc24e 100644 --- a/src/Squidex/app/shared/guards/resolve-app-languages.guard.ts +++ b/src/Squidex/app/shared/guards/resolve-app-languages.guard.ts @@ -8,6 +8,8 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router'; +import { allParameters } from 'framework'; + import { AppLanguageDto, AppLanguagesService } from './../services/app-languages.service'; @Injectable() @@ -19,7 +21,9 @@ export class ResolveAppLanguagesGuard implements Resolve { } public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { - const appName = this.findParameter(route, 'appName'); + const params = allParameters(route); + + const appName = params['appName']; if (!appName) { throw 'Route must contain app name.'; @@ -43,20 +47,4 @@ export class ResolveAppLanguagesGuard implements Resolve { return result; } - - private findParameter(route: ActivatedRouteSnapshot, name: string): string | null { - let result: string | null = null; - - while (route) { - result = route.params[name]; - - if (result || !route.parent) { - break; - } - - route = route.parent; - } - - return result; - } } \ No newline at end of file diff --git a/src/Squidex/app/shared/guards/resolve-content.guard.ts b/src/Squidex/app/shared/guards/resolve-content.guard.ts index 7ad185e43..ff5ad1188 100644 --- a/src/Squidex/app/shared/guards/resolve-content.guard.ts +++ b/src/Squidex/app/shared/guards/resolve-content.guard.ts @@ -8,6 +8,8 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router'; +import { allParameters } from 'framework'; + import { ContentDto, ContentsService } from './../services/contents.service'; @Injectable() @@ -19,12 +21,24 @@ export class ResolveContentGuard implements Resolve { } public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { - const appName = this.findParameter(route, 'appName'); - const schemaName = this.findParameter(route, 'schemaName'); - const contentId = this.findParameter(route, 'contentId'); + const params = allParameters(route); + + const appName = params['appName']; + + if (!appName) { + throw 'Route must contain app name.'; + } + + const schemaName = params['schemaName']; + + if (!schemaName) { + throw 'Route must contain schema name.'; + } + + const contentId = params['contentId']; - if (!appName || !schemaName || !contentId) { - throw 'Route must contain app and schema name and id.'; + if (!contentId) { + throw 'Route must contain content id.'; } const result = @@ -45,20 +59,4 @@ export class ResolveContentGuard implements Resolve { return result; } - - private findParameter(route: ActivatedRouteSnapshot, name: string): string | null { - let result: string | null = null; - - while (route) { - result = route.params[name]; - - if (result || !route.parent) { - break; - } - - route = route.parent; - } - - return result; - } } \ No newline at end of file diff --git a/src/Squidex/app/shared/guards/resolve-published-schema.guard.ts b/src/Squidex/app/shared/guards/resolve-published-schema.guard.ts index 032b4e542..b8b8594ed 100644 --- a/src/Squidex/app/shared/guards/resolve-published-schema.guard.ts +++ b/src/Squidex/app/shared/guards/resolve-published-schema.guard.ts @@ -8,6 +8,8 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router'; +import { allParameters } from 'framework'; + import { SchemaDetailsDto, SchemasService } from './../services/schemas.service'; @Injectable() @@ -19,11 +21,18 @@ export class ResolvePublishedSchemaGuard implements Resolve { } public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { - const appName = this.findParameter(route, 'appName'); - const schemaName = this.findParameter(route, 'schemaName'); + const params = allParameters(route); + + const appName = params['appName']; - if (!appName || !schemaName) { - throw 'Route must contain app and schema name.'; + if (!appName) { + throw 'Route must contain app name.'; + } + + const schemaName = params['schemaName']; + + if (!schemaName) { + throw 'Route must contain schema name.'; } const result = @@ -44,20 +53,4 @@ export class ResolvePublishedSchemaGuard implements Resolve { return result; } - - private findParameter(route: ActivatedRouteSnapshot, name: string): string | null { - let result: string | null = null; - - while (route) { - result = route.params[name]; - - if (result || !route.parent) { - break; - } - - route = route.parent; - } - - return result; - } } \ No newline at end of file diff --git a/src/Squidex/app/shared/guards/resolve-schema.guard.ts b/src/Squidex/app/shared/guards/resolve-schema.guard.ts index 3fbe17889..161916ff2 100644 --- a/src/Squidex/app/shared/guards/resolve-schema.guard.ts +++ b/src/Squidex/app/shared/guards/resolve-schema.guard.ts @@ -8,6 +8,8 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router'; +import { allParameters } from 'framework'; + import { SchemaDetailsDto, SchemasService } from './../services/schemas.service'; @Injectable() @@ -19,11 +21,18 @@ export class ResolveSchemaGuard implements Resolve { } public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { - const appName = this.findParameter(route, 'appName'); - const schemaName = this.findParameter(route, 'schemaName'); + const params = allParameters(route); + + const appName = params['appName']; - if (!appName || !schemaName) { - throw 'Route must contain app and schema name.'; + if (!appName) { + throw 'Route must contain app name.'; + } + + const schemaName = params['schemaName']; + + if (!schemaName) { + throw 'Route must contain schema name.'; } const result = @@ -44,20 +53,4 @@ export class ResolveSchemaGuard implements Resolve { return result; } - - private findParameter(route: ActivatedRouteSnapshot, name: string): string | null { - let result: string | null = null; - - while (route) { - result = route.params[name]; - - if (result || !route.parent) { - break; - } - - route = route.parent; - } - - return result; - } } \ No newline at end of file diff --git a/src/Squidex/app/shared/guards/resolve-user.guard.ts b/src/Squidex/app/shared/guards/resolve-user.guard.ts index 212627c96..0d331e5bf 100644 --- a/src/Squidex/app/shared/guards/resolve-user.guard.ts +++ b/src/Squidex/app/shared/guards/resolve-user.guard.ts @@ -8,6 +8,8 @@ import { Injectable } from '@angular/core'; import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router'; +import { allParameters } from 'framework'; + import { UserDto, UserManagementService } from './../services/users.service'; @Injectable() @@ -19,7 +21,9 @@ export class ResolveUserGuard implements Resolve { } public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise { - const userId = this.findParameter(route, 'userId'); + const params = allParameters(route); + + const userId = params['userId']; if (!userId) { throw 'Route must contain user id.'; @@ -43,20 +47,4 @@ export class ResolveUserGuard implements Resolve { return result; } - - private findParameter(route: ActivatedRouteSnapshot, name: string): string | null { - let result: string | null = null; - - while (route) { - result = route.params[name]; - - if (result || !route.parent) { - break; - } - - route = route.parent; - } - - return result; - } } \ No newline at end of file diff --git a/src/Squidex/app/shared/module.ts b/src/Squidex/app/shared/module.ts index 681a859ff..be42623bd 100644 --- a/src/Squidex/app/shared/module.ts +++ b/src/Squidex/app/shared/module.ts @@ -20,7 +20,6 @@ import { AppsService, AppMustExistGuard, AssetComponent, - AssetsEditorComponent, AssetsService, AuthService, ContentsService, @@ -64,7 +63,6 @@ import { declarations: [ AppFormComponent, AssetComponent, - AssetsEditorComponent, HelpComponent, HistoryComponent, LanguageSelectorComponent, @@ -80,7 +78,6 @@ import { exports: [ AppFormComponent, AssetComponent, - AssetsEditorComponent, HelpComponent, HistoryComponent, LanguageSelectorComponent, diff --git a/src/Squidex/app/shared/services/contents.service.ts b/src/Squidex/app/shared/services/contents.service.ts index 16dc22443..47a86fc79 100644 --- a/src/Squidex/app/shared/services/contents.service.ts +++ b/src/Squidex/app/shared/services/contents.service.ts @@ -48,7 +48,7 @@ export class ContentsService { ) { } - public getContents(appName: string, schemaName: string, take: number, skip: number, query?: string): Observable { + public getContents(appName: string, schemaName: string, take: number, skip: number, query?: string, ids?: string[]): Observable { let fullQuery = query ? query.trim() : ''; if (fullQuery.length > 0) { @@ -69,6 +69,10 @@ export class ContentsService { fullQuery += `&$skip=${skip}`; } + if (ids && ids.length > 0) { + fullQuery += `&ids=${ids.join(',')}`; + } + const url = this.apiUrl.buildUrl(`/api/content/${appName}/${schemaName}?nonPublished=true&hidden=true${fullQuery}`); return this.authService.authGet(url) diff --git a/src/Squidex/app/shared/services/schemas.service.ts b/src/Squidex/app/shared/services/schemas.service.ts index d5b6b2e61..68db677d4 100644 --- a/src/Squidex/app/shared/services/schemas.service.ts +++ b/src/Squidex/app/shared/services/schemas.service.ts @@ -19,6 +19,17 @@ import { import { AuthService } from './auth.service'; +export const fieldTypes: string[] = [ + 'Assets', + 'Boolean', + 'DateTime', + 'Geolocation', + 'Json', + 'Number', + 'References', + 'String' +]; + export function createProperties(fieldType: string, values: Object | null = null): FieldPropertiesDto { let properties: FieldPropertiesDto; diff --git a/src/Squidex/app/theme/icomoon/demo-files/demo.css b/src/Squidex/app/theme/icomoon/demo-files/demo.css index 29cb6dba6..f2a23ed9f 100644 --- a/src/Squidex/app/theme/icomoon/demo-files/demo.css +++ b/src/Squidex/app/theme/icomoon/demo-files/demo.css @@ -147,16 +147,16 @@ p { font-size: 16px; } .fs1 { - font-size: 28px; + font-size: 32px; } .fs2 { font-size: 32px; } .fs3 { - font-size: 32px; + font-size: 24px; } .fs4 { - font-size: 24px; + font-size: 28px; } .fs5 { font-size: 20px; diff --git a/src/Squidex/app/theme/icomoon/demo.html b/src/Squidex/app/theme/icomoon/demo.html index 74cbe7c50..0b3f476a8 100644 --- a/src/Squidex/app/theme/icomoon/demo.html +++ b/src/Squidex/app/theme/icomoon/demo.html @@ -9,20 +9,20 @@
-

Font Name: icomoon (Glyphs: 71)

+

Font Name: icomoon (Glyphs: 69)

-

Grid Size: 14

+

Grid Size: 16

- + - icon-grid + icon-elapsed
- - + +
liga: @@ -31,14 +31,14 @@
- + - icon-list + icon-timeout
- - + +
liga: @@ -47,14 +47,14 @@
- + - icon-bug + icon-checkmark
- - + +
liga: @@ -63,14 +63,14 @@
- + - icon-control-Markdown + icon-microsoft
- - + +
liga: @@ -79,14 +79,14 @@
- + - icon-control-Date + icon-google
- - + +
liga: @@ -95,14 +95,14 @@
- + - icon-control-DateTime + icon-unlocked
- - + +
liga: @@ -111,14 +111,14 @@
- + - icon-angle-right + icon-lock
- - + +
liga: @@ -127,14 +127,14 @@
- + - icon-user-o + icon-reset
- - + +
liga: @@ -143,14 +143,14 @@
- + - icon-caret-right + icon-pause
- - + +
liga: @@ -159,14 +159,14 @@
- + - icon-caret-left + icon-play
- - + +
liga: @@ -175,14 +175,14 @@
- + - icon-caret-up + icon-settings2
- - + +
liga: @@ -191,81 +191,81 @@
- + - icon-caret-down + icon-bin2
- - + +
liga:
-
+
+
+

Grid Size: Unknown

+
- + - icon-angle-up + icon-webhook
- - + +
liga:
-
+
- + - icon-angle-down + icon-github
- - + +
liga:
-
+
- + - icon-angle-left + icon-activity
- - + +
liga:
-
-
-

Grid Size: 16

- + - icon-elapsed + icon-history
- - + +
liga: @@ -274,14 +274,14 @@
- + - icon-timeout + icon-time
- - + +
liga: @@ -290,14 +290,14 @@
- + - icon-checkmark + icon-add
- - + +
liga: @@ -306,14 +306,14 @@
- + - icon-microsoft + icon-plus
- - + +
liga: @@ -322,14 +322,14 @@
- + - icon-google + icon-check-circle
- - + +
liga: @@ -338,14 +338,14 @@
- + - icon-unlocked + icon-check-circle-filled
- - + +
liga: @@ -354,14 +354,14 @@
- + - icon-lock + icon-close
- - + +
liga: @@ -370,14 +370,14 @@
- + - icon-reset + icon-content
- - + +
liga: @@ -386,14 +386,14 @@
- + - icon-pause + icon-type-References
- - + +
liga: @@ -402,14 +402,14 @@
- + - icon-play + icon-control-Checkbox
- - + +
liga: @@ -418,14 +418,14 @@
- + - icon-settings2 + icon-control-Dropdown
- - + +
liga: @@ -434,593 +434,577 @@
- + - icon-bin2 + icon-control-Input
- - + +
liga:
-
-
-

Grid Size: Unknown

-
+
- + - icon-webhook + icon-control-Radio
- - + +
liga:
-
+
- + - icon-github + icon-control-TextArea
- - + +
liga:
-
+
- + - icon-activity + icon-control-Toggle
- - + +
liga:
-
+
- - - - icon-history -
-
- - -
-
- liga: - -
-
-
-
- + - icon-time + icon-copy
- - + +
liga:
-
+
- + - icon-add + icon-dashboard
- - + +
liga:
-
+
- + - icon-plus + icon-delete
- - + +
liga:
-
+
- + - icon-check-circle + icon-bin
- - + +
liga:
-
+
- + - icon-check-circle-filled + icon-delete-filled
- - + +
liga:
-
+
- + - icon-close + icon-document-delete
- - + +
liga:
-
+
- + - icon-content + icon-document-disable
- - + +
liga:
-
+
- + - icon-control-Checkbox + icon-document-publish
- - + +
liga:
-
+
- + - icon-control-Dropdown + icon-drag
- - + +
liga:
-
+
- + - icon-control-Input + icon-filter
- - + +
liga:
-
+
- + - icon-control-Radio + icon-help
- - + +
liga:
-
+
- + - icon-control-TextArea + icon-type-Json
- - + +
liga:
-
+
- + - icon-control-Toggle + icon-json
- - + +
liga:
-
+
- + - icon-copy + icon-location
- - + +
liga:
-
+
- + - icon-dashboard + icon-control-Map
- - + +
liga:
-
+
- + - icon-delete + icon-type-Geolocation
- - + +
liga:
-
+
- + - icon-bin + icon-logo
- - + +
liga:
-
+
- + - icon-delete-filled + icon-media
- - + +
liga:
-
+
- + - icon-document-delete + icon-type-Assets
- - + +
liga:
-
+
- + - icon-document-disable + icon-more
- - + +
liga:
-
+
- + - icon-document-publish + icon-dots
- - + +
liga:
-
+
- + - icon-drag + icon-pencil
- - + +
liga:
-
+
- + - icon-filter + icon-reference
- - + +
liga:
-
+
- + - icon-help + icon-schemas
- - + +
liga:
-
+
- + - icon-type-Json + icon-search
- - + +
liga:
-
+
- + - icon-json + icon-settings
- - + +
liga:
-
+
- + - icon-location + icon-type-Boolean
- - + +
liga:
-
+
- + - icon-control-Map + icon-type-DateTime
- - + +
liga:
-
+
- + - icon-type-Geolocation + icon-type-Number
- - + +
liga:
-
+
-
- - + +
liga:
-
+
- + - icon-media + icon-user
- - + +
liga:
+
+
+

Grid Size: 24

- + - icon-type-Assets + icon-download
- - + +
liga: @@ -1029,209 +1013,209 @@
- + - icon-more + icon-control-RichText
- - + +
liga:
-
+
+
+

Grid Size: 14

+
- + - icon-dots + icon-bug
- - + +
liga:
-
+
- + - icon-pencil + icon-control-Markdown
- - + +
liga:
-
+
- + - icon-reference + icon-control-Date
- - + +
liga:
-
+
- + - icon-schemas + icon-control-DateTime
- - + +
liga:
-
+
- + - icon-search + icon-angle-right
- - + +
liga:
-
+
- + - icon-settings + icon-user-o
- - + +
liga:
-
+
- + - icon-type-Boolean + icon-caret-right
- - + +
liga:
-
+
- + - icon-type-DateTime + icon-caret-left
- - + +
liga:
-
+
- + - icon-type-Number + icon-caret-up
- - + +
liga:
-
+
- + - icon-type-String + icon-caret-down
- - + +
liga:
-
+
- + - icon-user + icon-angle-up
- - + +
liga:
-
-
-

Grid Size: 24

- + - icon-download + icon-angle-down
- - + +
liga: @@ -1240,14 +1224,14 @@
- + - icon-control-RichText + icon-angle-left
- - + +
liga: diff --git a/src/Squidex/app/theme/icomoon/fonts/icomoon.eot b/src/Squidex/app/theme/icomoon/fonts/icomoon.eot index 7d0019b8d37be517838cb866fe2c361caf3f446a..b33df005009430f5bac6439c084803a5272ab7b2 100644 GIT binary patch delta 341 zcmZ3{$Jo=w$j0N!z>wiGkRef7ANMz~ICKl%JBDSW&>BpTHOe6j%ThP{>Qn zO`Yk*`5VYT0MyV{kY8NFzzmdOaPa`iGcYqBn4H3B$Mn)=@)AaCZ400v7Xt?a6A&^o zD7DS sK=kAkXLCl+&2yYp7$=`_nIZr*n1R8QF@ix1tdwc8pu6zqX4j>R0F~xer2qf` delta 868 zcmeC_VqDS3$fn`Wz|i70kP#=r z-$4EypoX@B{NfS@W}pm%TM9^?fth*#o+2F|aW(GAO)s z`M~wziVL6V!fnAGj-Ie{!s76#@#27U&427Ly5 z27iWlhJ1#4hJFSHRZ$T(ZAK+ETSgNzYepk6K{P%i#r%3y3(yV3uuxS|Y`eIEg7|hZ zMFfj6S3!ImNEAr?n~YtYF%O~X-z2#DdIe+&gpzl8AoKEowlMPLf|wxU-%+?IlQDz| z5+0p6RvoDVSq0O-NEcmMzZ diff --git a/src/Squidex/app/theme/icomoon/fonts/icomoon.svg b/src/Squidex/app/theme/icomoon/fonts/icomoon.svg index e5afc5579..05aebbad4 100644 --- a/src/Squidex/app/theme/icomoon/fonts/icomoon.svg +++ b/src/Squidex/app/theme/icomoon/fonts/icomoon.svg @@ -16,7 +16,7 @@ - + @@ -76,6 +76,4 @@ - - \ No newline at end of file diff --git a/src/Squidex/app/theme/icomoon/fonts/icomoon.ttf b/src/Squidex/app/theme/icomoon/fonts/icomoon.ttf index cc80d7ad9bf94f84c42b66a1f538bfaf27d88fea..06cd7e4f2320c65bbee8ea4faccde524b4cf94c3 100644 GIT binary patch delta 335 zcmZqaWqi@WSkJ)7z`(%G(7?dVVBjCDZ^SRerUw++1H=i*xrqhhVV6!bFfhsh`620v z#RWiG0LTZ?9O*fgX(p?rF9P`$3=H-n8L5dW{Lx9G3=9rEKzXwapa3Tms~3=e1jtv( z$StYhO6ETVBadQ z$Ugwo&{mLNTmp0`5V&}Nk zF_6swW;6bOEY1QnfN^sfqq@B^$m>9G-R<7)cz&C&4BRYW{?)p^Qy>P2o_xT`oY8ah j4<{AI$to^W1b_xJFnBUXFo=PbGEFXY6W)B^Who;7-6m3e delta 880 zcmaFS!PwHvSkJ)7z`(%G(7?dVVBjCDZ^SRey9p?=2Z$4ra}x{LJ|5Y?z`!U2+GO_vq`B#8^ zm5khy3c-u~hk*PaKs`D+`N@unHa6Zs%`PfH1v7FJD+(Br7^8rEkOLI*5_3~$dU5^+ z^6vmOv=!tRmjE3K1a2uHc?M?Y{Syz^F?~2WiP75H11QADz`?-8zy|b{!b_JATpv#U z|NH+xNCS}10OvFQe=N=-V9(zOGz{c_hRtb=>h|@VK$a&919Kh&KZ87jK7&1jKSMl2 zK0`f2KLdlRs0f=jqmr5}qluX{qmh^(8lRD3em$xM=mug~sH!NoU0gvye7l$;g2kAt zAifPG3MBqb#xBm7htTwI5?p<~0MCa2qQ^f*T*7IZurL&mDd-F@9zh5Q&|=-4i6|$uBC( zC<-EhOffM=F^H%-kPS%=3=GvbdhW*a+k9odl3*eStWfjBQ-IFfx+PhP>mS~^G7F%W&i~l7#szFd=(Jp zWMcKo$StV=ikSfQ1H~9sxRUt~<>V(PGWat%MF15|0b%KHTK{qrD}WX|y#ewSz&L?1 zDlaiNm4U&T2dJ?PglBqj{w~NbE&+N4qz|Hw8OUO0J}{Yw(T?e*%VZx$Yi$dl5Esz> zOhCxUpzzY=|KI=rfnq={FB!mW#{ZASSs0iZ7&jkcRJT_K2M+_ob+>!F^I|X8Z=*c|J=8T@3U7S@ICl|O(VFMb>z~H%=&-D-^03qyBPyhe` delta 928 zcmdne$#|icQKa18&5ePP0SMeKFmQwEmdOiUgePxsVVS5cT+h2HIXAI@fq^jtD3=1l zY#)zoNKY&ViJbxRIiOe|J*P4aD0YQ`!BGT+Ppd4^&PYuJ>T!AjRAUCh{B93CGk}5& z49*Hbz6uC)GO_w(4RCeLKo&Fe{>eOyc1#~mPWEB6w)OxD z@d4e>#J~m&0fmM48nk}P=nKh%4m>?RTkz#&5ss-o< zVpyoED7IZ(K|y@Gm?DD3n5!VZ4I~OA{!PX%&X|YL^luVeeZ2y*1VYKXJdk;LKwB93 zazRWG@$V>Hl*t&v1c@FYUI8N?TpKVNfFWPcoX4yR4SH~JD=R{R+gK42-1zv+d1Cy3 z?(mC=@iVJ{NbKzGo*+3-eo;|IQ4k4aiit6bK}6MoY)EooV5q*)b2pyf<|_j?3n+as zTrKf^0;4DYaWH4}-fZHi!pH%V1*R8<$q`OdL=@Z