mirror of https://github.com/Squidex/squidex.git
58 changed files with 814 additions and 235 deletions
@ -0,0 +1,86 @@ |
|||
<div class="table-items-row"> |
|||
<label> |
|||
{{field|displayName:'properties.label':'name'}} <span class="field-required" [class.hidden]="!field.properties.isRequired">*</span> |
|||
</label> |
|||
|
|||
<span class="field-disabled" *ngIf="fieldForm.disabled">Disabled</span> |
|||
|
|||
<div [formGroup]="fieldForm"> |
|||
<div *ngIf="field.properties.isLocalizable"> |
|||
<div class="btn-group btn-group-sm languages-buttons" role="group"> |
|||
<button type="button" class="btn btn-secondary" *ngFor="let language of languages" [attr.title]="language.englishName" |
|||
[class.btn-danger]="fieldForm.controls[language.iso2Code].invalid && (fieldForm.controls[language.iso2Code].touched || contentFormSubmitted)" |
|||
[class.active]="language.iso2Code == selectedLanguage" (click)="selectLanguage(language)"> |
|||
{{language.iso2Code}} |
|||
</button> |
|||
</div> |
|||
</div> |
|||
|
|||
<div *ngFor="let language of fieldLanguages"> |
|||
<div *ngIf="language == selectedLanguage"> |
|||
<sqx-control-errors [for]="language" fieldName="{{field|displayName:'properties.label':'name'}}" [submitted]="contentFormSubmitted"></sqx-control-errors> |
|||
|
|||
<div [ngSwitch]="field.properties.fieldType"> |
|||
<div *ngSwitchCase="'number'"> |
|||
<div [ngSwitch]="field.properties.editor"> |
|||
<div *ngSwitchCase="'Input'"> |
|||
<input class="form-control" type="number" [formControlName]="language"> |
|||
</div> |
|||
<div *ngSwitchCase="'Dropdown'"> |
|||
<select class="form-control" [formControlName]="language"> |
|||
<option *ngFor="let value of field.properties.allowedValues">{{value}}</option> |
|||
</select> |
|||
</div> |
|||
<div *ngSwitchCase="'Radio'"> |
|||
<div class="form-check form-check-inline" *ngFor="let value of field.properties.allowedValues"> |
|||
<label class="form-check-label"> |
|||
<input class="form-check-input" type="radio" value="{{value}}" [formControlName]="language"> {{value}} |
|||
</label> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div *ngSwitchCase="'string'"> |
|||
<div [ngSwitch]="field.properties.editor"> |
|||
<div *ngSwitchCase="'Input'"> |
|||
<input class="form-control" type="text" [formControlName]="language"> |
|||
</div> |
|||
<div *ngSwitchCase="'Dropdown'"> |
|||
<select class="form-control" [formControlName]="language"> |
|||
<option *ngFor="let value of field.properties.allowedValues">{{value}}</option> |
|||
</select> |
|||
</div> |
|||
<div *ngSwitchCase="'TextArea'"> |
|||
<textarea class="form-control" [formControlName]="language"></textarea> |
|||
</div> |
|||
<div *ngSwitchCase="'Radio'"> |
|||
<div class="form-check form-check-inline" *ngFor="let value of field.properties.allowedValues"> |
|||
<label class="form-check-label"> |
|||
<input class="form-check-input" type="radio" value="{{value}}" [formControlName]="language"> {{value}} |
|||
</label> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div *ngSwitchCase="'boolean'"> |
|||
<div [ngSwitch]="field.properties.editor"> |
|||
<div *ngSwitchCase="'Checkbox'"> |
|||
<div class="form-check form-check-inline"> |
|||
<label class="form-check-label"> |
|||
<input class="form-check-input" type="checkbox" [formControlName]="language"> |
|||
</label> |
|||
</div> |
|||
</div> |
|||
<div *ngSwitchCase="'Toggle'"> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="form-hint" *ngIf="field.properties.hints && field.properties.hints.length > 0"> |
|||
{{field.properties.hints}} |
|||
</div> |
|||
</div> |
|||
@ -0,0 +1,22 @@ |
|||
@import '_vars'; |
|||
@import '_mixins'; |
|||
|
|||
.table-items-row { |
|||
position: relative; |
|||
} |
|||
|
|||
.languages-buttons { |
|||
@include absolute(1rem, 1.25rem, auto, auto); |
|||
} |
|||
|
|||
.field { |
|||
&-required { |
|||
color: $color-theme-error; |
|||
} |
|||
|
|||
&-disabled { |
|||
color: $color-border-dark; |
|||
font-size: .8rem; |
|||
font-weight: normal; |
|||
} |
|||
} |
|||
@ -0,0 +1,56 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Sebastian Stehle. All rights reserved |
|||
*/ |
|||
|
|||
import { Component, Input, OnInit } from '@angular/core'; |
|||
import { FormGroup } from '@angular/forms'; |
|||
|
|||
import { |
|||
AppLanguageDto, |
|||
FieldDto |
|||
} from 'shared'; |
|||
|
|||
@Component({ |
|||
selector: 'sqx-content-field', |
|||
styleUrls: ['./content-field.component.scss'], |
|||
templateUrl: './content-field.component.html' |
|||
}) |
|||
export class ContentFieldComponent implements OnInit { |
|||
@Input() |
|||
public field: FieldDto; |
|||
|
|||
@Input() |
|||
public fieldForm: FormGroup; |
|||
|
|||
@Input() |
|||
public languages: AppLanguageDto[]; |
|||
|
|||
@Input() |
|||
public contentFormSubmitted: boolean; |
|||
|
|||
public fieldLanguages: string[]; |
|||
|
|||
public selectedLanguage: string; |
|||
|
|||
public selectLanguage(language: AppLanguageDto) { |
|||
this.selectedLanguage = language.iso2Code; |
|||
} |
|||
|
|||
public ngOnInit() { |
|||
if (this.field.isDisabled) { |
|||
this.fieldForm.disable(); |
|||
} |
|||
|
|||
if (this.field.properties.isLocalizable) { |
|||
this.fieldLanguages = this.languages.map(t => t.iso2Code); |
|||
this.selectedLanguage = this.fieldLanguages[0]; |
|||
} else { |
|||
this.fieldLanguages = ['iv']; |
|||
this.selectedLanguage = 'iv'; |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,8 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Sebastian Stehle. All rights reserved |
|||
*/ |
|||
|
|||
export class ContentAdded { } |
|||
@ -0,0 +1,83 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Sebastian Stehle. All rights reserved |
|||
*/ |
|||
|
|||
import { IMock, Mock } from 'typemoq'; |
|||
import { Observable } from 'rxjs'; |
|||
|
|||
import { AppLanguageDto, AppLanguagesService } from 'shared'; |
|||
|
|||
import { ResolveAppLanguagesGuard } from './resolve-app-languages.guard'; |
|||
import { RouterMockup } from './router-mockup'; |
|||
|
|||
describe('ResolveAppLanguagesGuard', () => { |
|||
const route = { |
|||
params: { |
|||
appName: 'my-app' |
|||
} |
|||
}; |
|||
|
|||
let appLanguagesService: IMock<AppLanguagesService>; |
|||
|
|||
beforeEach(() => { |
|||
appLanguagesService = Mock.ofType(AppLanguagesService); |
|||
}); |
|||
|
|||
it('should throw if route does not contain parameter', () => { |
|||
const guard = new ResolveAppLanguagesGuard(appLanguagesService.object, <any>new RouterMockup()); |
|||
|
|||
expect(() => guard.resolve(<any>{ params: {} }, <any>{})).toThrow('Route must contain app.'); |
|||
}); |
|||
|
|||
it('should navigate to 404 page if languages are not found', (done) => { |
|||
appLanguagesService.setup(x => x.getLanguages('my-app')) |
|||
.returns(() => Observable.of(null!)); |
|||
const router = new RouterMockup(); |
|||
|
|||
const guard = new ResolveAppLanguagesGuard(appLanguagesService.object, <any>router); |
|||
|
|||
guard.resolve(<any>route, <any>{}) |
|||
.then(result => { |
|||
expect(result).toBeFalsy(); |
|||
expect(router.lastNavigation).toEqual(['/404']); |
|||
|
|||
done(); |
|||
}); |
|||
}); |
|||
|
|||
it('should navigate to 404 page if languages loading fails', (done) => { |
|||
appLanguagesService.setup(x => x.getLanguages('my-app')) |
|||
.returns(() => Observable.throw(null!)); |
|||
const router = new RouterMockup(); |
|||
|
|||
const guard = new ResolveAppLanguagesGuard(appLanguagesService.object, <any>router); |
|||
|
|||
guard.resolve(<any>route, <any>{}) |
|||
.then(result => { |
|||
expect(result).toBeFalsy(); |
|||
expect(router.lastNavigation).toEqual(['/404']); |
|||
|
|||
done(); |
|||
}); |
|||
}); |
|||
|
|||
it('should return schema if loading succeeded', (done) => { |
|||
const languages: AppLanguageDto[] = []; |
|||
|
|||
appLanguagesService.setup(x => x.getLanguages('my-app')) |
|||
.returns(() => Observable.of(languages)); |
|||
const router = new RouterMockup(); |
|||
|
|||
const guard = new ResolveAppLanguagesGuard(appLanguagesService.object, <any>router); |
|||
|
|||
guard.resolve(<any>route, <any>{}) |
|||
.then(result => { |
|||
expect(result).toBe(languages); |
|||
|
|||
done(); |
|||
}); |
|||
}); |
|||
}); |
|||
@ -0,0 +1,62 @@ |
|||
/* |
|||
* Squidex Headless CMS |
|||
* |
|||
* @license |
|||
* Copyright (c) Sebastian Stehle. All rights reserved |
|||
*/ |
|||
|
|||
import { Injectable } from '@angular/core'; |
|||
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router'; |
|||
|
|||
import { AppLanguageDto, AppLanguagesService } from './../services/app-languages.service'; |
|||
|
|||
@Injectable() |
|||
export class ResolveAppLanguagesGuard implements Resolve<AppLanguageDto[]> { |
|||
constructor( |
|||
private readonly appLanguagesService: AppLanguagesService, |
|||
private readonly router: Router |
|||
) { |
|||
} |
|||
|
|||
public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise<AppLanguageDto[]> { |
|||
const appName = this.findParameter(route, 'appName'); |
|||
|
|||
if (!appName) { |
|||
throw 'Route must contain app and schema name.'; |
|||
} |
|||
|
|||
const result = |
|||
this.appLanguagesService.getLanguages(appName).toPromise() |
|||
.then(dto => { |
|||
if (!dto) { |
|||
this.router.navigate(['/404']); |
|||
|
|||
return null; |
|||
} |
|||
|
|||
return dto; |
|||
}).catch(() => { |
|||
this.router.navigate(['/404']); |
|||
|
|||
return null; |
|||
}); |
|||
|
|||
return result; |
|||
} |
|||
|
|||
private findParameter(route: ActivatedRouteSnapshot, name: string): string | null { |
|||
let result: string | null = null; |
|||
|
|||
while (route) { |
|||
result = route.params[name]; |
|||
|
|||
if (result) { |
|||
break; |
|||
} |
|||
|
|||
route = route.parent; |
|||
} |
|||
|
|||
return result; |
|||
} |
|||
} |
|||
Loading…
Reference in new issue