mirror of https://github.com/Squidex/squidex.git
committed by
GitHub
162 changed files with 2179 additions and 1315 deletions
@ -0,0 +1,66 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
// tslint:disable: component-selector
|
||||
|
|
||||
|
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; |
||||
|
|
||||
|
import { EventConsumerDto, EventConsumersState } from '@app/features/administration/internal'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: '[sqxEventConsumer]', |
||||
|
template: ` |
||||
|
<tr [class.faulted]="eventConsumer.error && eventConsumer.error?.length > 0"> |
||||
|
<td class="auto-auto"> |
||||
|
<span class="truncate"> |
||||
|
<i class="faulted-icon icon icon-bug" (click)="error.emit()" [class.hidden]="!eventConsumer.error || eventConsumer.error?.length === 0"></i> |
||||
|
|
||||
|
{{eventConsumer.name}} |
||||
|
</span> |
||||
|
</td> |
||||
|
<td class="cell-auto-right"> |
||||
|
<span>{{eventConsumer.position}}</span> |
||||
|
</td> |
||||
|
<td class="cell-actions-lg"> |
||||
|
<button type="button" class="btn btn-text" (click)="reset()" *ngIf="eventConsumer.canReset" title="Reset Event Consumer"> |
||||
|
<i class="icon icon-reset"></i> |
||||
|
</button> |
||||
|
<button type="button" class="btn btn-text" (click)="start()" *ngIf="eventConsumer.canStart" title="Start Event Consumer"> |
||||
|
<i class="icon icon-play"></i> |
||||
|
</button> |
||||
|
<button type="button" class="btn btn-text" (click)="stop()" *ngIf="eventConsumer.canStop" title="Stop Event Consumer"> |
||||
|
<i class="icon icon-pause"></i> |
||||
|
</button> |
||||
|
</td> |
||||
|
</tr> |
||||
|
<tr class="spacer"></tr>`,
|
||||
|
changeDetection: ChangeDetectionStrategy.OnPush |
||||
|
}) |
||||
|
export class EventConsumerComponent { |
||||
|
@Output() |
||||
|
public error = new EventEmitter(); |
||||
|
|
||||
|
@Input('sqxEventConsumer') |
||||
|
public eventConsumer: EventConsumerDto; |
||||
|
|
||||
|
constructor( |
||||
|
public readonly eventConsumersState: EventConsumersState |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public start() { |
||||
|
this.eventConsumersState.start(this.eventConsumer); |
||||
|
} |
||||
|
|
||||
|
public stop() { |
||||
|
this.eventConsumersState.stop(this.eventConsumer); |
||||
|
} |
||||
|
|
||||
|
public reset() { |
||||
|
this.eventConsumersState.reset(this.eventConsumer); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,55 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
// tslint:disable: component-selector
|
||||
|
|
||||
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; |
||||
|
|
||||
|
import { UserDto, UsersState } from '@app/features/administration/internal'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: '[sqxUser]', |
||||
|
template: ` |
||||
|
<tr [routerLink]="user.id" routerLinkActive="active"> |
||||
|
<td class="cell-user"> |
||||
|
<img class="user-picture" title="{{user.displayName}}" [attr.src]="user | sqxUserDtoPicture" /> |
||||
|
</td> |
||||
|
<td class="cell-auto"> |
||||
|
<span class="user-name table-cell">{{user.displayName}}</span> |
||||
|
</td> |
||||
|
<td class="cell-auto"> |
||||
|
<span class="user-email table-cell">{{user.email}}</span> |
||||
|
</td> |
||||
|
<td class="cell-actions"> |
||||
|
<button type="button" class="btn btn-text" (click)="lock()" sqxStopClick *ngIf="user.canLock" title="Lock User"> |
||||
|
<i class="icon icon-unlocked"></i> |
||||
|
</button> |
||||
|
<button type="button" class="btn btn-text" (click)="unlock()" sqxStopClick *ngIf="user.canUnlock" title="Unlock User"> |
||||
|
<i class="icon icon-lock"></i> |
||||
|
</button> |
||||
|
</td> |
||||
|
</tr> |
||||
|
<tr class="spacer"></tr>`,
|
||||
|
changeDetection: ChangeDetectionStrategy.OnPush |
||||
|
}) |
||||
|
export class UserComponent { |
||||
|
@Input('sqxUser') |
||||
|
public user: UserDto; |
||||
|
|
||||
|
constructor( |
||||
|
private readonly usersState: UsersState |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public lock() { |
||||
|
this.usersState.lock(this.user); |
||||
|
} |
||||
|
|
||||
|
public unlock() { |
||||
|
this.usersState.unlock(this.user); |
||||
|
} |
||||
|
} |
||||
@ -1,4 +1,4 @@ |
|||||
<sqx-title message="{app} | API | GraphQL" parameter1="app" [value1]="appsState.appDisplayName"></sqx-title> |
<sqx-title message="GraphQL"></sqx-title> |
||||
|
|
||||
<sqx-panel desiredWidth="*" minWidth="50rem" isFullSize="true"> |
<sqx-panel desiredWidth="*" minWidth="50rem" isFullSize="true"> |
||||
<div inner #graphiQLContainer></div> |
<div inner #graphiQLContainer></div> |
||||
|
|||||
@ -1,98 +0,0 @@ |
|||||
<td class="cell-select" sqxStopClick> |
|
||||
<ng-container *ngIf="!isReference; else referenceTemplate"> |
|
||||
<input type="checkbox" class="form-check" |
|
||||
[disabled]="!selectable" |
|
||||
[ngModel]="selected || !selectable" |
|
||||
(ngModelChange)="selectedChange.emit($event)" /> |
|
||||
</ng-container> |
|
||||
|
|
||||
<ng-template #referenceTemplate> |
|
||||
<i class="icon-drag2 drag-handle"></i> |
|
||||
</ng-template> |
|
||||
</td> |
|
||||
|
|
||||
<td class="cell-actions cell-actions-left" *ngIf="!isReadOnly && !isDirty" sqxStopClick> |
|
||||
<div class="dropdown dropdown-options" *ngIf="content"> |
|
||||
<button type="button" class="btn btn-text-secondary" (click)="dropdown.toggle()" [class.active]="dropdown.isOpen | async" #buttonOptions> |
|
||||
<i class="icon-dots"></i> |
|
||||
</button> |
|
||||
|
|
||||
<ng-container *sqxModal="dropdown;closeAlways:true"> |
|
||||
<div class="dropdown-menu" [sqxAnchoredTo]="buttonOptions" position="bottom-left" @fade> |
|
||||
<a class="dropdown-item" *ngFor="let info of content.statusUpdates" (click)="emitChangeStatus(info.status)"> |
|
||||
Change to <i class="icon-circle icon-sm" [style.color]="info.color"></i> {{info.status}} |
|
||||
</a> |
|
||||
<a class="dropdown-item" (click)="emitClone(); dropdown.hide()" *ngIf="canClone"> |
|
||||
Clone |
|
||||
</a> |
|
||||
|
|
||||
<div class="dropdown-divider"></div> |
|
||||
|
|
||||
<a class="dropdown-item dropdown-item-delete" |
|
||||
(sqxConfirmClick)="emitDelete()" |
|
||||
confirmTitle="Delete content" |
|
||||
confirmText="Do you really want to delete the content?"> |
|
||||
Delete |
|
||||
</a> |
|
||||
</div> |
|
||||
</ng-container> |
|
||||
</div> |
|
||||
</td> |
|
||||
|
|
||||
<ng-container *ngIf="isDirty"> |
|
||||
<td class="cell-actions" > |
|
||||
<button type="button" class="btn btn-text-secondary btn-cancel" (click)="cancel()" sqxStopClick> |
|
||||
<i class="icon-close"></i> |
|
||||
</button> |
|
||||
</td> |
|
||||
|
|
||||
<td class="cell-user" > |
|
||||
<button type="button" class="btn btn-success" (click)="save()" sqxStopClick> |
|
||||
<i class="icon-checkmark"></i> |
|
||||
</button> |
|
||||
</td> |
|
||||
</ng-container> |
|
||||
|
|
||||
<td class="cell-user" *ngIf="!isCompact && !isDirty" [sqxStopClick]="isDirty"> |
|
||||
<img class="user-picture" title="{{content.lastModifiedBy | sqxUserNameRef}}" [attr.src]="content.lastModifiedBy | sqxUserPictureRef" /> |
|
||||
</td> |
|
||||
|
|
||||
<td class="cell-auto cell-content" *ngFor="let field of schemaFields; let i = index; trackBy: trackByFieldFn" [sqxStopClick]="isDirty || (field.isInlineEditable && patchAllowed)"> |
|
||||
<ng-container *ngIf="field.isInlineEditable && patchAllowed; else displayTemplate"> |
|
||||
<sqx-content-value-editor [form]="patchForm.form" [field]="field"></sqx-content-value-editor> |
|
||||
</ng-container> |
|
||||
|
|
||||
<ng-template #displayTemplate> |
|
||||
<sqx-content-value [value]="values[i]"></sqx-content-value> |
|
||||
</ng-template> |
|
||||
</td> |
|
||||
|
|
||||
<td class="cell-time" *ngIf="!isCompact" [sqxStopClick]="isDirty"> |
|
||||
<sqx-content-status |
|
||||
[status]="content.status" |
|
||||
[statusColor]="content.statusColor" |
|
||||
[scheduledTo]="content.scheduleJob?.status" |
|
||||
[scheduledAt]="content.scheduleJob?.dueTime" |
|
||||
[isPending]="content.isPending"> |
|
||||
</sqx-content-status> |
|
||||
|
|
||||
<small class="item-modified">{{content.lastModified | sqxFromNow}}</small> |
|
||||
</td> |
|
||||
|
|
||||
<td class="cell-actions" *ngIf="isReference" [sqxStopClick]="isDirty"> |
|
||||
<div class="reference-edit"> |
|
||||
<button type="button" class="btn btn-text-secondary"> |
|
||||
<i class="icon-dots"></i> |
|
||||
</button> |
|
||||
|
|
||||
<div class="reference-menu"> |
|
||||
<a class="btn btn-text-secondary" [routerLink]="['../..', schema.name, content.id]"> |
|
||||
<i class="icon-pencil"></i> |
|
||||
</a> |
|
||||
|
|
||||
<button type="button" class="btn btn-text-secondary" (click)="emitDelete()"> |
|
||||
<i class="icon-close"></i> |
|
||||
</button> |
|
||||
</div> |
|
||||
</div> |
|
||||
</td> |
|
||||
@ -0,0 +1,101 @@ |
|||||
|
<tr [routerLink]="link" routerLinkActive="active"> |
||||
|
<td class="cell-select" sqxStopClick> |
||||
|
<ng-container *ngIf="!isReference; else referenceTemplate"> |
||||
|
<input type="checkbox" class="form-check" |
||||
|
[disabled]="!selectable" |
||||
|
[ngModel]="selected || !selectable" |
||||
|
(ngModelChange)="selectedChange.emit($event)" /> |
||||
|
</ng-container> |
||||
|
|
||||
|
<ng-template #referenceTemplate> |
||||
|
<i class="icon-drag2 drag-handle"></i> |
||||
|
</ng-template> |
||||
|
</td> |
||||
|
|
||||
|
<td class="cell-actions cell-actions-left" *ngIf="!isReadOnly && !isDirty" sqxStopClick> |
||||
|
<div class="dropdown dropdown-options" *ngIf="content"> |
||||
|
<button type="button" class="btn btn-text-secondary" (click)="dropdown.toggle()" [class.active]="dropdown.isOpen | async" #buttonOptions> |
||||
|
<i class="icon-dots"></i> |
||||
|
</button> |
||||
|
|
||||
|
<ng-container *sqxModal="dropdown;closeAlways:true"> |
||||
|
<div class="dropdown-menu" [sqxAnchoredTo]="buttonOptions" position="bottom-left" @fade> |
||||
|
<a class="dropdown-item" *ngFor="let info of content.statusUpdates" (click)="emitChangeStatus(info.status)"> |
||||
|
Change to <i class="icon-circle icon-sm" [style.color]="info.color"></i> {{info.status}} |
||||
|
</a> |
||||
|
<a class="dropdown-item" (click)="emitClone(); dropdown.hide()" *ngIf="canClone"> |
||||
|
Clone |
||||
|
</a> |
||||
|
|
||||
|
<div class="dropdown-divider"></div> |
||||
|
|
||||
|
<a class="dropdown-item dropdown-item-delete" |
||||
|
(sqxConfirmClick)="emitDelete()" |
||||
|
confirmTitle="Delete content" |
||||
|
confirmText="Do you really want to delete the content?"> |
||||
|
Delete |
||||
|
</a> |
||||
|
</div> |
||||
|
</ng-container> |
||||
|
</div> |
||||
|
</td> |
||||
|
|
||||
|
<ng-container *ngIf="isDirty"> |
||||
|
<td class="cell-actions" > |
||||
|
<button type="button" class="btn btn-text-secondary btn-cancel" (click)="cancel()" sqxStopClick> |
||||
|
<i class="icon-close"></i> |
||||
|
</button> |
||||
|
</td> |
||||
|
|
||||
|
<td class="cell-user" > |
||||
|
<button type="button" class="btn btn-success" (click)="save()" sqxStopClick> |
||||
|
<i class="icon-checkmark"></i> |
||||
|
</button> |
||||
|
</td> |
||||
|
</ng-container> |
||||
|
|
||||
|
<td class="cell-user" *ngIf="!isCompact && !isDirty" [sqxStopClick]="isDirty"> |
||||
|
<img class="user-picture" title="{{content.lastModifiedBy | sqxUserNameRef}}" [attr.src]="content.lastModifiedBy | sqxUserPictureRef" /> |
||||
|
</td> |
||||
|
|
||||
|
<td class="cell-auto cell-content" *ngFor="let field of schemaFields; let i = index; trackBy: trackByFieldFn" [sqxStopClick]="isDirty || (field.isInlineEditable && patchAllowed)"> |
||||
|
<ng-container *ngIf="field.isInlineEditable && patchAllowed; else displayTemplate"> |
||||
|
<sqx-content-value-editor [form]="patchForm.form" [field]="field"></sqx-content-value-editor> |
||||
|
</ng-container> |
||||
|
|
||||
|
<ng-template #displayTemplate> |
||||
|
<sqx-content-value [value]="values[i]"></sqx-content-value> |
||||
|
</ng-template> |
||||
|
</td> |
||||
|
|
||||
|
<td class="cell-time" *ngIf="!isCompact" [sqxStopClick]="isDirty"> |
||||
|
<sqx-content-status |
||||
|
[status]="content.status" |
||||
|
[statusColor]="content.statusColor" |
||||
|
[scheduledTo]="content.scheduleJob?.status" |
||||
|
[scheduledAt]="content.scheduleJob?.dueTime" |
||||
|
[isPending]="content.isPending"> |
||||
|
</sqx-content-status> |
||||
|
|
||||
|
<small class="item-modified">{{content.lastModified | sqxFromNow}}</small> |
||||
|
</td> |
||||
|
|
||||
|
<td class="cell-actions" *ngIf="isReference" [sqxStopClick]="isDirty"> |
||||
|
<div class="reference-edit"> |
||||
|
<button type="button" class="btn btn-text-secondary"> |
||||
|
<i class="icon-dots"></i> |
||||
|
</button> |
||||
|
|
||||
|
<div class="reference-menu"> |
||||
|
<a class="btn btn-text-secondary" [routerLink]="['../..', schema.name, content.id]"> |
||||
|
<i class="icon-pencil"></i> |
||||
|
</a> |
||||
|
|
||||
|
<button type="button" class="btn btn-text-secondary" (click)="emitDelete()"> |
||||
|
<i class="icon-close"></i> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</td> |
||||
|
</tr> |
||||
|
<tr class="spacer"></tr> |
||||
@ -0,0 +1,87 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
// tslint:disable: component-selector
|
||||
|
|
||||
|
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core'; |
||||
|
|
||||
|
import { |
||||
|
ActionsDto, |
||||
|
RuleDto, |
||||
|
RulesState, |
||||
|
TriggersDto |
||||
|
} from '@app/shared'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: '[sqxRule]', |
||||
|
template: ` |
||||
|
<tr> |
||||
|
<td class="cell-separator"> |
||||
|
<h3>If</h3> |
||||
|
</td> |
||||
|
<td class="cell-auto"> |
||||
|
<span (click)="editTrigger.emit()"> |
||||
|
<sqx-rule-element [type]="rule.triggerType" [element]="ruleTriggers[rule.triggerType]"></sqx-rule-element> |
||||
|
</span> |
||||
|
</td> |
||||
|
<td class="cell-separator"> |
||||
|
<h3>then</h3> |
||||
|
</td> |
||||
|
<td class="cell-auto"> |
||||
|
<span (click)="editAction.emit()"> |
||||
|
<sqx-rule-element [type]="rule.actionType" [element]="ruleActions[rule.actionType]"></sqx-rule-element> |
||||
|
</span> |
||||
|
</td> |
||||
|
<td class="cell-actions"> |
||||
|
<sqx-toggle [disabled]="!rule.canDisable && !rule.canEnable" [ngModel]="rule.isEnabled" (ngModelChange)="toggle()"></sqx-toggle> |
||||
|
</td> |
||||
|
<td class="cell-actions"> |
||||
|
<button type="button" class="btn btn-text-danger" |
||||
|
[disabled]="!rule.canDelete" |
||||
|
(sqxConfirmClick)="delete()" |
||||
|
confirmTitle="Delete rule" |
||||
|
confirmText="Do you really want to delete the rule?"> |
||||
|
<i class="icon-bin2"></i> |
||||
|
</button> |
||||
|
</td> |
||||
|
</tr> |
||||
|
<tr class="spacer"></tr>`,
|
||||
|
changeDetection: ChangeDetectionStrategy.OnPush |
||||
|
}) |
||||
|
export class RuleComponent { |
||||
|
@Output() |
||||
|
public editTrigger = new EventEmitter(); |
||||
|
|
||||
|
@Output() |
||||
|
public editAction = new EventEmitter(); |
||||
|
|
||||
|
@Input() |
||||
|
public ruleTriggers: TriggersDto; |
||||
|
|
||||
|
@Input() |
||||
|
public ruleActions: ActionsDto; |
||||
|
|
||||
|
@Input('sqxRule') |
||||
|
public rule: RuleDto; |
||||
|
|
||||
|
constructor( |
||||
|
private readonly rulesState: RulesState |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public delete() { |
||||
|
this.rulesState.delete(this.rule); |
||||
|
} |
||||
|
|
||||
|
public toggle() { |
||||
|
if (this.rule.isEnabled) { |
||||
|
this.rulesState.disable(this.rule); |
||||
|
} else { |
||||
|
this.rulesState.enable(this.rule); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,87 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; |
||||
|
|
||||
|
import { |
||||
|
ApiUrlConfig, |
||||
|
BackupDto, |
||||
|
BackupsState, |
||||
|
Duration |
||||
|
} from '@app/shared'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'sqx-backup', |
||||
|
template: ` |
||||
|
<div class="table-items-row"> |
||||
|
<div class="row"> |
||||
|
<div class="col-auto" [ngSwitch]="backup.status"> |
||||
|
<sqx-status-icon size="lg" [status]="backup.status"></sqx-status-icon> |
||||
|
</div> |
||||
|
<div class="col-auto"> |
||||
|
<div> |
||||
|
Started: |
||||
|
</div> |
||||
|
<div> |
||||
|
Duration: |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-3"> |
||||
|
<div> |
||||
|
{{backup.started | sqxFromNow}} |
||||
|
</div> |
||||
|
<div *ngIf="backup.stopped"> |
||||
|
{{duration}} |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col"> |
||||
|
<div> |
||||
|
<span title="Archived events"> |
||||
|
Events: <strong class="backup-progress">{{backup.handledEvents | sqxKNumber}}</strong> |
||||
|
</span>, |
||||
|
<span title="Archived assets"> |
||||
|
Assets: <strong class="backup-progress">{{backup.handledAssets | sqxKNumber}}</strong> |
||||
|
</span> |
||||
|
</div> |
||||
|
<div *ngIf="backup.stopped && !backup.isFailed"> |
||||
|
Download: |
||||
|
|
||||
|
<a href="{{apiUrl.buildUrl(backup.downloadUrl)}}" sqxExternalLink="noicon"> |
||||
|
Ready <i class="icon-external-link"></i> |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-auto"> |
||||
|
<button type="button" class="btn btn-text-danger mt-1" |
||||
|
[disabled]="!backup.canDelete" |
||||
|
(sqxConfirmClick)="delete()" |
||||
|
confirmTitle="Delete backup" |
||||
|
confirmText="Do you really want to delete the backup?"> |
||||
|
<i class="icon-bin2"></i> |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div>`,
|
||||
|
changeDetection: ChangeDetectionStrategy.OnPush |
||||
|
}) |
||||
|
export class BackupComponent { |
||||
|
@Input() |
||||
|
public backup: BackupDto; |
||||
|
|
||||
|
public get duration() { |
||||
|
return Duration.create(this.backup.started, this.backup.stopped!).toString(); |
||||
|
} |
||||
|
|
||||
|
constructor( |
||||
|
public readonly apiUrl: ApiUrlConfig, private readonly backupsState: BackupsState |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public delete() { |
||||
|
this.backupsState.delete(this.backup); |
||||
|
} |
||||
|
} |
||||
@ -1,20 +0,0 @@ |
|||||
/* |
|
||||
* Squidex Headless CMS |
|
||||
* |
|
||||
* @license |
|
||||
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
|
||||
*/ |
|
||||
|
|
||||
import { Pipe, PipeTransform } from '@angular/core'; |
|
||||
|
|
||||
import { BackupDto, Duration } from '@app/shared'; |
|
||||
|
|
||||
@Pipe({ |
|
||||
name: 'sqxBackupDuration', |
|
||||
pure: true |
|
||||
}) |
|
||||
export class BackupDurationPipe implements PipeTransform { |
|
||||
public transform(backup: BackupDto) { |
|
||||
return Duration.create(backup.started, backup.stopped!).toString(); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,60 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
import { ChangeDetectionStrategy, Component } from '@angular/core'; |
||||
|
import { FormBuilder } from '@angular/forms'; |
||||
|
|
||||
|
import { AddClientForm, ClientsState } from '@app/shared'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'sqx-client-add-form', |
||||
|
template: ` |
||||
|
<div class="table-items-footer"> |
||||
|
<form [formGroup]="addClientForm.form" (ngSubmit)="addClient()"> |
||||
|
<div class="row no-gutters"> |
||||
|
<div class="col"> |
||||
|
<sqx-control-errors for="name" [submitted]="addClientForm.submitted | async"></sqx-control-errors> |
||||
|
|
||||
|
<input type="text" class="form-control" formControlName="name" maxlength="40" placeholder="Enter client name" autocomplete="off" sqxTransformInput="LowerCase" /> |
||||
|
</div> |
||||
|
<div class="col-auto pl-1"> |
||||
|
<button type="submit" class="btn btn-success" [disabled]="addClientForm.hasNoName | async">Add Client</button> |
||||
|
</div> |
||||
|
<div class="col-auto pl-1"> |
||||
|
<button type="reset" class="btn btn-secondary" (click)="cancel()">Cancel</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</form> |
||||
|
</div>`,
|
||||
|
changeDetection: ChangeDetectionStrategy.OnPush |
||||
|
}) |
||||
|
export class ClientAddFormComponent { |
||||
|
public addClientForm = new AddClientForm(this.formBuilder); |
||||
|
|
||||
|
constructor( |
||||
|
private readonly clientsState: ClientsState, |
||||
|
private readonly formBuilder: FormBuilder |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public addClient() { |
||||
|
const value = this.addClientForm.submit(); |
||||
|
|
||||
|
if (value) { |
||||
|
this.clientsState.attach({ id: value.name }) |
||||
|
.subscribe(() => { |
||||
|
this.addClientForm.submitCompleted(); |
||||
|
}, error => { |
||||
|
this.addClientForm.submitFailed(error); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public cancel() { |
||||
|
this.addClientForm.submitCompleted(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
<div class="table-items-header" style="margin: 0"> |
||||
|
<sqx-form-alert marginTop="0" marginBottom="2" light="true"> |
||||
|
Just enter the email address to invite someone with no account to the app. |
||||
|
</sqx-form-alert> |
||||
|
|
||||
|
<form [formGroup]="assignContributorForm.form" (ngSubmit)="assignContributor()"> |
||||
|
<div class="row no-gutters"> |
||||
|
<div class="col"> |
||||
|
<sqx-autocomplete [source]="usersDataSource" formControlName="user" [inputName]="'contributor'" placeholder="Find existing user" displayProperty="displayName"> |
||||
|
<ng-template let-user="$implicit"> |
||||
|
<span class="autocomplete-user"> |
||||
|
<img class="user-picture autocomplete-user-picture" [attr.src]="user | sqxUserDtoPicture" /> |
||||
|
|
||||
|
<span class="user-name autocomplete-user-name">{{user.displayName}}</span> |
||||
|
</span> |
||||
|
</ng-template> |
||||
|
</sqx-autocomplete> |
||||
|
</div> |
||||
|
<div class="col-auto pl-1"> |
||||
|
<button type="submit" class="btn btn-success" [disabled]="assignContributorForm.hasNoUser | async">Add Contributor</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</form> |
||||
|
</div> |
||||
@ -0,0 +1,16 @@ |
|||||
|
@import '_vars'; |
||||
|
@import '_mixins'; |
||||
|
|
||||
|
.autocomplete-user { |
||||
|
& { |
||||
|
@include truncate; |
||||
|
} |
||||
|
|
||||
|
&-name { |
||||
|
margin-left: .25rem; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.table-items-header { |
||||
|
margin: 0; |
||||
|
} |
||||
@ -0,0 +1,84 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
import { Component, Injectable } from '@angular/core'; |
||||
|
import { FormBuilder } from '@angular/forms'; |
||||
|
import { Observable } from 'rxjs'; |
||||
|
import { withLatestFrom } from 'rxjs/operators'; |
||||
|
|
||||
|
import { |
||||
|
AssignContributorForm, |
||||
|
AutocompleteSource, |
||||
|
ContributorsState, |
||||
|
DialogModel, |
||||
|
DialogService, |
||||
|
UsersService |
||||
|
} from '@app/shared'; |
||||
|
|
||||
|
@Injectable() |
||||
|
export class UsersDataSource implements AutocompleteSource { |
||||
|
constructor( |
||||
|
private readonly contributorsState: ContributorsState, |
||||
|
private readonly usersService: UsersService |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public find(query: string): Observable<any[]> { |
||||
|
return this.usersService.getUsers(query).pipe( |
||||
|
withLatestFrom(this.contributorsState.contributors, (users, contributors) => { |
||||
|
const results: any[] = []; |
||||
|
|
||||
|
for (let user of users) { |
||||
|
if (!contributors!.find(t => t.contributorId === user.id)) { |
||||
|
results.push(user); |
||||
|
} |
||||
|
} |
||||
|
return results; |
||||
|
})); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'sqx-contributor-add-form', |
||||
|
styleUrls: ['./contributor-add-form.component.scss'], |
||||
|
templateUrl: './contributor-add-form.component.html', |
||||
|
providers: [ |
||||
|
UsersDataSource |
||||
|
] |
||||
|
}) |
||||
|
export class ContributorAddFormComponent { |
||||
|
public assignContributorForm = new AssignContributorForm(this.formBuilder); |
||||
|
|
||||
|
public importDialog = new DialogModel(); |
||||
|
|
||||
|
constructor( |
||||
|
public readonly contributorsState: ContributorsState, |
||||
|
public readonly usersDataSource: UsersDataSource, |
||||
|
private readonly dialogs: DialogService, |
||||
|
private readonly formBuilder: FormBuilder |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public assignContributor() { |
||||
|
const value = this.assignContributorForm.submit(); |
||||
|
|
||||
|
if (value) { |
||||
|
this.contributorsState.assign(value) |
||||
|
.subscribe(isCreated => { |
||||
|
this.assignContributorForm.submitCompleted(); |
||||
|
|
||||
|
if (isCreated) { |
||||
|
this.dialogs.notifyInfo('A new user with the entered email address has been created and assigned as contributor.'); |
||||
|
} else { |
||||
|
this.dialogs.notifyInfo('User has been added as contributor.'); |
||||
|
} |
||||
|
}, error => { |
||||
|
this.assignContributorForm.submitFailed(error); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,67 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
// tslint:disable: component-selector
|
||||
|
|
||||
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; |
||||
|
|
||||
|
import { |
||||
|
ContributorDto, |
||||
|
ContributorsState, |
||||
|
RoleDto |
||||
|
} from '@app/shared'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: '[sqxContributor]', |
||||
|
template: ` |
||||
|
<tr> |
||||
|
<td class="cell-user"> |
||||
|
<img class="user-picture" title="{{contributor.contributorName}}" [attr.src]="contributor.contributorId | sqxUserPicture" /> |
||||
|
</td> |
||||
|
<td class="cell-auto"> |
||||
|
<span class="user-name table-cell" [innerHTML]="contributor.contributorName | sqxHighlight:search"></span> |
||||
|
</td> |
||||
|
<td class="cell-time"> |
||||
|
<select class="form-control" |
||||
|
[ngModel]="contributor.role" |
||||
|
(ngModelChange)="changeRole($event)" |
||||
|
[disabled]="!contributor.canUpdate"> |
||||
|
<option *ngFor="let role of roles" [ngValue]="role.name">{{role.name}}</option> |
||||
|
</select> |
||||
|
</td> |
||||
|
<td class="cell-actions"> |
||||
|
<button type="button" class="btn btn-text-danger" [disabled]="!contributor.canRevoke" (click)="remove()"> |
||||
|
<i class="icon-bin2"></i> |
||||
|
</button> |
||||
|
</td> |
||||
|
</tr> |
||||
|
<tr class="spacer"></tr>`,
|
||||
|
changeDetection: ChangeDetectionStrategy.OnPush |
||||
|
}) |
||||
|
export class ContributorComponent { |
||||
|
@Input() |
||||
|
public roles: RoleDto[]; |
||||
|
|
||||
|
@Input() |
||||
|
public search: string; |
||||
|
|
||||
|
@Input('sqxContributor') |
||||
|
public contributor: ContributorDto; |
||||
|
|
||||
|
constructor( |
||||
|
private readonly contributorsState: ContributorsState |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public remove() { |
||||
|
this.contributorsState.revoke(this.contributor); |
||||
|
} |
||||
|
|
||||
|
public changeRole(role: string) { |
||||
|
this.contributorsState.assign({ contributorId: this.contributor.contributorId, role }); |
||||
|
} |
||||
|
} |
||||
@ -1,22 +1,8 @@ |
|||||
@import '_vars'; |
@import '_vars'; |
||||
@import '_mixins'; |
@import '_mixins'; |
||||
|
|
||||
.autocomplete-user { |
|
||||
& { |
|
||||
@include truncate; |
|
||||
} |
|
||||
|
|
||||
&-name { |
|
||||
margin-left: .25rem; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.import-hint { |
.import-hint { |
||||
margin-bottom: 1rem; |
margin-bottom: 1rem; |
||||
font-weight: normal; |
font-weight: normal; |
||||
font-size: 90%; |
font-size: 90%; |
||||
} |
|
||||
|
|
||||
.table-items-header { |
|
||||
margin: 0; |
|
||||
} |
} |
||||
@ -0,0 +1,69 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core'; |
||||
|
import { FormBuilder } from '@angular/forms'; |
||||
|
|
||||
|
import { |
||||
|
AddLanguageForm, |
||||
|
ImmutableArray, |
||||
|
LanguageDto, |
||||
|
LanguagesState |
||||
|
} from '@app/shared'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'sqx-language-add-form', |
||||
|
template: ` |
||||
|
<div class="table-items-footer"> |
||||
|
<form [formGroup]="addLanguageForm.form" (ngSubmit)="addLanguage()"> |
||||
|
<div class="row no-gutters"> |
||||
|
<div class="col"> |
||||
|
<select class="form-control" formControlName="language"> |
||||
|
<option *ngFor="let language of newLanguages" [ngValue]="language">{{language.englishName}}</option> |
||||
|
</select> |
||||
|
</div> |
||||
|
<div class="col-auto pl-1"> |
||||
|
<button type="submit" class="btn btn-success">Add Language</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</form> |
||||
|
</div>`,
|
||||
|
changeDetection: ChangeDetectionStrategy.OnPush |
||||
|
}) |
||||
|
export class LanguageAddFormComponent implements OnChanges { |
||||
|
@Input() |
||||
|
public newLanguages: ImmutableArray<LanguageDto>; |
||||
|
|
||||
|
public addLanguageForm = new AddLanguageForm(this.formBuilder); |
||||
|
|
||||
|
constructor( |
||||
|
private readonly languagesState: LanguagesState, |
||||
|
private readonly formBuilder: FormBuilder |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public ngOnChanges() { |
||||
|
if (this.newLanguages.length > 0) { |
||||
|
const language = this.newLanguages.at(0); |
||||
|
|
||||
|
this.addLanguageForm.load({ language }); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public addLanguage() { |
||||
|
const value = this.addLanguageForm.submit(); |
||||
|
|
||||
|
if (value) { |
||||
|
this.languagesState.add(value.language) |
||||
|
.subscribe(() => { |
||||
|
this.addLanguageForm.submitCompleted(); |
||||
|
}, error => { |
||||
|
this.addLanguageForm.submitFailed(error); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
<div class="card plan float-left"> |
||||
|
<div class="card-header text-center"> |
||||
|
<h4 class="card-title">{{planInfo.plan.name}}</h4> |
||||
|
<h5 class="plan-price">{{planInfo.plan.costs}}</h5> |
||||
|
|
||||
|
<small class="text-muted">Per Month</small> |
||||
|
</div> |
||||
|
<div class="card-body"> |
||||
|
<div class="plan-fact text-center"> |
||||
|
<div> |
||||
|
<strong>{{planInfo.plan.maxApiCalls | sqxKNumber}}</strong> API Calls |
||||
|
</div> |
||||
|
<div> |
||||
|
{{planInfo.plan.maxAssetSize | sqxFileSize}} Storage |
||||
|
</div> |
||||
|
<div> |
||||
|
{{planInfo.plan.maxContributors}} Contributors |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<button *ngIf="planInfo.isSelected" class="btn btn-block btn-text-success plan-selected"> |
||||
|
✓ Selected |
||||
|
</button> |
||||
|
|
||||
|
<button *ngIf="!planInfo.isSelected" class="btn btn-block btn-success" [disabled]="plansState.isDisabled | async" (click)="changeMonthly()"> |
||||
|
Change |
||||
|
</button> |
||||
|
</div> |
||||
|
<div class="card-footer" *ngIf="planInfo.plan.yearlyId"> |
||||
|
<div class="text-center"> |
||||
|
<h5 class="plan-price">{{planInfo.plan.yearlyCosts}}</h5> |
||||
|
|
||||
|
<small class="text-muted">Per Year</small> |
||||
|
</div> |
||||
|
|
||||
|
<button *ngIf="planInfo.isYearlySelected" class="btn btn-block btn-text-success plan-selected"> |
||||
|
✓ Selected |
||||
|
</button> |
||||
|
|
||||
|
<button *ngIf="!planInfo.isYearlySelected" class="btn btn-block btn-success" [disabled]="plansState.isDisabled | async" (click)="changeYearly()"> |
||||
|
Change |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
@ -0,0 +1,34 @@ |
|||||
|
@import '_vars'; |
||||
|
@import '_mixins'; |
||||
|
|
||||
|
.plan { |
||||
|
& { |
||||
|
min-width: 13rem; |
||||
|
max-width: 20rem; |
||||
|
margin: .5rem; |
||||
|
} |
||||
|
|
||||
|
&-price { |
||||
|
color: $color-theme-blue; |
||||
|
margin-top: 0; |
||||
|
margin-bottom: 0; |
||||
|
} |
||||
|
|
||||
|
&-selected { |
||||
|
pointer-events: none; |
||||
|
} |
||||
|
|
||||
|
&-fact { |
||||
|
line-height: 1.8rem; |
||||
|
} |
||||
|
|
||||
|
.btn { |
||||
|
margin-top: 1rem; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.card-footer, |
||||
|
.card-header, |
||||
|
.card-body { |
||||
|
padding: 1rem; |
||||
|
} |
||||
@ -0,0 +1,41 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; |
||||
|
|
||||
|
import { |
||||
|
fadeAnimation, |
||||
|
PlanInfo, |
||||
|
PlansState |
||||
|
} from '@app/shared'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'sqx-plan', |
||||
|
styleUrls: ['./plan.component.scss'], |
||||
|
templateUrl: './plan.component.html', |
||||
|
animations: [ |
||||
|
fadeAnimation |
||||
|
], |
||||
|
changeDetection: ChangeDetectionStrategy.OnPush |
||||
|
}) |
||||
|
export class PlanComponent { |
||||
|
@Input() |
||||
|
public planInfo: PlanInfo; |
||||
|
|
||||
|
constructor( |
||||
|
public readonly plansState: PlansState |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public changeMonthly() { |
||||
|
this.plansState.change(this.planInfo.plan.id); |
||||
|
} |
||||
|
|
||||
|
public changeYearly() { |
||||
|
this.plansState.change(this.planInfo.plan.yearlyId); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,60 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
import { ChangeDetectionStrategy, Component } from '@angular/core'; |
||||
|
import { FormBuilder } from '@angular/forms'; |
||||
|
|
||||
|
import { AddRoleForm, RolesState } from '@app/shared'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'sqx-role-add-form', |
||||
|
template: ` |
||||
|
<div class="table-items-footer"> |
||||
|
<form [formGroup]="addRoleForm.form" (ngSubmit)="addRole()"> |
||||
|
<div class="row no-gutters"> |
||||
|
<div class="col"> |
||||
|
<sqx-control-errors for="name" [submitted]="addRoleForm.submitted | async"></sqx-control-errors> |
||||
|
|
||||
|
<input type="text" class="form-control" formControlName="name" maxlength="40" placeholder="Enter role name" autocomplete="off" /> |
||||
|
</div> |
||||
|
<div class="col-auto pl-1"> |
||||
|
<button type="submit" class="btn btn-success" [disabled]="addRoleForm.hasNoName | async">Add role</button> |
||||
|
</div> |
||||
|
<div class="col-auto pl-1"> |
||||
|
<button type="reset" class="btn btn-secondary" (click)="cancel()">Cancel</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</form> |
||||
|
</div>`,
|
||||
|
changeDetection: ChangeDetectionStrategy.OnPush |
||||
|
}) |
||||
|
export class RoleAddFormComponent { |
||||
|
public addRoleForm = new AddRoleForm(this.formBuilder); |
||||
|
|
||||
|
constructor( |
||||
|
private readonly rolesState: RolesState, |
||||
|
private readonly formBuilder: FormBuilder |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public addRole() { |
||||
|
const value = this.addRoleForm.submit(); |
||||
|
|
||||
|
if (value) { |
||||
|
this.rolesState.add(value) |
||||
|
.subscribe(() => { |
||||
|
this.addRoleForm.submitCompleted(); |
||||
|
}, error => { |
||||
|
this.addRoleForm.submitFailed(error); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public cancel() { |
||||
|
this.addRoleForm.submitCompleted(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,60 @@ |
|||||
|
/* |
||||
|
* Squidex Headless CMS |
||||
|
* |
||||
|
* @license |
||||
|
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. |
||||
|
*/ |
||||
|
|
||||
|
import { ChangeDetectionStrategy, Component } from '@angular/core'; |
||||
|
import { FormBuilder } from '@angular/forms'; |
||||
|
|
||||
|
import { AddWorkflowForm, WorkflowsState } from '@app/shared'; |
||||
|
|
||||
|
@Component({ |
||||
|
selector: 'sqx-workflow-add-form', |
||||
|
template: ` |
||||
|
<div class="table-items-footer"> |
||||
|
<form [formGroup]="addWorkflowForm.form" (ngSubmit)="addWorkflow()"> |
||||
|
<div class="row no-gutters"> |
||||
|
<div class="col"> |
||||
|
<sqx-control-errors for="name" [submitted]="addWorkflowForm.submitted | async"></sqx-control-errors> |
||||
|
|
||||
|
<input type="text" class="form-control" formControlName="name" maxlength="40" placeholder="Enter workflow name" autocomplete="off" /> |
||||
|
</div> |
||||
|
<div class="col-auto pl-1"> |
||||
|
<button type="submit" class="btn btn-success" [disabled]="addWorkflowForm.hasNoName | async">Add Workflow</button> |
||||
|
</div> |
||||
|
<div class="col-auto pl-1"> |
||||
|
<button type="reset" class="btn btn-secondary" (click)="cancel()">Cancel</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</form> |
||||
|
</div>`,
|
||||
|
changeDetection: ChangeDetectionStrategy.OnPush |
||||
|
}) |
||||
|
export class WorkflowAddFormComponent { |
||||
|
public addWorkflowForm = new AddWorkflowForm(this.formBuilder); |
||||
|
|
||||
|
constructor( |
||||
|
private readonly workflowsState: WorkflowsState, |
||||
|
private readonly formBuilder: FormBuilder |
||||
|
) { |
||||
|
} |
||||
|
|
||||
|
public addWorkflow() { |
||||
|
const value = this.addWorkflowForm.submit(); |
||||
|
|
||||
|
if (value) { |
||||
|
this.workflowsState.add(value.name) |
||||
|
.subscribe(() => { |
||||
|
this.addWorkflowForm.submitCompleted(); |
||||
|
}, error => { |
||||
|
this.addWorkflowForm.submitFailed(error); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public cancel() { |
||||
|
this.addWorkflowForm.submitCompleted(); |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue