Browse Source

Schemas list

pull/1/head
Sebastian 9 years ago
parent
commit
6a03a2309c
  1. 2
      src/Squidex.Read/Schemas/Repositories/ISchemaEntity.cs
  2. 9
      src/Squidex.Store.MongoDb/Schemas/MongoSchemaEntity.cs
  3. 1
      src/Squidex/Controllers/Api/Apps/AppClientsController.cs
  4. 13
      src/Squidex/Controllers/Api/Schemas/Models/SchemaDetailsDto.cs
  5. 13
      src/Squidex/Controllers/Api/Schemas/Models/SchemaDto.cs
  6. 2
      src/Squidex/app/app.module.ts
  7. 2
      src/Squidex/app/features/content/pages/content-page.component.html
  8. 2
      src/Squidex/app/features/dashboard/pages/dashboard-page.component.html
  9. 2
      src/Squidex/app/features/media/pages/media-page.component.html
  10. 4
      src/Squidex/app/features/schemas/declarations.ts
  11. 14
      src/Squidex/app/features/schemas/module.ts
  12. 16
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.html
  13. 7
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.scss
  14. 32
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts
  15. 46
      src/Squidex/app/features/schemas/pages/schemas-page.component.html
  16. 76
      src/Squidex/app/features/schemas/pages/schemas-page.component.scss
  17. 38
      src/Squidex/app/features/schemas/pages/schemas-page.component.ts
  18. 41
      src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html
  19. 2
      src/Squidex/app/features/schemas/pages/schemas/schema-form.component.scss
  20. 101
      src/Squidex/app/features/schemas/pages/schemas/schema-form.component.ts
  21. 70
      src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html
  22. 155
      src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.scss
  23. 87
      src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts
  24. 2
      src/Squidex/app/features/settings/pages/clients/clients-page.component.html
  25. 2
      src/Squidex/app/features/settings/pages/contributors/contributors-page.component.html
  26. 2
      src/Squidex/app/features/settings/pages/languages/languages-page.component.html
  27. 24
      src/Squidex/app/framework/angular/title.component.ts
  28. 7
      src/Squidex/app/framework/utils/immutable-array.spec.ts
  29. 8
      src/Squidex/app/framework/utils/immutable-array.ts
  30. 18
      src/Squidex/app/shared/app-component-base.ts
  31. 7
      src/Squidex/app/shared/components/app-form.component.html
  32. 2
      src/Squidex/app/shared/components/app-form.component.scss
  33. 6
      src/Squidex/app/shared/components/app-form.component.ts
  34. 2
      src/Squidex/app/shared/components/history.component.html
  35. 1
      src/Squidex/app/shared/declarations.ts
  36. 8
      src/Squidex/app/shared/services/apps-store.service.ts
  37. 24
      src/Squidex/app/shared/services/schemas.service.ts
  38. 2
      src/Squidex/app/shell/pages/app/app-area.component.scss
  39. 15
      src/Squidex/app/shell/pages/not-found/not-found-page.component.scss
  40. 13
      src/Squidex/app/theme/_bootstrap.scss
  41. 4
      src/Squidex/app/theme/_panels.scss
  42. 6
      src/Squidex/app/theme/_vars.scss
  43. BIN
      src/Squidex/app/theme/icomoon/fonts/icomoon.eot
  44. 1
      src/Squidex/app/theme/icomoon/fonts/icomoon.svg
  45. BIN
      src/Squidex/app/theme/icomoon/fonts/icomoon.ttf
  46. BIN
      src/Squidex/app/theme/icomoon/fonts/icomoon.woff
  47. 27
      src/Squidex/app/theme/icomoon/selection.json
  48. 13
      src/Squidex/app/theme/icomoon/style.css

2
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; }
}

9
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; }

1
src/Squidex/Controllers/Api/Apps/AppClientsController.cs

