Browse Source

Expert mode.

pull/540/head
Sebastian 6 years ago
parent
commit
2a6bf05e51
  1. 1
      frontend/app/features/dashboard/declarations.ts
  2. 3
      frontend/app/features/dashboard/module.ts
  3. 55
      frontend/app/features/dashboard/pages/dashboard-config.component.html
  4. 5
      frontend/app/features/dashboard/pages/dashboard-config.component.scss
  5. 125
      frontend/app/features/dashboard/pages/dashboard-config.component.ts
  6. 36
      frontend/app/features/dashboard/pages/dashboard-page.component.html
  7. 98
      frontend/app/features/dashboard/pages/dashboard-page.component.ts
  8. 6
      frontend/app/framework/angular/modals/modal-dialog.component.html
  9. 4
      frontend/app/framework/utils/types.ts

1
frontend/app/features/dashboard/declarations.ts

@ -17,4 +17,5 @@ export * from './pages/cards/github-card.component';
export * from './pages/cards/history-card.component';
export * from './pages/cards/schema-card.component';
export * from './pages/cards/support-card.component';
export * from './pages/dashboard-config.component';
export * from './pages/dashboard-page.component';

3
frontend/app/features/dashboard/module.ts

@ -12,7 +12,7 @@ import { RouterModule, Routes } from '@angular/router';
import { SqxFrameworkModule, SqxSharedModule } from '@app/shared';
import { GridsterModule } from 'angular-gridster2';
import { ChartModule } from 'angular2-chartjs';
import { ApiCallsCardComponent, ApiCallsSummaryCardComponent, ApiCardComponent, ApiPerformanceCardComponent, ApiTrafficCardComponent, AssetUploadsCountCardComponent, AssetUploadsSizeCardComponent, AssetUploadsSizeSummaryCardComponent, DashboardPageComponent, GithubCardComponent, HistoryCardComponent, SchemaCardComponent, SupportCardComponent } from './declarations';
import { ApiCallsCardComponent, ApiCallsSummaryCardComponent, ApiCardComponent, ApiPerformanceCardComponent, ApiTrafficCardComponent, AssetUploadsCountCardComponent, AssetUploadsSizeCardComponent, AssetUploadsSizeSummaryCardComponent, DashboardConfigComponent, DashboardPageComponent, GithubCardComponent, HistoryCardComponent, SchemaCardComponent, SupportCardComponent } from './declarations';
const routes: Routes = [
{
@ -38,6 +38,7 @@ const routes: Routes = [
AssetUploadsCountCardComponent,
AssetUploadsSizeCardComponent,
AssetUploadsSizeSummaryCardComponent,
DashboardConfigComponent,
DashboardPageComponent,
GithubCardComponent,
HistoryCardComponent,

55
frontend/app/features/dashboard/pages/dashboard-config.component.html

@ -0,0 +1,55 @@
<ng-container *ngIf="config">
<button type="button" class="btn settings-button" (click)="dropdownModal.toggle()" #buttonSettings>
<i class="icon-settings"></i>
</button>
<ng-container *sqxModal="dropdownModal">
<div class="dropdown-menu" [sqxAnchoredTo]="buttonSettings" @fade position="bottom-right">
<div class="dropdown-item" *ngFor="let item of configDefaults">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="field_{{item.type}}"
[ngModel]="isSelected(item)"
(ngModelChange)="addOrRemove(item, $event)" />
<label class="form-check-label" for="field_{{item.type}}">
{{item.name}}
</label>
</div>
</div>
<div class="dropdown-divider"></div>
<a class="dropdown-item" (click)="startExpertMode()">Expert Mode</a>
<a class="dropdown-item" (click)="saveConfig()">Save</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item dropdown-item-delete" (beforeClick)="dropdownModal.hide()"
(sqxConfirmClick)="resetConfig()"
confirmTitle="Reset config"
confirmText="Do you really want to reset the dashboard to the default?">
Reset
</a>
</div>
</ng-container>
<ng-container *sqxModal="expertDialog">
<sqx-modal-dialog (close)="expertDialog.hide()" fullHeight="true" size="lg">
<ng-container title>
Edit Config
</ng-container>
<ng-container content>
<div class="json-editor">
<sqx-json-editor [noBorder]="true" [(ngModel)]="expertConfig"></sqx-json-editor>
</div>
</ng-container>
<ng-container footer>
<button type="button" class="btn btn-secondary" (click)="expertDialog.hide()">Cancel</button>
<button type="button" class="btn btn-primary" (click)="completeExpertMode()">Save</button>
</ng-container>
</sqx-modal-dialog>
</ng-container>
</ng-container>

5
frontend/app/features/dashboard/pages/dashboard-config.component.scss

@ -0,0 +1,5 @@
.json-editor ::ng-deep {
.editor {
@include absolute(0, 0, 0, 0);
}
}

125
frontend/app/features/dashboard/pages/dashboard-config.component.ts

@ -0,0 +1,125 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
// tslint:disable: readonly-array
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { AppDto, AppsState, AuthService, DialogModel, DialogService, fadeAnimation, ModalModel, Types, UIState } from '@app/shared';
import { GridsterItem } from 'angular-gridster2';
import { take } from 'rxjs/operators';
@Component({
selector: 'sqx-dashboard-config',
styleUrls: ['./dashboard-config.component.scss'],
templateUrl: './dashboard-config.component.html',
animations: [
fadeAnimation
]
})
export class DashboardConfigComponent implements OnChanges {
@Input()
public app: AppDto[];
@Input()
public config: GridsterItem[];
@Output()
public configChange = new EventEmitter<GridsterItem[]>();
public configDefaults = DEFAULT_CONFIG;
public expertDialog = new DialogModel();
public expertConfig: GridsterItem[];
public dropdownModal = new ModalModel();
constructor(
public readonly appsState: AppsState,
public readonly authState: AuthService,
private readonly dialogs: DialogService,
private readonly uiState: UIState
) {
}
public ngOnChanges(changes: SimpleChanges) {
if (changes['app']) {
this.uiState.getUser('dashboard.grid', DEFAULT_CONFIG).pipe(take(1))
.subscribe(dto => {
this.setConfig(dto);
});
}
}
private setConfig(config: any) {
if (!Types.isArrayOfObject(config)) {
config = DEFAULT_CONFIG;
}
this.configChange.emit(Types.clone(config));
}
public startExpertMode() {
this.dropdownModal.hide();
this.expertConfig = Types.clone(this.config);
this.expertDialog.show();
}
public completeExpertMode() {
this.setConfig(this.expertConfig);
this.expertConfig = null!;
this.expertDialog.hide();
this.saveConfig();
}
public resetConfig() {
this.setConfig(Types.clone(DEFAULT_CONFIG));
this.saveConfig();
}
public saveConfig() {
this.uiState.set('dashboard.grid', this.config, true);
this.dialogs.notifyInfo('Configuration saved.');
}
public addOrRemove(item: GridsterItem) {
const current = this.config.find(x => x.type === item.type);
if (current) {
this.config.splice(this.config.indexOf(current), 1);
} else {
this.config.push(Types.clone(item));
}
}
public isSelected(item: GridsterItem) {
return this.config.find(x => x.type === item.type);
}
}
const DEFAULT_CONFIG: GridsterItem[] = [
{ cols: 1, rows: 1, x: 0, y: 0, type: 'schemas', name: 'Schema' },
{ cols: 1, rows: 1, x: 1, y: 0, type: 'api', name: 'API Documentation' },
{ cols: 1, rows: 1, x: 2, y: 0, type: 'support', name: 'Support' },
{ cols: 1, rows: 1, x: 3, y: 0, type: 'github', name: 'Github' },
{ cols: 2, rows: 1, x: 0, y: 1, type: 'api-calls', name: 'API Calls Chart' },
{ cols: 2, rows: 1, x: 2, y: 1, type: 'api-performance', name: 'API Performance Chart' },
{ cols: 1, rows: 1, x: 0, y: 2, type: 'api-calls-summary', name: 'API Calls Summary' },
{ cols: 2, rows: 1, x: 1, y: 2, type: 'asset-uploads-count', name: 'Asset Uploads Count Chart' },
{ cols: 1, rows: 1, x: 2, y: 2, type: 'asset-uploads-size-summary', name: 'Asset Uploads Size Chart' },
{ cols: 2, rows: 1, x: 0, y: 3, type: 'asset-uploads-size', name: 'Asset Total Storage Size' },
{ cols: 2, rows: 1, x: 2, y: 3, type: 'api-traffic', name: 'API Traffic Chart' },
{ cols: 2, rows: 1, x: 0, y: 4, type: 'history', name: 'History' }
];

36
frontend/app/features/dashboard/pages/dashboard-page.component.html

@ -11,7 +11,7 @@
</div>
<gridster [options]="gridOptions" #grid>
<gridster-item [item]="item" *ngFor="let item of gridConfig; trackBy: trackByItem">
<gridster-item [item]="item" *ngFor="let item of gridConfig">
<ng-container [ngSwitch]="item.type">
<ng-container *ngSwitchCase="'schemas'">
<sqx-schema-card
@ -82,39 +82,7 @@
</gridster>
<div class="dashboard-settings">
<button type="button" class="btn settings-button" (click)="gridModal.toggle()" #buttonSettings>
<i class="icon-settings"></i>
</button>
<ng-container *sqxModal="gridModal">
<div class="dropdown-menu" [sqxAnchoredTo]="buttonSettings" @fade position="bottom-right">
<div class="dropdown-item" *ngFor="let item of allItems">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="field_{{item.type}}"
[ngModel]="isSelected(item)"
(ngModelChange)="addOrRemove(item)" />
<label class="form-check-label" for="field_{{item.type}}">
{{item.name}}
</label>
</div>
</div>
<div class="dropdown-divider"></div>
<a class="dropdown-item" (click)="saveConfig()">Save</a>
<div class="dropdown-divider"></div>
<a class="dropdown-item dropdown-item-delete" (beforeClick)="gridModal.hide()"
(sqxConfirmClick)="resetConfig()"
confirmTitle="Reset config"
confirmText="Do you really want to reset the dashboard to the default?">
Reset
</a>
</div>
</ng-container>
<sqx-dashboard-config [app]="app" [config]="gridConfig" (configChange)="changeConfig($event)"></sqx-dashboard-config>
</div>
</div>
</ng-container>

98
frontend/app/features/dashboard/pages/dashboard-page.component.ts

@ -8,47 +8,10 @@
// tslint:disable: readonly-array
import { AfterViewInit, Component, OnInit, Renderer2, ViewChild } from '@angular/core';
import { AppsState, AuthService, CallsUsageDto, CurrentStorageDto, DateTime, DialogService, fadeAnimation, LocalStoreService, ModalModel, ResourceOwner, StorageUsagePerDateDto, UIState, UsagesService } from '@app/shared';
import { AppsState, AuthService, CallsUsageDto, CurrentStorageDto, DateTime, fadeAnimation, LocalStoreService, ResourceOwner, StorageUsagePerDateDto, UsagesService } from '@app/shared';
import { GridsterComponent, GridsterItem, GridType } from 'angular-gridster2';
import { switchMap } from 'rxjs/operators';
const DEFAULT_CONFIG: ReadonlyArray<GridsterItem> = [
{ cols: 1, rows: 1, x: 0, y: 0, type: 'schemas', name: 'Schema' },
{ cols: 1, rows: 1, x: 1, y: 0, type: 'api', name: 'API Documentation' },
{ cols: 1, rows: 1, x: 2, y: 0, type: 'support', name: 'Support' },
{ cols: 1, rows: 1, x: 3, y: 0, type: 'github', name: 'Github' },
{ cols: 2, rows: 1, x: 0, y: 1, type: 'api-calls', name: 'API Calls Chart' },
{ cols: 2, rows: 1, x: 2, y: 1, type: 'api-performance', name: 'API Performance Chart' },
{ cols: 1, rows: 1, x: 0, y: 2, type: 'api-calls-summary', name: 'API Calls Summary' },
{ cols: 2, rows: 1, x: 1, y: 2, type: 'asset-uploads-count', name: 'Asset Uploads Count Chart' },
{ cols: 1, rows: 1, x: 2, y: 2, type: 'asset-uploads-size-summary', name: 'Asset Uploads Size Chart' },
{ cols: 2, rows: 1, x: 0, y: 3, type: 'asset-uploads-size', name: 'Asset Total Storage Size' },
{ cols: 2, rows: 1, x: 2, y: 3, type: 'api-traffic', name: 'API Traffic Chart' },
{ cols: 2, rows: 1, x: 0, y: 4, type: 'history', name: 'History' }
];
const DEFAULT_OPTIONS = {
displayGrid: 'onDrag&Resize',
fixedColWidth: 254,
fixedRowHeight: 254,
gridType: GridType.Fixed,
outerMargin: true,
outerMarginBottom: 16,
outerMarginLeft: 16,
outerMarginRight: 16,
outerMarginTop: 120,
draggable: {
enabled: true
},
resizable: {
enabled: false
}
};
@Component({
selector: 'sqx-dashboard-page',
styleUrls: ['./dashboard-page.component.scss'],
@ -69,21 +32,16 @@ export class DashboardPageComponent extends ResourceOwner implements AfterViewIn
public callsUsage: CallsUsageDto;
public gridConfig: GridsterItem[];
public gridModal = new ModalModel();
public gridOptions = DEFAULT_OPTIONS;
public allItems = DEFAULT_CONFIG;
public isScrolled = false;
constructor(
public readonly appsState: AppsState,
public readonly authState: AuthService,
private readonly renderer: Renderer2,
private readonly dialogs: DialogService,
private readonly usagesService: UsagesService,
private readonly localStore: LocalStoreService,
private readonly uiState: UIState
private readonly localStore: LocalStoreService
) {
super();
@ -94,12 +52,6 @@ export class DashboardPageComponent extends ResourceOwner implements AfterViewIn
const dateTo = DateTime.today().toStringFormat('yyyy-MM-dd');
const dateFrom = DateTime.today().addDays(-20).toStringFormat('yyyy-MM-dd');
this.own(
this.uiState.getUser('dashboard.grid', DEFAULT_CONFIG)
.subscribe(dto => {
this.gridConfig = [...dto];
}));
this.own(
this.appsState.selectedApp.pipe(
switchMap(app => this.usagesService.getTodayStorage(app.name)))
@ -134,33 +86,27 @@ export class DashboardPageComponent extends ResourceOwner implements AfterViewIn
this.isStacked = value;
}
public resetConfig() {
this.gridConfig = [...this.allItems];
this.saveConfig();
}
public saveConfig() {
this.uiState.set('dashboard.grid', this.gridConfig, true);
this.dialogs.notifyInfo('Configuration saved.');
}
public changeConfig(config: GridsterItem[]) {
this.gridConfig = config;
public isSelected(item: GridsterItem) {
return this.gridConfig && this.gridConfig.find(x => x.type === item.type);
this.grid.updateGrid();
}
}
public addOrRemove(item: GridsterItem) {
const found = this.gridConfig.find(x => x.type === item.type);
if (found) {
this.gridConfig.splice(this.gridConfig.indexOf(found), 1);
} else {
this.gridConfig.push({ ...item });
}
}
public trackByItem(index: number, item: GridsterItem) {
return item.type;
const DEFAULT_OPTIONS = {
displayGrid: 'onDrag&Resize',
fixedColWidth: 254,
fixedRowHeight: 254,
gridType: GridType.Fixed,
outerMargin: true,
outerMarginBottom: 16,
outerMarginLeft: 16,
outerMarginRight: 16,
outerMarginTop: 120,
draggable: {
enabled: true
},
resizable: {
enabled: false
}
}
};

6
frontend/app/framework/angular/modals/modal-dialog.component.html

@ -21,10 +21,8 @@
<ng-content select="[content]"></ng-content>
</div>
<div class="modal-footer" #footerElement *ngIf="showFooter">
<div class="clearfix">
<ng-content select="[footer]"></ng-content>
</div>
<div class="modal-footer justify-content-between" #footerElement *ngIf="showFooter">
<ng-content select="[footer]"></ng-content>
</div>
</div>
</div>

4
frontend/app/framework/utils/types.ts

@ -56,6 +56,10 @@ export module Types {
return isArrayOf(value, v => isNumber(v));
}
export function isArrayOfObject(value: any): value is Array<Object> {
return isArrayOf(value, v => isObject(v));
}
export function isArrayOfString(value: any): value is Array<string> {
return isArrayOf(value, v => isString(v));
}

Loading…
Cancel
Save