From 7dac86caf64affa89334bd64a92837e9b1ec935e Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sat, 13 Jul 2019 16:30:56 +0200 Subject: [PATCH] Modals fixed. --- .../event-consumers-page.component.html | 4 +- .../apps/pages/apps-page.component.html | 12 +- .../pages/content/content-page.component.html | 52 ++-- .../shared/assets-editor.component.html | 4 +- .../shared/content-item.component.html | 6 +- .../shared/due-time-selector.component.html | 4 +- .../shared/preview-button.component.html | 6 +- .../shared/references-editor.component.html | 4 +- .../pages/rules/rules-page.component.html | 4 +- .../app/features/schemas/declarations.ts | 3 +- src/Squidex/app/features/schemas/module.ts | 2 + .../schemas/pages/schema/field.component.html | 10 +- .../schema/schema-export-form.component.html | 9 + .../schema/schema-export-form.component.scss | 2 + .../schema/schema-export-form.component.ts | 33 +++ .../pages/schema/schema-page.component.html | 42 ++- .../pages/schema/schema-page.component.ts | 62 +---- .../types/string-validation.component.html | 6 +- .../pages/schemas/schemas-page.component.html | 4 +- .../pages/clients/client.component.html | 4 +- .../angular/forms/autocomplete.component.html | 6 +- .../angular/forms/color-picker.component.html | 6 +- .../angular/forms/dropdown.component.html | 6 +- .../angular/forms/tag-editor.component.html | 6 +- .../modals/dialog-renderer.component.html | 12 +- .../modals/modal-placement.directive.ts | 114 ++++++++ .../angular/modals/modal.component.ts | 254 ------------------ .../angular/modals/modal.directive.ts | 171 ++++++++++++ .../modals/onboarding-tooltip.component.html | 11 +- src/Squidex/app/framework/declarations.ts | 3 +- src/Squidex/app/framework/module.ts | 9 +- .../shared/components/asset.component.html | 4 +- .../language-selector.component.html | 6 +- .../components/markdown-editor.component.html | 4 +- .../components/rich-editor.component.html | 4 +- .../components/search-form.component.html | 10 +- .../components/search-form.component.ts | 4 + .../app/shared/services/schemas.service.ts | 46 ++++ .../pages/internal/apps-menu.component.html | 10 +- .../internal/profile-menu.component.html | 4 +- 40 files changed, 516 insertions(+), 447 deletions(-) create mode 100644 src/Squidex/app/features/schemas/pages/schema/schema-export-form.component.html create mode 100644 src/Squidex/app/features/schemas/pages/schema/schema-export-form.component.scss create mode 100644 src/Squidex/app/features/schemas/pages/schema/schema-export-form.component.ts create mode 100644 src/Squidex/app/framework/angular/modals/modal-placement.directive.ts delete mode 100644 src/Squidex/app/framework/angular/modals/modal.component.ts create mode 100644 src/Squidex/app/framework/angular/modals/modal.directive.ts diff --git a/src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.html b/src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.html index ba71728e3..2d5008929 100644 --- a/src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.html +++ b/src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.html @@ -59,7 +59,7 @@ - + Error @@ -69,4 +69,4 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/Squidex/app/features/apps/pages/apps-page.component.html b/src/Squidex/app/features/apps/pages/apps-page.component.html index 5c6484bfd..104f08827 100644 --- a/src/Squidex/app/features/apps/pages/apps-page.component.html +++ b/src/Squidex/app/features/apps/pages/apps-page.component.html @@ -102,20 +102,20 @@
{{info}}
- + - + - + - + - + - \ No newline at end of file + \ No newline at end of file diff --git a/src/Squidex/app/features/content/pages/content/content-page.component.html b/src/Squidex/app/features/content/pages/content/content-page.component.html index ff204bf58..cc47f0d29 100644 --- a/src/Squidex/app/features/content/pages/content/content-page.component.html +++ b/src/Squidex/app/features/content/pages/content/content-page.component.html @@ -44,35 +44,37 @@ - - + + - - diff --git a/src/Squidex/app/features/content/shared/due-time-selector.component.html b/src/Squidex/app/features/content/shared/due-time-selector.component.html index e13db5acb..933b331a9 100644 --- a/src/Squidex/app/features/content/shared/due-time-selector.component.html +++ b/src/Squidex/app/features/content/shared/due-time-selector.component.html @@ -1,4 +1,4 @@ - + {{dueTimeAction}} content item(s) @@ -28,4 +28,4 @@ - + diff --git a/src/Squidex/app/features/content/shared/preview-button.component.html b/src/Squidex/app/features/content/shared/preview-button.component.html index a745867c2..fad7cd9ed 100644 --- a/src/Squidex/app/features/content/shared/preview-button.component.html +++ b/src/Squidex/app/features/content/shared/preview-button.component.html @@ -9,11 +9,11 @@
- -
\ No newline at end of file diff --git a/src/Squidex/app/features/content/shared/references-editor.component.html b/src/Squidex/app/features/content/shared/references-editor.component.html index cc1b9ebcc..cd11c82ac 100644 --- a/src/Squidex/app/features/content/shared/references-editor.component.html +++ b/src/Squidex/app/features/content/shared/references-editor.component.html @@ -27,7 +27,7 @@ - + - \ No newline at end of file + \ No newline at end of file diff --git a/src/Squidex/app/features/rules/pages/rules/rules-page.component.html b/src/Squidex/app/features/rules/pages/rules/rules-page.component.html index 0d8076b6e..bbc51051f 100644 --- a/src/Squidex/app/features/rules/pages/rules/rules-page.component.html +++ b/src/Squidex/app/features/rules/pages/rules/rules-page.component.html @@ -67,7 +67,7 @@ - + - + diff --git a/src/Squidex/app/features/schemas/declarations.ts b/src/Squidex/app/features/schemas/declarations.ts index 490fc96bb..fb22fd1b8 100644 --- a/src/Squidex/app/features/schemas/declarations.ts +++ b/src/Squidex/app/features/schemas/declarations.ts @@ -28,9 +28,10 @@ export * from './pages/schema/forms/field-form-ui.component'; export * from './pages/schema/forms/field-form-validation.component'; export * from './pages/schema/forms/field-form.component'; -export * from './pages/schema/field.component'; export * from './pages/schema/field-wizard.component'; +export * from './pages/schema/field.component'; export * from './pages/schema/schema-edit-form.component'; +export * from './pages/schema/schema-export-form.component'; export * from './pages/schema/schema-page.component'; export * from './pages/schema/schema-preview-urls-form.component'; export * from './pages/schema/schema-scripts-form.component'; diff --git a/src/Squidex/app/features/schemas/module.ts b/src/Squidex/app/features/schemas/module.ts index ad7b2c59e..3ce75e43f 100644 --- a/src/Squidex/app/features/schemas/module.ts +++ b/src/Squidex/app/features/schemas/module.ts @@ -37,6 +37,7 @@ import { ReferencesUIComponent, ReferencesValidationComponent, SchemaEditFormComponent, + SchemaExportFormComponent, SchemaFormComponent, SchemaPageComponent, SchemaPreviewUrlsFormComponent, @@ -101,6 +102,7 @@ const routes: Routes = [ ReferencesUIComponent, ReferencesValidationComponent, SchemaEditFormComponent, + SchemaExportFormComponent, SchemaFormComponent, SchemaPageComponent, SchemaPreviewUrlsFormComponent, diff --git a/src/Squidex/app/features/schemas/pages/schema/field.component.html b/src/Squidex/app/features/schemas/pages/schema/field.component.html index f048ceaac..03fdd2a4d 100644 --- a/src/Squidex/app/features/schemas/pages/schema/field.component.html +++ b/src/Squidex/app/features/schemas/pages/schema/field.component.html @@ -29,8 +29,8 @@ - - @@ -113,11 +113,11 @@ - + - + \ No newline at end of file diff --git a/src/Squidex/app/features/schemas/pages/schema/schema-export-form.component.html b/src/Squidex/app/features/schemas/pages/schema/schema-export-form.component.html new file mode 100644 index 000000000..56dec217d --- /dev/null +++ b/src/Squidex/app/features/schemas/pages/schema/schema-export-form.component.html @@ -0,0 +1,9 @@ + + + Export Schema + + + + + + \ No newline at end of file diff --git a/src/Squidex/app/features/schemas/pages/schema/schema-export-form.component.scss b/src/Squidex/app/features/schemas/pages/schema/schema-export-form.component.scss new file mode 100644 index 000000000..fbb752506 --- /dev/null +++ b/src/Squidex/app/features/schemas/pages/schema/schema-export-form.component.scss @@ -0,0 +1,2 @@ +@import '_vars'; +@import '_mixins'; \ No newline at end of file diff --git a/src/Squidex/app/features/schemas/pages/schema/schema-export-form.component.ts b/src/Squidex/app/features/schemas/pages/schema/schema-export-form.component.ts new file mode 100644 index 000000000..dcf003200 --- /dev/null +++ b/src/Squidex/app/features/schemas/pages/schema/schema-export-form.component.ts @@ -0,0 +1,33 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; + +import { SchemaDetailsDto } from '@app/shared'; + +@Component({ + selector: 'sqx-schema-export-form', + styleUrls: ['./schema-export-form.component.scss'], + templateUrl: './schema-export-form.component.html' +}) +export class SchemaExportFormComponent implements OnInit { + @Output() + public complete = new EventEmitter(); + + @Input() + public schema: SchemaDetailsDto; + + public export: any; + + public ngOnInit() { + this.export = this.schema.export(); + } + + public emitComplete() { + this.complete.emit(); + } +} \ No newline at end of file diff --git a/src/Squidex/app/features/schemas/pages/schema/schema-page.component.html b/src/Squidex/app/features/schemas/pages/schema/schema-page.component.html index 65c6d67de..a3aad8268 100644 --- a/src/Squidex/app/features/schemas/pages/schema/schema-page.component.html +++ b/src/Squidex/app/features/schemas/pages/schema/schema-page.component.html @@ -6,7 +6,7 @@ - @@ -24,8 +24,8 @@ - - @@ -101,40 +101,32 @@ - + - + - + - + - + - + - + - + - - - - Export Schema - - - -
- -
-
-
-
\ No newline at end of file + + + + \ No newline at end of file diff --git a/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts b/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts index d9957a1f0..acd0faf18 100644 --- a/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts +++ b/src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts @@ -21,8 +21,7 @@ import { PatternsState, ResourceOwner, SchemaDetailsDto, - SchemasState, - Types + SchemasState } from '@app/shared'; import { @@ -40,18 +39,14 @@ import { export class SchemaPageComponent extends ResourceOwner implements OnInit { public fieldTypes = fieldTypes; - public schemaExport: any; public schema: SchemaDetailsDto; - public exportSchemaDialog = new DialogModel(); - - public configureScriptsDialog = new DialogModel(); + public addFieldDialog = new DialogModel(); public configurePreviewUrlsDialog = new DialogModel(); - + public configureScriptsDialog = new DialogModel(); public editOptionsDropdown = new ModalModel(); public editSchemaDialog = new DialogModel(); - - public addFieldDialog = new DialogModel(); + public exportDialog = new DialogModel(); constructor( public readonly appsState: AppsState, @@ -72,8 +67,6 @@ export class SchemaPageComponent extends ResourceOwner implements OnInit { .subscribe(schema => { if (schema) { this.schema = schema; - - this.export(); } })); } @@ -102,52 +95,7 @@ export class SchemaPageComponent extends ResourceOwner implements OnInit { } public cloneSchema() { - this.messageBus.emit(new SchemaCloning(this.schemaExport)); - } - - private export() { - const cleanup = (source: any, ...exclude: string[]): any => { - const clone = {}; - - for (const key in source) { - if (source.hasOwnProperty(key) && exclude.indexOf(key) < 0) { - const value = source[key]; - - if (value) { - clone[key] = value; - } - } - } - - return clone; - }; - - const result: any = { - fields: this.schema.fields.map(field => { - const copy = cleanup(field, 'fieldId'); - - copy.properties = cleanup(field.properties); - - if (Types.isArray(copy.nested)) { - if (copy.nested.length === 0) { - delete copy['nested']; - } else { - copy.nested = field.nested.map(nestedField => { - const nestedCopy = cleanup(nestedField, 'fieldId', 'parentId'); - - nestedCopy.properties = cleanup(nestedField.properties); - - return nestedCopy; - }); - } - } - - return copy; - }), - properties: cleanup(this.schema.properties) - }; - - this.schemaExport = result; + this.messageBus.emit(new SchemaCloning(this.schema.export())); } private back() { diff --git a/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.html b/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.html index 42ad9da3c..3a9f2e30e 100644 --- a/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.html +++ b/src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.html @@ -45,8 +45,8 @@ (focus)="patternsModal.show()" (blur)="patternsModal.hide()" /> - -
+ +

Suggestions

@@ -54,7 +54,7 @@
{{pattern.pattern}}
- +
diff --git a/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html b/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html index 9e81f0c2c..7ffc491fd 100644 --- a/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html +++ b/src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.html @@ -38,10 +38,10 @@
- + - + \ No newline at end of file diff --git a/src/Squidex/app/features/settings/pages/clients/client.component.html b/src/Squidex/app/features/settings/pages/clients/client.component.html index 34e338fc6..66a1ee7c0 100644 --- a/src/Squidex/app/features/settings/pages/clients/client.component.html +++ b/src/Squidex/app/features/settings/pages/clients/client.component.html @@ -70,7 +70,7 @@ - + Connect @@ -138,4 +138,4 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/forms/autocomplete.component.html b/src/Squidex/app/framework/angular/forms/autocomplete.component.html index af0359ff0..f83b6014c 100644 --- a/src/Squidex/app/framework/angular/forms/autocomplete.component.html +++ b/src/Squidex/app/framework/angular/forms/autocomplete.component.html @@ -5,8 +5,8 @@ autocorrect="off" autocapitalize="off"> - -
+ +
- +
\ No newline at end of file diff --git a/src/Squidex/app/framework/angular/forms/color-picker.component.html b/src/Squidex/app/framework/angular/forms/color-picker.component.html index b2bde8331..abf2e76d2 100644 --- a/src/Squidex/app/framework/angular/forms/color-picker.component.html +++ b/src/Squidex/app/framework/angular/forms/color-picker.component.html @@ -14,8 +14,8 @@ - -
+
- \ No newline at end of file + \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/forms/dropdown.component.html b/src/Squidex/app/framework/angular/forms/dropdown.component.html index ba59796c3..72ba5a1d4 100644 --- a/src/Squidex/app/framework/angular/forms/dropdown.component.html +++ b/src/Squidex/app/framework/angular/forms/dropdown.component.html @@ -15,14 +15,14 @@
- -
+ +
{{item}}
- +
\ No newline at end of file diff --git a/src/Squidex/app/framework/angular/forms/tag-editor.component.html b/src/Squidex/app/framework/angular/forms/tag-editor.component.html index 5d0350a5a..41566bc53 100644 --- a/src/Squidex/app/framework/angular/forms/tag-editor.component.html +++ b/src/Squidex/app/framework/angular/forms/tag-editor.component.html @@ -22,8 +22,8 @@ spellcheck="false">
- -
+ +
{{item}}
- \ No newline at end of file +
\ No newline at end of file diff --git a/src/Squidex/app/framework/angular/modals/dialog-renderer.component.html b/src/Squidex/app/framework/angular/modals/dialog-renderer.component.html index 31d6888b4..c25ea063f 100644 --- a/src/Squidex/app/framework/angular/modals/dialog-renderer.component.html +++ b/src/Squidex/app/framework/angular/modals/dialog-renderer.component.html @@ -1,7 +1,7 @@ - + {{snapshot.dialogRequest?.title}} @@ -16,7 +16,7 @@ - +
@@ -27,9 +27,7 @@
- -
- {{tooltip.text}} -
-
+
+ {{tooltip.text}} +
\ No newline at end of file diff --git a/src/Squidex/app/framework/angular/modals/modal-placement.directive.ts b/src/Squidex/app/framework/angular/modals/modal-placement.directive.ts new file mode 100644 index 000000000..befa42c5a --- /dev/null +++ b/src/Squidex/app/framework/angular/modals/modal-placement.directive.ts @@ -0,0 +1,114 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { AfterViewInit, Directive, ElementRef, Input, OnDestroy, Renderer2 } from '@angular/core'; +import { timer } from 'rxjs'; + +import { positionModal, ResourceOwner } from '@app/framework/internal'; + +@Directive({ + selector: '[sqxAnchoredTo]' +}) +export class ModalPlacementDirective extends ResourceOwner implements AfterViewInit, OnDestroy { + private targetElement: Element; + + @Input('sqxAnchoredTo') + public set target(element: Element) { + if (element !== this.targetElement) { + this.unsubscribeAll(); + + this.targetElement = element; + + if (element) { + this.listenToElement(element); + this.updatePosition(); + } + } + } + + @Input() + public offset = 2; + + @Input() + public position = 'bottom-right'; + + @Input() + public update = true; + + constructor( + private readonly renderer: Renderer2, + private readonly element: ElementRef + ) { + super(); + } + + private listenToElement(element: any) { + this.own( + this.renderer.listen(element, 'resize', () => { + this.updatePosition(); + })); + + this.own( + this.renderer.listen(this.element.nativeElement, 'resize', () => { + this.updatePosition(); + })); + + this.own(timer(100, 100).subscribe(() => this.updatePosition())); + } + + public ngAfterViewInit() { + const modalRef = this.element.nativeElement; + + this.renderer.setStyle(modalRef, 'position', 'fixed'); + this.renderer.setStyle(modalRef, 'z-index', '1000000'); + this.renderer.setStyle(modalRef, 'right', 'auto'); + this.renderer.setStyle(modalRef, 'bottom', 'auto'); + this.renderer.setStyle(modalRef, 'margin', '0'); + + this.updatePosition(); + } + + private updatePosition() { + if (!this.targetElement) { + return; + } + + const modalRef = this.element.nativeElement; + const modalRect = this.element.nativeElement.getBoundingClientRect(); + + if (modalRect.width === 0 || modalRect.height === 0) { + return; + } + + const targetRect = this.targetElement.getBoundingClientRect(); + + let y = 0; + let x = 0; + + if (this.position === 'full') { + x = -this.offset + targetRect.left; + y = -this.offset + targetRect.top; + + const w = 2 * this.offset + targetRect.width; + const h = 2 * this.offset + targetRect.height; + + this.renderer.setStyle(modalRef, 'width', `${w}px`); + this.renderer.setStyle(modalRef, 'height', `${h}px`); + } else { + const viewH = document.documentElement!.clientHeight; + const viewW = document.documentElement!.clientWidth; + + const position = positionModal(targetRect, modalRect, this.position, this.offset, this.update, viewW, viewH); + + x = position.x; + y = position.y; + } + + this.renderer.setStyle(modalRef, 'top', `${y}px`); + this.renderer.setStyle(modalRef, 'left', `${x}px`); + } +} \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/modals/modal.component.ts b/src/Squidex/app/framework/angular/modals/modal.component.ts deleted file mode 100644 index 608f58d30..000000000 --- a/src/Squidex/app/framework/angular/modals/modal.component.ts +++ /dev/null @@ -1,254 +0,0 @@ -/* - * Squidex Headless CMS - * - * @license - * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. - */ - -import { AfterViewInit, ChangeDetectorRef, Component, EmbeddedViewRef, Input, OnDestroy, Renderer2, TemplateRef, ViewChild } from '@angular/core'; -import { timer } from 'rxjs'; - -import { - DialogModel, - ModalModel, - positionModal, - ResourceOwner, - Types -} from '@app/framework/internal'; - -import { RootViewComponent } from './root-view.component'; - -declare type Model = DialogModel | ModalModel | any; - -@Component({ - selector: 'sqx-modal', - template: ` - - - - ` -}) -export class ModalComponent implements AfterViewInit, OnDestroy { - private readonly eventsView = new ResourceOwner(); - private readonly eventsModel = new ResourceOwner(); - private currentTarget: Element | null = null; - private currentModel: DialogModel | ModalModel | null = null; - private renderedView: EmbeddedViewRef | null = null; - private renderRoot: HTMLElement | null = null; - private isOpen: boolean; - - @Input() - public target: Element; - - @Input() - public set model(value: Model) { - if (this.currentModel !== value) { - this.currentModel = value; - - this.eventsModel.unsubscribeAll(); - - this.subscribeToModel(value); - } - } - - @Input() - public offset = 2; - - @Input() - public position = 'bottom-right'; - - @Input() - public autoPosition = true; - - @Input() - public backdrop = true; - - @Input() - public closeAuto = true; - - @Input() - public closeAlways = false; - - @ViewChild('templatePortalContent', { static: false }) - public templateRef: TemplateRef; - - constructor( - private readonly changeDetector: ChangeDetectorRef, - private readonly renderer: Renderer2, - private readonly rootView: RootViewComponent - ) { - } - - public ngAfterViewInit() { - this.update(this.isOpen); - } - - public ngOnDestroy() { - hideModal(this.currentModel); - - this.eventsView.unsubscribeAll(); - this.eventsModel.unsubscribeAll(); - } - - public onClick() { - if (this.closeAlways) { - this.model.hide(); - } - } - - private update(isOpen: boolean) { - if (!this.templateRef || this.isOpen === isOpen) { - return; - } - - this.eventsView.unsubscribeAll(); - - if (isOpen) { - if (!this.renderedView) { - this.currentTarget = this.target; - - this.renderedView = this.rootView.viewContainer.createEmbeddedView(this.templateRef); - this.renderRoot = this.renderedView.rootNodes[0]; - - this.setupStyles(); - this.subscribeToView(); - - this.changeDetector.detectChanges(); - } - } else { - if (this.renderedView) { - this.renderedView.destroy(); - this.renderedView = null; - this.renderRoot = null; - - this.changeDetector.detectChanges(); - } - } - - this.isOpen = isOpen; - } - - private setupStyles() { - this.renderer.setStyle(this.renderRoot, 'display', 'block'); - this.renderer.setStyle(this.renderRoot, 'right', 'auto'); - this.renderer.setStyle(this.renderRoot, 'bottom', 'auto'); - this.renderer.setStyle(this.renderRoot, 'margin', '0'); - this.renderer.setStyle(this.renderRoot, 'position', 'fixed'); - this.renderer.setStyle(this.renderRoot, 'z-index', '1000000'); - } - - private subscribeToModel(value: Model) { - if (isModalModel(value)) { - this.currentModel = value; - - this.eventsModel.own(value.isOpen.subscribe(update => { - this.update(update); - })); - } else { - this.update(value === true); - } - } - - private subscribeToView() { - if (this.renderRoot) { - this.eventsView.own(this.renderer.listen(this.renderRoot, 'resize', () => { - this.updatePosition(); - })); - - if (this.currentTarget) { - this.eventsView.own(this.renderer.listen(this.currentTarget, 'resize', () => { - this.updatePosition(); - })); - - this.eventsView.own(timer(100, 100).subscribe(() => { - this.updatePosition(); - })); - } - } - - if (this.closeAuto) { - document.addEventListener('click', this.documentClickListener, true); - - this.eventsView.own(() => { - document.removeEventListener('click', this.documentClickListener); - }); - } - } - - private documentClickListener = (event: MouseEvent) => { - if (!event.target || this.renderRoot === null) { - return; - } - - const model = this.currentModel; - - if (this.closeAlways) { - setTimeout(() => { - hideModal(model); - }, 100); - } else { - try { - const rootBounds = this.renderRoot.getBoundingClientRect(); - - if (rootBounds.width > 0 && rootBounds.height > 0) { - const clickedInside = this.renderRoot.contains(event.target); - - if (!clickedInside && this.model) { - this.model.hide(); - } - } - } catch (ex) { - return; - } - } - } - - private updatePosition() { - if (!this.renderRoot || !this.currentTarget) { - return; - } - - const modalRect = this.renderRoot.getBoundingClientRect(); - - if ((modalRect.width === 0 || modalRect.height === 0) && this.position !== 'full') { - return; - } - - const targetRect = this.currentTarget.getBoundingClientRect(); - - let y = 0; - let x = 0; - - if (this.position === 'full') { - x = -this.offset + targetRect.left; - y = -this.offset + targetRect.top; - - const w = 2 * this.offset + targetRect.width; - const h = 2 * this.offset + targetRect.height; - - this.renderer.setStyle(this.renderRoot, 'width', `${w}px`); - this.renderer.setStyle(this.renderRoot, 'height', `${h}px`); - } else { - const viewH = document.documentElement!.clientHeight; - const viewW = document.documentElement!.clientWidth; - - const position = positionModal(targetRect, modalRect, this.position, this.offset, this.autoPosition, viewW, viewH); - - x = position.x; - y = position.y; - } - - this.renderer.setStyle(this.renderRoot, 'top', `${y}px`); - this.renderer.setStyle(this.renderRoot, 'left', `${x}px`); - } -} - -function hideModal(model: Model) { - if (model && isModalModel(model)) { - model.hide(); - } -} - -function isModalModel(model: Model): model is DialogModel | ModalModel { - return Types.is(model, DialogModel) || Types.is(model, ModalModel); -} \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/modals/modal.directive.ts b/src/Squidex/app/framework/angular/modals/modal.directive.ts new file mode 100644 index 000000000..c1de0d80a --- /dev/null +++ b/src/Squidex/app/framework/angular/modals/modal.directive.ts @@ -0,0 +1,171 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { ChangeDetectorRef, Directive, EmbeddedViewRef, Input, OnDestroy, Renderer2, TemplateRef } from '@angular/core'; + +import { + DialogModel, + ModalModel, + ResourceOwner, + Types +} from '@app/framework/internal'; + +import { RootViewComponent } from './root-view.component'; + +declare type Model = DialogModel | ModalModel | any; + +@Directive({ + selector: '[sqxModal]' +}) +export class ModalDirective implements OnDestroy { + private readonly eventsView = new ResourceOwner(); + private readonly eventsModel = new ResourceOwner(); + private currentModel: DialogModel | ModalModel | null = null; + private renderedView: EmbeddedViewRef | null = null; + private renderRoots: HTMLElement[] | null; + private isOpen: boolean; + + @Input('sqxModal') + public set model(value: Model) { + if (this.currentModel !== value) { + this.currentModel = value; + + this.eventsModel.unsubscribeAll(); + + this.subscribeToModel(value); + } + } + + @Input('sqxModalCloseAuto') + public closeAuto = true; + + @Input('sqxModalCloseAlways') + public closeAlways = false; + + constructor( + private readonly changeDetector: ChangeDetectorRef, + private readonly renderer: Renderer2, + private readonly rootView: RootViewComponent, + private readonly templateRef: TemplateRef + ) { + } + + public ngOnDestroy() { + hideModal(this.currentModel); + + this.eventsView.unsubscribeAll(); + this.eventsModel.unsubscribeAll(); + } + + private update(isOpen: boolean) { + if (!this.templateRef || this.isOpen === isOpen) { + return; + } + + this.eventsView.unsubscribeAll(); + + if (isOpen) { + if (!this.renderedView) { + this.renderedView = this.rootView.viewContainer.createEmbeddedView(this.templateRef); + this.renderRoots = this.renderedView.rootNodes.filter(x => !!x.style); + + this.setupStyles(); + this.subscribeToView(); + + this.changeDetector.detectChanges(); + } + } else { + if (this.renderedView) { + this.renderedView.destroy(); + this.renderedView = null; + this.renderRoots = null; + + this.changeDetector.detectChanges(); + } + } + + this.isOpen = isOpen; + } + + private setupStyles() { + if (this.renderRoots) { + for (let node of this.renderRoots) { + this.renderer.setStyle(node, 'display', 'block'); + } + } + } + + private subscribeToModel(value: Model) { + if (isModalModel(value)) { + this.currentModel = value; + + this.eventsModel.own(value.isOpen.subscribe(update => { + this.update(update); + })); + } else { + this.update(value === true); + } + } + + private subscribeToView() { + if (this.closeAuto) { + document.addEventListener('click', this.documentClickListener, true); + + this.eventsView.own(() => { + document.removeEventListener('click', this.documentClickListener); + }); + } + + if (this.closeAlways && this.renderRoots) { + for (let node of this.renderRoots) { + this.eventsView.own(this.renderer.listen(node, 'click', this.elementListener)); + } + } + } + + private elementListener = (event: MouseEvent) => { + if (this.isClickedInside(event)) { + hideModal(this.currentModel); + } + } + + private documentClickListener = (event: MouseEvent) => { + if (!this.isClickedInside(event)) { + hideModal(this.currentModel); + } + } + + private isClickedInside(event: MouseEvent) { + try { + if (!this.renderRoots) { + return false; + } + + for (let node of this.renderRoots) { + const bounds = node.getBoundingClientRect(); + + if (bounds.width > 0 && bounds.height > 0 && node.contains(event.target)) { + return true; + } + } + + return false; + } catch (ex) { + return false; + } + } +} + +function hideModal(model: Model) { + if (model && isModalModel(model)) { + model.hide(); + } +} + +function isModalModel(model: Model): model is DialogModel | ModalModel { + return Types.is(model, DialogModel) || Types.is(model, ModalModel); +} \ No newline at end of file diff --git a/src/Squidex/app/framework/angular/modals/onboarding-tooltip.component.html b/src/Squidex/app/framework/angular/modals/onboarding-tooltip.component.html index d173fccec..1e5175886 100644 --- a/src/Squidex/app/framework/angular/modals/onboarding-tooltip.component.html +++ b/src/Squidex/app/framework/angular/modals/onboarding-tooltip.component.html @@ -1,9 +1,6 @@ - -
-
- - -
+ +
+
@@ -18,4 +15,4 @@
-
\ No newline at end of file + \ No newline at end of file diff --git a/src/Squidex/app/framework/declarations.ts b/src/Squidex/app/framework/declarations.ts index 01d533a13..689aeaf68 100644 --- a/src/Squidex/app/framework/declarations.ts +++ b/src/Squidex/app/framework/declarations.ts @@ -36,8 +36,9 @@ export * from './angular/http/loading.interceptor'; export * from './angular/http/http-extensions'; export * from './angular/modals/dialog-renderer.component'; -export * from './angular/modals/modal.component'; export * from './angular/modals/modal-dialog.component'; +export * from './angular/modals/modal-placement.directive'; +export * from './angular/modals/modal.directive'; export * from './angular/modals/onboarding-tooltip.component'; export * from './angular/modals/root-view.component'; export * from './angular/modals/tooltip.directive'; diff --git a/src/Squidex/app/framework/module.ts b/src/Squidex/app/framework/module.ts index b2264893a..eb5937a90 100644 --- a/src/Squidex/app/framework/module.ts +++ b/src/Squidex/app/framework/module.ts @@ -58,8 +58,9 @@ import { LoadingService, LocalStoreService, MessageBus, - ModalComponent, ModalDialogComponent, + ModalDirective, + ModalPlacementDirective, MoneyPipe, MonthPipe, OnboardingService, @@ -135,8 +136,9 @@ import { KeysPipe, KNumberPipe, LightenPipe, - ModalComponent, ModalDialogComponent, + ModalDirective, + ModalPlacementDirective, MoneyPipe, MonthPipe, OnboardingTooltipComponent, @@ -202,8 +204,9 @@ import { KeysPipe, KNumberPipe, LightenPipe, - ModalComponent, + ModalDirective, ModalDialogComponent, + ModalPlacementDirective, MoneyPipe, MonthPipe, OnboardingTooltipComponent, diff --git a/src/Squidex/app/shared/components/asset.component.html b/src/Squidex/app/shared/components/asset.component.html index c70b74092..bd125c193 100644 --- a/src/Squidex/app/shared/components/asset.component.html +++ b/src/Squidex/app/shared/components/asset.component.html @@ -137,12 +137,12 @@ - + - + \ No newline at end of file diff --git a/src/Squidex/app/shared/components/language-selector.component.html b/src/Squidex/app/shared/components/language-selector.component.html index 5c9fd366c..06a619277 100644 --- a/src/Squidex/app/shared/components/language-selector.component.html +++ b/src/Squidex/app/shared/components/language-selector.component.html @@ -9,11 +9,11 @@ {{selectedLanguage.iso2Code}} - - \ No newline at end of file diff --git a/src/Squidex/app/shared/components/markdown-editor.component.html b/src/Squidex/app/shared/components/markdown-editor.component.html index d78a78f94..f0947d526 100644 --- a/src/Squidex/app/shared/components/markdown-editor.component.html +++ b/src/Squidex/app/shared/components/markdown-editor.component.html @@ -4,8 +4,8 @@
- + - \ No newline at end of file + \ No newline at end of file diff --git a/src/Squidex/app/shared/components/rich-editor.component.html b/src/Squidex/app/shared/components/rich-editor.component.html index 4be2031bf..9a6ea240a 100644 --- a/src/Squidex/app/shared/components/rich-editor.component.html +++ b/src/Squidex/app/shared/components/rich-editor.component.html @@ -2,8 +2,8 @@
Loading editor...
- + - \ No newline at end of file + \ No newline at end of file diff --git a/src/Squidex/app/shared/components/search-form.component.html b/src/Squidex/app/shared/components/search-form.component.html index 20430ead9..3033c4bfa 100644 --- a/src/Squidex/app/shared/components/search-form.component.html +++ b/src/Squidex/app/shared/components/search-form.component.html @@ -37,8 +37,8 @@ Search for content using full text search over all fields and languages! - - - + @@ -55,8 +55,8 @@ - + - \ No newline at end of file + \ No newline at end of file diff --git a/src/Squidex/app/shell/pages/internal/profile-menu.component.html b/src/Squidex/app/shell/pages/internal/profile-menu.component.html index a1b5bed47..8cf8f507f 100644 --- a/src/Squidex/app/shell/pages/internal/profile-menu.component.html +++ b/src/Squidex/app/shell/pages/internal/profile-menu.component.html @@ -8,7 +8,7 @@ - + -
+ \ No newline at end of file