@ -54,7 +54,6 @@ namespace Squidex.Controllers.Api.Apps
[ProducesResponseType(typeof(ClientDto[]), 200)]
public async Task<IActionResult> GetClients(string app)
{
return StatusCode(401);
var entity = await appProvider.FindAppByNameAsync(app);
if (entity == null)

13
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; }
/// <summary>
/// The user that has created the schema.
/// </summary>
[Required]
public RefToken CreatedBy { get; set; }
/// <summary>
/// The user that has updated the schema.
/// </summary>
[Required]
public RefToken LastModifiedBy { get; set; }
/// <summary>
/// The date and time when the schema has been creaed.
/// </summary>

13
src/Squidex/Controllers/Api/Schemas/Models/SchemaDto.cs

@ -6,6 +6,7 @@
// All rights reserved.
// ==========================================================================
using Squidex.Infrastructure;
using System;
using System.ComponentModel.DataAnnotations;
@ -25,6 +26,18 @@ namespace Squidex.Controllers.Api.Schemas.Models
[RegularExpression("^[a-z0-9]+(\\-[a-z0-9]+)*$")]
public string Name { get; set; }
/// <summary>
/// The user that has created the schema.
/// </summary>
[Required]
public RefToken CreatedBy { get; set; }
/// <summary>
/// The user that has updated the schema.
/// </summary>
[Required]
public RefToken LastModifiedBy { get; set; }
/// <summary>
/// The date and time when the schema has been creaed.
/// </summary>

2
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,

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

@ -1,4 +1,4 @@
<sqx-title message="{app} | Content" parameter="app" value="{{appName() | async}}"></sqx-title>
<sqx-title message="{app} | Content" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<div class="panel panel-light">
<div class="panel-header">

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

@ -1,4 +1,4 @@
<sqx-title message="{app} | Dashboard" parameter="app" value="{{appName() | async}}"></sqx-title>
<sqx-title message="{app} | Dashboard" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<div class="panel panel-light">
<div class="panel-header">

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

@ -1,4 +1,4 @@
<sqx-title message="{app} | Media" parameter="app" value="{{appName() | async}}"></sqx-title>
<sqx-title message="{app} | Media" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<div class="panel panel-light">
<div class="panel-header">

4
src/Squidex/app/features/schemas/declarations.ts

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

14
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
]
})

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

@ -0,0 +1,16 @@
<sqx-title message="{app} | Schema" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<div class="panel panel-light">
<div class="panel-header">
<div>
<h3 class="panel-title">Schema</h3>
<a class="panel-close" routerLink="../">
<i class="icon-close"></i>
</a>
</div>
</div>
<div class="panel-content">
</div>
</div>

7
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;
}

32
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);
}
}

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

@ -1,46 +0,0 @@
<sqx-title message="{app} | Schemas" parameter="app" value="{{appName() | async}}"></sqx-title>
<div class="panel panel-dark">
<div class="panel-header">
<div>
<h3 class="panel-title">Schemas</h3>
<a class="panel-close" dashboardLink>
<i class="icon-close"></i>
</a>
</div>
<div class="subheader">
<div class="clearfix">
<button class="btn btn-new" (click)="createSchema()">
<i class="icon-plus"></i> New Schema
</button>
</div>
<div class="search-form">
<input class="form-control form-control-dark" placeholder="Search for schemas..." />
<i class="icon-search"></i>
</div>
</div>
</div>
<div class="panel-content">
asdasd
</div>
</div>
<div class="modal" *sqxModalView="modalDialog" [@fade]>
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close" (click)="modalDialog.hide()">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">Create Schema</h4>
</div>
<div class="modal-body">
</div>
</div>
</div>
</div>

76
src/Squidex/app/features/schemas/pages/schemas-page.component.scss

@ -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;
}
}
}

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

@ -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();
}
}

41
src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html

