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

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

@ -30,45 +30,44 @@
</div> </div>
<div class="panel-main"> <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"> <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> <thead>
<tr> <tr>
<th> <th class="cell-user">
&nbsp; &nbsp;
</th> </th>
<th> <th class="cell-auto">
Name Name
</th> </th>
<th> <th class="cell-auto">
Email Email
</th> </th>
<th> <th class="cell-options">
Actions Actions
</th> </th>
</tr> </tr>
</thead> </thead>
</table>
</div>
<div class="grid-content">
<div sqxIgnoreScrollbar>
<table class="table table-items table-fixed">
<tbody> <tbody>
<ng-template ngFor let-user [ngForOf]="usersItems"> <ng-template ngFor let-user [ngForOf]="usersItems">
<tr [routerLink]="user.id" routerLinkActive="active"> <tr [routerLink]="user.id" routerLinkActive="active">
<td> <td class="cell-user">
<img class="user-picture" [attr.title]="user.name" [attr.src]="user | sqxUserDtoPicture" /> <img class="user-picture" [attr.title]="user.name" [attr.src]="user | sqxUserDtoPicture" />
</td> </td>
<td> <td class="cell-auto">
<span class="user-name table-cell">{{user.displayName}}</span> <span class="user-name table-cell">{{user.displayName}}</span>
</td> </td>
<td> <td class="cell-auto">
<span class="user-email table-cell">{{user.email}}</span> <span class="user-email table-cell">{{user.email}}</span>
</td> </td>
<td class="text-right"> <td class="cell-options">
<span *ngIf="user.id !== ctx.userId"> <span *ngIf="user.id !== ctx.userId">
<button class="btn btn-link" (click)="lock(user); $event.stopPropagation();" *ngIf="!user.isLocked" title="Lock User"> <button class="btn btn-link" (click)="lock(user); $event.stopPropagation();" *ngIf="!user.isLocked" title="Lock User">
<i class="icon icon-unlocked"></i> <i class="icon icon-unlocked"></i>
@ -77,14 +76,19 @@
<i class="icon icon-lock"></i> <i class="icon icon-lock"></i>
</button> </button>
</span> </span>
<button *ngIf="user.id === ctx.userId" class="btn btn-link invisible">
&nbsp;
</button>
</td> </td>
</tr> </tr>
<tr class="spacer"></tr> <tr class="spacer"></tr>
</ng-template> </ng-template>
</tbody> </tbody>
</table> </table>
</div>
</div>
<div class="clearfix" *ngIf="usersPager.numberOfItems > 0"> <div class="grid-footer clearfix" *ngIf="usersPager.numberOfItems > 0">
<div class="float-right pagination"> <div class="float-right pagination">
<span class="pagination-text">{{usersPager.itemFirst}}-{{usersPager.itemLast}} of {{usersPager.numberOfItems}}</span> <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>
<div class="panel-main"> <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"> <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> <thead>
<tr> <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> <span class="field">{{field | sqxDisplayName:'properties.label':'name'}}</span>
</th> </th>
<th> <th class="cell-time">
Updated Updated
</th> </th>
<th> <th class="cell-user">
By By
</th> </th>
<th *ngIf="!isReadOnly"> <th class="cell-options" *ngIf="!isReadOnly">
Options Options
</th> </th>
</tr> </tr>
</thead> </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"> <tbody *ngIf="!isReadOnly">
<ng-template ngFor let-content [ngForOf]="contentItems"> <ng-template ngFor let-content [ngForOf]="contentItems">
<tr [sqxContent]="content" [routerLink]="[content.id]" routerLinkActive="active" <tr [sqxContent]="content" [routerLink]="[content.id]" routerLinkActive="active"
[languageCode]="languageSelected.iso2Code" [languageCode]="languageSelected.iso2Code"
[schemaFields]="contentFields" [schemaFields]="contentFields"
[schema]="schema" [schema]="schema"
[selected]="isItemSelected(content)"
(selectedChange)="selectItem(content, $event)"
(unpublishing)="unpublishContent(content)" (unpublishing)="unpublishContent(content)"
(publishing)="publishContent(content)" (publishing)="publishContent(content)"
(archiving)="archiveContent(content)" (archiving)="archiveContent(content)"
@ -118,8 +149,10 @@
</ng-template> </ng-template>
</tbody> </tbody>
</table> </table>
</div>
</div>
<div class="clearfix" *ngIf="contentsPager.numberOfItems > 0"> <div class="grid-footer clearfix" *ngIf="contentsPager.numberOfItems > 0">
<div class="float-right pagination"> <div class="float-right pagination">
<span class="pagination-text">{{contentsPager.itemFirst}}-{{contentsPager.itemLast}} of {{contentsPager.numberOfItems}}</span> <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; font-size: .8rem;
} }
.form-control {
width: 20rem;
}
.form-inline { .form-inline {
position: relative; position: relative;
} }
@ -28,3 +24,9 @@
font-weight: normal; font-weight: normal;
cursor: pointer !important; 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 { Component, OnDestroy, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms'; import { FormControl } from '@angular/forms';
import { Subscription } from 'rxjs'; import { Observable, Subscription } from 'rxjs';
import { import {
ContentCreated, ContentCreated,
@ -52,15 +52,20 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
public contentsQuery = ''; public contentsQuery = '';
public contentsPager = new Pager(0); public contentsPager = new Pager(0);
public selectedItems: { [id: string]: boolean; } = {};
public selectionCount = 0;
public canUnpublish = false;
public canPublish = false;
public languages: AppLanguageDto[] = []; public languages: AppLanguageDto[] = [];
public languageSelected: AppLanguageDto; public languageSelected: AppLanguageDto;
public languageParameter: string; public languageParameter: string;
public isAllSelected = false;
public isReadOnly = false; public isReadOnly = false;
public isArchive = false; public isArchive = false;
public columnWidth: number;
constructor(public readonly ctx: AppContext, constructor(public readonly ctx: AppContext,
private readonly contentsService: ContentsService private readonly contentsService: ContentsService
) { ) {
@ -113,64 +118,156 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
} }
public publishContent(content: ContentDto) { public publishContent(content: ContentDto) {
this.contentsService.publishContent(this.ctx.appName, this.schema.name, content.id, content.version) this.publishContentItem(content).subscribe();
.subscribe(dto => { }
content = content.publish(this.ctx.userToken, dto.version);
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); private publishContentItem(content: ContentDto): Observable<any> {
}, error => { return this.contentsService.publishContent(this.ctx.appName, this.schema.name, content.id, content.version)
.catch(error => {
this.ctx.notifyError(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) { public unpublishContent(content: ContentDto) {
this.contentsService.unpublishContent(this.ctx.appName, this.schema.name, content.id, content.version) this.unpublishContentItem(content).subscribe();
.subscribe(dto => { }
content = content.unpublish(this.ctx.userToken, dto.version);
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); private unpublishContentItem(content: ContentDto): Observable<any> {
}, error => { return this.contentsService.unpublishContent(this.ctx.appName, this.schema.name, content.id, content.version)
.catch(error => {
this.ctx.notifyError(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) { public archiveContent(content: ContentDto) {
this.contentsService.archiveContent(this.ctx.appName, this.schema.name, content.id, content.version) this.archiveContentItem(content)
.subscribe(dto => { .finally(() => {
content = content.archive(this.ctx.userToken, dto.version); this.load();
})
.subscribe();
}
this.removeContent(content); public archiveContentItem(content: ContentDto): Observable<any> {
}, error => { return this.contentsService.archiveContent(this.ctx.appName, this.schema.name, content.id, content.version)
.catch(error => {
this.ctx.notifyError(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) { public restoreContent(content: ContentDto) {
this.contentsService.restoreContent(this.ctx.appName, this.schema.name, content.id, content.version) this.restoreContentItem(content)
.subscribe(dto => { .finally(() => {
content = content.restore(this.ctx.userToken, dto.version); this.load();
})
.subscribe();
}
this.removeContent(content); public restoreContentItem(content: ContentDto): Observable<any> {
}, error => { return this.contentsService.restoreContent(this.ctx.appName, this.schema.name, content.id, content.version)
.catch(error => {
this.ctx.notifyError(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) { public deleteContent(content: ContentDto) {
this.contentsService.deleteContent(this.ctx.appName, this.schema.name, content.id, content.version) this.deleteContentItem(content)
.subscribe(() => { .finally(() => {
this.removeContent(content); this.load();
}, error => { })
.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); this.ctx.notifyError(error);
return Observable.throw(error);
}); });
} }
public load(showInfo = false) { public load(showInfo = false) {
this.contentsService.getContents(this.ctx.appName, this.schema.name, this.contentsPager.pageSize, this.contentsPager.skip, this.contentsQuery, undefined, this.isArchive) 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 => { .subscribe(dtos => {
this.contentItems = ImmutableArray.of(dtos.items); this.contentItems = ImmutableArray.of(dtos.items);
this.contentsPager = this.contentsPager.setCount(dtos.total); this.contentsPager = this.contentsPager.setCount(dtos.total);
@ -213,6 +310,51 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
this.load(); 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) { public selectLanguage(language: AppLanguageDto) {
this.languageSelected = language; this.languageSelected = language;
} }
@ -234,17 +376,12 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
this.contentsQuery = ''; this.contentsQuery = '';
this.contentsFilter.setValue(''); this.contentsFilter.setValue('');
this.contentsPager = new Pager(0); this.contentsPager = new Pager(0);
this.selectedItems = {};
this.updateSelectionSummary();
this.loadFields(); 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() { private loadFields() {
this.contentFields = this.schema.fields.filter(x => x.properties.isListField); 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) { if (this.contentFields.length === 0) {
this.contentFields = [<any>{}]; 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"> <span class="table-cell">
{{value}} {{value}}
</span> </span>
</td> </td>
<td> <td class="cell-time">
<span class="item-published" [class.unpublished]="content.status !== 'Published'"></span> <span class="item-published" [class.unpublished]="content.status !== 'Published'"></span>
<small class="item-modified">{{content.lastModified | sqxFromNow}}</small> <small class="item-modified">{{content.lastModified | sqxFromNow}}</small>
</td> </td>
<td> <td class="cell-user">
<img class="user-picture" [attr.title]="content.lastModifiedBy | sqxUserNameRef" [attr.src]="content.lastModifiedBy | sqxUserPictureRef" /> <img class="user-picture" [attr.title]="content.lastModifiedBy | sqxUserNameRef" [attr.src]="content.lastModifiedBy | sqxUserPictureRef" />
</td> </td>
<td *ngIf="!isReadOnly"> <td class="cell-options" *ngIf="!isReadOnly">
<div class="dropdown dropdown-options" *ngIf="content"> <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> <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> <i class="icon-dots"></i>
@ -38,7 +44,7 @@
</div> </div>
</div> </div>
</td> </td>
<td *ngIf="isReference"> <td class="cell-options" *ngIf="isReference">
<button type="button" class="btn btn-link btn-secondary" (click)="deleting.emit(); $event.stopPropagation()"> <button type="button" class="btn btn-link btn-secondary" (click)="deleting.emit(); $event.stopPropagation()">
<i class="icon-close"></i> <i class="icon-close"></i>
</button> </button>

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

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

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

@ -10,13 +10,6 @@
</div> </div>
<table class="table table-items table-fixed" [class.disabled]="isDisabled" *ngIf="contentItems && contentItems.length > 0"> <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"> <tbody dnd-sortable-container [sortableData]="contentItems.mutableValues">
<ng-template ngFor let-content let-i="index" [ngForOf]="contentItems"> <ng-template ngFor let-content let-i="index" [ngForOf]="contentItems">
<tr [sqxContent]="content" dnd-sortable [sortableIndex]="i" (sqxSorted)="onContentsSorted($event)" <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 contentItems = ImmutableArray.empty<ContentDto>();
public contentFields: FieldDto[]; public contentFields: FieldDto[];
public columnWidth: number;
public isDisabled = false; public isDisabled = false;
public isInvalidSchema = false; public isInvalidSchema = false;
@ -154,11 +152,5 @@ export class ReferencesEditorComponent implements ControlValueAccessor, OnInit {
if (this.contentFields.length === 0) { if (this.contentFields.length === 0) {
this.contentFields = [<any>{}]; 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> </div>
<table class="table table-items table-fixed" *ngIf="rules && rules.length > 0"> <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> <tbody>
<ng-template ngFor let-rule [ngForOf]="rules"> <ng-template ngFor let-rule [ngForOf]="rules">
<tr> <tr>
<td class="step-if"> <td class="cell-separator">
<h3>If</h3> <h3>If</h3>
</td> </td>
<td> <td class="cell-auto">
<span class="rule-element rule-element-{{rule.triggerType}}" (click)="editTrigger(rule)"> <span class="rule-element rule-element-{{rule.triggerType}}" (click)="editTrigger(rule)">
<span class="rule-element-icon"> <span class="rule-element-icon">
<i class="icon-trigger-{{rule.triggerType}}"></i> <i class="icon-trigger-{{rule.triggerType}}"></i>
@ -56,10 +47,10 @@
</span> </span>
</span> </span>
</td> </td>
<td class="step-then"> <td class="cell-separator">
<h3>then</h3> <h3>then</h3>
</td> </td>
<td> <td class="cell-auto">
<span class="rule-element rule-element-{{rule.actionType}}" (click)="editAction(rule)"> <span class="rule-element rule-element-{{rule.actionType}}" (click)="editAction(rule)">
<span class="rule-element-icon"> <span class="rule-element-icon">
<i class="icon-action-{{rule.actionType}}"></i> <i class="icon-action-{{rule.actionType}}"></i>
@ -69,10 +60,10 @@
</span> </span>
</span> </span>
</td> </td>
<td> <td class="cell-options">
<sqx-toggle [ngModel]="rule.isEnabled" (ngModelChange)="toggleRule(rule)"></sqx-toggle> <sqx-toggle [ngModel]="rule.isEnabled" (ngModelChange)="toggleRule(rule)"></sqx-toggle>
</td> </td>
<td> <td class="cell-options">
<button type="button" class="btn btn-link btn-danger" <button type="button" class="btn btn-link btn-danger"
(sqxConfirmClick)="deleteRule(rule)" (sqxConfirmClick)="deleteRule(rule)"
confirmTitle="Delete 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"> <div class="table-items-row">
<table class="table table-middle table-sm table-borderless table-fixed client-info"> <div class="row no-gutters">
<colgroup> <div class="col">
<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="client-header"> <div class="client-header">
<form *ngIf="isRenaming" class="form-inline" [formGroup]="renameForm" (ngSubmit)="renaming.emit(renameForm.controls.name.value)"> <form *ngIf="isRenaming" class="form-inline" [formGroup]="renameForm" (ngSubmit)="renaming.emit(renameForm.controls.name.value)">
<div class="form-group mr-1"> <div class="form-group mr-1">
@ -37,47 +26,55 @@
</div> </div>
<div class="client-expires">Access tokens expire after 30 days</div> <div class="client-expires">Access tokens expire after 30 days</div>
</td> </div>
<td class="client-delete"> <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" <button type="button" class="btn btn-link btn-danger"
(sqxConfirmClick)="revoking.emit()" (sqxConfirmClick)="revoking.emit()"
confirmTitle="Revoke client" confirmTitle="Revoke client"
confirmText="Do you really want to revoke the client?"> confirmText="Do you really want to revoke the client?">
<i class="icon-bin2"></i> <i class="icon-bin2"></i>
</button> </button>
</td> </div>
</tr> </div>
<tr> <div class="row no-gutters form-group">
<td>Client Id:</td> <div class="col-4 col-form-label">
<td> Client Id:
</div>
<div class="col">
<input readonly class="form-control" value="{{ctx.appName}}:{{client.id}}" #inputName /> <input readonly class="form-control" value="{{ctx.appName}}:{{client.id}}" #inputName />
</td> </div>
<td> <div class="col col-auto cell-options">
<button type="button" class="btn btn-primary btn-link" [sqxCopy]="inputName"> <button type="button" class="btn btn-primary btn-link" [sqxCopy]="inputName">
<i class="icon-copy"></i> <i class="icon-copy"></i>
</button> </button>
</td> </div>
</tr> </div>
<tr> <div class="row no-gutters form-group">
<td>Client Secret:</td> <div class="col-4 col-form-label">
<td> Client Secret:
</div>
<div class="col">
<input readonly class="form-control" [attr.value]="client.secret" #inputSecret /> <input readonly class="form-control" [attr.value]="client.secret" #inputSecret />
</td> </div>
<td> <div class="col col-auto cell-options">
<button type="button" class="btn btn-primary btn-link" [sqxCopy]="inputSecret"> <button type="button" class="btn btn-primary btn-link" [sqxCopy]="inputSecret">
<i class="icon-copy"></i> <i class="icon-copy"></i>
</button> </button>
</td> </div>
</tr> </div>
<tr> <div class="row no-gutters">
<td>Permission</td> <div class="col-4 col-form-label">
<td> Permission:
<select class="form-control" [ngModel]="client.permission" (ngModelChange)="updating.emit($event)"> </div>
<option *ngFor="let permission of clientPermissions" [ngValue]="permission">{{permission}}</option> <div class="col">
</select> <input readonly class="form-control" value="{{ctx.appName}}:{{client.id}}" #inputName />
</td> </div>
</tr> <div class="col col-auto cell-options">
</table> </div>
</div>
</div> </div>
<div class="modal" *sqxModalView="tokenDialog;onRoot:true" @fade> <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 { .btn-cancel {
padding: .4rem; padding: .4rem;
} }

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

@ -18,32 +18,24 @@
</div> </div>
<table class="table table-items table-fixed"> <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> <tbody>
<ng-template ngFor let-contributor [ngForOf]="appContributors?.contributors"> <ng-template ngFor let-contributor [ngForOf]="appContributors?.contributors">
<tr> <tr>
<td> <td class="cell-user">
<img class="user-picture" [attr.title]="contributor.contributorId | sqxUserName" [attr.src]="contributor.contributorId | sqxUserPicture" /> <img class="user-picture" [attr.title]="contributor.contributorId | sqxUserName" [attr.src]="contributor.contributorId | sqxUserPicture" />
</td> </td>
<td> <td class="cell-auto">
<span class="user-name table-cell">{{contributor.contributorId | sqxUserName}}</span> <span class="user-name table-cell">{{contributor.contributorId | sqxUserName}}</span>
</td> </td>
<td> <td class="cell-auto">
<span class="user-email table-cell">{{contributor.contributorId | sqxUserEmail}}</span> <span class="user-email table-cell">{{contributor.contributorId | sqxUserEmail}}</span>
</td> </td>
<td> <td class="cell-time">
<select class="form-control" [ngModel]="contributor.permission" (ngModelChange)="changePermission(contributor, $event)" [disabled]="userId === contributor.contributorId"> <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> <option *ngFor="let permission of usersPermissions" [ngValue]="permission">{{permission}}</option>
</select> </select>
</td> </td>
<td> <td class="cell-options">
<button type="button" class="btn btn-link btn-danger" [disabled]="userId === contributor.contributorId" (click)="removeContributor(contributor)"> <button type="button" class="btn btn-link btn-danger" [disabled]="userId === contributor.contributorId" (click)="removeContributor(contributor)">
<i class="icon-bin2"></i> <i class="icon-bin2"></i>
</button> </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() { public ngOnInit() {
if (this.parent === null) { if (!this.parent) {
this.parent = this.element.nativeElement.parentElement; 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/file-drop.directive';
export * from './angular/focus-on-init.directive'; export * from './angular/focus-on-init.directive';
export * from './angular/http-extensions-impl'; export * from './angular/http-extensions-impl';
export * from './angular/ignore-scrollbar.directive';
export * from './angular/image-source.directive'; export * from './angular/image-source.directive';
export * from './angular/indeterminate-value.directive'; export * from './angular/indeterminate-value.directive';
export * from './angular/jscript-editor.component'; export * from './angular/jscript-editor.component';

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

@ -32,6 +32,7 @@ import {
FileSizePipe, FileSizePipe,
FocusOnInitDirective, FocusOnInitDirective,
FromNowPipe, FromNowPipe,
IgnoreScrollbarDirective,
ImageSourceDirective, ImageSourceDirective,
IndeterminateValueDirective, IndeterminateValueDirective,
JscriptEditorComponent, JscriptEditorComponent,
@ -97,6 +98,7 @@ import {
FileSizePipe, FileSizePipe,
FocusOnInitDirective, FocusOnInitDirective,
FromNowPipe, FromNowPipe,
IgnoreScrollbarDirective,
ImageSourceDirective, ImageSourceDirective,
IndeterminateValueDirective, IndeterminateValueDirective,
JscriptEditorComponent, JscriptEditorComponent,
@ -146,6 +148,7 @@ import {
FileSizePipe, FileSizePipe,
FocusOnInitDirective, FocusOnInitDirective,
FromNowPipe, FromNowPipe,
IgnoreScrollbarDirective,
ImageSourceDirective, ImageSourceDirective,
IndeterminateValueDirective, IndeterminateValueDirective,
JscriptEditorComponent, 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. // 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