Browse Source

1) Fix singletons

2) Fix modals.
pull/305/head
Sebastian 8 years ago
parent
commit
8e39ec198d
  1. 3
      src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs
  2. 2
      src/Squidex.Domain.Apps.Entities/Contents/SingletonCommandMiddleware.cs
  3. 4
      src/Squidex/app/features/administration/pages/event-consumers/event-consumers-page.component.ts
  4. 7
      src/Squidex/app/features/apps/pages/apps-page.component.ts
  5. 2
      src/Squidex/app/features/content/pages/content/content-page.component.html
  6. 4
      src/Squidex/app/features/content/pages/content/content-page.component.ts
  7. 4
      src/Squidex/app/features/content/pages/contents/contents-page.component.ts
  8. 4
      src/Squidex/app/features/content/pages/contents/search-form.component.ts
  9. 6
      src/Squidex/app/features/content/shared/assets-editor.component.ts
  10. 2
      src/Squidex/app/features/content/shared/content-item.component.html
  11. 4
      src/Squidex/app/features/content/shared/content-item.component.ts
  12. 4
      src/Squidex/app/features/content/shared/contents-selector.component.ts
  13. 4
      src/Squidex/app/features/content/shared/due-time-selector.component.ts
  14. 4
      src/Squidex/app/features/rules/pages/rules/rules-page.component.ts
  15. 2
      src/Squidex/app/features/schemas/pages/schema/field.component.html
  16. 7
      src/Squidex/app/features/schemas/pages/schema/field.component.ts
  17. 13
      src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts
  18. 4
      src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.ts
  19. 4
      src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts
  20. 4
      src/Squidex/app/features/settings/pages/clients/client.component.ts
  21. 4
      src/Squidex/app/framework/angular/forms/dropdown.component.ts
  22. 4
      src/Squidex/app/framework/angular/modals/dialog-renderer.component.ts
  23. 59
      src/Squidex/app/framework/angular/modals/modal-view.directive.ts
  24. 4
      src/Squidex/app/framework/angular/modals/onboarding-tooltip.component.ts
  25. 4
      src/Squidex/app/framework/angular/modals/tooltip.component.ts
  26. 100
      src/Squidex/app/framework/utils/modal-view.spec.ts
  27. 77
      src/Squidex/app/framework/utils/modal-view.ts
  28. 4
      src/Squidex/app/shared/components/asset.component.ts
  29. 2
      src/Squidex/app/shared/components/language-selector.component.html
  30. 4
      src/Squidex/app/shared/components/language-selector.component.ts
  31. 8
      src/Squidex/app/shared/components/markdown-editor.component.ts
  32. 8
      src/Squidex/app/shared/components/rich-editor.component.ts
  33. 8
      src/Squidex/app/shared/guards/schema-must-exist-published.guard.spec.ts
  34. 6
      src/Squidex/app/shared/guards/schema-must-exist-published.guard.ts
  35. 2
      src/Squidex/app/shell/pages/internal/apps-menu.component.html
  36. 7
      src/Squidex/app/shell/pages/internal/apps-menu.component.ts
  37. 2
      src/Squidex/app/shell/pages/internal/profile-menu.component.html
  38. 4
      src/Squidex/app/shell/pages/internal/profile-menu.component.ts
  39. 4
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs

3
src/Squidex.Domain.Apps.Entities/Contents/Guards/GuardContent.cs

@ -5,7 +5,6 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System;
using NodaTime;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities.Contents.Commands;
@ -25,7 +24,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guards
ValidateData(command, e);
});
if (schema.IsSingleton && command.ContentId != Guid.Empty)
if (schema.IsSingleton && command.ContentId != schema.Id)
{
throw new DomainException("Singleton content cannot be created.");
}

2
src/Squidex.Domain.Apps.Entities/Contents/SingletonCommandMiddleware.cs

@ -30,7 +30,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var data = new NamedContentData();
var contentId = Guid.Empty;
var contentId = schemaId.Id;
var content = new CreateContent { Data = data, ContentId = contentId, SchemaId = schemaId, Publish = true };
SimpleMapper.Map(context.Command, content);

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

