Browse Source

Test

pull/356/head
Sebastian Stehle 7 years ago
parent
commit
d99a3998bd
  1. 16
      src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.ts
  2. 88
      src/Squidex/app/features/administration/state/event-consumers.state.ts
  3. 9
      src/Squidex/app/framework/angular/forms/progress-bar.component.ts
  4. 4
      src/Squidex/app/framework/utils/immutable-array.ts
  5. 12
      src/Squidex/app/framework/utils/modal-view.ts
  6. 31
      src/Squidex/app/shared/components/asset-uploader.component.html
  7. 70
      src/Squidex/app/shared/components/asset-uploader.component.scss
  8. 49
      src/Squidex/app/shared/components/asset-uploader.component.ts
  9. 20
      src/Squidex/app/shared/components/asset.component.html
  10. 91
      src/Squidex/app/shared/components/asset.component.ts
  11. 17
      src/Squidex/app/shared/components/assets-list.component.html
  12. 34
      src/Squidex/app/shared/components/assets-list.component.ts
  13. 4
      src/Squidex/app/shared/components/assets-selector.component.ts
  14. 1
      src/Squidex/app/shared/declarations.ts
  15. 7
      src/Squidex/app/shared/module.ts
  16. 12
      src/Squidex/app/shared/state/assets.state.spec.ts
  17. 194
      src/Squidex/app/shared/state/assets.state.ts
  18. 1
      src/Squidex/app/shell/pages/internal/apps-menu.component.html
  19. 4
      src/Squidex/app/shell/pages/internal/internal-area.component.html
  20. 7
      src/Squidex/app/theme/icomoon/Read Me.txt
  21. 4
      src/Squidex/app/theme/icomoon/demo-files/demo.css
  22. 1118
      src/Squidex/app/theme/icomoon/demo.html
  23. BIN
      src/Squidex/app/theme/icomoon/fonts/icomoon.eot
  24. 4
      src/Squidex/app/theme/icomoon/fonts/icomoon.svg
  25. BIN
      src/Squidex/app/theme/icomoon/fonts/icomoon.ttf
  26. BIN
      src/Squidex/app/theme/icomoon/fonts/icomoon.woff
  27. 2
      src/Squidex/app/theme/icomoon/selection.json
  28. 172
      src/Squidex/app/theme/icomoon/style.css

16
src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.ts

