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