Browse Source

Bulk edit for content.

pull/221/head
Sebastian Stehle 8 years ago
parent
commit
546b644658
  1. 18
      src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.html
  2. 38
      src/Squidex/app/features/administration/pages/users/users-page.component.html
  3. 59
      src/Squidex/app/features/content/pages/contents/contents-page.component.html
  4. 10
      src/Squidex/app/features/content/pages/contents/contents-page.component.scss
  5. 215
      src/Squidex/app/features/content/pages/contents/contents-page.component.ts
  6. 16
      src/Squidex/app/features/content/shared/content-item.component.html
  7. 9
      src/Squidex/app/features/content/shared/content-item.component.ts
  8. 7
      src/Squidex/app/features/content/shared/references-editor.component.html
  9. 8
      src/Squidex/app/features/content/shared/references-editor.component.ts
  10. 21
      src/Squidex/app/features/rules/pages/rules/rules-page.component.html
  11. 12
      src/Squidex/app/features/rules/pages/rules/rules-page.component.scss
  12. 77
      src/Squidex/app/features/settings/pages/clients/client.component.html
  13. 4
      src/Squidex/app/features/settings/pages/clients/client.component.scss
  14. 18
      src/Squidex/app/features/settings/pages/contributors/contributors-page.component.html
  15. 67
      src/Squidex/app/framework/angular/ignore-scrollbar.directive.ts
  16. 2
      src/Squidex/app/framework/angular/image-source.directive.ts
  17. 1
      src/Squidex/app/framework/declarations.ts
  18. 3
      src/Squidex/app/framework/module.ts
  19. 40
      src/Squidex/app/theme/_lists.scss
  20. 39
      src/Squidex/app/theme/_panels.scss

18
src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.html

@ -22,21 +22,15 @@
<div class="panel-main">
<div class="panel-content panel-content-scroll">
<table class="table table-items table-fixed">
<colgroup>
<col style="width: 50%" />
<col style="width: 50%" />
<col style="width: 160px" />
</colgroup>
<thead>
<tr>
<th>
<th class="cell-auto">
Name
</th>
<th class="text-right">
<th class="cell-auto-right">
Position
</th>
<th class="text-right">
<th class="cell-options-lg">
Actions
</th>
</tr>
@ -45,17 +39,17 @@
<tbody>
<ng-template ngFor let-eventConsumer [ngForOf]="eventConsumers">
<tr [class.faulted]="eventConsumer.error && eventConsumer.error.length > 0">
<td>
<td class="auto-auto">
<span class="truncate">
<i class="faulted-icon icon icon-bug" (click)="showError(eventConsumer)" [class.hidden]="!eventConsumer.error || eventConsumer.error.length === 0"></i>
{{eventConsumer.name}}
</span>
</td>
<td class="text-right">
<td class="cell-auto-right">
<span>{{eventConsumer.position}}</span>
</td>
<td class="text-right">
<td class="cell-options-lg">
<button class="btn btn-link" (click)="reset(eventConsumer)" *ngIf="!eventConsumer.isResetting" title="Reset Event Consumer">
<i class="icon icon-reset"></i>
</button>

38
src/Squidex/app/features/administration/pages/users/users-page.component.html

@ -30,45 +30,44 @@
</div>
<div class="panel-main">
<div class="panel-content panel-content-scroll">
<div class="panel-content grid">
<div class="grid-header">
<table class="table table-items table-fixed">
<colgroup>
<col style="width: 70px" />
<col style="width: 50%" />
<col style="width: 50%" />
<col style="width: 120px" />
</colgroup>
<thead>
<tr>
<th>
<th class="cell-user">
&nbsp;
</th>
<th>
<th class="cell-auto">
Name
</th>
<th>
<th class="cell-auto">
Email
</th>
<th>
<th class="cell-options">
Actions
</th>
</tr>
</thead>
</table>
</div>
<div class="grid-content">
<div sqxIgnoreScrollbar>
<table class="table table-items table-fixed">
<tbody>
<ng-template ngFor let-user [ngForOf]="usersItems">
<tr [routerLink]="user.id" routerLinkActive="active">
<td>
<td class="cell-user">
<img class="user-picture" [attr.title]="user.name" [attr.src]="user | sqxUserDtoPicture" />
</td>
<td>
<td class="cell-auto">
<span class="user-name table-cell">{{user.displayName}}</span>
</td>
<td>
<td class="cell-auto">
<span class="user-email table-cell">{{user.email}}</span>
</td>
<td class="text-right">
<td class="cell-options">
<span *ngIf="user.id !== ctx.userId">
<button class="btn btn-link" (click)="lock(user); $event.stopPropagation();" *ngIf="!user.isLocked" title="Lock User">
<i class="icon icon-unlocked"></i>
@ -77,14 +76,19 @@
<i class="icon icon-lock"></i>
</button>
</span>
<button *ngIf="user.id === ctx.userId" class="btn btn-link invisible">
&nbsp;
</button>
</td>
</tr>
<tr class="spacer"></tr>
</ng-template>
</tbody>
</table>
</div>
</div>
<div class="clearfix" *ngIf="usersPager.numberOfItems > 0">
<div class="grid-footer clearfix" *ngIf="usersPager.numberOfItems > 0">
<div class="float-right pagination">
<span class="pagination-text">{{usersPager.itemFirst}}-{{usersPager.itemLast}} of {{usersPager.numberOfItems}}</span>

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

@ -66,38 +66,69 @@
</div>
<div class="panel-main">
<div class="panel-content panel-content-scroll">
<div class="panel-content grid">
<div class="grid-header">
<table class="table table-items table-fixed" *ngIf="contentItems">
<colgroup>
<col *ngFor="let field of contentFields" [style.width]="columnWidth + '%'" />
<col style="width: 180px" />
<col style="width: 50px" />
<col style="width: 70px" *ngIf="!isReadOnly" />
</colgroup>
<thead>
<tr>
<th *ngFor="let field of contentFields">
<th class="cell-select" *ngIf="!isReadOnly">
<input type="checkbox" class="form-control" [ngModel]="isAllSelected" (ngModelChange)="selectAll($event)" />
</th>
<th class="cell-auto" *ngFor="let field of contentFields">
<span class="field">{{field | sqxDisplayName:'properties.label':'name'}}</span>
</th>
<th>
<th class="cell-time">
Updated
</th>
<th>
<th class="cell-user">
By
</th>
<th *ngIf="!isReadOnly">
<th class="cell-options" *ngIf="!isReadOnly">
Options
</th>
</tr>
</thead>
</table>
</div>
<div class="selection" *ngIf="selectionCount > 0">
{{selectionCount}} items selected:
<button class="btn btn-sm btn-link btn-default" (click)="publishSelected()" *ngIf="canPublish">
Publish
</button>
<button class="btn btn-sm btn-link btn-default" (click)="unpublishSelected()" *ngIf="canUnpublish">
Unublish
</button>
<button class="btn btn-sm btn-link btn-default" (click)="archiveSelected()" *ngIf="!isArchive">
Archive
</button>
<button class="btn btn-sm btn-link btn-default" (click)="restoreSelected()" *ngIf="isArchive">
Restore
</button>
<button class="btn btn-sm btn-link btn-danger"
(sqxConfirmClick)="deleteSelected()"
confirmTitle="Delete content"
confirmText="Do you really want to delete the selected content items?">
Delete
</button>
</div>
<div class="grid-content">
<div sqxIgnoreScrollbar>
<table class="table table-items table-fixed" *ngIf="contentItems" >
<tbody *ngIf="!isReadOnly">
<ng-template ngFor let-content [ngForOf]="contentItems">
<tr [sqxContent]="content" [routerLink]="[content.id]" routerLinkActive="active"
[languageCode]="languageSelected.iso2Code"
[schemaFields]="contentFields"
[schema]="schema"
[selected]="isItemSelected(content)"
(selectedChange)="selectItem(content, $event)"
(unpublishing)="unpublishContent(content)"
(publishing)="publishContent(content)"
(archiving)="archiveContent(content)"
@ -118,8 +149,10 @@
</ng-template>
</tbody>
</table>
</div>
</div>
<div class="clearfix" *ngIf="contentsPager.numberOfItems > 0">
<div class="grid-footer clearfix" *ngIf="contentsPager.numberOfItems > 0">
<div class="float-right pagination">
<span class="pagination-text">{{contentsPager.itemFirst}}-{{contentsPager.itemLast}} of {{contentsPager.numberOfItems}}</span>

