From 6a03a2309c39e35b788105f91b1c8fe25fcdaad5 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Sat, 17 Dec 2016 21:30:17 +0100 Subject: [PATCH] Schemas list --- .../Schemas/Repositories/ISchemaEntity.cs | 2 +- .../Schemas/MongoSchemaEntity.cs | 9 + .../Api/Apps/AppClientsController.cs | 1 - .../Api/Schemas/Models/SchemaDetailsDto.cs | 13 ++ .../Api/Schemas/Models/SchemaDto.cs | 17 +- src/Squidex/app/app.module.ts | 2 + .../content/pages/content-page.component.html | 2 +- .../pages/dashboard-page.component.html | 2 +- .../media/pages/media-page.component.html | 2 +- .../app/features/schemas/declarations.ts | 4 +- src/Squidex/app/features/schemas/module.ts | 14 +- .../pages/schema/schema-page.component.html | 16 ++ .../pages/schema/schema-page.component.scss | 7 + .../pages/schema/schema-page.component.ts | 32 ++++ .../schemas/pages/schemas-page.component.html | 46 ------ .../schemas/pages/schemas-page.component.scss | 76 --------- .../schemas/pages/schemas-page.component.ts | 38 ----- .../pages/schemas/schema-form.component.html | 41 +++++ .../pages/schemas/schema-form.component.scss | 2 + .../pages/schemas/schema-form.component.ts | 101 ++++++++++++ .../pages/schemas/schemas-page.component.html | 70 ++++++++ .../pages/schemas/schemas-page.component.scss | 155 ++++++++++++++++++ .../pages/schemas/schemas-page.component.ts | 87 ++++++++++ .../pages/clients/clients-page.component.html | 2 +- .../contributors-page.component.html | 2 +- .../languages/languages-page.component.html | 2 +- .../app/framework/angular/title.component.ts | 24 ++- .../framework/utils/immutable-array.spec.ts | 7 + .../app/framework/utils/immutable-array.ts | 8 + src/Squidex/app/shared/app-component-base.ts | 18 +- .../shared/components/app-form.component.html | 9 +- .../shared/components/app-form.component.scss | 2 + .../shared/components/app-form.component.ts | 6 +- .../shared/components/history.component.html | 2 +- src/Squidex/app/shared/declarations.ts | 1 + .../app/shared/services/apps-store.service.ts | 8 +- .../app/shared/services/schemas.service.ts | 24 ++- .../shell/pages/app/app-area.component.scss | 2 + .../not-found/not-found-page.component.scss | 15 +- src/Squidex/app/theme/_bootstrap.scss | 15 +- src/Squidex/app/theme/_panels.scss | 4 - src/Squidex/app/theme/_vars.scss | 6 + .../app/theme/icomoon/fonts/icomoon.eot | Bin 4256 -> 4348 bytes .../app/theme/icomoon/fonts/icomoon.svg | 1 + .../app/theme/icomoon/fonts/icomoon.ttf | Bin 4092 -> 4184 bytes .../app/theme/icomoon/fonts/icomoon.woff | Bin 4168 -> 4260 bytes src/Squidex/app/theme/icomoon/selection.json | 27 +++ src/Squidex/app/theme/icomoon/style.css | 13 +- 48 files changed, 717 insertions(+), 220 deletions(-) create mode 100644 src/Squidex/app/features/schemas/pages/schema/schema-page.component.html create mode 100644 src/Squidex/app/features/schemas/pages/schema/schema-page.component.scss create mode 100644 src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts delete mode 100644 src/Squidex/app/features/schemas/pages/schemas-page.component.html delete mode 100644 src/Squidex/app/features/schemas/pages/schemas-page.component.scss delete mode 100644 src/Squidex/app/features/schemas/pages/schemas-page.component.ts create mode 100644 src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html create mode 100644 src/Squidex/app/features/schemas/pages/schemas/schema-form.component.scss create mode 100644 src/Squidex/app/features/schemas/pages/schemas/schema-form.component.ts create mode 100644 src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html create mode 100644 src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.scss create mode 100644 src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts diff --git a/src/Squidex.Read/Schemas/Repositories/ISchemaEntity.cs b/src/Squidex.Read/Schemas/Repositories/ISchemaEntity.cs index cbb3443c9..332aba541 100644 --- a/src/Squidex.Read/Schemas/Repositories/ISchemaEntity.cs +++ b/src/Squidex.Read/Schemas/Repositories/ISchemaEntity.cs @@ -7,7 +7,7 @@ // ========================================================================== namespace Squidex.Read.Schemas.Repositories { - public interface ISchemaEntity : IAppRefEntity + public interface ISchemaEntity : IAppRefEntity, ITrackCreatedByEntity, ITrackLastModifiedByEntity { string Name { get; } } diff --git a/src/Squidex.Store.MongoDb/Schemas/MongoSchemaEntity.cs b/src/Squidex.Store.MongoDb/Schemas/MongoSchemaEntity.cs index e5b2712ca..84bc0a5df 100644 --- a/src/Squidex.Store.MongoDb/Schemas/MongoSchemaEntity.cs +++ b/src/Squidex.Store.MongoDb/Schemas/MongoSchemaEntity.cs @@ -11,6 +11,7 @@ using MongoDB.Bson; using MongoDB.Bson.Serialization.Attributes; using Squidex.Core.Schemas; using Squidex.Core.Schemas.Json; +using Squidex.Infrastructure; using Squidex.Read.Schemas.Repositories; using Squidex.Store.MongoDb.Utils; @@ -32,6 +33,14 @@ namespace Squidex.Store.MongoDb.Schemas [BsonElement] public bool IsDeleted { get; set; } + [BsonRequired] + [BsonElement] + public RefToken CreatedBy { get; set; } + + [BsonRequired] + [BsonElement] + public RefToken LastModifiedBy { get; set; } + [BsonRequired] [BsonElement] public BsonDocument Schema { get; set; } diff --git a/src/Squidex/Controllers/Api/Apps/AppClientsController.cs b/src/Squidex/Controllers/Api/Apps/AppClientsController.cs index cef404207..dd9ca45ee 100644 --- a/src/Squidex/Controllers/Api/Apps/AppClientsController.cs +++ b/src/Squidex/Controllers/Api/Apps/AppClientsController.cs @@ -54,7 +54,6 @@ namespace Squidex.Controllers.Api.Apps [ProducesResponseType(typeof(ClientDto[]), 200)] public async Task GetClients(string app) { - return StatusCode(401); var entity = await appProvider.FindAppByNameAsync(app); if (entity == null) diff --git a/src/Squidex/Controllers/Api/Schemas/Models/SchemaDetailsDto.cs b/src/Squidex/Controllers/Api/Schemas/Models/SchemaDetailsDto.cs index 40579f11e..c7d641469 100644 --- a/src/Squidex/Controllers/Api/Schemas/Models/SchemaDetailsDto.cs +++ b/src/Squidex/Controllers/Api/Schemas/Models/SchemaDetailsDto.cs @@ -6,6 +6,7 @@ // All rights reserved. // ========================================================================== +using Squidex.Infrastructure; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; @@ -44,6 +45,18 @@ namespace Squidex.Controllers.Api.Schemas.Models [StringLength(1000)] public string Hints { get; set; } + /// + /// The user that has created the schema. + /// + [Required] + public RefToken CreatedBy { get; set; } + + /// + /// The user that has updated the schema. + /// + [Required] + public RefToken LastModifiedBy { get; set; } + /// /// The date and time when the schema has been creaed. /// diff --git a/src/Squidex/Controllers/Api/Schemas/Models/SchemaDto.cs b/src/Squidex/Controllers/Api/Schemas/Models/SchemaDto.cs index 3395a846e..6f45c75c4 100644 --- a/src/Squidex/Controllers/Api/Schemas/Models/SchemaDto.cs +++ b/src/Squidex/Controllers/Api/Schemas/Models/SchemaDto.cs @@ -6,6 +6,7 @@ // All rights reserved. // ========================================================================== +using Squidex.Infrastructure; using System; using System.ComponentModel.DataAnnotations; @@ -24,12 +25,24 @@ namespace Squidex.Controllers.Api.Schemas.Models [Required] [RegularExpression("^[a-z0-9]+(\\-[a-z0-9]+)*$")] public string Name { get; set; } - + + /// + /// The user that has created the schema. + /// + [Required] + public RefToken CreatedBy { get; set; } + + /// + /// The user that has updated the schema. + /// + [Required] + public RefToken LastModifiedBy { get; set; } + /// /// The date and time when the schema has been creaed. /// public DateTime Created { get; set; } - + /// /// The date and time when the schema has been modified last. /// diff --git a/src/Squidex/app/app.module.ts b/src/Squidex/app/app.module.ts index 522ecfa96..98d609574 100644 --- a/src/Squidex/app/app.module.ts +++ b/src/Squidex/app/app.module.ts @@ -29,6 +29,7 @@ import { MustBeNotAuthenticatedGuard, NotificationService, PanelService, + SchemasService, SqxFrameworkModule, SqxSharedModule, TitlesConfig, @@ -84,6 +85,7 @@ export function configCurrency() { MustBeNotAuthenticatedGuard, NotificationService, PanelService, + SchemasService, TitleService, UsersProviderService, UsersService, diff --git a/src/Squidex/app/features/content/pages/content-page.component.html b/src/Squidex/app/features/content/pages/content-page.component.html index 8c56f98e4..56e978053 100644 --- a/src/Squidex/app/features/content/pages/content-page.component.html +++ b/src/Squidex/app/features/content/pages/content-page.component.html @@ -1,4 +1,4 @@ - +
diff --git a/src/Squidex/app/features/dashboard/pages/dashboard-page.component.html b/src/Squidex/app/features/dashboard/pages/dashboard-page.component.html index cfdabe36a..0f3ddb664 100644 --- a/src/Squidex/app/features/dashboard/pages/dashboard-page.component.html +++ b/src/Squidex/app/features/dashboard/pages/dashboard-page.component.html @@ -1,4 +1,4 @@ - +
diff --git a/src/Squidex/app/features/media/pages/media-page.component.html b/src/Squidex/app/features/media/pages/media-page.component.html index 251c6c312..2d0e936cf 100644 --- a/src/Squidex/app/features/media/pages/media-page.component.html +++ b/src/Squidex/app/features/media/pages/media-page.component.html @@ -1,4 +1,4 @@ - +
diff --git a/src/Squidex/app/features/schemas/declarations.ts b/src/Squidex/app/features/schemas/declarations.ts index 403b79b70..4c8160db6 100644 --- a/src/Squidex/app/features/schemas/declarations.ts +++ b/src/Squidex/app/features/schemas/declarations.ts @@ -5,4 +5,6 @@ * Copyright (c) Sebastian Stehle. All rights reserved */ -export * from './pages/schemas-page.component'; \ No newline at end of file +export * from './pages/schema/schema-page.component'; +export * from './pages/schemas/schema-form.component'; +export * from './pages/schemas/schemas-page.component'; \ 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 f80aac992..04bd0c0fa 100644 --- a/src/Squidex/app/features/schemas/module.ts +++ b/src/Squidex/app/features/schemas/module.ts @@ -11,13 +11,23 @@ import { RouterModule, Routes } from '@angular/router'; import { SqxFrameworkModule, SqxSharedModule } from 'shared'; import { + SchemaFormComponent, + SchemaPageComponent, SchemasPageComponent } from './declarations'; const routes: Routes = [ { path: '', - component: SchemasPageComponent + component: SchemasPageComponent, + children: [ + { + path: '' + }, + { + path: ':schemaName', + component: SchemaPageComponent + }] } ]; @@ -28,6 +38,8 @@ const routes: Routes = [ RouterModule.forChild(routes) ], declarations: [ + SchemaFormComponent, + SchemaPageComponent, SchemasPageComponent ] }) diff --git a/src/Squidex/app/features/schemas/pages/schema/schema-page.component.html b/src/Squidex/app/features/schemas/pages/schema/schema-page.component.html new file mode 100644 index 000000000..45e70633f --- /dev/null +++ b/src/Squidex/app/features/schemas/pages/schema/schema-page.component.html @@ -0,0 +1,16 @@ + + +
+
+
+

