Browse Source

Improved contributor form.

pull/419/head
Sebastian Stehle 7 years ago
parent
commit
ba1fcb4551
  1. 2
      src/Squidex/app/features/settings/pages/contributors/contributor-add-form.component.html
  2. 2
      src/Squidex/app/features/settings/pages/contributors/contributors-page.component.html
  3. 61
      src/Squidex/app/features/settings/pages/contributors/import-contributors-dialog.component.html
  4. 4
      src/Squidex/app/features/settings/pages/contributors/import-contributors-dialog.component.scss
  5. 80
      src/Squidex/app/features/settings/pages/contributors/import-contributors-dialog.component.ts
  6. 2
      src/Squidex/app/framework/angular/status-icon.component.scss
  7. 14
      src/Squidex/app/shared/state/contributors.forms.ts

2
src/Squidex/app/features/settings/pages/contributors/contributor-add-form.component.html

@ -2,7 +2,7 @@
<form [formGroup]="assignContributorForm.form" (ngSubmit)="assignContributor()"> <form [formGroup]="assignContributorForm.form" (ngSubmit)="assignContributor()">
<div class="row no-gutters"> <div class="row no-gutters">
<div class="col"> <div class="col">
<sqx-autocomplete [source]="usersDataSource" formControlName="user" inputName="contributor" placeholder="Find existing user or add by email" displayProperty="displayName"> <sqx-autocomplete [source]="usersDataSource" formControlName="user" inputName="contributor" placeholder="Find existing user or invite by email" displayProperty="displayName">
<ng-template let-user="$implicit"> <ng-template let-user="$implicit">
<span class="autocomplete-user"> <span class="autocomplete-user">
<img class="user-picture autocomplete-user-picture" [attr.src]="user | sqxUserDtoPicture" /> <img class="user-picture autocomplete-user-picture" [attr.src]="user | sqxUserDtoPicture" />

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

@ -78,7 +78,7 @@
</sqx-panel> </sqx-panel>
<ng-container *sqxModal="importDialog"> <ng-container *sqxModal="importDialog">
<sqx-import-contributors-dialog <sqx-import-contributors-dialog [roles]="rolesState.roles | async"
(close)="importDialog.hide()"> (close)="importDialog.hide()">
</sqx-import-contributors-dialog> </sqx-import-contributors-dialog>
</ng-container> </ng-container>

61
src/Squidex/app/features/settings/pages/contributors/import-contributors-dialog.component.html