@ -0,0 +1,41 @@
<form [formGroup]="createForm" (ngSubmit)="createSchema()">
<div *ngIf="creationError">
<div class="form-error">
{{creationError}}
</div>
</div>
<div class="form-group">
<label for="app-name">Name</label>
<div class="errors-box" *ngIf="createForm.get('name').invalid && createForm.get('name').touched" [@fade]>
<div class="errors">
<span *ngIf="createForm.get('name').hasError('required')">
Name is required.
</span>
<span *ngIf="createForm.get('name').hasError('maxlength')">
Name can not have more than 40 characters.
</span>
<span *ngIf="createForm.get('name').hasError('pattern')">
Name can contain lower case letters (a-z), numbers and dashes only (not at the end).
</span>
</div>
</div>
<input type="text" class="form-control" id="schema-name" formControlName="name" />
<span class="form-hint">
<p>
The schema name becomes part of the api url,<br /> e.g https://{{appName}}.squidex.io/<b>{{schemaName}}</b>/.
</p>
<p>
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.
</p>
</span>
</div>
<div class="form-group clearfix">
<button type="reset" class="float-xs-left btn btn-secondary" (click)="cancel()">Cancel</button>
<button type="submit" class="float-xs-right btn btn-primary" >Create</button>
</div>
</form>

2
src/Squidex/app/features/schemas/pages/schemas/schema-form.component.scss

@ -0,0 +1,2 @@
@import '_vars';
@import '_mixins';

101
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<SchemaDto>();
@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();
}
}

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

@ -0,0 +1,70 @@
<sqx-title message="{app} | Schemas" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<div class="panel panel-dark">
<div class="panel-header">
<div>
<h3 class="panel-title">Schemas</h3>
<a class="panel-close" dashboardLink>
<i class="icon-close"></i>
</a>
</div>
<div class="subheader">
<div class="clearfix">
<button class="btn btn-new" (click)="modalDialog.show()">
<i class="icon-plus"></i> New Schema
</button>
</div>
<div class="search-form">
<input class="form-control form-control-dark" [(ngModel)]="schemasFilter" [ngModelOptions]="{standalone: true}" placeholder="Search for schemas..." />
<i class="icon-search"></i>
</div>
</div>
</div>
<div class="panel-content">
<div class="schemas">
<div class="schema" *ngFor="let schema of filteredSchemas" [routerLink]="[schema.name]" routerLinkActive="active">
<div class="schema-inner">
<div class="row">
<div class="col-xs-4">
<span class="schema-name">{{schema.name}}</span>
</div>
<div class="col-xs-4">
<span class="schema-user">
<i class="icon-person"></i> <span class="schema-user-text">{{userName(schema.lastModifiedBy, true) | async}}</span>
</span>
</div>
<div class="col-xs-4 schema-col-right">
<span class="schema-modified">{{schema.lastModified.toLocal() | fromNow}}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="modal" *sqxModalView="modalDialog" [@fade]>
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close" (click)="modalDialog.hide()">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">Create Schema</h4>
</div>
<div class="modal-body">
<sqx-schema-form
[appName]="appName() | async"
(created)="onSchemaCreationCompleted($event)"
(cancelled)="modalDialog.hide()"></sqx-schema-form>
</div>
</div>
</div>
</div>
<router-outlet></router-outlet>

155
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;
}
}
}

87
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<SchemaDto>();
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();
}
}

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

@ -1,4 +1,4 @@
<sqx-title message="{app} | Clients | Settings" parameter="app" value="{{appName() | async}}"></sqx-title>
<sqx-title message="{app} | Clients | Settings" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<div class="panel panel-light">
<div class="panel-header">

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

@ -1,4 +1,4 @@
<sqx-title message="{app} | Contributors | Settings" parameter="app" value="{{appName() | async}}"></sqx-title>
<sqx-title message="{app} | Contributors | Settings" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<div class="panel panel-light">
<div class="panel-header">

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

@ -1,4 +1,4 @@
<sqx-title message="{app} | Languages | Settings" parameter="app" value="{{appName() | async}}"></sqx-title>
<sqx-title message="{app} | Languages | Settings" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<div class="panel panel-light">
<div class="panel-header">

24
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);

7
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]);
});
});

8
src/Squidex/app/framework/utils/immutable-array.ts