Schema

+ + + + +
+
+ +
+
+
\ No newline at end of file diff --git a/src/Squidex/app/features/schemas/pages/schema/schema-page.component.scss b/src/Squidex/app/features/schemas/pages/schema/schema-page.component.scss new file mode 100644 index 000000000..933e7d3d7 --- /dev/null +++ b/src/Squidex/app/features/schemas/pages/schema/schema-page.component.scss @@ -0,0 +1,7 @@ +@import '_vars'; +@import '_mixins'; + +.panel { + min-width: 700px; + max-width: 700px; +} \ No newline at end of file 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 new file mode 100644 index 000000000..eb6bd8a51 --- /dev/null +++ b/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts @@ -0,0 +1,32 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Sebastian Stehle. All rights reserved + */ + +import { Component, OnInit } from '@angular/core'; + +import { + AppComponentBase, + AppsStoreService, + NotificationService, + SchemasService, + UsersProviderService +} from 'shared'; + +const FALLBACK_NAME = 'my-schema'; + +@Component({ + selector: 'sqx-schema-page', + styleUrls: ['./schema-page.component.scss'], + templateUrl: './schema-page.component.html' +}) +export class SchemaPageComponent extends AppComponentBase { + constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService, + private readonly schemasService: SchemasService + ) { + super(apps, notifications, users); + } +} + diff --git a/src/Squidex/app/features/schemas/pages/schemas-page.component.html b/src/Squidex/app/features/schemas/pages/schemas-page.component.html deleted file mode 100644 index bf77e8bbe..000000000 --- a/src/Squidex/app/features/schemas/pages/schemas-page.component.html +++ /dev/null @@ -1,46 +0,0 @@ - - -
-
-
-