10
src/Squidex/app/features/content/pages/contents/contents-page.component.scss

@ -9,10 +9,6 @@
font-size: .8rem;
}
.form-control {
width: 20rem;
}
.form-inline {
position: relative;
}
@ -28,3 +24,9 @@
font-weight: normal;
cursor: pointer !important;
}
.selection {
background: $color-table-footer;
border-bottom: 2px solid $color-border;
padding: .25 * $panel-padding $panel-padding;
}

215
src/Squidex/app/features/content/pages/contents/contents-page.component.ts

@ -7,7 +7,7 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import { Subscription } from 'rxjs';
import { Observable, Subscription } from 'rxjs';
import {
ContentCreated,
@ -52,15 +52,20 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
public contentsQuery = '';
public contentsPager = new Pager(0);
public selectedItems: { [id: string]: boolean; } = {};
public selectionCount = 0;
public canUnpublish = false;
public canPublish = false;
public languages: AppLanguageDto[] = [];
public languageSelected: AppLanguageDto;
public languageParameter: string;
public isAllSelected = false;
public isReadOnly = false;
public isArchive = false;
public columnWidth: number;
constructor(public readonly ctx: AppContext,
private readonly contentsService: ContentsService
) {
@ -113,64 +118,156 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
}
public publishContent(content: ContentDto) {
this.contentsService.publishContent(this.ctx.appName, this.schema.name, content.id, content.version)
.subscribe(dto => {
content = content.publish(this.ctx.userToken, dto.version);
this.publishContentItem(content).subscribe();
}
this.contentItems = this.contentItems.replaceBy('id', content);
public publishSelected() {
Observable.forkJoin(
this.contentItems.values
.filter(c => this.selectedItems[c.id])
.filter(c => c.status !== 'Published')
.map(c => this.publishContentItem(c)))
.finally(() => {
this.updateSelectionSummary();
})
.subscribe();
}
this.emitContentPublished(content);
}, error => {
private publishContentItem(content: ContentDto): Observable<any> {
return this.contentsService.publishContent(this.ctx.appName, this.schema.name, content.id, content.version)
.catch(error => {
this.ctx.notifyError(error);
return Observable.throw(error);
})
.do(dto => {
this.contentItems = this.contentItems.replaceBy('id', content.publish(this.ctx.userToken, dto.version));
this.emitContentPublished(content);
});
}
public unpublishContent(content: ContentDto) {
this.contentsService.unpublishContent(this.ctx.appName, this.schema.name, content.id, content.version)
.subscribe(dto => {
content = content.unpublish(this.ctx.userToken, dto.version);
this.unpublishContentItem(content).subscribe();
}
this.contentItems = this.contentItems.replaceBy('id', content);
public unpublishSelected() {
Observable.forkJoin(
this.contentItems.values
.filter(c => this.selectedItems[c.id])
.filter(c => c.status !== 'Unpublished')
.map(c => this.unpublishContentItem(c)))
.finally(() => {
this.updateSelectionSummary();
})
.subscribe();
}
this.emitContentUnpublished(content);
}, error => {
private unpublishContentItem(content: ContentDto): Observable<any> {
return this.contentsService.unpublishContent(this.ctx.appName, this.schema.name, content.id, content.version)
.catch(error => {
this.ctx.notifyError(error);
return Observable.throw(error);
})
.do(dto => {
this.contentItems = this.contentItems.replaceBy('id', content.unpublish(this.ctx.userToken, dto.version));
this.emitContentUnpublished(content);
});
}
public archiveSelected() {
Observable.forkJoin(
this.contentItems.values.filter(c => this.selectedItems[c.id])
.map(c => this.archiveContentItem(c)))
.finally(() => {
this.load();
})
.subscribe();
}
public archiveContent(content: ContentDto) {
this.contentsService.archiveContent(this.ctx.appName, this.schema.name, content.id, content.version)
.subscribe(dto => {
content = content.archive(this.ctx.userToken, dto.version);
this.archiveContentItem(content)
.finally(() => {
this.load();
})
.subscribe();
}
this.removeContent(content);
}, error => {
public archiveContentItem(content: ContentDto): Observable<any> {
return this.contentsService.archiveContent(this.ctx.appName, this.schema.name, content.id, content.version)
.catch(error => {
this.ctx.notifyError(error);
return Observable.throw(error);
});
}
public restoreSelected() {
Observable.forkJoin(
this.contentItems.values.filter(c => this.selectedItems[c.id])
.map(c => this.restoreContentItem(c)))
.finally(() => {
this.load();
})
.subscribe();
}
public restoreContent(content: ContentDto) {
this.contentsService.restoreContent(this.ctx.appName, this.schema.name, content.id, content.version)
.subscribe(dto => {
content = content.restore(this.ctx.userToken, dto.version);
this.restoreContentItem(content)
.finally(() => {
this.load();
})
.subscribe();
}
this.removeContent(content);
}, error => {
public restoreContentItem(content: ContentDto): Observable<any> {
return this.contentsService.restoreContent(this.ctx.appName, this.schema.name, content.id, content.version)
.catch(error => {
this.ctx.notifyError(error);
return Observable.throw(error);
});
}
public deleteSelected(content: ContentDto) {
Observable.forkJoin(
this.contentItems.values.filter(c => this.selectedItems[c.id])
.map(c => this.deleteContentItem(c)))
.finally(() => {
this.load();
})
.subscribe();
}
public deleteContent(content: ContentDto) {
this.contentsService.deleteContent(this.ctx.appName, this.schema.name, content.id, content.version)
.subscribe(() => {
this.removeContent(content);
}, error => {
this.deleteContentItem(content)
.finally(() => {
this.load();
})
.subscribe();
}
public deleteContentItem(content: ContentDto): Observable<any> {
return this.contentsService.deleteContent(this.ctx.appName, this.schema.name, content.id, content.version)
.do(() => {
this.emitContentRemoved(content);
})
.catch(error => {
this.ctx.notifyError(error);
return Observable.throw(error);
});
}
public load(showInfo = false) {
this.contentsService.getContents(this.ctx.appName, this.schema.name, this.contentsPager.pageSize, this.contentsPager.skip, this.contentsQuery, undefined, this.isArchive)
.finally(() => {
this.selectedItems = {};
this.updateSelectionSummary();
})
.subscribe(dtos => {
this.contentItems = ImmutableArray.of(dtos.items);
this.contentsPager = this.contentsPager.setCount(dtos.total);
@ -213,6 +310,51 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
this.load();
}
public isItemSelected(content: ContentDto): boolean {
return !!this.selectedItems[content.id];
}
public selectItem(content: ContentDto, isSelected: boolean) {
this.selectedItems[content.id] = isSelected;
this.updateSelectionSummary();
}
public selectAll(isSelected: boolean) {
this.selectedItems = {};
if (isSelected) {
for (let c of this.contentItems.values) {
this.selectedItems[c.id] = true;
}
}
this.updateSelectionSummary();
}
private updateSelectionSummary() {
this.isAllSelected = this.contentItems.length > 0;
this.selectionCount = 0;
this.canPublish = true;
this.canUnpublish = true;
for (let c of this.contentItems.values) {
if (this.selectedItems[c.id]) {
this.selectionCount++;
if (c.status !== 'Published') {
this.canUnpublish = false;
}
if (c.status === 'Published') {
this.canPublish = false;
}
} else {
this.isAllSelected = false;
}
}
}
public selectLanguage(language: AppLanguageDto) {
this.languageSelected = language;
}
@ -234,17 +376,12 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
this.contentsQuery = '';
this.contentsFilter.setValue('');
this.contentsPager = new Pager(0);
this.selectedItems = {};
this.updateSelectionSummary();
this.loadFields();
}
private removeContent(content: ContentDto) {
this.contentItems = this.contentItems.removeAll(x => x.id === content.id);
this.contentsPager = this.contentsPager.decrementCount();
this.emitContentRemoved(content);
}
private loadFields() {
this.contentFields = this.schema.fields.filter(x => x.properties.isListField);
@ -255,12 +392,6 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
if (this.contentFields.length === 0) {
this.contentFields = [<any>{}];
}
if (this.contentFields.length > 0) {
this.columnWidth = 100 / this.contentFields.length;
} else {
this.columnWidth = 100;
}
}
}

16
src/Squidex/app/features/content/shared/content-item.component.html

@ -1,17 +1,23 @@
<td *ngFor="let value of values">
<td class="cell-select" *ngIf="!isReadOnly">
<input type="checkbox" class="form-control"
[ngModel]="selected"
(ngModelChange)="selectedChange.emit($event);"
(click)="$event.stopPropagation()" />
</td>
<td class="cell-auto" *ngFor="let value of values">
<span class="table-cell">
{{value}}
</span>
</td>
<td>
<td class="cell-time">
<span class="item-published" [class.unpublished]="content.status !== 'Published'"></span>
<small class="item-modified">{{content.lastModified | sqxFromNow}}</small>
</td>
<td>
<td class="cell-user">
<img class="user-picture" [attr.title]="content.lastModifiedBy | sqxUserNameRef" [attr.src]="content.lastModifiedBy | sqxUserPictureRef" />
</td>
<td *ngIf="!isReadOnly">
<td class="cell-options" *ngIf="!isReadOnly">
<div class="dropdown dropdown-options" *ngIf="content">
<button type="button" class="btn btn-link btn-decent" (click)="dropdown.toggle(); $event.stopPropagation()" [class.active]="dropdown.isOpen | async" #optionsButton>
<i class="icon-dots"></i>
@ -38,7 +44,7 @@
</div>
</div>
</td>
<td *ngIf="isReference">
<td class="cell-options" *ngIf="isReference">
<button type="button" class="btn btn-link btn-secondary" (click)="deleting.emit(); $event.stopPropagation()">
<i class="icon-close"></i>
</button>

9
src/Squidex/app/features/content/shared/content-item.component.ts

@ -45,6 +45,15 @@ export class ContentItemComponent implements OnInit, OnChanges {
@Output()
public deleting = new EventEmitter();
@Output()
public selectedChange = new EventEmitter<boolean>();
@Input()
public selected = false;
@Input()
public columnWidth: number;
@Input()
public languageCode: string;

7
src/Squidex/app/features/content/shared/references-editor.component.html

@ -10,13 +10,6 @@
</div>
<table class="table table-items table-fixed" [class.disabled]="isDisabled" *ngIf="contentItems && contentItems.length > 0">
<colgroup>
<col *ngFor="let field of contentFields" [style.width]="columnWidth + '%'" />
<col style="width: 180px" />
<col style="width: 50px" />
<col style="width: 70px" />
</colgroup>
<tbody dnd-sortable-container [sortableData]="contentItems.mutableValues">
<ng-template ngFor let-content let-i="index" [ngForOf]="contentItems">
<tr [sqxContent]="content" dnd-sortable [sortableIndex]="i" (sqxSorted)="onContentsSorted($event)"

8
src/Squidex/app/features/content/shared/references-editor.component.ts

@ -50,8 +50,6 @@ export class ReferencesEditorComponent implements ControlValueAccessor, OnInit {
public contentItems = ImmutableArray.empty<ContentDto>();
public contentFields: FieldDto[];
public columnWidth: number;
public isDisabled = false;
public isInvalidSchema = false;
@ -154,11 +152,5 @@ export class ReferencesEditorComponent implements ControlValueAccessor, OnInit {
if (this.contentFields.length === 0) {
this.contentFields = [<any>{}];
}
if (this.contentFields.length > 0) {
this.columnWidth = 100 / this.contentFields.length;
} else {
this.columnWidth = 100;
}
}
}

21
src/Squidex/app/features/rules/pages/rules/rules-page.component.html

@ -31,22 +31,13 @@
</div>
<table class="table table-items table-fixed" *ngIf="rules && rules.length > 0">
<colgroup>
<col style="width: 40px" />
<col style="width: 50%" />
<col style="width: 50px" />
<col style="width: 50%" />
<col style="width: 50px" />
<col style="width: 70px" />
</colgroup>
<tbody>
<ng-template ngFor let-rule [ngForOf]="rules">
<tr>
<td class="step-if">
<td class="cell-separator">
<h3>If</h3>
</td>
<td>
<td class="cell-auto">
<span class="rule-element rule-element-{{rule.triggerType}}" (click)="editTrigger(rule)">
<span class="rule-element-icon">
<i class="icon-trigger-{{rule.triggerType}}"></i>
@ -56,10 +47,10 @@
</span>
</span>
</td>
<td class="step-then">
<td class="cell-separator">
<h3>then</h3>
</td>
<td>
<td class="cell-auto">
<span class="rule-element rule-element-{{rule.actionType}}" (click)="editAction(rule)">
<span class="rule-element-icon">
<i class="icon-action-{{rule.actionType}}"></i>
@ -69,10 +60,10 @@
</span>
</span>
</td>
<td>
<td class="cell-options">
<sqx-toggle [ngModel]="rule.isEnabled" (ngModelChange)="toggleRule(rule)"></sqx-toggle>
</td>
<td>
<td class="cell-options">
<button type="button" class="btn btn-link btn-danger"
(sqxConfirmClick)="deleteRule(rule)"
confirmTitle="Delete rule"

12
src/Squidex/app/features/rules/pages/rules/rules-page.component.scss

@ -12,15 +12,3 @@ sqx-toggle {
}
}
}
.step-if {
padding-left: 1.25rem;
padding-right: 0;
text-align: left;
}
.step-then {
padding-left: 0;
padding-right: 0;
text-align: center;
}

77
src/Squidex/app/features/settings/pages/clients/client.component.html

@ -1,17 +1,6 @@
<div class="table-items-row">
<table class="table table-middle table-sm table-borderless table-fixed client-info">
<colgroup>
<col style="width: 160px; text-align: right;" />
<col style="width: 100%" />
<col style="width: 40px" />
</colgroup>
<tr>
<td colspan="2">
<div class="float-right">
<button class="btn btn-secondary" (click)="createToken(client)">Create Token</button>
</div>
<div class="row no-gutters">
<div class="col">
<div class="client-header">
<form *ngIf="isRenaming" class="form-inline" [formGroup]="renameForm" (ngSubmit)="renaming.emit(renameForm.controls.name.value)">
<div class="form-group mr-1">
@ -37,47 +26,55 @@
</div>
<div class="client-expires">Access tokens expire after 30 days</div>
</td>
<td class="client-delete">
</div>
<div class="col col-auto">
<button class="btn btn-secondary" (click)="createToken(client)">Create Token</button>
</div>
<div class="col col-auto cell-options">
<button type="button" class="btn btn-link btn-danger"
(sqxConfirmClick)="revoking.emit()"
confirmTitle="Revoke client"
confirmText="Do you really want to revoke the client?">
<i class="icon-bin2"></i>
</button>
</td>
</tr>
<tr>
<td>Client Id:</td>
<td>
</div>
</div>
<div class="row no-gutters form-group">
<div class="col-4 col-form-label">
Client Id:
</div>
<div class="col">
<input readonly class="form-control" value="{{ctx.appName}}:{{client.id}}" #inputName />
</td>
<td>
</div>
<div class="col col-auto cell-options">
<button type="button" class="btn btn-primary btn-link" [sqxCopy]="inputName">
<i class="icon-copy"></i>
</button>
</td>
</tr>
<tr>
<td>Client Secret:</td>
<td>
</div>
</div>
<div class="row no-gutters form-group">
<div class="col-4 col-form-label">
Client Secret:
</div>
<div class="col">
<input readonly class="form-control" [attr.value]="client.secret" #inputSecret />
</td>
<td>
</div>
<div class="col col-auto cell-options">
<button type="button" class="btn btn-primary btn-link" [sqxCopy]="inputSecret">
<i class="icon-copy"></i>
</button>
</td>
</tr>
<tr>
<td>Permission</td>
<td>
<select class="form-control" [ngModel]="client.permission" (ngModelChange)="updating.emit($event)">
<option *ngFor="let permission of clientPermissions" [ngValue]="permission">{{permission}}</option>
</select>
</td>
</tr>
</table>
</div>
</div>
<div class="row no-gutters">
<div class="col-4 col-form-label">
Permission:
</div>
<div class="col">
<input readonly class="form-control" value="{{ctx.appName}}:{{client.id}}" #inputName />
</div>
<div class="col col-auto cell-options">
</div>
</div>
</div>
<div class="modal" *sqxModalView="tokenDialog;onRoot:true" @fade>

4
src/Squidex/app/features/settings/pages/clients/client.component.scss

@ -66,6 +66,10 @@ $color-editor: #eceeef;
}
}
.col-form-label {
text-align: left;
}
.btn-cancel {
padding: .4rem;
}

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

@ -18,32 +18,24 @@
</div>
<table class="table table-items table-fixed">
<colgroup>
<col style="width: 70px" />
<col style="width: 50%" />
<col style="width: 50%" />
<col style="width: 160px" />
<col style="width: 80px" />
</colgroup>
<tbody>
<ng-template ngFor let-contributor [ngForOf]="appContributors?.contributors">
<tr>
<td>
<td class="cell-user">
<img class="user-picture" [attr.title]="contributor.contributorId | sqxUserName" [attr.src]="contributor.contributorId | sqxUserPicture" />
</td>
<td>
<td class="cell-auto">
<span class="user-name table-cell">{{contributor.contributorId | sqxUserName}}</span>
</td>
<td>
<td class="cell-auto">
<span class="user-email table-cell">{{contributor.contributorId | sqxUserEmail}}</span>
</td>
<td>
<td class="cell-time">
<select class="form-control" [ngModel]="contributor.permission" (ngModelChange)="changePermission(contributor, $event)" [disabled]="userId === contributor.contributorId">
<option *ngFor="let permission of usersPermissions" [ngValue]="permission">{{permission}}</option>
</select>
</td>
<td>
<td class="cell-options">
<button type="button" class="btn btn-link btn-danger" [disabled]="userId === contributor.contributorId" (click)="removeContributor(contributor)">
<i class="icon-bin2"></i>
</button>

67
src/Squidex/app/framework/angular/ignore-scrollbar.directive.ts

@ -0,0 +1,67 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { AfterViewInit, Directive, ElementRef, OnDestroy, OnInit, Renderer } from '@angular/core';
@Directive({
selector: '[sqxIgnoreScrollbar]'
})
export class IgnoreScrollbarDirective implements OnDestroy, OnInit, AfterViewInit {
private resizeListener: Function;
private parent: any;
private checkTimer: any;
private scollbarWidth = 0;
constructor(
private readonly element: ElementRef,
private readonly renderer: Renderer
) {
}
public ngOnDestroy() {
clearTimeout(this.checkTimer);
this.resizeListener();
}
public ngOnInit() {
if (!this.parent) {
this.parent = this.element.nativeElement.parentElement;
}
this.resizeListener =
this.renderer.listen(this.element.nativeElement, 'resize', () => {
this.reposition();
});
this.checkTimer =
setTimeout(() => {
this.reposition();
}, 100);
}
public ngAfterViewInit() {
this.reposition();
}
private reposition() {
if (!this.parent) {
return;
}
const parentOuter = this.parent.offsetWidth;
const parentInner = this.parent.clientWidth;
const scrollbarWidth = parentOuter - parentInner;
if (scrollbarWidth !== this.scollbarWidth) {
this.scollbarWidth = scrollbarWidth;
this.renderer.setElementStyle(this.element.nativeElement, 'marginRight', `-${scrollbarWidth}px`);
}
}
}

2
src/Squidex/app/framework/angular/image-source.directive.ts

@ -42,7 +42,7 @@ export class ImageSourceDirective implements OnChanges, OnDestroy, OnInit, After
}
public ngOnInit() {
if (this.parent === null) {
if (!this.parent) {
this.parent = this.element.nativeElement.parentElement;
}

1
src/Squidex/app/framework/declarations.ts

@ -18,6 +18,7 @@ export * from './angular/dropdown.component';
export * from './angular/file-drop.directive';
export * from './angular/focus-on-init.directive';
export * from './angular/http-extensions-impl';
export * from './angular/ignore-scrollbar.directive';
export * from './angular/image-source.directive';
export * from './angular/indeterminate-value.directive';
export * from './angular/jscript-editor.component';

3
src/Squidex/app/framework/module.ts

@ -32,6 +32,7 @@ import {
FileSizePipe,
FocusOnInitDirective,
FromNowPipe,
IgnoreScrollbarDirective,
ImageSourceDirective,
IndeterminateValueDirective,
JscriptEditorComponent,
@ -97,6 +98,7 @@ import {
FileSizePipe,
FocusOnInitDirective,
FromNowPipe,
IgnoreScrollbarDirective,
ImageSourceDirective,
IndeterminateValueDirective,
JscriptEditorComponent,
@ -146,6 +148,7 @@ import {
FileSizePipe,
FocusOnInitDirective,
FromNowPipe,
IgnoreScrollbarDirective,
ImageSourceDirective,
IndeterminateValueDirective,
JscriptEditorComponent,

40
src/Squidex/app/theme/_lists.scss

@ -177,6 +177,46 @@
}
}
//
// Cell styles
//
.cell {
&-select {
width: 50px;
}
&-separator {
width: 60px;
}
&-user {
width: 55px;
}
&-time {
width: 180px;
}
&-options {
width: 70px;
}
&-options-lg {
width: 150px;
}
&-separator,
&-select {
text-align: center;
}
&-auto-right,
&-options,
&-options-lg {
text-align: right;
}
}
//
// Table cell with truncated content.
//

39
src/Squidex/app/theme/_panels.scss

@ -269,3 +269,42 @@
}
}
}
//
// Grid with footer and header
//
.grid {
& {
@include flex-box;
@include flex-direction(column);
overflow: hidden;
padding: 0;
}
.grid-content,
.grid-header,
.grid-footer {
padding: 0 $panel-padding;
}
.grid-content {
@include flex-grow(1);
margin: 0;
margin-top: .25rem;
overflow-y: scroll;
}
.grid-header {
padding-top: .75 * $panel-padding;
border: 0;
border-bottom: 2px solid $color-border;
}
.grid-footer {
border-top: 2px solid $color-border;
}
.pagination {
margin-top: .25rem;
}
}
Loading…
Cancel
Save