@ -51,6 +51,14 @@ export class ImmutableArray<T> implements Iterable<T> {
return this.items.find(predicate);
}
public sort(compareFn?: (a: T, b: T) => number): ImmutableArray<T> {
const clone = [...this.items];
clone.sort(compareFn);
return new ImmutableArray<T>(clone);
}
public push(...items: T[]): ImmutableArray<T> {
if (!items || items.length === 0) {
return this;

18
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<string> {
return this.usersProvider.getUser(userId).map(u => u.displayName);
}
public userPicture(userId: string): Observable<string> {
return this.usersProvider.getUser(userId).map(u => u.pictureUrl);
}
public userName(userId: string, isRef: boolean = false): Observable<string> {
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));

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

@ -25,9 +25,12 @@
<input type="text" class="form-control" id="app-name" formControlName="name" />
<span class="form-hint">
The app name becomes part of the api url, e.g, https://<b>{{appName}}</b>.squidex.io/.<br />
<p>
The app name becomes part of the api url,<br /> e.g https://<b>{{appName}}</b>.squidex.io/.
</p>
<p>
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.
</p>
</span>
</div>

2
src/Squidex/app/shared/components/app-form.component.scss

@ -0,0 +1,2 @@
@import '_vars';
@import '_mixins';

6
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;

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

@ -1,4 +1,4 @@
<sqx-title message="{app} | Contributors | Settings" parameter="app" value="{{appName() | async}}"></sqx-title>
<sqx-title message="{app} | Contributors | Settings" parameter1="app" value1="{{appName() | async}}"></sqx-title>
<div class="panel panel-light">
<div class="panel-header">

1
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';

8
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<AppDto> {
return this.appService.postApp(dto)
return this.appsService.postApp(dto)
.map(created => {
now = now || DateTime.now();

24
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<EntityCreatedDto> {
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));
}
}

2
src/Squidex/app/shell/pages/app/app-area.component.scss

@ -0,0 +1,2 @@
@import '_vars';
@import '_mixins';

15
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;
}

13
src/Squidex/app/theme/_bootstrap.scss