Schemas

- - - - -
-
-
- -
-
- - - -
-
-
- -
- asdasd -
-
- - \ No newline at end of file diff --git a/src/Squidex/app/features/schemas/pages/schemas-page.component.scss b/src/Squidex/app/features/schemas/pages/schemas-page.component.scss deleted file mode 100644 index fb77971f2..000000000 --- a/src/Squidex/app/features/schemas/pages/schemas-page.component.scss +++ /dev/null @@ -1,76 +0,0 @@ -@import '_vars'; -@import '_mixins'; - -.panel { - min-width: 450px; - max-width: 450px; -} - -.panel-header { - min-height: 150px; - max-height: 150px; -} - -.subheader { - margin-top: 1rem; - margin-right: -40px; -} - -.search-form { - & { - position: relative; - margin-top: .5rem; - } - - .form-control { - padding-left: 50px; - } - - .icon-search { - @include absolute(8px, auto, auto, 12px); - color: $color-accent-dark; - font-size: 1.3rem; - font-weight: lighter; - } -} - -.btn-new { - & { - padding-left: 0; - background: transparent; - border: 0; - color: darken($color-accent-dark, 15%); - font-size: 1.05rem; - } - - &:hover { - color: darken($color-accent-dark, 5%); - outline: 0; - } - - &:focus { - color: $color-accent-dark; - outline: 0; - } - - .icon-plus { - & { - @include circle(24px); - background: transparent; - border: 2px solid darken($color-accent-dark, 15%); - font-size: .8rem; - font-weight: bold; - vertical-align: baseline; - line-height: 20px; - display: inline-block; - } - - &:hover { - border-color: darken($color-accent-dark, 5%); - } - - &:focus { - border-color: $color-accent-dark; - } - } -} \ No newline at end of file diff --git a/src/Squidex/app/features/schemas/pages/schemas-page.component.ts b/src/Squidex/app/features/schemas/pages/schemas-page.component.ts deleted file mode 100644 index 4150517cb..000000000 --- a/src/Squidex/app/features/schemas/pages/schemas-page.component.ts +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Squidex Headless CMS - * - * @license - * Copyright (c) Sebastian Stehle. All rights reserved - */ - -import { Component } from '@angular/core'; - -import { - AppComponentBase, - AppsStoreService, - fadeAnimation, - ModalView, - NotificationService, - UsersProviderService - } from 'shared'; - -@Component({ - selector: 'sqx-schemas-page', - styleUrls: ['./schemas-page.component.scss'], - templateUrl: './schemas-page.component.html', - animations: [ - fadeAnimation - ] -}) -export class SchemasPageComponent extends AppComponentBase { - public modalDialog = new ModalView(); - - constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService) { - super(apps, notifications, users); - } - - public createSchema() { - this.modalDialog.show(); - } -} - diff --git a/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html b/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html new file mode 100644 index 000000000..ef42272fb --- /dev/null +++ b/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html @@ -0,0 +1,41 @@ +
+
+
+ {{creationError}} +
+
+ +
+ + +
+
+ + Name is required. + + + Name can not have more than 40 characters. + + + Name can contain lower case letters (a-z), numbers and dashes only (not at the end). + +
+
+ + + + +

+ The schema name becomes part of the api url,
e.g https://{{appName}}.squidex.io/{{schemaName}}/. +

+