@ -1,18 +1,44 @@
<form [formGroup]="importForm.form" (ngSubmit)="import()"> <form [formGroup]="importForm.form" (ngSubmit)="detect()">
<sqx-modal-dialog (close)="emitClose()"> <sqx-modal-dialog (close)="emitClose()">
<ng-container title> <ng-container title>
Import contributors Import contributors
</ng-container> </ng-container>
<ng-container content> <ng-container content>
<div class="content"> <ng-container [ngSwitch]="importStage">
<ng-container *ngIf="importStatus; else noStatus"> <ng-container *ngSwitchCase="'Start'">
<textarea class="form-control content" placeholder="user1@squidex.io;user2@squidex.io" formControlName="import"></textarea>
<sqx-form-hint>
<ng-container *ngIf="importForm.numberOfEmails | async; let emails">
Emails detected: {{emails}}
</ng-container>
&nbsp;
</sqx-form-hint>
</ng-container>
<ng-container *ngSwitchCase="'Change'">
<div class="row pb-2" *ngFor="let status of importStatus">
<div class="col truncate aligned">
{{status.email}}
</div>
<div class="col-5 pl-1">
<select class="form-control" [(ngModel)]="status.role" [ngModelOptions]="standalone">
<option *ngFor="let role of roles" [ngValue]="role.name">{{role.name}}</option>
</select>
</div>
</div>
</ng-container>
<ng-container *ngSwitchCase="'Wait'">
<div class="row pb-2" *ngFor="let status of importStatus"> <div class="row pb-2" *ngFor="let status of importStatus">
<div class="col truncate"> <div class="col truncate aligned">
{{status.email}} {{status.email}}
</div> </div>
<div class="col-auto"> <div class="col-auto aligned">
<sqx-status-icon size="sm" <sqx-status-icon size="sm"
[status]="status.result" [status]="status.result"
[statusText]="status.resultText"> [statusText]="status.resultText">
@ -20,24 +46,21 @@
</div> </div>
</div> </div>
</ng-container> </ng-container>
</ng-container>
<ng-template #noStatus>
<textarea class="form-control content" placeholder="user1@squidex.io;user2@squidex.io" formControlName="import"></textarea>
</ng-template>
</div>
<sqx-form-hint>
<ng-container *ngIf="importForm.numberOfEmails | async; let emails">
Emails detected: {{emails}}
</ng-container>
&nbsp;
</sqx-form-hint>
</ng-container> </ng-container>
<ng-container footer> <ng-container footer>
<button type="reset" class="float-left btn btn-secondary" (click)="emitClose()">Cancel</button> <button type="reset" class="float-left btn btn-secondary" (click)="emitClose()">Cancel</button>
<button type="submit" class="float-right btn btn-success" [disabled]="importStatus || (importForm.hasNoUser | async)">Add Contributors</button>
<ng-container [ngSwitch]="importStage">
<ng-container *ngSwitchCase="'Start'">
<button type="submit" class="float-right btn btn-success" [disabled]="importForm.hasNoUser | async">Add Contributors</button>
</ng-container>
<ng-container *ngSwitchCase="'Change'">
<button type="button" class="float-right btn btn-success" [disabled]="importStatus.length === 0" (click)="import()">Import</button>
</ng-container>
</ng-container>
</ng-container> </ng-container>
</sqx-modal-dialog> </sqx-modal-dialog>
</form> </form>

4
src/Squidex/app/features/settings/pages/contributors/import-contributors-dialog.component.scss

@ -5,6 +5,10 @@ textarea {
resize: none; resize: none;
} }
.aligned {
line-height: 2.5rem;
}
.content { .content {
min-height: 300px; min-height: 300px;
} }

80
src/Squidex/app/features/settings/pages/contributors/import-contributors-dialog.component.ts

@ -5,7 +5,7 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { Component, EventEmitter, Output } from '@angular/core'; import { Component, EventEmitter, Input, Output } from '@angular/core';
import { FormBuilder } from '@angular/forms'; import { FormBuilder } from '@angular/forms';
import { empty, of } from 'rxjs'; import { empty, of } from 'rxjs';
import { catchError, mergeMap, tap } from 'rxjs/operators'; import { catchError, mergeMap, tap } from 'rxjs/operators';
@ -13,13 +13,16 @@ import { catchError, mergeMap, tap } from 'rxjs/operators';
import { import {
ContributorsState, ContributorsState,
ErrorDto, ErrorDto,
ImportContributorsForm ImmutableArray,
ImportContributorsForm,
RoleDto
} from '@app/shared'; } from '@app/shared';
interface ImportStatus { interface ImportStatus {
email: string; email: string;
result: 'Pending' | 'Failed' | 'Success'; result: 'Pending' | 'Failed' | 'Success';
resultText: string; resultText: string;
role: string;
} }
@Component({ @Component({
@ -28,11 +31,17 @@ interface ImportStatus {
templateUrl: './import-contributors-dialog.component.html' templateUrl: './import-contributors-dialog.component.html'
}) })
export class ImportContributorsDialogComponent { export class ImportContributorsDialogComponent {
public readonly standalone = { standalone: true };
@Output() @Output()
public close = new EventEmitter(); public close = new EventEmitter();
@Input()
public roles: ImmutableArray<RoleDto>;
public importForm = new ImportContributorsForm(this.formBuilder); public importForm = new ImportContributorsForm(this.formBuilder);
public importStatus: ImportStatus[]; public importStatus: ImportStatus[] = [];
public importStage: 'Start' | 'Change' | 'Wait' = 'Start';
constructor( constructor(
private readonly formBuilder: FormBuilder, private readonly formBuilder: FormBuilder,
@ -40,51 +49,60 @@ export class ImportContributorsDialogComponent {
) { ) {
} }
public import() { public detect() {
this.importStage = 'Change';
const contributors = this.importForm.submit(); const contributors = this.importForm.submit();
if (contributors && contributors.length > 0) { if (contributors && contributors.length > 0) {
this.importStatus = [];
for (let contributor of contributors) { for (let contributor of contributors) {
this.importStatus.push({ this.importStatus.push({
email: contributor.contributorId, email: contributor.contributorId,
result: 'Pending', result: 'Pending',
resultText: 'Pending' resultText: 'Pending',
role: 'Developer'
}); });
} }
of(...contributors).pipe(
mergeMap(c =>
this.contributorsState.assign(c, { silent: true }).pipe(
tap(created => {
let status = this.importStatus.find(x => x.email === c.contributorId);
if (status) {
status.resultText = getSuccess(created);
status.result = 'Success';
}
}),
catchError((error: ErrorDto) => {
let status = this.importStatus.find(x => x.email === c.contributorId);
if (status) {
status.resultText = getError(error);
status.result = 'Failed';
}
return empty();
})
), 1)
).subscribe();
} }
} }
public import() {
this.importStage = 'Wait';
of(...this.importStatus).pipe(
mergeMap(s =>
this.contributorsState.assign(createRequest(s), { silent: true }).pipe(
tap(created => {
let status = this.importStatus.find(x => x.email === s.email);
if (status) {
status.resultText = getSuccess(created);
status.result = 'Success';
}
}),
catchError((error: ErrorDto) => {
let status = this.importStatus.find(x => x.email === s.email);
if (status) {
status.resultText = getError(error);
status.result = 'Failed';
}
return empty();
})
), 1)
).subscribe();
}
public emitClose() { public emitClose() {
this.close.emit(); this.close.emit();
} }
} }
function createRequest(status: ImportStatus) {
return { contributorId: status.email, role: status.role, invite: true };
}
function getError(error: ErrorDto): string { function getError(error: ErrorDto): string {
return error.details[0]; return error.details[0];
} }

2
src/Squidex/app/framework/angular/status-icon.component.scss

@ -8,7 +8,9 @@ $circle-size-lg: 2.8rem;
.status { .status {
& { & {
text-align: center; text-align: center;
display: inline-block;
background: $color-border; background: $color-border;
border: 0;
color: $color-dark-foreground; color: $color-dark-foreground;
vertical-align: middle; vertical-align: middle;
} }

14
src/Squidex/app/shared/state/contributors.forms.ts

@ -44,7 +44,7 @@ export class AssignContributorForm extends Form<FormGroup, AssignContributorDto>
contributorId = contributorId.id; contributorId = contributorId.id;
} }
return { contributorId, role: value.string, invite: true }; return { contributorId, role: value.role, invite: true };
} }
} }
@ -69,14 +69,20 @@ export class ImportContributorsForm extends Form<FormGroup, AssignContributorDto
} }
function extractEmails(value: string) { function extractEmails(value: string) {
let result: AssignContributorDto[] = []; const result: AssignContributorDto[] = [];
if (value) { if (value) {
let emails = value.match(EMAIL_REGEX); const added: { [email: string]: boolean } = {};
const emails = value.match(EMAIL_REGEX);
if (emails) { if (emails) {
for (let match of emails) { for (let match of emails) {
result.push({ contributorId: match, role: 'Editor', invite: true }); if (!added[match]) {
result.push({ contributorId: match, role: 'Editor', invite: true });
added[match] = true;
}
} }
} }
} }

Loading…
Cancel
Save