Browse Source

Feature/reactive ui (#485)

* Reactive UI

* Disabled improved.

* Resized service.
pull/486/head
Sebastian Stehle 7 years ago
committed by GitHub
parent
commit
7690205376
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 17
      frontend/app/features/content/shared/forms/assets-editor.component.html
  2. 45
      frontend/app/framework/angular/resized.directive.ts
  3. 36
      frontend/app/framework/angular/sync-width.directive.ts
  4. 1
      frontend/app/framework/internal.ts
  5. 2
      frontend/app/framework/module.ts
  6. 58
      frontend/app/framework/services/resize.service.ts
  7. 28
      frontend/app/shared/components/assets/asset.component.html

17
frontend/app/features/content/shared/forms/assets-editor.component.html

@ -1,8 +1,13 @@
<div class="assets-container" [class.disabled]="snapshot.isDisabled" (sqxDropFile)="addFiles($event)" tabindex="1000"> <div class="assets-container"
(sqxDropFile)="addFiles($event)"
(sqxDropDisabled)="snapshot.isDisabled"
tabindex="1000">
<div class="header list"> <div class="header list">
<div class="row no-gutters"> <div class="row no-gutters">
<div class="col"> <div class="col" [class.disabled]="snapshot.isDisabled">
<div class="drop-area align-items-center" (click)="assetsDialog.show()" (sqxDropFile)="addFiles($event)"> <div class="drop-area align-items-center" (click)="assetsDialog.show()"
(sqxDropFile)="addFiles($event)"
(sqxDropDisabled)="snapshot.isDisabled">
Drop files or click Drop files or click
</div> </div>
</div> </div>
@ -26,11 +31,15 @@
<ng-container *ngIf="!snapshot.isListView; else listTemplate"> <ng-container *ngIf="!snapshot.isListView; else listTemplate">
<div class="row no-gutters"> <div class="row no-gutters">
<sqx-asset *ngFor="let file of snapshot.assetFiles" [assetFile]="file" <sqx-asset *ngFor="let file of snapshot.assetFiles" [assetFile]="file"
[isDisabled]="snapshot.isDisabled"
[isCompact]="snapshot.isCompact"
(loadError)="removeLoadingAsset(file)" (loadError)="removeLoadingAsset(file)"
(load)="addAsset(file, $event)"> (load)="addAsset(file, $event)">
</sqx-asset> </sqx-asset>
<sqx-asset *ngFor="let asset of snapshot.assets; trackBy: trackByAsset" <sqx-asset *ngFor="let asset of snapshot.assets; trackBy: trackByAsset"
[asset]="asset" [asset]="asset"
[isDisabled]="snapshot.isDisabled"
[isCompact]="snapshot.isCompact"
(update)="notifyOthers(asset)" (update)="notifyOthers(asset)"
[removeMode]="true" [removeMode]="true"
(remove)="removeLoadedAsset(asset)"> (remove)="removeLoadedAsset(asset)">
@ -42,6 +51,7 @@
<div class="list-view"> <div class="list-view">
<sqx-asset *ngFor="let file of snapshot.assetFiles" [assetFile]="file" <sqx-asset *ngFor="let file of snapshot.assetFiles" [assetFile]="file"
[isListView]="true" [isListView]="true"
[isDisabled]="snapshot.isDisabled"
[isCompact]="snapshot.isCompact" [isCompact]="snapshot.isCompact"
(loadError)="removeLoadingAsset(file)" (loadError)="removeLoadingAsset(file)"
(load)="addAsset(file, $event)"> (load)="addAsset(file, $event)">
@ -59,6 +69,7 @@
<sqx-asset <sqx-asset
[asset]="asset" [asset]="asset"
[isListView]="true" [isListView]="true"
[isDisabled]="snapshot.isDisabled"
[isCompact]="snapshot.isCompact" [isCompact]="snapshot.isCompact"
(update)="notifyOthers(asset)" (update)="notifyOthers(asset)"
[removeMode]="true" [removeMode]="true"

45
frontend/app/framework/angular/resized.directive.ts

@ -5,25 +5,18 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { Directive, ElementRef, EventEmitter, Input, OnChanges, OnDestroy, Output } from '@angular/core'; import { Directive, ElementRef, EventEmitter, Input, NgZone, OnChanges, OnDestroy, Output } from '@angular/core';
import ResizeObserver from 'resize-observer-polyfill';
const entriesMap = new WeakMap(); import {
ResizeListener,
const observer = new ResizeObserver(entries => { ResizeService,
for (const entry of entries) { ResourceOwner
if (entriesMap.has(entry.target)) { } from '@app/framework/internal';
const component = entriesMap.get(entry.target);
component.onResized(entry);
}
}
});
@Directive({ @Directive({
selector: '[sqxResized], [sqxResizeCondition]' selector: '[sqxResized], [sqxResizeCondition]'
}) })
export class ResizedDirective implements OnDestroy, OnChanges { export class ResizedDirective extends ResourceOwner implements OnDestroy, OnChanges, ResizeListener {
private condition: ((rect: ClientRect) => boolean) | undefined; private condition: ((rect: ClientRect) => boolean) | undefined;
private conditionValue = false; private conditionValue = false;
@ -39,12 +32,12 @@ export class ResizedDirective implements OnDestroy, OnChanges {
@Output('sqxResized') @Output('sqxResized')
public resize = new EventEmitter<ClientRect>(); public resize = new EventEmitter<ClientRect>();
constructor( constructor(resizeService: ResizeService, element: ElementRef,
private readonly element: ElementRef private readonly zone: NgZone
) { ) {
entriesMap.set(element.nativeElement, this); super();
observer.observe(element.nativeElement); this.own(resizeService.listen(element.nativeElement, this));
} }
public ngOnChanges() { public ngOnChanges() {
@ -62,21 +55,21 @@ export class ResizedDirective implements OnDestroy, OnChanges {
} }
} }
public ngOnDestroy() { public onResize(rect: ClientRect) {
observer.unobserve(this.element.nativeElement);
}
public onResized(entry: ResizeObserverEntry) {
if (this.condition) { if (this.condition) {
const value = this.condition(entry.contentRect); const value = this.condition(rect);
if (this.conditionValue !== value) { if (this.conditionValue !== value) {
this.resizeCondition.emit(value); this.zone.run(() => {
this.resizeCondition.emit(value);
});
this.conditionValue = value; this.conditionValue = value;
} }
} else { } else {
this.resize.emit(entry.contentRect); this.zone.run(() => {
this.resize.emit(rect);
});
} }
} }
} }

36
frontend/app/framework/angular/sync-width.directive.ts

@ -5,40 +5,46 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { AfterViewInit, Directive, ElementRef, Input, OnInit, Renderer2 } from '@angular/core'; import { AfterViewInit, Directive, ElementRef, Input, Renderer2 } from '@angular/core';
import { timer } from 'rxjs';
import { ResourceOwner } from '@app/framework/internal'; import {
ResizeListener,
ResizeService,
ResourceOwner
} from '@app/framework/internal';
@Directive({ @Directive({
selector: '[sqxSyncWidth]' selector: '[sqxSyncWidth]'
}) })
export class SyncWidthDirective extends ResourceOwner implements OnInit, AfterViewInit { export class SyncWidthDirective extends ResourceOwner implements AfterViewInit, ResizeListener {
@Input('sqxSyncWidth') @Input('sqxSyncWidth')
public target: HTMLElement; public target: HTMLElement;
constructor( constructor(
private readonly element: ElementRef<HTMLElement>, private readonly element: ElementRef<HTMLElement>,
private readonly renderer: Renderer2 private readonly renderer: Renderer2,
private readonly resizeService: ResizeService
) { ) {
super(); super();
}
public ngOnInit() { this.own(this.resizeService.listen(this.element.nativeElement, this));
this.own(timer(100, 100).subscribe(() => this.reposition()));
} }
public ngAfterViewInit() { public ngAfterViewInit() {
this.reposition(); this.onReposition();
} }
private reposition() { public onResize(size: ClientRect) {
if (!this.target) { this.resize(size.width);
return; }
}
const size = this.element.nativeElement.clientWidth; private onReposition() {
this.resize(this.element.nativeElement.clientWidth);
}
this.renderer.setStyle(this.target, 'width', `${size}px`); private resize(width: number) {
if (this.target) {
this.renderer.setStyle(this.target, 'width', `${width}px`);
}
} }
} }

1
frontend/app/framework/internal.ts

@ -16,6 +16,7 @@ export * from './services/loading.service';
export * from './services/local-store.service'; export * from './services/local-store.service';
export * from './services/message-bus.service'; export * from './services/message-bus.service';
export * from './services/onboarding.service'; export * from './services/onboarding.service';
export * from './services/resize.service';
export * from './services/resource-loader.service'; export * from './services/resource-loader.service';
export * from './services/shortcut.service'; export * from './services/shortcut.service';
export * from './services/temp.service'; export * from './services/temp.service';

2
frontend/app/framework/module.ts

@ -75,6 +75,7 @@ import {
PopupLinkDirective, PopupLinkDirective,
ProgressBarComponent, ProgressBarComponent,
ResizedDirective, ResizedDirective,
ResizeService,
ResourceLoaderService, ResourceLoaderService,
RootViewComponent, RootViewComponent,
SafeHtmlPipe, SafeHtmlPipe,
@ -269,6 +270,7 @@ export class SqxFrameworkModule {
LoadingService, LoadingService,
MessageBus, MessageBus,
OnboardingService, OnboardingService,
ResizeService,
ResourceLoaderService, ResourceLoaderService,
ShortcutService, ShortcutService,
TempService, TempService,

58
frontend/app/framework/services/resize.service.ts

@ -0,0 +1,58 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { Injectable, OnDestroy } from '@angular/core';
import ResizeObserver from 'resize-observer-polyfill';
export interface ResizeListener {
onResize(rect: DOMRect, element: Element): void;
}
export const ResizeServiceFactory = () => {
return new ResizeService();
};
@Injectable()
export class ResizeService implements OnDestroy {
private readonly listeners = new WeakMap<Element, ResizeListener>();
private observer: ResizeObserver;
public ngOnDestroy() {
if (this.observer) {
this.observer.disconnect();
}
}
public listen(target: Element, listener: ResizeListener) {
if (!this.observer) {
this.observer = new ResizeObserver(entries => {
for (const entry of entries) {
if (this.listeners.has(entry.target)) {
const component = this.listeners.get(entry.target);
if (component) {
component.onResize(entry.contentRect as any, entry.target);
}
}
}
});
}
this.listeners.set(target, listener);
this.observer.observe(target);
return () => {
this.unlisten(target);
};
}
public unlisten(target: Element) {
this.observer.unobserve(target);
}
}

28
frontend/app/shared/components/assets/asset.component.html

@ -30,18 +30,23 @@
<a class="file-edit ml-2" (click)="edit()" *ngIf="!isDisabled"> <a class="file-edit ml-2" (click)="edit()" *ngIf="!isDisabled">
<i class="icon-pencil"></i> <i class="icon-pencil"></i>
</a> </a>
<a class="file-download ml-2" [href]="asset | sqxAssetUrl" sqxStopClick sqxExternalLink="noicon"> <a class="file-download ml-2" [href]="asset | sqxAssetUrl" sqxStopClick sqxExternalLink="noicon">
<i class="icon-download"></i> <i class="icon-download"></i>
</a> </a>
<a class="file-delete ml-2" *ngIf="!isDisabled && !removeMode && asset.canDelete"
(sqxConfirmClick)="delete.emit()" <ng-container *ngIf="!isDisabled">
confirmTitle="Delete asset" <a class="file-delete ml-2" *ngIf="!removeMode && asset.canDelete"
confirmText="Do you really want to delete the asset?"> (sqxConfirmClick)="delete.emit()"
<i class="icon-delete"></i> confirmTitle="Delete asset"
</a> confirmText="Do you really want to delete the asset?">
<a class="file-delete ml-2" (click)="remove.emit()" *ngIf="removeMode"> <i class="icon-delete"></i>
<i class="icon-close"></i> </a>
</a>
<a class="file-delete ml-2" (click)="remove.emit()" *ngIf="removeMode">
<i class="icon-close"></i>
</a>
</ng-container>
</div> </div>
<span class="overlay-type" *ngIf="asset.fileType"> <span class="overlay-type" *ngIf="asset.fileType">
@ -133,13 +138,14 @@
</td> </td>
</ng-container> </ng-container>
<td class="col-actions text-right" *ngIf="!isDisabled || removeMode"> <td class="col-actions text-right" *ngIf="!isDisabled">
<button type="button" class="btn btn-text-danger" *ngIf="!isDisabled && !removeMode && asset.canDelete" <button type="button" class="btn btn-text-danger" *ngIf="!removeMode && asset.canDelete"
(sqxConfirmClick)="remove.emit()" (sqxConfirmClick)="remove.emit()"
confirmTitle="Delete asset" confirmTitle="Delete asset"
confirmText="Do you really want to delete the asset?"> confirmText="Do you really want to delete the asset?">
<i class="icon-bin2"></i> <i class="icon-bin2"></i>
</button> </button>
<button type="button" class="btn btn-text-secondary" (click)="remove.emit()" *ngIf="removeMode"> <button type="button" class="btn btn-text-secondary" (click)="remove.emit()" *ngIf="removeMode">
<i class="icon-close"></i> <i class="icon-close"></i>
</button> </button>

Loading…
Cancel
Save