+ It must contain lower case letters (a-z), numbers and dashes only, and cannot be longer than 40 characters. The name cannot be changed later. +

+
+
+ +
+ + +
+
\ No newline at end of file diff --git a/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.scss b/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.scss new file mode 100644 index 000000000..fbb752506 --- /dev/null +++ b/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.scss @@ -0,0 +1,2 @@ +@import '_vars'; +@import '_mixins'; \ No newline at end of file diff --git a/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.ts b/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.ts new file mode 100644 index 000000000..e93f4b6c2 --- /dev/null +++ b/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.ts @@ -0,0 +1,101 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Sebastian Stehle. All rights reserved + */ + +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; + +import { + AuthService, + CreateSchemaDto, + DateTime, + fadeAnimation, + SchemaDto, + SchemasService +} from 'shared'; + +const FALLBACK_NAME = 'my-schema'; + +@Component({ + selector: 'sqx-schema-form', + styleUrls: ['./schema-form.component.scss'], + templateUrl: './schema-form.component.html', + animations: [ + fadeAnimation + ] +}) +export class SchemaFormComponent implements OnInit { + @Input() + public showClose = false; + + @Input() + public appName: string; + + @Output() + public created = new EventEmitter(); + + @Output() + public cancelled = new EventEmitter(); + + public creationError = ''; + public createForm: FormGroup = + this.formBuilder.group({ + name: ['', + [ + Validators.required, + Validators.maxLength(40), + Validators.pattern('[a-z0-9]+(\-[a-z0-9]+)*') + ]] + }); + + public schemaName = FALLBACK_NAME; + + constructor( + private readonly schemas: SchemasService, + private readonly formBuilder: FormBuilder, + private readonly authService: AuthService + ) { + } + + public ngOnInit() { + this.createForm.controls['name'].valueChanges.subscribe(value => { + this.schemaName = value || FALLBACK_NAME; + }); + } + + public createSchema() { + this.createForm.markAsTouched(); + + if (this.createForm.valid) { + this.createForm.disable(); + + const name = this.createForm.controls['name'].value; + const dto = new CreateSchemaDto(name); + const now = DateTime.now(); + + const me = `subject:${this.authService.user.id}`; + + this.schemas.postSchema(this.appName, dto) + .subscribe(dto => { + this.createForm.reset(); + this.created.emit(new SchemaDto(dto.id, name, now, now, me, me)); + }, error => { + this.reset(); + this.creationError = error.displayMessage; + }); + } + } + + private reset() { + this.createForm.enable(); + this.creationError = ''; + } + + public cancel() { + this.reset(); + this.cancelled.emit(); + } +} \ No newline at end of file diff --git a/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html b/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html new file mode 100644 index 000000000..fb7bf2e31 --- /dev/null +++ b/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html @@ -0,0 +1,70 @@ + + +
+
+
+

Schemas