@ -141,7 +141,14 @@
}
.form-hint {
& {
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 {

4
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;

6
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;

BIN
src/Squidex/app/theme/icomoon/fonts/icomoon.eot

Binary file not shown.

1
src/Squidex/app/theme/icomoon/fonts/icomoon.svg

@ -8,6 +8,7 @@
<missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" horiz-adv-x="512" d="" />
<glyph unicode="&#xe5cd;" glyph-name="close" d="M810 664.667l-238-238 238-238-60-60-238 238-238-238-60 60 238 238-238 238 60 60 238-238 238 238z" />
<glyph unicode="&#xe7fd;" glyph-name="person" d="M512 340.667c114 0 342-56 342-170v-86h-684v86c0 114 228 170 342 170zM512 426.667c-94 0-170 76-170 170s76 172 170 172 170-78 170-172-76-170-170-170z" />
<glyph unicode="&#xe8b5;" glyph-name="time" d="M534 640.667v-224l192-114-32-54-224 136v256h64zM512 84.667c188 0 342 154 342 342s-154 342-342 342-342-154-342-342 154-342 342-342zM512 852.667c236 0 426-190 426-426s-190-426-426-426-426 190-426 426 190 426 426 426z" />
<glyph unicode="&#xe8b6;" glyph-name="search" d="M406 340.667c106 0 192 86 192 192s-86 192-192 192-192-86-192-192 86-192 192-192zM662 340.667l212-212-64-64-212 212v34l-12 12c-48-42-112-66-180-66-154 0-278 122-278 276s124 278 278 278 276-124 276-278c0-68-24-132-66-180l12-12h34z" />
<glyph unicode="&#xe900;" glyph-name="logo" d="M512.34 939.52c-0.174-0.065-226.41-96.5-283.902-294.74-5.545-19.035 40.453-10.673 38.399-31.6-2.517-25.409-55.264-35.821-48.385-108.78 7.001-74.289 74.617-149.342 84.791-194.72v-31.78c-0.615-9.802-5.639-36.405-22.285-49.4-9.13-7.105-21.442-9.661-37.671-7.78-22.528 2.612-31.493 16.604-35.078 27.9-5.881 18.616-0.409 40.331 12.793 50.52 13.271 10.243 15.084 28.513 4.029 40.82-11.055 12.296-30.785 13.965-44.056 3.7-32.168-24.839-45.65-70.615-32.785-111.34 12.146-38.328 44.789-64.147 87.363-69.080 6.067-0.699 11.848-1.040 17.335-1.040 32.945 0 55.27 11.669 68.785 22.32 40.671 32.105 43.867 85.623 44.099 91.62 0.011 0.355 0.022 0.705 0.022 1.060v24.36h0.129v1.64c0 14.177 12.394 25.66 27.707 25.66 14.869 0 26.889-10.843 27.578-24.46v-232.2c-0.255-3.343-3.155-34.297-22.157-49.28-9.118-7.201-21.512-9.802-37.799-7.9-22.54 2.612-31.526 16.605-35.099 27.88-5.893 18.627-0.387 40.341 12.814 50.52 13.271 10.254 15.062 28.523 4.007 40.84-11.044 12.274-30.764 13.945-44.035 3.68-32.191-24.828-45.65-70.615-32.785-111.34 12.122-38.328 44.789-64.136 87.363-69.080 6.067-0.699 11.848-1.040 17.335-1.040 32.945 0 55.262 11.669 68.742 22.32 40.683 32.105 43.879 85.623 44.099 91.62 0.024 0.376 0.042 0.696 0.042 1.040v259l0.129 0.060v1.14c0 14.456 12.65 26.18 28.264 26.18 15.288 0 27.657-11.292 28.135-25.36v-261.020c0-0.355-0.002-0.675 0.022-1.040 0.232-5.987 3.438-59.515 44.121-91.62 13.504-10.652 35.819-22.32 68.764-22.32 5.499 0 11.258 0.341 17.314 1.040 42.562 4.944 75.24 30.763 87.363 69.080 12.876 40.725-0.584 86.501-32.764 111.34-13.294 10.265-33.013 8.584-44.056-3.68-11.055-12.328-9.264-30.586 4.007-40.84 13.201-10.179 18.697-31.893 12.793-50.52-3.561-11.275-12.55-25.268-35.078-27.88-16.217-1.892-28.531 0.675-37.649 7.78-16.716 13.038-21.715 39.783-22.307 49.36v231.8c0.445 13.816 12.612 24.9 27.642 24.9 15.313 0 27.707-11.472 27.707-25.66v-1.64h0.085v-24.36c0-0.365-0.002-0.716 0.022-1.060 0.22-5.987 3.438-59.515 44.121-91.62 13.503-10.651 35.818-22.32 68.763-22.32 5.487 0 11.259 0.332 17.314 1.020 42.562 4.933 75.24 30.783 87.363 69.1 12.876 40.725-0.606 86.49-32.785 111.34-13.294 10.254-33.003 8.576-44.035-3.72-11.067-12.307-9.285-30.557 3.986-40.8 13.201-10.189 18.719-31.904 12.814-50.52-3.561-11.296-12.571-25.299-35.099-27.9-16.194-1.892-28.51 0.686-37.628 7.78-16.716 13.048-21.727 39.785-22.307 49.34v24.24c6.634 62.066 78.084 123.637 85.499 202.32 6.844 72.959-45.943 83.371-48.449 108.78-2.065 20.927 43.943 12.565 38.421 31.6-57.503 198.24-283.718 294.675-283.88 294.74z" />

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

BIN
src/Squidex/app/theme/icomoon/fonts/icomoon.ttf

Binary file not shown.

BIN
src/Squidex/app/theme/icomoon/fonts/icomoon.woff

Binary file not shown.

27
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": [

13
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";
}

Loading…
Cancel
Save