@ -9,7 +9,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subscription, timer } from 'rxjs';
import { onErrorResumeNext, switchMap } from 'rxjs/operators';
import { ModalView } from '@app/shared';
import { DialogModel } from '@app/shared';
import { EventConsumerDto } from './../../services/event-consumers.service';
import { EventConsumersState } from './../../state/event-consumers.state';
@ -22,7 +22,7 @@ import { EventConsumersState } from './../../state/event-consumers.state';
export class EventConsumersPageComponent implements OnDestroy, OnInit {
private timerSubscription: Subscription;
public eventConsumerErrorDialog = new ModalView();
public eventConsumerErrorDialog = new DialogModel();
public eventConsumerError = '';
constructor(

7
src/Squidex/app/features/apps/pages/apps-page.component.ts

@ -11,7 +11,8 @@ import { take } from 'rxjs/operators';
import {
AppsState,
AuthService,
ModalView,
DialogModel,
ModalModel,
OnboardingService
} from '@app/shared';
@ -21,10 +22,10 @@ import {
templateUrl: './apps-page.component.html'
})
export class AppsPageComponent implements OnInit {
public addAppDialog = new ModalView();
public addAppDialog = new DialogModel();
public addAppTemplate = '';
public onboardingModal = new ModalView();
public onboardingModal = new ModalModel();
constructor(
public readonly appsState: AppsState,

2
src/Squidex/app/features/content/pages/content/content-page.component.html

@ -44,7 +44,7 @@
</button>
<ng-container *ngIf="content.isPending || !schema.isSingleton">
<div class="dropdown-menu" *sqxModalView="dropdown" [sqxModalTarget]="optionsButton" @fade>
<div class="dropdown-menu" *sqxModalView="dropdown;closeAlways:true" [sqxModalTarget]="optionsButton" @fade>
<ng-container *ngIf="content.isPending">
<a class="dropdown-item" (click)="discardChanges()">
Discard changes

4
src/Squidex/app/features/content/pages/content/content-page.component.ts

@ -24,7 +24,7 @@ import {
ImmutableArray,
LanguagesState,
MessageBus,
ModalView,
ModalModel,
SchemaDetailsDto,
SchemasState,
Version
@ -52,7 +52,7 @@ export class ContentPageComponent implements CanComponentDeactivate, OnDestroy,
public contentVersion: Version | null;
public contentForm: EditContentForm;
public dropdown = new ModalView(false, true);
public dropdown = new ModalModel();
public language: AppLanguageDto;
public languages: ImmutableArray<AppLanguageDto>;

4
src/Squidex/app/features/content/pages/contents/contents-page.component.ts

@ -16,7 +16,7 @@ import {
ContentsState,
ImmutableArray,
LanguagesState,
ModalView,
ModalModel,
SchemaDetailsDto,
SchemasState
} from '@app/shared';
@ -35,7 +35,7 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
public schema: SchemaDetailsDto;
public searchModal = new ModalView();
public searchModal = new ModalModel();
public selectedItems: { [id: string]: boolean; } = {};
public selectionCount = 0;

4
src/Squidex/app/features/content/pages/contents/search-form.component.ts

@ -8,7 +8,7 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
import { FormBuilder, FormControl } from '@angular/forms';
import { ModalView } from '@app/shared';
import { ModalModel } from '@app/shared';
@Component({
selector: 'sqx-search-form',
@ -40,7 +40,7 @@ export class SearchFormComponent implements OnChanges {
public contentsFilter = new FormControl();
public searchModal = new ModalView();
public searchModal = new ModalModel();
public searchForm =
this.formBuilder.group({
odataOrderBy: '',

6
src/Squidex/app/features/content/shared/assets-editor.component.ts

@ -15,7 +15,7 @@ import {
AssetDto,
AssetsService,
ImmutableArray,
ModalView,
ModalModel,
Types
} from '@app/shared';
@ -35,7 +35,7 @@ export class AssetsEditorComponent implements ControlValueAccessor {
private callChange = (v: any) => { /* NOOP */ };
private callTouched = () => { /* NOOP */ };
public selectorModal = new ModalView();
public assetsDialog = new ModalModel();
public newAssets = ImmutableArray.empty<File>();
public oldAssets = ImmutableArray.empty<AssetDto>();
@ -96,7 +96,7 @@ export class AssetsEditorComponent implements ControlValueAccessor {
this.updateValue();
}
this.selectorModal.hide();
this.assetsDialog.hide();
}
public onAssetRemoving(asset: AssetDto) {

2
src/Squidex/app/features/content/shared/content-item.component.html

@ -86,7 +86,7 @@
<button type="button" class="btn btn-link btn-secondary" (click)="dropdown.toggle(); $event.stopPropagation()" [class.active]="dropdown.isOpen | async" #optionsButton>
<i class="icon-dots"></i>
</button>
<div class="dropdown-menu" *sqxModalView="dropdown" [sqxModalTarget]="optionsButton" @fade>
<div class="dropdown-menu" *sqxModalView="dropdown;closeAlways:true" [sqxModalTarget]="optionsButton" @fade>
<a class="dropdown-item" (click)="publishing.emit(); $event.stopPropagation()" *ngIf="content.status === 'Draft'">
Publish
</a>

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

@ -14,7 +14,7 @@ import {
fadeAnimation,
FieldFormatter,
fieldInvariant,
ModalView,
ModalModel,
PatchContentForm,
RootFieldDto,
SchemaDetailsDto,
@ -70,7 +70,7 @@ export class ContentItemComponent implements OnChanges {
public patchForm: PatchContentForm;
public dropdown = new ModalView(false, true);
public dropdown = new ModalModel();
public values: any[] = [];

4
src/Squidex/app/features/content/shared/contents-selector.component.ts

@ -12,7 +12,7 @@ import {
ContentDto,
LanguageDto,
ManualContentsState,
ModalView,
ModalModel,
SchemaDetailsDto
} from '@app/shared';
@ -37,7 +37,7 @@ export class ContentsSelectorComponent implements OnInit {
@Output()
public selected = new EventEmitter<ContentDto[]>();
public searchModal = new ModalView();
public searchModal = new ModalModel();
public selectedItems: { [id: string]: ContentDto; } = {};
public selectionCount = 0;

4
src/Squidex/app/features/content/shared/due-time-selector.component.ts

@ -8,7 +8,7 @@
import { Component } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { fadeAnimation, ModalView } from '@app/shared';
import { DialogModel, fadeAnimation } from '@app/shared';
@Component({
selector: 'sqx-due-time-selector',
@ -19,7 +19,7 @@ import { fadeAnimation, ModalView } from '@app/shared';
]
})
export class DueTimeSelectorComponent {
public dueTimeDialog = new ModalView();
public dueTimeDialog = new DialogModel();
public dueTime: string | null = '';
public dueTimeFunction: Subject<string | null>;
public dueTimeAction: string | null = '';

4
src/Squidex/app/features/rules/pages/rules/rules-page.component.ts

@ -10,7 +10,7 @@ import { onErrorResumeNext } from 'rxjs/operators';
import {
AppsState,
ModalView,
DialogModel,
ruleActions,
RuleDto,
RulesState,
@ -27,7 +27,7 @@ export class RulesPageComponent implements OnInit {
public ruleActions = ruleActions;
public ruleTriggers = ruleTriggers;
public addRuleDialog = new ModalView();
public addRuleDialog = new DialogModel();
public wizardMode = 'Wizard';
public wizardRule: RuleDto | null;

2
src/Squidex/app/features/schemas/pages/schema/field.component.html

@ -121,7 +121,7 @@
</button>
</div>
<ng-container *sqxModalView="addFieldDialog;onRoot:true;closeAuto:false">
<ng-container *sqxModalView="addFieldDialog;onRoot:true;closeAuto:false;closeAlways:true">
<sqx-field-wizard [schema]="schema" [parent]="field"
(completed)="addFieldDialog.hide()">
</sqx-field-wizard>

7
src/Squidex/app/features/schemas/pages/schema/field.component.ts

@ -12,10 +12,11 @@ import { onErrorResumeNext } from 'rxjs/operators';
import {
AppPatternDto,
createProperties,
DialogModel,
EditFieldForm,
fadeAnimation,
ImmutableArray,
ModalView,
ModalModel,
NestedFieldDto,
RootFieldDto,
SchemaDetailsDto,
@ -44,14 +45,14 @@ export class FieldComponent implements OnInit {
@Input()
public patterns: ImmutableArray<AppPatternDto>;
public dropdown = new ModalView(false, true);
public dropdown = new ModalModel();
public isEditing = false;
public selectedTab = 0;
public editForm: EditFieldForm;
public addFieldDialog = new ModalView();
public addFieldDialog = new DialogModel();
constructor(
private readonly formBuilder: FormBuilder,

13
src/Squidex/app/features/schemas/pages/schema/schema-page.component.ts

@ -14,11 +14,12 @@ import { filter, map, onErrorResumeNext } from 'rxjs/operators';
import {
AppsState,
DialogModel,
fadeAnimation,
FieldDto,
fieldTypes,
MessageBus,
ModalView,
ModalModel,
PatternsState,
SchemaDetailsDto,
SchemasState,
@ -45,14 +46,14 @@ export class SchemaPageComponent implements OnDestroy, OnInit {
public schemaExport: any;
public schema: SchemaDetailsDto;
public exportSchemaDialog = new ModalView();
public exportSchemaDialog = new DialogModel();
public configureScriptsDialog = new ModalView();
public configureScriptsDialog = new DialogModel();
public editOptionsDropdown = new ModalView();
public editSchemaDialog = new ModalView();
public editOptionsDropdown = new ModalModel();
public editSchemaDialog = new DialogModel();
public addFieldDialog = new ModalView();
public addFieldDialog = new DialogModel();
constructor(
public readonly appsState: AppsState,

4
src/Squidex/app/features/schemas/pages/schema/types/string-validation.component.ts

@ -14,7 +14,7 @@ import {
AppPatternDto,
FieldDto,
ImmutableArray,
ModalView,
ModalModel,
StringFieldPropertiesDto
} from '@app/shared';
@ -43,7 +43,7 @@ export class StringValidationComponent implements OnDestroy, OnInit {
public showPatternSuggestions: Observable<boolean>;
public patternName: string;
public patternsModal = new ModalView(false, false);
public patternsModal = new ModalModel();
public ngOnDestroy() {
this.patternSubscription.unsubscribe();

4
src/Squidex/app/features/schemas/pages/schemas/schemas-page.component.ts

@ -14,8 +14,8 @@ import { map, onErrorResumeNext } from 'rxjs/operators';
import {
AppsState,
CreateCategoryForm,
DialogModel,
MessageBus,
ModalView,
SchemaDto,
SchemasState
} from '@app/shared';
@ -30,7 +30,7 @@ import { SchemaCloning } from './../messages';
export class SchemasPageComponent implements OnDestroy, OnInit {
private schemaCloningSubscription: Subscription;
public addSchemaDialog = new ModalView();
public addSchemaDialog = new DialogModel();
public addCategoryForm = new CreateCategoryForm(this.formBuilder);
public schemasFilter = new FormControl();

4
src/Squidex/app/features/settings/pages/clients/client.component.ts

@ -15,8 +15,8 @@ import {
AppClientsService,
AppsState,
ClientsState,
DialogModel,
DialogService,
ModalView,
RenameClientForm,
UpdateAppClientDto
} from '@app/shared';
@ -37,7 +37,7 @@ export class ClientComponent implements OnChanges {
public isRenaming = false;
public token: AccessTokenDto;
public tokenDialog = new ModalView();
public tokenDialog = new DialogModel();
public renameForm = new RenameClientForm(this.formBuilder);

4
src/Squidex/app/framework/angular/forms/dropdown.component.ts

@ -13,7 +13,7 @@ const KEY_ESCAPE = 27;
const KEY_UP = 38;
const KEY_DOWN = 40;
import { ModalView } from '@app/framework/internal';
import { ModalModel } from '@app/framework/internal';
export const SQX_DROPDOWN_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => DropdownComponent), multi: true
@ -35,7 +35,7 @@ export class DropdownComponent implements AfterContentInit, ControlValueAccessor
@ContentChildren(TemplateRef)
public templates: QueryList<any>;
public dropdown = new ModalView();
public dropdown = new ModalModel();
public selectedItem: any;
public selectedIndex = -1;

4
src/Squidex/app/framework/angular/modals/dialog-renderer.component.ts

@ -9,10 +9,10 @@ import { Component, Input, OnDestroy, OnInit } from '@angular/core';
import { Subscription } from 'rxjs';
import {
DialogModel,
DialogRequest,
DialogService,
fadeAnimation,
ModalView,
Notification
} from '@app/framework/internal';
@ -29,7 +29,7 @@ export class DialogRendererComponent implements OnDestroy, OnInit {
private dialogsSubscription: Subscription;
private notificationsSubscription: Subscription;
public dialogView = new ModalView(false, true);
public dialogView = new DialogModel();
public dialogRequest: DialogRequest | null = null;
public notifications: Notification[] = [];

59
src/Squidex/app/framework/angular/modals/modal-view.directive.ts

@ -8,7 +8,11 @@
import { Directive, EmbeddedViewRef, Input, OnChanges, OnDestroy, Renderer2, SimpleChanges, TemplateRef, ViewContainerRef } from '@angular/core';
import { Subscription } from 'rxjs';
import { ModalView, Types } from '@app/framework/internal';
import {
DialogModel,
ModalModel,
Types
} from '@app/framework/internal';
import { RootViewComponent } from './root-view.component';
@ -21,7 +25,7 @@ export class ModalViewDirective implements OnChanges, OnDestroy {
private renderedView: EmbeddedViewRef<any> | null = null;
@Input('sqxModalView')
public modalView: ModalView | any;
public modalView: DialogModel | ModalModel | any;
@Input('sqxModalViewOnRoot')
public placeOnRoot = false;
@ -29,6 +33,9 @@ export class ModalViewDirective implements OnChanges, OnDestroy {
@Input('sqxModalViewCloseAuto')
public closeAuto = true;
@Input('sqxModalViewCloseAlways')
public closeAlways = false;
constructor(
private readonly templateRef: TemplateRef<any>,
private readonly renderer: Renderer2,
@ -38,9 +45,10 @@ export class ModalViewDirective implements OnChanges, OnDestroy {
}
public ngOnDestroy() {
this.stopListening();
this.unsubscribeToModal();
this.unsubscribeToClick();
if (Types.is(this.modalView, ModalView)) {
if (Types.is(this.modalView, DialogModel) || Types.is(this.modalView, ModalModel)) {
this.modalView.hide();
}
}
@ -50,12 +58,9 @@ export class ModalViewDirective implements OnChanges, OnDestroy {
return;
}
if (this.subscription) {
this.subscription.unsubscribe();
this.subscription = null;
}
this.unsubscribeToModal();
if (Types.is(this.modalView, ModalView)) {
if (Types.is(this.modalView, DialogModel) || Types.is(this.modalView, ModalModel)) {
this.subscription =
this.modalView.isOpen.subscribe(isOpen => {
this.update(isOpen);
@ -71,11 +76,9 @@ export class ModalViewDirective implements OnChanges, OnDestroy {
}
if (isOpen && !this.renderedView) {
if (this.placeOnRoot) {
this.renderedView = this.rootView.viewContainer.createEmbeddedView(this.templateRef);
} else {
this.renderedView = this.viewContainer.createEmbeddedView(this.templateRef);
}
const container = this.getContainer();
this.renderedView = container.createEmbeddedView(this.templateRef);
if (this.renderedView.rootNodes[0].style) {
this.renderer.setStyle(this.renderedView.rootNodes[0], 'display', 'block');
@ -85,18 +88,21 @@ export class ModalViewDirective implements OnChanges, OnDestroy {
this.startListening();
});
} else if (!isOpen && this.renderedView) {
this.renderedView = null;
const container = this.getContainer();
const containerIndex = container.indexOf(this.renderedView);
if (this.placeOnRoot) {
this.rootView.viewContainer.clear();
} else {
this.viewContainer.clear();
}
container.remove(containerIndex);
this.stopListening();
this.renderedView = null;
this.unsubscribeToClick();
}
}
private getContainer() {
return this.placeOnRoot ? this.rootView.viewContainer : this.viewContainer;
}
private startListening() {
if (!this.closeAuto) {
return;
@ -112,7 +118,7 @@ export class ModalViewDirective implements OnChanges, OnDestroy {
return;
}
if (this.modalView.closeAlways) {
if (this.closeAlways) {
this.modalView.hide();
} else {
try {
@ -133,7 +139,14 @@ export class ModalViewDirective implements OnChanges, OnDestroy {
});
}
private stopListening() {
private unsubscribeToModal() {
if (this.subscription) {
this.subscription.unsubscribe();
this.subscription = null;
}
}
private unsubscribeToClick() {
if (this.documentClickListener) {
this.documentClickListener();
this.documentClickListener = null;

4
src/Squidex/app/framework/angular/modals/onboarding-tooltip.component.ts

@ -9,7 +9,7 @@ import { Component, Input, OnDestroy, OnInit, Renderer2 } from '@angular/core';
import {
fadeAnimation,
ModalView,
ModalModel,
OnboardingService,
Types
} from '@app/framework/internal';
@ -27,7 +27,7 @@ export class OnboardingTooltipComponent implements OnDestroy, OnInit {
private closeTimer: any;
private forMouseDownListener: Function | null;
public tooltipModal = new ModalView();
public tooltipModal = new ModalModel();
@Input()
public for: any;

4
src/Squidex/app/framework/angular/modals/tooltip.component.ts

@ -7,7 +7,7 @@
import { ChangeDetectionStrategy, Component, Input, OnDestroy, OnInit, Renderer2 } from '@angular/core';
import { ModalView } from './../../utils/modal-view';
import { ModalModel } from './../../utils/modal-view';
import { fadeAnimation } from './../animations';
@ -30,7 +30,7 @@ export class TooltipComponent implements OnDestroy, OnInit {
@Input()
public position = 'topLeft';
public modal = new ModalView(false, false);
public modal = new ModalModel();
constructor(
private readonly renderer: Renderer2

100
src/Squidex/app/framework/utils/modal-view.spec.ts

@ -5,75 +5,115 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
import { ModalView } from './modal-view';
import { DialogModel, ModalModel, Openable } from './modal-view';
describe('ModalView', () => {
describe('DialogModel', () => {
it('should have default values', () => {
const dialog = new ModalView();
const dialog = new DialogModel();
checkValue(dialog, false);
});
it('should become open after show', () => {
const dialog = new DialogModel();
dialog.show();
expect(dialog.closeAlways).toBeFalsy();
checkValue(dialog, true);
});
it('should have initial true value', () => {
const dialog = new ModalView(true);
it('should become open after toggle', () => {
const dialog = new DialogModel();
dialog.toggle();
checkValue(dialog, true);
});
it('should have initial false value', () => {
const dialog = new ModalView(false);
it('should become closed after hide', () => {
const dialog = new DialogModel().show();
dialog.hide();
checkValue(dialog, false);
});
it('should have close always set by constructor', () => {
const dialog = new ModalView(false, true);
it('should become closed after toggle', () => {
const dialog = new DialogModel().show();
dialog.toggle();
expect(dialog.closeAlways).toBeTruthy();
checkValue(dialog, false);
});
it('should not hide other dialog', () => {
const dialog1 = new DialogModel().show();
const dialog2 = new DialogModel();
dialog2.toggle();
checkValue(dialog1, true);
checkValue(dialog2, true);
});
});
describe('ModalModel', () => {
it('should have default values', () => {
const modal = new ModalModel();
checkValue(modal, false);
});
it('should become open after show', () => {
const dialog = new ModalView(false);
const modal = new ModalModel();
dialog.show();
modal.show();
checkValue(dialog, true);
checkValue(modal, true);
});
it('should become open after toggle', () => {
const dialog = new ModalView(false);
const modal = new ModalModel();
dialog.toggle();
modal.toggle();
checkValue(dialog, true);
checkValue(modal, true);
});
it('should become closed after hide', () => {
const dialog = new ModalView(true);
const modal = new ModalModel().show();
dialog.hide();
modal.hide();
checkValue(dialog, false);
checkValue(modal, false);
});
it('should become closed after toggle', () => {
const dialog = new ModalView(true);
const modal = new ModalModel().show();
dialog.toggle();
modal.toggle();
checkValue(dialog, false);
checkValue(modal, false);
});
function checkValue(dialog: ModalView, expected: boolean) {
let result: boolean;
it('should hide other modal', () => {
const modal1 = new ModalModel().show();
const modal2 = new ModalModel();
dialog.isOpen.subscribe(value => {
result = value;
}).unsubscribe();
modal2.toggle();
expect(result!).toBe(expected);
}
checkValue(modal1, false);
checkValue(modal2, true);
});
});
function checkValue(modal: Openable, expected: boolean) {
let result: boolean;
modal.isOpen.subscribe(value => {
result = value;
}).unsubscribe();
expect(result!).toBe(expected);
}

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

@ -7,49 +7,78 @@
import { BehaviorSubject, Observable } from 'rxjs';
export class ModalView {
private readonly isOpen$: BehaviorSubject<boolean>;
private static openView: ModalView | null = null;
export interface Openable {
isOpen: Observable<boolean>;
}
export class DialogModel implements Openable {
private readonly isOpen$ = new BehaviorSubject<boolean>(false);
public get isOpen(): Observable<boolean> {
return this.isOpen$;
}
constructor(isOpen = false,
public readonly closeAlways: boolean = false
) {
this.isOpen$ = new BehaviorSubject(isOpen);
public show(): DialogModel {
this.isOpen$.next(true);
return this;
}
public show() {
if (ModalView.openView !== this && ModalView.openView) {
ModalView.openView.hide();
}
public hide(): DialogModel {
this.isOpen$.next(false);
ModalView.openView = this;
return this;
}
this.isOpen$.next(true);
public toggle(): DialogModel {
this.isOpen$.next(!this.isOpen$.value);
return this;
}
}
export class ModalModel implements Openable {
private readonly isOpen$ = new BehaviorSubject<boolean>(false);
public hide() {
if (ModalView.openView === this) {
ModalView.openView = null;
public get isOpen(): Observable<boolean> {
return this.isOpen$;
}
public show(): ModalModel {
if (!this.isOpen$.value) {
if (openModal && openModal !== this) {
openModal.hide();
}
openModal = this;
this.isOpen$.next(true);
}
this.isOpen$.next(false);
return this;
}
public toggle() {
let isOpenSnapshot = false;
public hide(): ModalModel {
if (this.isOpen$.value) {
if (openModal === this) {
openModal = null;
}
this.isOpen.subscribe(v => {
isOpenSnapshot = v;
}).unsubscribe();
this.isOpen$.next(false);
}
return this;
}
if (isOpenSnapshot) {
public toggle(): ModalModel {
if (this.isOpen$.value) {
this.hide();
} else {
this.show();
}
return this;
}
}
}
let openModal: ModalModel | null = null;

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

@ -14,9 +14,9 @@ import {
AssetsService,
AuthService,
DateTime,
DialogModel,
DialogService,
fadeAnimation,
ModalView,
RenameAssetForm,
Types,
UpdateAssetDto,
@ -68,7 +68,7 @@ export class AssetComponent implements OnInit {
@Output()
public failed = new EventEmitter();
public renameDialog = new ModalView();
public renameDialog = new DialogModel();
public renameForm = new RenameAssetForm(this.formBuilder);
public progress = 0;

2
src/Squidex/app/shared/components/language-selector.component.html

@ -8,7 +8,7 @@
<button type="button" class="btn btn-secondary dropdown-toggle" [attr.title]="selectedLanguage.englishName" (click)="dropdown.toggle(); $event.stopPropagation()" #button tabindex="-1">
{{selectedLanguage.iso2Code}}
</button>
<div class="dropdown-menu" *sqxModalView="dropdown" [sqxModalTarget]="button" @fade>
<div class="dropdown-menu" *sqxModalView="dropdown;closeAlways:true" [sqxModalTarget]="button" @fade>
<div class="dropdown-item" *ngFor="let language of languages" [class.active]="language == selectedLanguage" (click)="selectLanguage(language)">
<strong class="iso-code">{{language.iso2Code}}</strong> &nbsp; &nbsp; ({{language.englishName}})
</div>

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

@ -7,7 +7,7 @@
import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core';
import { fadeAnimation, ModalView } from '@app/shared/internal';
import { fadeAnimation, ModalModel } from '@app/shared/internal';
export interface Language { iso2Code: string; englishName: string; isMasterLanguage: true; }
@ -20,7 +20,7 @@ export interface Language { iso2Code: string; englishName: string; isMasterLangu
]
})
export class LanguageSelectorComponent implements OnChanges, OnInit {
public dropdown = new ModalView(false, true);
public dropdown = new ModalModel();
@Input()
public size: string;

8
src/Squidex/app/shared/components/markdown-editor.component.ts

@ -10,7 +10,7 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import {
AssetDto,
ModalView,
DialogModel,
ResourceLoaderService,
Types
} from '@app/shared/internal';
@ -34,7 +34,7 @@ export class MarkdownEditorComponent implements ControlValueAccessor, AfterViewI
private value: string;
private isDisabled = false;
public selectorModal = new ModalView();
public assetsDialog = new DialogModel();
@ViewChild('editor')
public editor: ElementRef;
@ -79,7 +79,7 @@ export class MarkdownEditorComponent implements ControlValueAccessor, AfterViewI
}
private showSelector = () => {
this.selectorModal.show();
this.assetsDialog.show();
}
public ngAfterViewInit() {
@ -206,6 +206,6 @@ export class MarkdownEditorComponent implements ControlValueAccessor, AfterViewI
this.simplemde.codemirror.replaceSelection(content);
}
this.selectorModal.hide();
this.assetsDialog.hide();
}
}

8
src/Squidex/app/shared/components/rich-editor.component.ts

@ -10,7 +10,7 @@ import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import {
AssetDto,
ModalView,
DialogModel,
ResourceLoaderService,
Types
} from '@app/shared/internal';
@ -35,7 +35,7 @@ export class RichEditorComponent implements ControlValueAccessor, AfterViewInit,
private value: string;
private isDisabled = false;
public selectorModal = new ModalView();
public assetsDialog = new DialogModel();
@ViewChild('editor')
public editor: ElementRef;
@ -63,7 +63,7 @@ export class RichEditorComponent implements ControlValueAccessor, AfterViewInit,
}
private showSelector = () => {
this.selectorModal.show();
this.assetsDialog.show();
}
private getEditorOptions() {
@ -147,6 +147,6 @@ export class RichEditorComponent implements ControlValueAccessor, AfterViewInit,
this.tinyEditor.execCommand('mceInsertContent', false, content);
}
this.selectorModal.hide();
this.assetsDialog.hide();
}
}

8
src/Squidex/app/shared/guards/schema-must-exist-published.guard.spec.ts

@ -9,8 +9,6 @@ import { Router, RouterStateSnapshot } from '@angular/router';
import { of } from 'rxjs';
import { IMock, Mock, Times } from 'typemoq';
import { MathHelper } from '@app/framework';
import { SchemaDetailsDto } from './../services/schemas.service';
import { SchemasState } from './../state/schemas.state';
import { SchemaMustExistPublishedGuard } from './schema-must-exist-published.guard';
@ -50,7 +48,7 @@ describe('SchemaMustExistPublishedGuard', () => {
it('should load schema and return false when not found', () => {
schemasState.setup(x => x.select('123'))
.returns(() => of(<SchemaDetailsDto>{ isPublished: false }));
.returns(() => of(<SchemaDetailsDto>{ isPublished: false }));
let result: boolean;
@ -80,7 +78,7 @@ describe('SchemaMustExistPublishedGuard', () => {
it('should redirect to content when singleton', () => {
schemasState.setup(x => x.select('123'))
.returns(() => of(<SchemaDetailsDto>{ isSingleton: true }));
.returns(() => of(<SchemaDetailsDto>{ isSingleton: true, id: 'schema-id' }));
let result: boolean;
@ -90,6 +88,6 @@ describe('SchemaMustExistPublishedGuard', () => {
expect(result!).toBeFalsy();
router.verify(x => x.navigate([state.url, MathHelper.EMPTY_GUID]), Times.once());
router.verify(x => x.navigate([state.url, 'schema-id']), Times.once());
});
});

6
src/Squidex/app/shared/guards/schema-must-exist-published.guard.ts

@ -10,7 +10,7 @@ import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from
import { Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import { allParams, MathHelper } from '@app/framework';
import { allParams } from '@app/framework';
import { SchemasState } from './../state/schemas.state';
@ -32,8 +32,8 @@ export class SchemaMustExistPublishedGuard implements CanActivate {
this.router.navigate(['/404']);
}
if (dto && dto.isSingleton && state.url.indexOf(MathHelper.EMPTY_GUID) < 0) {
this.router.navigate([state.url, MathHelper.EMPTY_GUID]);
if (dto && dto.isSingleton && state.url.indexOf(dto.id) < 0) {
this.router.navigate([state.url, dto.id]);
}
}),
map(s => s !== null && s.isPublished));

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

@ -11,7 +11,7 @@
</span>
<ng-container *ngIf="appsState.apps | async; let apps">
<div class="dropdown-menu" *sqxModalView="appsMenu" @fade>
<div class="dropdown-menu" *sqxModalView="appsMenu;closeAlways:true" @fade>
<a class="dropdown-item all-apps" routerLink="/app">
<span class="all-apps-text">All Apps</span>
<span class="all-apps-pill badge badge-pill badge-primary">{{apps.length}}</span>

7
src/Squidex/app/shell/pages/internal/apps-menu.component.ts

@ -9,8 +9,9 @@ import { Component } from '@angular/core';
import {
AppsState,
DialogModel,
fadeAnimation,
ModalView
ModalModel
} from '@app/shared';
@Component({
@ -22,9 +23,9 @@ import {
]
})
export class AppsMenuComponent {
public addAppDialog = new ModalView();
public addAppDialog = new DialogModel();
public appsMenu = new ModalView(false, true);
public appsMenu = new ModalModel();
constructor(
public readonly appsState: AppsState

2
src/Squidex/app/shell/pages/internal/profile-menu.component.html

@ -8,7 +8,7 @@
</span>
</span>
<div class="dropdown-menu" *sqxModalView="modalMenu" @fade>
<div class="dropdown-menu" *sqxModalView="modalMenu;closeAlways:true" @fade>
<a class="dropdown-item" routerLink="/app/administration" *ngIf="isAdmin">
Administration
</a>

4
src/Squidex/app/shell/pages/internal/profile-menu.component.ts

@ -13,7 +13,7 @@ import {
ApiUrlConfig,
AuthService,
fadeAnimation,
ModalView
ModalModel
} from '@app/shared';
@Component({
@ -27,7 +27,7 @@ import {
export class ProfileMenuComponent implements OnDestroy, OnInit {
private authenticationSubscription: Subscription;
public modalMenu = new ModalView(false, true);
public modalMenu = new ModalModel();
public profileDisplayName = '';
public profileId = '';

4
tests/Squidex.Domain.Apps.Entities.Tests/Contents/Guard/GuardContentTests.cs

@ -43,11 +43,11 @@ namespace Squidex.Domain.Apps.Entities.Contents.Guard
}
[Fact]
public void CanCreate_should_not_throw_exception_if_singleton_and_id_empty()
public void CanCreate_should_not_throw_exception_if_singleton_and_id_is_schema_id()
{
A.CallTo(() => schema.IsSingleton).Returns(true);
var command = new CreateContent { Data = new NamedContentData(), ContentId = Guid.Empty };
var command = new CreateContent { Data = new NamedContentData(), ContentId = schema.Id };
GuardContent.CanCreate(schema, command);
}

Loading…
Cancel
Save