+ + + + +
+
+
+ +
+
+ + + +
+
+
+ +
+
+
+
+
+
+ {{schema.name}} +
+
+ + {{userName(schema.lastModifiedBy, true) | async}} + +
+
+ {{schema.lastModified.toLocal() | fromNow}} +
+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.scss b/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.scss new file mode 100644 index 000000000..63676d218 --- /dev/null +++ b/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.scss @@ -0,0 +1,155 @@ +@import '_vars'; +@import '_mixins'; + +.panel { + min-width: 450px; + max-width: 450px; +} + +.panel-header { + min-height: 170px; + max-height: 170px; +} + +.subheader { + margin-top: 1rem; + margin-right: -40px; +} + +.search-form { + & { + position: relative; + margin-top: .5rem; + } + + .form-control { + padding-left: 50px; + } + + .icon-search { + @include absolute(8px, auto, auto, 12px); + color: $color-dark-foreground-selected; + font-size: 1.3rem; + font-weight: lighter; + } +} + +.btn-new { + & { + padding-left: 0; + background: transparent; + border: 0; + color: $color-dark-foreground-selected; + font-size: 1.05rem; + } + + &:hover { + color: lighten($color-dark-foreground-selected, 5%); + outline: 0; + } + + &:focus { + color: lighten($color-dark-foreground-selected, 10%); + outline: 0; + } + + .icon-plus { + & { + @include circle(24px); + display: inline-block; + background: transparent; + border: 2px solid $color-dark-foreground-selected; + margin-right: .5rem; + font-size: .8rem; + font-weight: bold; + vertical-align: baseline; + line-height: 20px; + } + + &:hover { + border-color: lighten($color-dark-foreground-selected, 5%); + } + + &:focus { + border-color: lighten($color-dark-foreground-selected, 10%); + } + } +} + +.schemas { + margin-left: -$panel-padding; + margin-right: -$panel-padding; +} + +.schema { + & { + padding-left: $panel-padding; + padding-right: $panel-padding; + } + + &:hover, + &.active { + & { + background: $color-dark-background-selected; + } + + & + .schema > .schema-inner { + border-color: transparent; + } + + .schema-inner { + border-color: transparent; + } + } + + &-inner { + padding-top: 1rem; + padding-bottom: 1rem; + border-top: 1px solid darken($color-dark-foreground, 10%); + } + + &-col-left { + @include truncate; + } + + &-col-right { + text-align: right; + } + + &-modified { + font-size: .8rem; + } + + &-name { + @include truncate; + color: $color-dark-foreground-selected; + font-size: 1rem; + font-weight: normal; + } + + &-user { + & { + @include border-radius(1px); + display: inline-block; + background: $color-dark-background-accent; + padding: .1rem .3rem; + font-size: .8rem; + font-weight: normal; + margin-left: 10px; + max-width: 100%; + vertical-align: baseline; + } + + &-text { + @include truncate; + display: inline-block; + vertical-align: bottom; + } + } + + &:first-child { + .schema-inner { + border: 0; + } + } +} \ No newline at end of file diff --git a/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts b/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts new file mode 100644 index 000000000..2c9faead4 --- /dev/null +++ b/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts @@ -0,0 +1,87 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Sebastian Stehle. All rights reserved + */ + +import { Component, OnInit } from '@angular/core'; +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; + +import { + AppComponentBase, + AppsStoreService, + fadeAnimation, + ImmutableArray, + ModalView, + NotificationService, + SchemaDto, + SchemasService, + UsersProviderService +} from 'shared'; + +const FALLBACK_NAME = 'my-schema'; + +@Component({ + selector: 'sqx-schemas-page', + styleUrls: ['./schemas-page.component.scss'], + templateUrl: './schemas-page.component.html', + animations: [ + fadeAnimation + ] +}) +export class SchemasPageComponent extends AppComponentBase { + public modalDialog = new ModalView(); + + public schemasFilter: string; + public schemas = ImmutableArray.empty(); + + public get filteredSchemas() { + let result = this.schemas; + + if (this.schemasFilter && this.schemasFilter.length > 0) { + result = result.filter(t => t.name.indexOf(this.schemasFilter) >= 0); + } + + result = + result.sort((a, b) => { + if (a.name < b.name) { + return -1; + } + if (a.name > b.name) { + return 1; + } + return 0; + }); + + return result; + } + + constructor(apps: AppsStoreService, notifications: NotificationService, users: UsersProviderService, + private readonly formBuilder: FormBuilder, + private readonly schemasService: SchemasService + ) { + super(apps, notifications, users); + } + + public ngOnInit() { + this.load(); + } + + public load() { + this.appName() + .switchMap(app => this.schemasService.getSchemas(app).retry(2)) + .subscribe(dtos => { + this.schemas = ImmutableArray.of(dtos); + }, error => { + this.notifyError(error); + }); + } + + public onSchemaCreationCompleted(dto: SchemaDto) { + this.schemas = this.schemas.push(dto); + + this.modalDialog.hide(); + } +} + diff --git a/src/Squidex/app/features/settings/pages/clients/clients-page.component.html b/src/Squidex/app/features/settings/pages/clients/clients-page.component.html index fea45164a..618adfdcc 100644 --- a/src/Squidex/app/features/settings/pages/clients/clients-page.component.html +++ b/src/Squidex/app/features/settings/pages/clients/clients-page.component.html @@ -1,4 +1,4 @@ - +
diff --git a/src/Squidex/app/features/settings/pages/contributors/contributors-page.component.html b/src/Squidex/app/features/settings/pages/contributors/contributors-page.component.html index 9ae4ef147..8245d3dd0 100644 --- a/src/Squidex/app/features/settings/pages/contributors/contributors-page.component.html +++ b/src/Squidex/app/features/settings/pages/contributors/contributors-page.component.html @@ -1,4 +1,4 @@ - +
diff --git a/src/Squidex/app/features/settings/pages/languages/languages-page.component.html b/src/Squidex/app/features/settings/pages/languages/languages-page.component.html index c3f1c5dc2..4e5c3f62f 100644 --- a/src/Squidex/app/features/settings/pages/languages/languages-page.component.html +++ b/src/Squidex/app/features/settings/pages/languages/languages-page.component.html @@ -1,4 +1,4 @@ - +
diff --git a/src/Squidex/app/framework/angular/title.component.ts b/src/Squidex/app/framework/angular/title.component.ts index f9380260c..4ae64fb6a 100644 --- a/src/Squidex/app/framework/angular/title.component.ts +++ b/src/Squidex/app/framework/angular/title.component.ts @@ -18,10 +18,16 @@ export class TitleComponent implements OnChanges { public message: any; @Input() - public parameter: string; + public parameter1: string; @Input() - public value: any; + public parameter2: string; + + @Input() + public value1: any; + + @Input() + public value2: any; constructor( private readonly titleService: TitleService @@ -31,12 +37,20 @@ export class TitleComponent implements OnChanges { public ngOnChanges() { const parameters = {}; - if (this.parameter) { - if (!this.value) { + if (this.parameter1) { + if (!this.value1) { + return; + } + + parameters[this.parameter1] = this.value1; + } + + if (this.parameter2) { + if (!this.value2) { return; } - parameters[this.parameter] = this.value; + parameters[this.parameter2] = this.value2; } this.titleService.setTitle(this.message, parameters); diff --git a/src/Squidex/app/framework/utils/immutable-array.spec.ts b/src/Squidex/app/framework/utils/immutable-array.spec.ts index 84a6040b4..7723951ae 100644 --- a/src/Squidex/app/framework/utils/immutable-array.spec.ts +++ b/src/Squidex/app/framework/utils/immutable-array.spec.ts @@ -131,4 +131,11 @@ describe('ImmutableArray', () => { expect(result).toBeUndefined(); }); + + it('should sort items', () => { + const array_1 = ImmutableArray.of([3, 1, 4, 2]); + const array_2 = array_1.sort((x, y) => x - y); + + expect(array_2.values).toEqual([1, 2, 3, 4]); + }); }); \ No newline at end of file diff --git a/src/Squidex/app/framework/utils/immutable-array.ts b/src/Squidex/app/framework/utils/immutable-array.ts index 383c2c28b..86923a1bb 100644 --- a/src/Squidex/app/framework/utils/immutable-array.ts +++ b/src/Squidex/app/framework/utils/immutable-array.ts @@ -51,6 +51,14 @@ export class ImmutableArray implements Iterable { return this.items.find(predicate); } + public sort(compareFn?: (a: T, b: T) => number): ImmutableArray { + const clone = [...this.items]; + + clone.sort(compareFn); + + return new ImmutableArray(clone); + } + public push(...items: T[]): ImmutableArray { if (!items || items.length === 0) { return this; diff --git a/src/Squidex/app/shared/app-component-base.ts b/src/Squidex/app/shared/app-component-base.ts index ca3261904..6f385f990 100644 --- a/src/Squidex/app/shared/app-component-base.ts +++ b/src/Squidex/app/shared/app-component-base.ts @@ -31,14 +31,24 @@ export abstract class AppComponentBase { return this.usersProvider.getUser(userId).map(u => u.email); } - public userName(userId: string): Observable { - return this.usersProvider.getUser(userId).map(u => u.displayName); - } - public userPicture(userId: string): Observable { return this.usersProvider.getUser(userId).map(u => u.pictureUrl); } + public userName(userId: string, isRef: boolean = false): Observable { + if (isRef) { + const parts = userId.split(':'); + + if (parts[0] === 'subject') { + return this.usersProvider.getUser(parts[1]).map(u => u.displayName); + } else { + return Observable.of(parts[1]); + } + } else { + return this.usersProvider.getUser(userId).map(u => u.displayName); + } + } + public notifyError(error: string | ErrorDto) { if (error instanceof ErrorDto) { this.notifications.notify(Notification.error(error.displayMessage)); diff --git a/src/Squidex/app/shared/components/app-form.component.html b/src/Squidex/app/shared/components/app-form.component.html index 4b5c44cdf..f4baa2aa3 100644 --- a/src/Squidex/app/shared/components/app-form.component.html +++ b/src/Squidex/app/shared/components/app-form.component.html @@ -25,9 +25,12 @@ - The app name becomes part of the api url, e.g, https://{{appName}}.squidex.io/.
- - It must contain lower case letters (a-z), numbers and dashes only, and cannot be longer than 40 characters. The name cannot be changed later. +

+ The app name becomes part of the api url,
e.g https://{{appName}}.squidex.io/. +

+

+ It must contain lower case letters (a-z), numbers and dashes only, and cannot be longer than 40 characters. The name cannot be changed later. +

diff --git a/src/Squidex/app/shared/components/app-form.component.scss b/src/Squidex/app/shared/components/app-form.component.scss index e69de29bb..fbb752506 100644 --- a/src/Squidex/app/shared/components/app-form.component.scss +++ b/src/Squidex/app/shared/components/app-form.component.scss @@ -0,0 +1,2 @@ +@import '_vars'; +@import '_mixins'; \ No newline at end of file diff --git a/src/Squidex/app/shared/components/app-form.component.ts b/src/Squidex/app/shared/components/app-form.component.ts index ce73e9f47..404278449 100644 --- a/src/Squidex/app/shared/components/app-form.component.ts +++ b/src/Squidex/app/shared/components/app-form.component.ts @@ -54,7 +54,7 @@ export class AppFormComponent implements OnInit { public ngOnInit() { this.createForm.controls['name'].valueChanges.subscribe(value => { - this.appName = value; + this.appName = value || FALLBACK_NAME; }); } @@ -67,9 +67,9 @@ export class AppFormComponent implements OnInit { const dto = new CreateAppDto(this.createForm.controls['name'].value); this.appsStore.createApp(dto) - .subscribe(app => { + .subscribe(dto => { this.createForm.reset(); - this.created.emit(app); + this.created.emit(dto); }, error => { this.reset(); this.creationError = error.displayMessage; diff --git a/src/Squidex/app/shared/components/history.component.html b/src/Squidex/app/shared/components/history.component.html index 307f0c837..39cedd7b4 100644 --- a/src/Squidex/app/shared/components/history.component.html +++ b/src/Squidex/app/shared/components/history.component.html @@ -1,4 +1,4 @@ - +
diff --git a/src/Squidex/app/shared/declarations.ts b/src/Squidex/app/shared/declarations.ts index 28211a8f7..72122194c 100644 --- a/src/Squidex/app/shared/declarations.ts +++ b/src/Squidex/app/shared/declarations.ts @@ -21,6 +21,7 @@ export * from './services/apps.service'; export * from './services/auth.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'; diff --git a/src/Squidex/app/shared/services/apps-store.service.ts b/src/Squidex/app/shared/services/apps-store.service.ts index d89cdfcce..0ad71728f 100644 --- a/src/Squidex/app/shared/services/apps-store.service.ts +++ b/src/Squidex/app/shared/services/apps-store.service.ts @@ -45,9 +45,9 @@ export class AppsStoreService { constructor( private readonly auth: AuthService, - private readonly appService: AppsService + private readonly appsService: AppsService ) { - if (!auth || !appService) { + if (!auth || !appsService) { return; } @@ -74,7 +74,7 @@ export class AppsStoreService { } private load() { - this.appService.getApps().subscribe(apps => { + this.appsService.getApps().subscribe(apps => { this.apps$.next(apps); }); } @@ -86,7 +86,7 @@ export class AppsStoreService { } public createApp(dto: CreateAppDto, now?: DateTime): Observable { - return this.appService.postApp(dto) + return this.appsService.postApp(dto) .map(created => { now = now || DateTime.now(); diff --git a/src/Squidex/app/shared/services/schemas.service.ts b/src/Squidex/app/shared/services/schemas.service.ts index 3dc3e9bbb..2f55d361c 100644 --- a/src/Squidex/app/shared/services/schemas.service.ts +++ b/src/Squidex/app/shared/services/schemas.service.ts @@ -11,6 +11,7 @@ import { Observable } from 'rxjs'; import { ApiUrlConfig, DateTime, + EntityCreatedDto, handleError } from 'framework'; @@ -21,7 +22,9 @@ export class SchemaDto { public readonly id: string, public readonly name: string, public readonly created: DateTime, - public readonly lastModified: DateTime + public readonly lastModified: DateTime, + public readonly createdBy: string, + public readonly lastModifiedBy: string ) { } } @@ -32,6 +35,8 @@ export class SchemaDetailsDto { public readonly name: string, public readonly created: DateTime, public readonly lastModified: DateTime, + public readonly createdBy: string, + public readonly lastModifiedBy: string, public readonly fields: FieldDto[] ) { } @@ -128,7 +133,9 @@ export class SchemasService { item.id, item.name, DateTime.parseISO_UTC(item.created), - DateTime.parseISO_UTC(item.lastModified)); + DateTime.parseISO_UTC(item.lastModified), + item.createdBy, + item.lastModifiedBy); }); }) .catch(response => handleError('Failed to load schemas. Please reload.', response)); @@ -182,8 +189,21 @@ export class SchemasService { response.name, DateTime.parseISO_UTC(response.created), DateTime.parseISO_UTC(response.lastModified), + response.createdBy, + response.lastModifiedBy, fields); }) .catch(response => handleError('Failed to load schema. Please reload.', response)); } + + public postSchema(appName: string, dto: CreateSchemaDto): Observable { + const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/`); + + return this.authService.authPost(url, dto) + .map(response => response.json()) + .map(response => { + return new EntityCreatedDto(response.id); + }) + .catch(response => handleError('Failed to create schema. Please reload.', response)); + } } \ No newline at end of file diff --git a/src/Squidex/app/shell/pages/app/app-area.component.scss b/src/Squidex/app/shell/pages/app/app-area.component.scss index e69de29bb..fbb752506 100644 --- a/src/Squidex/app/shell/pages/app/app-area.component.scss +++ b/src/Squidex/app/shell/pages/app/app-area.component.scss @@ -0,0 +1,2 @@ +@import '_vars'; +@import '_mixins'; \ No newline at end of file diff --git a/src/Squidex/app/shell/pages/not-found/not-found-page.component.scss b/src/Squidex/app/shell/pages/not-found/not-found-page.component.scss index 0f20f6600..a398fd44d 100644 --- a/src/Squidex/app/shell/pages/not-found/not-found-page.component.scss +++ b/src/Squidex/app/shell/pages/not-found/not-found-page.component.scss @@ -9,19 +9,8 @@ .logo { height: 60px; - display: display-block; } -.login-button { - margin: 40px 0; -} - -.login-hint { - font-style: italic; -} - -.proudly-made { - margin-top: 100px; - font-size: 11px; - font-style: italic; +h1 { + margin-top: 40px; } \ No newline at end of file diff --git a/src/Squidex/app/theme/_bootstrap.scss b/src/Squidex/app/theme/_bootstrap.scss index c49c4f33b..d1a6ddeb3 100644 --- a/src/Squidex/app/theme/_bootstrap.scss +++ b/src/Squidex/app/theme/_bootstrap.scss @@ -141,7 +141,14 @@ } .form-hint { - font-size: .8rem; + & { + font-size: .8rem; + } + + p { + margin-top: .4rem; + margin-bottom: 0; + } } .form-error { @@ -164,13 +171,13 @@ & { @include transition(background-color .3s ease); @include placeholder-color(darken($color-accent-dark, 30%)); - background: lighten($color-dark-background, 5%); + background: $color-dark-background-accent; border: 0; color: darken($color-accent-dark, 20%); } &:focus { - background: lighten($color-dark-background, 7%); + background: lighten($color-dark-background-accent, 2%); border: 0; color: $color-accent-dark; } @@ -259,7 +266,7 @@ @include border-radius(0); background: $color-dark-background-selected; border: 0; - color: lighten($color-dark-foreground, 30%); + color: $color-dark-foreground-selected; } &:hover { diff --git a/src/Squidex/app/theme/_panels.scss b/src/Squidex/app/theme/_panels.scss index 44d197c44..f9d839bb3 100644 --- a/src/Squidex/app/theme/_panels.scss +++ b/src/Squidex/app/theme/_panels.scss @@ -1,10 +1,6 @@ @import '_mixins'; @import '_vars'; -$panel-padding: 20px; -$panel-header: 70px; -$panel-sidebar: 61px; - .panel-container { @include absolute($size-navbar-height, 0, 0, $size-sidebar-width); overflow-x: auto; diff --git a/src/Squidex/app/theme/_vars.scss b/src/Squidex/app/theme/_vars.scss index 5fb4df0cb..87ca497ce 100644 --- a/src/Squidex/app/theme/_vars.scss +++ b/src/Squidex/app/theme/_vars.scss @@ -16,7 +16,9 @@ $color-theme-error: #f00; $color-theme-error-dark: darken($color-theme-error, 5%); $color-dark-foreground: #6a7681; +$color-dark-foreground-selected: lighten($color-dark-foreground, 40%); $color-dark-background: #2e3842; +$color-dark-background-accent: lighten($color-dark-background, 5%); $color-dark-background-selected: #273039; $color-modal-header-background: #2e3842; @@ -33,3 +35,7 @@ $color-card-footer: #fff; $size-navbar-height: 52px; $size-sidebar-width: 100px; + +$panel-padding: 20px; +$panel-header: 70px; +$panel-sidebar: 61px; diff --git a/src/Squidex/app/theme/icomoon/fonts/icomoon.eot b/src/Squidex/app/theme/icomoon/fonts/icomoon.eot index f483e66981c3888a64b69c70573f7d6895aaa70f..203909085b44fcdd37fc88c1c9235a276eb16322 100644 GIT binary patch delta 468 zcmZ3W_(zfLj{pNhgup~LGZyhW+p7~D%Ik|67#Q{daYAx#VnL;*0UHAYqYRLLBt5aX z07wHhFg^g%9O*fgY0?eiK|XFofjfCnt*UlQshKQ-FM(+{B6k23b9Z^FV0C{RaJ_`dI10#dN)3eY2PX55C#ss9q zm{b%bIK%_RImB7T{)#;pdnoom?3~zPv0AYlv2@X$K=T+UCo;)QE@0xGT+3A9&&05b zA(T0cnUR5qK>}#2ksOmOqoS#a9h13|nyHDIv5}al2pbcFc4@Zv-w^NYQf;@CWOpED z3>DRO&$K1ri#p1`Zht-I+f%O^C O&kPKroA2=*Vgvx%u5Pse delta 385 zcmeyPxImF@fdB);AO4AKW-Q0I@?W3mP+niez`(Evh!c`?6AO|!<2e}^7-c|W>50V! zKpLol@dl9QNYANE+q|;*5s?3Yfq}awBQ-IFXUZp61_s_3puAZIP=LdUB^1b?0_3Y? zsL_((FJ9 z5Pz~1lZu>pfH=E2i`XBrXJQY;?u(rjJ0w;kmMxYhx&vq^ + diff --git a/src/Squidex/app/theme/icomoon/fonts/icomoon.ttf b/src/Squidex/app/theme/icomoon/fonts/icomoon.ttf index 2fe26f2e5a0871b36e2fc62a7224618fca70aefc..1fc8e111ff0b8808547a83a3c8590e36fe540784 100644 GIT binary patch delta 469 zcmew(e?y_3fsuiMft#U$ftkU;KUm+0Ux>XJD6$8L6OwZi3o11Y*cccXWq|x6>50V! zKw1FEe*mO8(sL@)q#OPf0r@Y0hMdVrO-$jLx_c!91Me1~yjccNfWwI;49I^2sK_J^TFcWFJN~w&#CeY<U3BN<4UC$TuQOKpGcl}U z2xSgqW@O-DkO10hB*!GnsAy_p$7HUgW@=()Y$PTs!p6j)U7GFvH^e);RNE~j*&T=( zLq)Y6w8Fx)9JEElfFfyWZtlqpn**3cxMi-|)``dS+k9ojr#UN!j)ucvAP!!m|PjDMKKm|U1vF-tJ}F*mVrvG}pX3D6$8L6OwZi3z9hFIT;ujWk6!-iNys# zS^&tu0i-$7b1Ks|uWWt<dhtFKViR?Q>x+|f6AKs^7&CwpM?knz(||2K zu^1@Eqyyx0K(Ro2PGuTU%!Gk~`wR$6H~cHgNKH&(VBiBPVK4(>o~gT6W&j0&VirKY z3J7yJv4myhmQ(=6Vu1V*5cWF2Qk0XQ3{>aGw*e>svPegKpR`eKVg=A*zB@p^0vO9O zoX<Con8yc*OXJNsP&bX%({svmbL43m1zY%N|xE c)&|yR;CL2gILp8e*3SftW#P?>`Ia&Q0M5;A`Tzg` delta 445 zcmZ3YctSy}+~3WOfsp|S1UwkHK{Wp#2FA&POkxvtgzJlva}x^~7#K5v;(I_ii8G!v zJ+T-l#-sw|b3m~`dQN2;P)vt`fx8BTH?M4dl#!a4!oa}00;t9egn6cXa?Jn=0>vHx z`5^r&98N5u8M!4D3=DiMKz;xSyB%OYk&~YcROiPR0u;~!Vaao1S-FW7K#TbrfP4io zmSH%bmzbLh6k7mvWE%+2^y2(okY8K^^p6Hy9W#){%=~sT4`a0;P$kf61qK!%WMoiy zdUosI diff --git a/src/Squidex/app/theme/icomoon/selection.json b/src/Squidex/app/theme/icomoon/selection.json index e707ca840..9ad5865b4 100644 --- a/src/Squidex/app/theme/icomoon/selection.json +++ b/src/Squidex/app/theme/icomoon/selection.json @@ -55,6 +55,33 @@ "setId": 2, "iconIdx": 157 }, + { + "icon": { + "paths": [ + "M512 598c114 0 342 56 342 170v86h-684v-86c0-114 228-170 342-170zM512 512c-94 0-170-76-170-170s76-172 170-172 170 78 170 172-76 170-170 170z" + ], + "attrs": [], + "isMulticolor": false, + "isMulticolor2": false, + "tags": [ + "person" + ], + "defaultCode": 59389, + "grid": 24 + }, + "attrs": [], + "properties": { + "order": 19, + "ligatures": "person", + "id": 557, + "prevSize": 24, + "code": 59389, + "name": "person" + }, + "setIdx": 0, + "setId": 2, + "iconIdx": 557 + }, { "icon": { "paths": [ diff --git a/src/Squidex/app/theme/icomoon/style.css b/src/Squidex/app/theme/icomoon/style.css index 77fc67564..13567c660 100644 --- a/src/Squidex/app/theme/icomoon/style.css +++ b/src/Squidex/app/theme/icomoon/style.css @@ -1,10 +1,10 @@ @font-face { font-family: 'icomoon'; - src: url('fonts/icomoon.eot?66xt8i'); - src: url('fonts/icomoon.eot?66xt8i#iefix') format('embedded-opentype'), - url('fonts/icomoon.ttf?66xt8i') format('truetype'), - url('fonts/icomoon.woff?66xt8i') format('woff'), - url('fonts/icomoon.svg?66xt8i#icomoon') format('svg'); + src: url('fonts/icomoon.eot?gkm8eh'); + src: url('fonts/icomoon.eot?gkm8eh#iefix') format('embedded-opentype'), + url('fonts/icomoon.ttf?gkm8eh') format('truetype'), + url('fonts/icomoon.woff?gkm8eh') format('woff'), + url('fonts/icomoon.svg?gkm8eh#icomoon') format('svg'); font-weight: normal; font-style: normal; } @@ -30,6 +30,9 @@ .icon-close:before { content: "\e5cd"; } +.icon-person:before { + content: "\e7fd"; +} .icon-search:before { content: "\e8b6"; }