@ -29,25 +29,25 @@ export class EventConsumersPageComponent extends ResourceOwner implements OnInit
}
public ngOnInit() {
this.eventConsumersState.load().pipe(onErrorResumeNext()).subscribe();
this.eventConsumersState.load();
this.own(timer(5000, 5000).pipe(switchMap(() => this.eventConsumersState.load(true, true)), onErrorResumeNext()));
}
public reload() {
this.eventConsumersState.load(true, false).pipe(onErrorResumeNext()).subscribe();
this.eventConsumersState.load(true, false);
}
public start(es: EventConsumerDto) {
this.eventConsumersState.start(es).pipe(onErrorResumeNext()).subscribe();
public start(eventConsumer: EventConsumerDto) {
this.eventConsumersState.start(eventConsumer);
}
public stop(es: EventConsumerDto) {
this.eventConsumersState.stop(es).pipe(onErrorResumeNext()).subscribe();
public stop(eventConsumer: EventConsumerDto) {
this.eventConsumersState.stop(eventConsumer);
}
public reset(es: EventConsumerDto) {
this.eventConsumersState.reset(es).pipe(onErrorResumeNext()).subscribe();
public reset(eventConsumer: EventConsumerDto) {
this.eventConsumersState.reset(eventConsumer);
}
public trackByEventConsumer(index: number, es: EventConsumerDto) {

88
src/Squidex/app/features/administration/state/event-consumers.state.ts

@ -6,13 +6,12 @@
*/
import { Injectable } from '@angular/core';
import { Observable, throwError } from 'rxjs';
import { catchError, distinctUntilChanged, map, tap } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { distinctUntilChanged, map, share } from 'rxjs/operators';
import {
DialogService,
ImmutableArray,
notify,
State
} from '@app/shared';
@ -43,54 +42,67 @@ export class EventConsumersState extends State<Snapshot> {
super({ eventConsumers: ImmutableArray.empty() });
}
public load(isReload = false, silent = false): Observable<any> {
public load(isReload = false, silent = false): Observable<EventConsumerDto[]> {
if (!isReload) {
this.resetState();
}
return this.eventConsumersService.getEventConsumers().pipe(
tap(dtos => {
if (isReload && !silent) {
this.dialogs.notifyInfo('Event Consumers reloaded.');
}
this.next(s => {
const eventConsumers = ImmutableArray.of(dtos);
return { ...s, eventConsumers, isLoaded: true };
});
}),
catchError(error => {
if (!silent) {
this.dialogs.notifyError(error);
}
return throwError(error);
}));
const stream = this.eventConsumersService.getEventConsumers().pipe(share());
stream.subscribe(dtos => {
if (isReload && !silent) {
this.dialogs.notifyInfo('Event Consumers reloaded.');
}
this.next(s => {
const eventConsumers = ImmutableArray.of(dtos);
return { ...s, eventConsumers, isLoaded: true };
});
}, error => {
if (!silent) {
this.dialogs.notifyError(error);
}
});
return stream;
}
public start(eventConsumer: EventConsumerDto): Observable<any> {
return this.eventConsumersService.putStart(eventConsumer.name).pipe(
tap(() => {
this.replaceEventConsumer(setStopped(eventConsumer, false));
}),
notify(this.dialogs));
const stream = this.eventConsumersService.putStart(eventConsumer.name).pipe(share());
stream.subscribe(() => {
this.replaceEventConsumer(setStopped(eventConsumer, false));
}, error => {
this.dialogs.notifyError(error);
});
return stream;
}
public stop(eventConsumer: EventConsumerDto): Observable<any> {
return this.eventConsumersService.putStop(eventConsumer.name).pipe(
tap(() => {
this.replaceEventConsumer(setStopped(eventConsumer, true));
}),
notify(this.dialogs));
const stream = this.eventConsumersService.putStop(eventConsumer.name).pipe(share());
stream.subscribe(() => {
this.replaceEventConsumer(setStopped(eventConsumer, true));
}, error => {
this.dialogs.notifyError(error);
});
return stream;
}
public reset(eventConsumer: EventConsumerDto): Observable<any> {
return this.eventConsumersService.putReset(eventConsumer.name).pipe(
tap(() => {
this.replaceEventConsumer(reset(eventConsumer));
}),
notify(this.dialogs));
const stream = this.eventConsumersService.putReset(eventConsumer.name).pipe(share());
stream.subscribe(() => {
this.replaceEventConsumer(reset(eventConsumer));
}, error => {
this.dialogs.notifyError(error);
});
return stream;
}
private replaceEventConsumer(eventConsumer: EventConsumerDto) {

9
src/Squidex/app/framework/angular/forms/progress-bar.component.ts

@ -35,6 +35,9 @@ export class ProgressBarComponent implements OnChanges, OnInit {
@Input()
public showText = true;
@Input()
public animated = true;
@Input()
public value = 0;
@ -74,7 +77,11 @@ export class ProgressBarComponent implements OnChanges, OnInit {
private updateValue() {
const value = this.value;
this.progressBar.animate(value / 100);
if (this.animated) {
this.progressBar.animate(value / 100);
} else {
this.progressBar.set(value / 100);
}
if (value > 0 && this.showText) {
this.progressBar.setText(Math.round(value) + '%');

4
src/Squidex/app/framework/utils/immutable-array.ts

@ -230,4 +230,8 @@ export class ImmutableArray<T> implements Iterable<T> {
public replaceBy(field: string, newValue: T, replacer?: (o: T, n: T) => T) {
return this.replaceAll(x => x[field] === newValue[field], o => replacer ? replacer(o, newValue) : newValue);
}
public removeBy(field: string, value: T) {
return this.removeAll(x => x[field] === value[field]);
}
}

12
src/Squidex/app/framework/utils/modal-view.ts

@ -12,12 +12,16 @@ export interface Openable {
}
export class DialogModel implements Openable {
private readonly isOpen$ = new BehaviorSubject<boolean>(false);
private readonly isOpen$: BehaviorSubject<boolean>;
public get isOpen(): Observable<boolean> {
return this.isOpen$;
}
constructor(isOpen = false) {
this.isOpen$ = new BehaviorSubject<boolean>(isOpen);
}
public show(): DialogModel {
this.isOpen$.next(true);
@ -38,12 +42,16 @@ export class DialogModel implements Openable {
}
export class ModalModel implements Openable {
private readonly isOpen$ = new BehaviorSubject<boolean>(false);
private readonly isOpen$: BehaviorSubject<boolean>;
public get isOpen(): Observable<boolean> {
return this.isOpen$;
}
constructor(isOpen = false) {
this.isOpen$ = new BehaviorSubject<boolean>(isOpen);
}
public show(): ModalModel {
if (!this.isOpen$.value) {
if (openModal && openModal !== this) {

31
src/Squidex/app/shared/components/asset-uploader.component.html

@ -0,0 +1,31 @@
<ul class="nav navbar-nav" *ngIf="assets.uploadsDelayed | async; let uploads" (sqxFileDrop)="addFiles($event)">
<li class="nav-item dropdown">
<span class="nav-link dropdown-toggle" (click)="modalMenu.toggle()">
<i class="icon-upload-3"></i>
<span>{{uploads.length}}</span>
</span>
<div class="dropdown-menu container" *ngIf="modalMenu.isOpen | async" (sqxFileDrop)="addFiles($event)" @fade>
<div class="uploads">
<small class="uploads-empty text-muted" *ngIf="uploads.length === 0">
No upload in progress, drop files here.
</small>
<div class="upload row no-gutters" *ngFor="let upload of uploads">
<div class="col-6">
<div class="upload-name">{{upload.name}}</div>
</div>
<div class="col">
<sqx-progress-bar [value]="upload.progress" [trailWidth]="0.8" [strokeWidth]="0.8" [showText]="false" [animated]="false"></sqx-progress-bar>
</div>
<div class="col-auto">
<button type="button" class="btn btn-text-secondary" (click)="stopUpload(upload)">
<i class="icon-close"></i>
</button>
</div>
</div>
</div>
</div>
</li>
</ul>

70
src/Squidex/app/shared/components/asset-uploader.component.scss

@ -0,0 +1,70 @@
@import '_vars';
@import '_mixins';
.nav {
& {
padding-right: 2rem;
}
.nav-item {
& {
line-height: 2rem;
}
.nav-link {
color: $color-dark-foreground;
padding-top: 0;
padding-bottom: 0;
}
}
}
.icon-upload-3 {
vertical-align: middle;
font-size: 1.4rem;
font-weight: lighter;
padding-right: .5rem;
}
.dropdown-menu {
@include absolute(2.6rem, 0, auto, auto);
display: block;
min-width: 30rem;
max-width: 60%;
min-height: 4rem;
padding: 1rem;
}
.uploads {
& {
border: 2px solid transparent;
}
&-empty {
line-height: 2rem;
}
}
.upload {
& {
line-height: 2rem;
min-height: 2rem;
max-height: 2rem;
margin-bottom: .5rem;
}
&:last-child {
margin: 0;
}
&-name {
@include truncate;
padding-right: .5rem;
padding-left: .5rem;
}
}
.drag {
& > .uploads {
border-color: $color-theme-blue;
}
}

49
src/Squidex/app/shared/components/asset-uploader.component.ts

@ -0,0 +1,49 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, Component } from '@angular/core';
import {
AssetsState,
DialogModel,
fadeAnimation,
UploadingAsset
} from '@app/shared/internal';
@Component({
selector: 'sqx-asset-uploader',
styleUrls: ['./asset-uploader.component.scss'],
templateUrl: './asset-uploader.component.html',
animations: [
fadeAnimation
],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AssetUploaderComponent {
public modalMenu = new DialogModel(true);
constructor(
public readonly assets: AssetsState
) {
}
public addFiles(files: File[]) {
for (let file of files) {
this.assets.upload(file).subscribe();
}
this.modalMenu.show();
}
public stopUpload(upload: UploadingAsset) {
this.assets.remove(upload);
}
public trackByUpload(index: number, upload: UploadingAsset) {
return upload.id;
}
}

20
src/Squidex/app/shared/components/asset.component.html

@ -1,7 +1,7 @@
<ng-container *ngIf="!isListView; else listTemplate">
<div class="card" [class.selectable]="isSelectable" [class.border-primary]="isSelected" (click)="emitSelect()" (sqxFileDrop)="updateFile($event)" [noDrop]="true">
<div class="card-body">
<div class="file-preview" *ngIf="asset && snapshot.progress === 0" @fade>
<div class="file-preview" *ngIf="asset && !upload" @fade>
<span class="file-type" *ngIf="asset.fileType">
{{asset.fileType}}
</span>
@ -46,11 +46,11 @@
</div>
</div>
<div class="upload-progress" *ngIf="snapshot.progress > 0">
<sqx-progress-bar mode="Circle" [value]="snapshot.progress"></sqx-progress-bar>
<div class="upload-progress" *ngIf="upload">
<sqx-progress-bar mode="Circle" [value]="upload.progress"></sqx-progress-bar>
</div>
<div class="drop-overlay align-items-center justify-content-center" *ngIf="asset && snapshot.progress === 0">
<div class="drop-overlay align-items-center justify-content-center" *ngIf="asset && !upload">
<div class="drop-overlay-background"></div>
<div class="drop-overlay-text">Drop to update</div>
</div>
@ -77,14 +77,14 @@
<div class="table-items-row" [class.selectable]="isSelectable" (click)="emitSelect()" (sqxFileDrop)="updateFile($event)" [noDrop]="true">
<div class="left-border" [class.hidden]="!isSelectable" [class.selected]="isSelected" ></div>
<div *ngIf="asset && asset.canPreview && snapshot.progress === 0" class="image drag-handle" [class.image-left]="!isSelectable" @fade>
<div *ngIf="asset && asset.canPreview && !upload" class="image drag-handle" [class.image-left]="!isSelectable" @fade>
<img [sqxImageSource]="asset | sqxAssetPreviewUrl" class="bg2" layoutKey="asset-small">
</div>
<div *ngIf="asset && !asset.canPreview && snapshot.progress === 0" class="image drag-handle image-padded" [class.image-left]="!isSelectable" @fade>
<div *ngIf="asset && !asset.canPreview && !upload" class="image drag-handle image-padded" [class.image-left]="!isSelectable" @fade>
<img class="icon" [attr.src]="asset | sqxFileIcon">
</div>
<table class="table-fixed" *ngIf="asset && snapshot.progress === 0" @fade>
<table class="table-fixed" *ngIf="asset && !upload" @fade>
<tr>
<td class="col-name">
<div class="file-name editable" (click)="edit()">
@ -113,11 +113,11 @@
</tr>
</table>
<div class="upload-progress" *ngIf="snapshot.progress > 0">
<sqx-progress-bar [value]="snapshot.progress" [trailWidth]="0.8" [strokeWidth]="0.8" [showText]="false"></sqx-progress-bar>
<div class="upload-progress" *ngIf="upload">
<sqx-progress-bar [value]="upload.progress" [trailWidth]="0.8" [strokeWidth]="0.8" [showText]="false"></sqx-progress-bar>
</div>
<div class="drop-overlay align-items-center justify-content-center" *ngIf="asset && snapshot.progress === 0">
<div class="drop-overlay align-items-center justify-content-center" *ngIf="asset && !upload">
<div class="drop-overlay-background"></div>
<div class="drop-overlay-text">Drop to update</div>
</div>

91
src/Squidex/app/shared/components/asset.component.ts

@ -5,26 +5,15 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, EventEmitter, HostBinding, Input, OnInit, Output } from '@angular/core';
import { ChangeDetectionStrategy, Component, EventEmitter, HostBinding, Input, Output } from '@angular/core';
import {
AppsState,
AssetDto,
AssetsService,
AuthService,
DateTime,
DialogModel,
DialogService,
fadeAnimation,
StatefulComponent,
Types,
Versioned
UploadingAsset
} from '@app/shared/internal';
interface State {
progress: number;
}
@Component({
selector: 'sqx-asset',
styleUrls: ['./asset.component.scss'],
@ -34,9 +23,9 @@ interface State {
],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AssetComponent extends StatefulComponent<State> implements OnInit {
export class AssetComponent {
@Input()
public initFile: File;
public upload: UploadingAsset;
@Input()
public asset: AssetDto;
@ -62,18 +51,15 @@ export class AssetComponent extends StatefulComponent<State> implements OnInit {
@Input()
public allTags: string[];
@Output()
public load = new EventEmitter<AssetDto>();
@Output()
public loadError = new EventEmitter();
@Output()
public remove = new EventEmitter<AssetDto>();
@Output()
public update = new EventEmitter<AssetDto>();
@Output()
public uploadFile = new EventEmitter<File>();
@Output()
public delete = new EventEmitter<AssetDto>();
@ -82,54 +68,9 @@ export class AssetComponent extends StatefulComponent<State> implements OnInit {
public editDialog = new DialogModel();
constructor(changeDetector: ChangeDetectorRef,
private readonly appsState: AppsState,
private readonly assetsService: AssetsService,
private readonly authState: AuthService,
private readonly dialogs: DialogService
) {
super(changeDetector, {
progress: 0
});
}
public ngOnInit() {
const initFile = this.initFile;
if (initFile) {
this.setProgress(1);
this.assetsService.uploadFile(this.appsState.appName, initFile, this.authState.user!.token, DateTime.now())
.subscribe(dto => {
if (Types.is(dto, AssetDto)) {
this.emitLoad(dto);
} else {
this.setProgress(dto);
}
}, error => {
this.dialogs.notifyError(error);
this.emitLoadError(error);
});
}
}
public updateFile(files: FileList) {
public updateFile(files: File[]) {
if (files.length === 1) {
this.setProgress(1);
this.assetsService.replaceFile(this.appsState.appName, this.asset.id, files[0], this.asset.version)
.subscribe(dto => {
if (Types.is(dto, Versioned)) {
this.updateAsset(this.asset.update(dto.payload, this.authState.user!.token, dto.version), true);
} else {
this.setProgress(dto);
}
}, error => {
this.dialogs.notifyError(error);
this.setProgress(0);
});
this.uploadFile.emit(files[0]);
}
}
@ -151,14 +92,6 @@ export class AssetComponent extends StatefulComponent<State> implements OnInit {
this.delete.emit(this.asset);
}
public emitLoad(asset: AssetDto) {
this.load.emit(asset);
}
public emitLoadError(error: any) {
this.loadError.emit(error);
}
public emitUpdate() {
this.update.emit(this.asset);
}
@ -167,10 +100,6 @@ export class AssetComponent extends StatefulComponent<State> implements OnInit {
this.remove.emit(this.asset);
}
private setProgress(progress: number) {
this.next(s => ({ ...s, progress }));
}
public updateAsset(asset: AssetDto, emitEvent: boolean) {
this.asset = asset;
@ -178,8 +107,6 @@ export class AssetComponent extends StatefulComponent<State> implements OnInit {
this.emitUpdate();
}
this.next(s => ({ ...s, progress: 0 }));
this.cancelEdit();
}
}

17
src/Squidex/app/shared/components/assets-list.component.html

@ -15,20 +15,21 @@
</div>
<div class="row assets" [class.unrow]="isListView" *ngIf="state.tagsNames | async; let tags" (paste)="addFiles($event)">
<sqx-asset *ngFor="let file of newFiles" [initFile]="file"
[isListView]="isListView"
(loadError)="remove(file)"
(load)="add(file, $event)">
</sqx-asset>
<ng-container *ngIf="state.uploads | async; let uploads">
<sqx-asset *ngFor="let upload of uploads; trackBy: trackByUpload" [upload]="upload"
[isListView]="isListView">
</sqx-asset>
</ng-container>
<ng-container *ngIf="state.assets | async; let assets">
<sqx-asset *ngFor="let asset of assets; trackBy: trackByAsset" [asset]="asset"
<ng-container *ngIf="state.assetsWithUploads | async; let assets">
<sqx-asset *ngFor="let a of assets; trackBy: trackByAsset" [asset]="a.asset" [upload]="a.upload"
[isListView]="isListView"
[isDisabled]="isDisabled"
[isSelectable]="selectedIds"
[isSelected]="isSelected(asset)"
[isSelected]="isSelected(a.asset)"
[allTags]="tags"
(update)="update($event)"
(uploadFile)="updateFile(a.asset, $event)"
(select)="emitSelect($event)"
(delete)="delete($event)">
</sqx-asset>

34
src/Squidex/app/shared/components/assets-list.component.ts

@ -11,8 +11,7 @@ import { onErrorResumeNext } from 'rxjs/operators';
import {
AssetDto,
AssetsState,
DialogService,
ImmutableArray
AssetWithUpload
} from '@app/shared/internal';
@Component({
@ -22,8 +21,6 @@ import {
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AssetsListComponent {
public newFiles = ImmutableArray.empty<File>();
@Input()
public state: AssetsState;
@ -39,21 +36,6 @@ export class AssetsListComponent {
@Output()
public select = new EventEmitter<AssetDto>();
constructor(
private readonly dialogs: DialogService
) {
}
public add(file: File, asset: AssetDto) {
this.newFiles = this.newFiles.remove(file);
if (asset.isDuplicate) {
this.dialogs.notifyError('The same asset has already been uploaded.');
} else {
this.state.add(asset);
}
}
public search() {
this.state.load().pipe(onErrorResumeNext()).subscribe();
}
@ -74,6 +56,10 @@ export class AssetsListComponent {
this.state.update(asset);
}
public updateFile(asset: AssetDto, file: File) {
this.state.replaceFile(asset, file).pipe(onErrorResumeNext()).subscribe();
}
public emitSelect(asset: AssetDto) {
this.select.emit(asset);
}
@ -82,13 +68,9 @@ export class AssetsListComponent {
return this.selectedIds && this.selectedIds[asset.id];
}
public remove(file: File) {
this.newFiles = this.newFiles.remove(file);
}
public addFiles(files: File[]) {
for (let file of files) {
this.newFiles = this.newFiles.pushFront(file);
this.state.upload(file);
}
return true;
@ -97,5 +79,9 @@ export class AssetsListComponent {
public trackByAsset(index: number, asset: AssetDto) {
return asset.id;
}
public trackByUpload(index: number, upload: AssetWithUpload) {
return upload.asset.id;
}
}

4
src/Squidex/app/shared/components/assets-selector.component.ts

@ -10,7 +10,7 @@ import { onErrorResumeNext } from 'rxjs/operators';
import {
AssetDto,
AssetsDialogState,
AssetsState,
fadeAnimation,
FilterState,
LocalStoreService,
@ -40,7 +40,7 @@ export class AssetsSelectorComponent extends StatefulComponent<State> implements
public filter = new FilterState();
constructor(changeDector: ChangeDetectorRef,
public readonly assetsState: AssetsDialogState,
public readonly assetsState: AssetsState,
public readonly localStore: LocalStoreService
) {
super(changeDector, {

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

@ -10,6 +10,7 @@ export * from './components/asset-dialog.component';
export * from './components/asset.component';
export * from './components/assets-list.component';
export * from './components/assets-selector.component';
export * from './components/asset-uploader.component';
export * from './components/comment.component';
export * from './components/comments.component';
export * from './components/help-markdown.pipe';

7
src/Squidex/app/shared/module.ts

@ -21,11 +21,11 @@ import {
AssetComponent,
AssetDialogComponent,
AssetPreviewUrlPipe,
AssetsDialogState,
AssetsListComponent,
AssetsSelectorComponent,
AssetsService,
AssetsState,
AssetUploaderComponent,
AssetUrlPipe,
AuthInterceptor,
AuthService,
@ -109,6 +109,7 @@ import {
AssetUrlPipe,
AssetsListComponent,
AssetsSelectorComponent,
AssetUploaderComponent,
CommentComponent,
CommentsComponent,
FileIconPipe,
@ -140,6 +141,7 @@ import {
AssetUrlPipe,
AssetsListComponent,
AssetsSelectorComponent,
AssetUploaderComponent,
CommentComponent,
CommentsComponent,
FileIconPipe,
@ -163,9 +165,6 @@ import {
UserPicturePipe,
UserPictureRefPipe,
TableHeaderComponent
],
providers: [
AssetsDialogState
]
})
export class SqxSharedModule {

12
src/Squidex/app/shared/state/assets.state.spec.ts

@ -23,6 +23,7 @@ describe('AssetsState', () => {
const {
app,
appsState,
authService,
creation,
creator,
modified,
@ -51,7 +52,7 @@ describe('AssetsState', () => {
assetsService.setup(x => x.getTags(app))
.returns(() => of({ tag1: 1, shared: 2, tag2: 1 }));
assetsState = new AssetsState(appsState.object, assetsService.object, dialogs.object);
assetsState = new AssetsState(appsState.object, assetsService.object, authService.object, dialogs.object);
assetsState.load().subscribe();
});
@ -76,15 +77,6 @@ describe('AssetsState', () => {
dialogs.verify(x => x.notifyInfo(It.isAnyString()), Times.once());
});
it('should add asset to snapshot when created', () => {
const newAsset = new AssetDto('id3', creator, creator, creation, creation, 'name3', 'hash3', 'type3', 3, 3, 'mime3', false, true, 0, 0, 'slug2', [], 'url3', version);
assetsState.add(newAsset);
expect(assetsState.snapshot.assets.values).toEqual([newAsset, ...oldAssets]);
expect(assetsState.snapshot.assetsPager.numberOfItems).toBe(201);
});
it('should update properties when updated', () => {
const newAsset = new AssetDto('id1', modifier, modifier, modified, modified, 'name3', 'hash3', 'type3', 3, 3, 'mime3', false, true, 0, 0, 'slug3', ['new'], 'url3', version);

194
src/Squidex/app/shared/state/assets.state.ts

@ -6,20 +6,49 @@
*/
import { Injectable } from '@angular/core';
import { combineLatest, Observable } from 'rxjs';
import { combineLatest, Observable, Subject, Subscription } from 'rxjs';
import { distinctUntilChanged, map, tap } from 'rxjs/operators';
import {
DateTime,
DialogService,
ImmutableArray,
MathHelper,
notify,
Pager,
State
State,
Types
} from '@app/framework';
import { AssetDto, AssetsService} from './../services/assets.service';
import { AuthService } from '../services/auth.service';
import { AssetDto, AssetsService } from './../services/assets.service';
import { AppsState } from './apps.state';
export interface UploadingAsset {
// Unique id.
id: string;
// The name of the asset.
name: string;
// The upload subscription.
subscription: Subscription;
// The progress.
progress: number;
// Indicates if the upload has been completed.
isCompleted?: boolean;
}
export interface AssetWithUpload {
// The asset.
asset: AssetDto;
// The corresponding upload.
upload?: UploadingAsset;
}
interface Snapshot {
// All assets tags.
tags: { [name: string]: number };
@ -27,6 +56,12 @@ interface Snapshot {
// The selected asset tags.
tagsSelected: { [name: string]: boolean };
// The uploads.
uploadsDirect: ImmutableArray<UploadingAsset>;
// The uploads removed with a delay.
uploadsDelayed: ImmutableArray<UploadingAsset>;
// The current assets.
assets: ImmutableArray<AssetDto>;
@ -58,6 +93,10 @@ export class AssetsState extends State<Snapshot> {
this.changes.pipe(map(x => x.assets),
distinctUntilChanged());
public assetsWithUploads =
this.changes.pipe(map(x => getAssetsWithUploads(x)),
distinctUntilChanged());
public assetsQuery =
this.changes.pipe(map(x => x.assetsQuery),
distinctUntilChanged());
@ -66,6 +105,14 @@ export class AssetsState extends State<Snapshot> {
this.changes.pipe(map(x => x.assetsPager),
distinctUntilChanged());
public uploads =
this.changes.pipe(map(x => x.uploadsDirect),
distinctUntilChanged());
public uploadsDelayed =
this.changes.pipe(map(x => x.uploadsDelayed),
distinctUntilChanged());
public isLoaded =
this.changes.pipe(map(x => !!x.isLoaded),
distinctUntilChanged());
@ -73,9 +120,17 @@ export class AssetsState extends State<Snapshot> {
constructor(
private readonly appsState: AppsState,
private readonly assetsService: AssetsService,
private readonly authService: AuthService,
private readonly dialogs: DialogService
) {
super({ assets: ImmutableArray.empty(), assetsPager: new Pager(0, 0, 30), tags: {}, tagsSelected: {} });
super({
assets: ImmutableArray.empty(),
assetsPager: new Pager(0, 0, 30),
uploadsDirect: ImmutableArray.empty(),
uploadsDelayed: ImmutableArray.empty(),
tags: {},
tagsSelected: {}
});
}
public load(isReload = false): Observable<any> {
@ -110,6 +165,100 @@ export class AssetsState extends State<Snapshot> {
notify(this.dialogs));
}
public remove(upload: UploadingAsset, delayed = false) {
upload.subscription.unsubscribe();
this.next(s => {
let uploadsDirect = s.uploadsDirect.removeBy('id', upload);
let uploadsDelayed = s.uploadsDelayed;
if (!delayed) {
uploadsDelayed = s.uploadsDelayed.removeBy('id', upload);
}
return { ...s, uploadsDirect, uploadsDelayed };
});
if (!delayed) {
setTimeout(() => {
this.remove(upload, true);
}, 10000);
}
}
public upload(file: File, now?: DateTime): Observable<number | AssetDto> {
const observable = this.assetsService.uploadFile(this.appName, file, this.user, now || DateTime.now());
let upload: UploadingAsset;
const subject = new Subject<number | AssetDto>();
const subscription = observable.subscribe(event => {
if (Types.isNumber(event)) {
this.setProgress(upload, event);
subject.next(event);
} else {
if (event.isDuplicate) {
this.dialogs.notifyError('The same asset has already been uploaded.');
} else {
this.add(event);
}
subject.next(event);
}
}, error => {
subject.error(error);
this.remove(upload, true);
}, () => {
subject.complete();
this.remove(upload, true);
});
upload = { id: MathHelper.guid(), name: file.name, progress: 0, subscription };
this.addUpload(upload);
return subject;
}
public replaceFile(asset: AssetDto, file: File, now?: DateTime): Observable<number | AssetDto> {
const observable = this.assetsService.replaceFile(this.appName, asset.id, file, asset.version);
let upload: UploadingAsset;
const subject = new Subject<number | AssetDto>();
const subscription = observable.subscribe(event => {
if (Types.isNumber(event)) {
this.setProgress(upload, event);
subject.next(event);
} else {
const newAsset = asset.update(event.payload, this.user, event.version, now || DateTime.now());
this.update(newAsset);
subject.next(newAsset);
}
}, error => {
subject.error(error);
this.remove(upload, true);
}, () => {
subject.complete();
this.remove(upload, true);
});
upload = { id: asset.id, name: file.name, progress: 0, subscription };
this.addUpload(upload);
return subject;
}
public add(asset: AssetDto) {
this.next(s => {
const assets = s.assets.pushFront(asset);
@ -224,9 +373,41 @@ export class AssetsState extends State<Snapshot> {
return Object.keys(this.snapshot.tagsSelected).length === 0;
}
private setProgress(upload: UploadingAsset, progress: number) {
this.next(s => {
const newUpload = { ...upload, progress };
const uploadsDirect = s.uploadsDirect.replaceBy('id', newUpload);
const uploadsDelayed = s.uploadsDelayed.replaceBy('id', newUpload);
return { ...s, uploadsDirect, uploadsDelayed };
});
}
private addUpload(upload: UploadingAsset) {
this.next(s => {
const uploadsDirect = s.uploadsDirect.pushFront(upload);
const uploadsDelayed = s.uploadsDelayed.pushFront(upload);
return { ...s, uploadsDirect, uploadsDelayed };
});
}
private get appName() {
return this.appsState.appName;
}
private get user() {
return this.authService.user!.token;
}
}
function getAssetsWithUploads(state: Snapshot) {
return state.assets.map(asset => {
const upload = state.uploadsDirect.find(x => x.id === x.id);
return { upload, asset };
});
}
function addTags(asset: AssetDto, tags: { [x: string]: number; }) {
@ -262,7 +443,4 @@ function sort(tags: { [name: string]: number }) {
}).map(key => {
return { name: key, count: tags[key] };
});
}
@Injectable()
export class AssetsDialogState extends AssetsState { }
}

1
src/Squidex/app/shell/pages/internal/apps-menu.component.html

@ -17,7 +17,6 @@
<span class="all-apps-pill badge badge-pill badge-primary">{{apps.length}}</span>
</a>
<ng-container *ngIf="apps.length > 0">
<div class="dropdown-divider"></div>

4
src/Squidex/app/shell/pages/internal/internal-area.component.html

@ -16,6 +16,10 @@
<div class="float-right profile-menu">
<sqx-profile-menu></sqx-profile-menu>
</div>
<div class="float-right assets-menu">
<sqx-asset-uploader></sqx-asset-uploader>
</div>
</nav>
<div class="main">

7
src/Squidex/app/theme/icomoon/Read Me.txt

@ -1,7 +0,0 @@
Open *demo.html* to see a list of all the glyphs in your font along with their codes/ligatures.
To use the generated font in desktop programs, you can install the TTF font. In order to copy the character associated with each icon, refer to the text box at the bottom right corner of each glyph in demo.html. The character inside this text box may be invisible; but it can still be copied. See this guide for more info: https://icomoon.io/#docs/local-fonts
You won't need any of the files located under the *demo-files* directory when including the generated font in your own projects.
You can import *selection.json* back to the IcoMoon app using the *Import Icons* button (or via Main Menu → Manage Projects) to retrieve your icon selection.

4
src/Squidex/app/theme/icomoon/demo-files/demo.css

@ -156,10 +156,10 @@ p {
font-size: 32px;
}
.fs4 {
font-size: 20px;
font-size: 32px;
}
.fs5 {
font-size: 24px;
font-size: 20px;
}
.fs6 {
font-size: 28px;

1118
src/Squidex/app/theme/icomoon/demo.html

File diff suppressed because it is too large

BIN
src/Squidex/app/theme/icomoon/fonts/icomoon.eot

Binary file not shown.

4
src/Squidex/app/theme/icomoon/fonts/icomoon.svg

@ -119,6 +119,10 @@
<glyph unicode="&#xe96d;" glyph-name="external-link" d="M768 426.667c-25.6 0-42.667-17.067-42.667-42.667v-256c0-25.6-17.067-42.667-42.667-42.667h-469.333c-25.6 0-42.667 17.067-42.667 42.667v469.333c0 25.6 17.067 42.667 42.667 42.667h256c25.6 0 42.667 17.067 42.667 42.667s-17.067 42.667-42.667 42.667h-256c-72.533 0-128-55.467-128-128v-469.333c0-72.533 55.467-128 128-128h469.333c72.533 0 128 55.467 128 128v256c0 25.6-17.067 42.667-42.667 42.667zM934.4 827.734c-4.267 8.533-12.8 17.067-21.333 21.333-4.267 4.267-12.8 4.267-17.067 4.267h-256c-25.6 0-42.667-17.067-42.667-42.667s17.067-42.667 42.667-42.667h153.6l-396.8-396.8c-17.067-17.067-17.067-42.667 0-59.733 8.533-8.533 17.067-12.8 29.867-12.8s21.333 4.267 29.867 12.8l396.8 396.8v-153.6c0-25.6 17.067-42.667 42.667-42.667s42.667 17.067 42.667 42.667v256c0 4.267 0 12.8-4.267 17.067z" />
<glyph unicode="&#xe96e;" glyph-name="arrow_back" d="M854 468.667v-84h-520l238-240-60-60-342 342 342 342 60-60-238-240h520z" />
<glyph unicode="&#xe96f;" glyph-name="translate" d="M678 212.667h138l-70 186zM790 512.667l192-512h-86l-48 128h-202l-48-128h-86l192 512h86zM550 296.667l-34-88-132 132-214-212-60 60 218 214c-54 60-96 124-128 194h86c26-50 58-98 98-142 62 68 108 146 136 228h-478v86h300v84h84v-84h300v-86h-126c-32-100-84-196-158-278l-2-2z" />
<glyph unicode="&#xe970;" glyph-name="upload" d="M448 384h128v256h192l-256 256-256-256h192zM640 528v-98.712l293.066-109.288-421.066-157.018-421.066 157.018 293.066 109.288v98.712l-384-144v-256l512-192 512 192v256z" />
<glyph unicode="&#xe971;" glyph-name="upload-4" d="M853.333 320v-170.667c0-5.845-1.152-11.349-3.2-16.299-2.133-5.205-5.333-9.899-9.301-13.867s-8.661-7.125-13.867-9.301c-4.949-2.048-10.453-3.2-16.299-3.2h-597.333c-5.845 0-11.349 1.152-16.299 3.2-5.205 2.133-9.899 5.333-13.867 9.301s-7.125 8.661-9.301 13.867c-2.048 4.949-3.2 10.453-3.2 16.299v170.667c0 23.552-19.115 42.667-42.667 42.667s-42.667-19.115-42.667-42.667v-170.667c0-17.28 3.456-33.835 9.728-48.981 6.485-15.701 16-29.781 27.776-41.557s25.856-21.291 41.557-27.776c15.104-6.229 31.659-9.685 48.939-9.685h597.333c17.28 0 33.835 3.456 48.981 9.728 15.701 6.485 29.781 16 41.557 27.776s21.291 25.856 27.776 41.557c6.229 15.104 9.685 31.659 9.685 48.939v170.667c0 23.552-19.115 42.667-42.667 42.667s-42.667-19.115-42.667-42.667zM469.333 729.003v-409.003c0-23.552 19.115-42.667 42.667-42.667s42.667 19.115 42.667 42.667v409.003l140.501-140.501c16.683-16.683 43.691-16.683 60.331 0s16.683 43.691 0 60.331l-213.333 213.333c-0.043 0.043-0.128 0.085-0.171 0.171-4.053 4.011-8.704 7.040-13.653 9.088-10.453 4.309-22.229 4.309-32.683 0-4.949-2.048-9.6-5.077-13.653-9.088-0.043-0.043-0.128-0.085-0.171-0.171l-213.333-213.333c-16.683-16.683-16.683-43.691 0-60.331s43.691-16.683 60.331 0z" />
<glyph unicode="&#xe972;" glyph-name="upload-2" d="M214 170.667h596v-86h-596v86zM384 256.667v256h-170l298 298 298-298h-170v-256h-256z" />
<glyph unicode="&#xe973;" glyph-name="upload-3" d="M469.333 345.003v-281.003c0-23.552 19.115-42.667 42.667-42.667s42.667 19.115 42.667 42.667v281.003l97.835-97.835c16.683-16.683 43.691-16.683 60.331 0s16.683 43.691 0 60.331l-170.667 170.667c-0.085 0.085-0.171 0.171-0.256 0.256-4.053 3.968-8.661 6.955-13.568 9.003-5.12 2.133-10.624 3.2-16.085 3.243-0.171 0-0.341 0-0.469 0-5.461-0.043-10.965-1.109-16.085-3.243-4.949-2.048-9.557-5.035-13.568-9.003-0.085-0.085-0.171-0.171-0.256-0.256l-170.667-170.667c-16.683-16.683-16.683-43.691 0-60.331s43.691-16.683 60.331 0zM890.411 137.899c30.379 16.555 56.149 38.443 76.672 63.915 21.333 26.411 36.949 56.619 46.379 88.576s12.629 65.835 9.003 99.584c-3.456 32.512-13.269 64.896-29.824 95.232-14.208 26.069-32.384 48.768-53.376 67.669-21.717 19.541-46.421 34.944-72.875 45.952-30.891 12.8-64.171 19.584-98.048 19.84h-22.528c-13.312 37.717-32.085 72.235-55.168 102.912-30.635 40.661-68.821 74.453-111.915 99.84s-91.179 42.411-141.568 49.536c-48.597 6.784-99.243 4.395-149.504-8.619s-95.744-35.413-134.912-64.939c-40.661-30.635-74.453-68.821-99.84-111.915s-42.411-91.179-49.493-141.568c-6.827-48.555-4.395-99.2 8.576-149.461 15.872-61.312 45.781-115.627 84.267-158.421 15.744-17.536 42.752-18.944 60.245-3.2s18.944 42.752 3.2 60.245c-29.355 32.64-52.693 74.667-65.109 122.752-10.155 39.253-11.989 78.592-6.699 116.224 5.504 39.125 18.773 76.501 38.571 110.123s46.080 63.317 77.653 87.083c30.379 22.869 65.664 40.32 104.917 50.475s78.592 11.989 116.224 6.699c39.125-5.504 76.544-18.731 110.123-38.528s63.317-46.080 87.083-77.653c22.869-30.379 40.32-65.664 50.475-104.917 4.907-18.56 21.547-32 41.301-32h53.461c22.869-0.171 45.269-4.736 65.92-13.312 17.707-7.339 34.133-17.621 48.512-30.592 13.909-12.501 25.984-27.605 35.541-45.099 11.093-20.352 17.579-41.899 19.883-63.488 2.389-22.443 0.256-45.013-6.016-66.432s-16.725-41.515-30.933-59.093c-13.611-16.896-30.763-31.445-51.115-42.581-20.693-11.264-28.331-37.205-17.024-57.899s37.205-28.331 57.899-17.024z" />
<glyph unicode="&#xe9ca;" glyph-name="earth" d="M512 960c-282.77 0-512-229.23-512-512s229.23-512 512-512 512 229.23 512 512-229.23 512-512 512zM512-0.002c-62.958 0-122.872 13.012-177.23 36.452l233.148 262.29c5.206 5.858 8.082 13.422 8.082 21.26v96c0 17.674-14.326 32-32 32-112.99 0-232.204 117.462-233.374 118.626-6 6.002-14.14 9.374-22.626 9.374h-128c-17.672 0-32-14.328-32-32v-192c0-12.122 6.848-23.202 17.69-28.622l110.31-55.156v-187.886c-116.052 80.956-192 215.432-192 367.664 0 68.714 15.49 133.806 43.138 192h116.862c8.488 0 16.626 3.372 22.628 9.372l128 128c6 6.002 9.372 14.14 9.372 22.628v77.412c40.562 12.074 83.518 18.588 128 18.588 70.406 0 137.004-16.26 196.282-45.2-4.144-3.502-8.176-7.164-12.046-11.036-36.266-36.264-56.236-84.478-56.236-135.764s19.97-99.5 56.236-135.764c36.434-36.432 85.218-56.264 135.634-56.26 3.166 0 6.342 0.080 9.518 0.236 13.814-51.802 38.752-186.656-8.404-372.334-0.444-1.744-0.696-3.488-0.842-5.224-81.324-83.080-194.7-134.656-320.142-134.656z" />
<glyph unicode="&#xf00a;" glyph-name="grid" d="M292.571 237.714v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM292.571 530.286v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM658.286 237.714v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM292.571 822.857v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM658.286 530.286v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM1024 237.714v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM658.286 822.857v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM1024 530.286v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857zM1024 822.857v-109.714c0-30.286-24.571-54.857-54.857-54.857h-182.857c-30.286 0-54.857 24.571-54.857 54.857v109.714c0 30.286 24.571 54.857 54.857 54.857h182.857c30.286 0 54.857-24.571 54.857-54.857z" />
<glyph unicode="&#xf0c9;" glyph-name="list1" horiz-adv-x="878" d="M877.714 182.857v-73.143c0-20-16.571-36.571-36.571-36.571h-804.571c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h804.571c20 0 36.571-16.571 36.571-36.571zM877.714 475.428v-73.143c0-20-16.571-36.571-36.571-36.571h-804.571c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h804.571c20 0 36.571-16.571 36.571-36.571zM877.714 768v-73.143c0-20-16.571-36.571-36.571-36.571h-804.571c-20 0-36.571 16.571-36.571 36.571v73.143c0 20 16.571 36.571 36.571 36.571h804.571c20 0 36.571-16.571 36.571-36.571z" />

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 92 KiB

BIN
src/Squidex/app/theme/icomoon/fonts/icomoon.ttf

Binary file not shown.

BIN
src/Squidex/app/theme/icomoon/fonts/icomoon.woff

Binary file not shown.

2
src/Squidex/app/theme/icomoon/selection.json

File diff suppressed because one or more lines are too long

172
src/Squidex/app/theme/icomoon/style.css

@ -1,10 +1,10 @@
@font-face {
font-family: 'icomoon';
src: url('fonts/icomoon.eot?dvmkhg');
src: url('fonts/icomoon.eot?dvmkhg#iefix') format('embedded-opentype'),
url('fonts/icomoon.ttf?dvmkhg') format('truetype'),
url('fonts/icomoon.woff?dvmkhg') format('woff'),
url('fonts/icomoon.svg?dvmkhg#icomoon') format('svg');
src: url('fonts/icomoon.eot?6bqpt3');
src: url('fonts/icomoon.eot?6bqpt3#iefix') format('embedded-opentype'),
url('fonts/icomoon.ttf?6bqpt3') format('truetype'),
url('fonts/icomoon.woff?6bqpt3') format('woff'),
url('fonts/icomoon.svg?6bqpt3#icomoon') format('svg');
font-weight: normal;
font-style: normal;
}
@ -24,6 +24,9 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-upload-2:before {
content: "\e972";
}
.icon-translate:before {
content: "\e96f";
}
@ -57,6 +60,84 @@
.icon-download:before {
content: "\e93e";
}
.icon-upload:before {
content: "\e970";
}
.icon-caret-bottom:before {
content: "\e96b";
}
.icon-caret-top:before {
content: "\e96c";
}
.icon-show:before {
content: "\e964";
}
.icon-show-all:before {
content: "\e965";
}
.icon-hide:before {
content: "\e966";
}
.icon-hide-all:before {
content: "\e967";
}
.icon-spinner2:before {
content: "\e959";
}
.icon-star-full:before {
content: "\e95d";
}
.icon-star-empty:before {
content: "\e95e";
}
.icon-twitter:before {
content: "\e95c";
}
.icon-hour-glass:before {
content: "\e954";
}
.icon-spinner:before {
content: "\e953";
}
.icon-clock:before {
content: "\e950";
}
.icon-bin2:before {
content: "\e902";
}
.icon-earth:before {
content: "\e9ca";
}
.icon-elapsed:before {
content: "\e943";
}
.icon-google:before {
content: "\e93b";
}
.icon-lock:before {
content: "\e934";
}
.icon-microsoft:before {
content: "\e940";
}
.icon-pause:before {
content: "\e92f";
}
.icon-play:before {
content: "\e930";
}
.icon-reset:before {
content: "\e92e";
}
.icon-settings2:before {
content: "\e92d";
}
.icon-timeout:before {
content: "\e944";
}
.icon-unlocked:before {
content: "\e933";
}
.icon-prerender:before {
content: "\e94c";
}
@ -213,6 +294,12 @@
.icon-user:before {
content: "\e928";
}
.icon-upload-3:before {
content: "\e973";
}
.icon-upload-4:before {
content: "\e971";
}
.icon-control-Color:before {
content: "\e94d";
}
@ -234,81 +321,6 @@
.icon-info:before {
content: "\e93c";
}
.icon-caret-bottom:before {
content: "\e96b";
}
.icon-caret-top:before {
content: "\e96c";
}
.icon-show:before {
content: "\e964";
}
.icon-show-all:before {
content: "\e965";
}
.icon-hide:before {
content: "\e966";
}
.icon-hide-all:before {
content: "\e967";
}
.icon-spinner2:before {
content: "\e959";
}
.icon-star-full:before {
content: "\e95d";
}
.icon-star-empty:before {
content: "\e95e";
}
.icon-twitter:before {
content: "\e95c";
}
.icon-hour-glass:before {
content: "\e954";
}
.icon-spinner:before {
content: "\e953";
}
.icon-clock:before {
content: "\e950";
}
.icon-bin2:before {
content: "\e902";
}
.icon-earth:before {
content: "\e9ca";
}
.icon-elapsed:before {
content: "\e943";
}
.icon-google:before {
content: "\e93b";
}
.icon-lock:before {
content: "\e934";
}
.icon-microsoft:before {
content: "\e940";
}
.icon-pause:before {
content: "\e92f";
}
.icon-play:before {
content: "\e930";
}
.icon-reset:before {
content: "\e92e";
}
.icon-settings2:before {
content: "\e92d";
}
.icon-timeout:before {
content: "\e944";
}
.icon-unlocked:before {
content: "\e933";
}
.icon-clone:before {
content: "\e96a";
}

Loading…
Cancel
Save