diff --git a/src/Squidex/app/features/content/declarations.ts b/src/Squidex/app/features/content/declarations.ts index 7d1afc96f..5cd795a82 100644 --- a/src/Squidex/app/features/content/declarations.ts +++ b/src/Squidex/app/features/content/declarations.ts @@ -11,6 +11,8 @@ export * from './pages/content/content-page.component'; export * from './pages/contents/contents-page.component'; export * from './pages/contents/search-form.component'; export * from './pages/schemas/schemas-page.component'; + export * from './shared/assets-editor.component'; export * from './shared/content-item.component'; +export * from './shared/contents-selector.component'; export * from './shared/references-editor.component'; \ No newline at end of file diff --git a/src/Squidex/app/features/content/module.ts b/src/Squidex/app/features/content/module.ts index d9da6b924..41e91051c 100644 --- a/src/Squidex/app/features/content/module.ts +++ b/src/Squidex/app/features/content/module.ts @@ -25,6 +25,7 @@ import { ContentPageComponent, ContentItemComponent, ContentsPageComponent, + ContentsSelectorComponent, ReferencesEditorComponent, SchemasPageComponent, SearchFormComponent @@ -48,19 +49,7 @@ const routes: Routes = [ { path: 'new', component: ContentPageComponent, - canDeactivate: [CanDeactivateGuard], - children: [ - { - path: 'references/:schemaName/:language', - component: ContentsPageComponent, - data: { - isReadOnly: true - }, - resolve: { - schema: ResolvePublishedSchemaGuard - } - } - ] + canDeactivate: [CanDeactivateGuard] }, { path: ':contentId', @@ -76,16 +65,6 @@ const routes: Routes = [ data: { channel: 'contents.{contentId}' } - }, - { - path: 'references/:schemaName/:language', - component: ContentsPageComponent, - data: { - isReadOnly: true - }, - resolve: { - schema: ResolvePublishedSchemaGuard - } } ] } @@ -108,6 +87,7 @@ const routes: Routes = [ ContentItemComponent, ContentPageComponent, ContentsPageComponent, + ContentsSelectorComponent, ReferencesEditorComponent, SchemasPageComponent, SearchFormComponent diff --git a/src/Squidex/app/features/content/pages/contents/contents-page.component.html b/src/Squidex/app/features/content/pages/contents/contents-page.component.html index bbf727529..e1558b1da 100644 --- a/src/Squidex/app/features/content/pages/contents/contents-page.component.html +++ b/src/Squidex/app/features/content/pages/contents/contents-page.component.html @@ -116,8 +116,8 @@ + @@ -17,7 +17,7 @@ diff --git a/src/Squidex/app/features/content/pages/schemas/schemas-page.component.ts b/src/Squidex/app/features/content/pages/schemas/schemas-page.component.ts index de20e283e..79e720949 100644 --- a/src/Squidex/app/features/content/pages/schemas/schemas-page.component.ts +++ b/src/Squidex/app/features/content/pages/schemas/schemas-page.component.ts @@ -5,65 +5,45 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { Component } from '@angular/core'; +import { Component, OnInit } from '@angular/core'; import { FormControl } from '@angular/forms'; -import { Observable } from 'rxjs'; import { - AppContext, + AppsState, SchemaDto, - SchemasService + SchemasState } from '@app/shared'; @Component({ selector: 'sqx-schemas-page', styleUrls: ['./schemas-page.component.scss'], - templateUrl: './schemas-page.component.html', - providers: [ - AppContext - ] + templateUrl: './schemas-page.component.html' }) -export class SchemasPageComponent { +export class SchemasPageComponent implements OnInit { public schemasFilter = new FormControl(); public schemasFiltered = - this.schemasFilter.valueChanges - .startWith(null) - .distinctUntilChanged() - .debounceTime(300) - .combineLatest(this.loadSchemas(), - (query, schemas) => { - this.schemasFilter.setValue(query); - - schemas = schemas.filter(t => t.isPublished); - + this.schemasState.publishedSchemas + .combineLatest(this.schemasFilter.valueChanges.startWith(''), + (schemas, query) => { if (query && query.length > 0) { - schemas = schemas.filter(t => t.name.indexOf(query) >= 0); - } - - return schemas; - }).map(schemas => { - return schemas.sort((a, b) => { - if (a.name < b.name) { - return -1; + return schemas.filter(t => t.name.indexOf(query) >= 0); + } else { + return schemas; } - if (a.name > b.name) { - return 1; - } - return 0; }); - }); - constructor(public readonly ctx: AppContext, - private readonly schemasService: SchemasService + constructor( + public readonly appsState: AppsState, + private readonly schemasState: SchemasState ) { } - private loadSchemas(): Observable { - return this.schemasService.getSchemas(this.ctx.appName) - .catch(error => { - this.ctx.notifyError(error); - return []; - }); + public ngOnInit() { + this.schemasState.loadSchemas().subscribe(); + } + + public trackBySchema(index: number, schema: SchemaDto) { + return schema.id; } } diff --git a/src/Squidex/app/features/content/shared/content-item.component.html b/src/Squidex/app/features/content/shared/content-item.component.html index dab478009..362a7ef72 100644 --- a/src/Squidex/app/features/content/shared/content-item.component.html +++ b/src/Squidex/app/features/content/shared/content-item.component.html @@ -1,4 +1,4 @@ - + + + Select contents + + + +
+ + + + + +
+ + +
+ + +
+ + + + + + + + + +
+ + + {{field.displayName}} + + Updated + + By +
+
+ +
+
+ + + + + + + +
+
+
+ + +
+ + + + + + \ No newline at end of file diff --git a/src/Squidex/app/features/content/shared/contents-selector.component.scss b/src/Squidex/app/features/content/shared/contents-selector.component.scss new file mode 100644 index 000000000..a40cd7ada --- /dev/null +++ b/src/Squidex/app/features/content/shared/contents-selector.component.scss @@ -0,0 +1,38 @@ +@import '_vars'; +@import '_mixins'; + +.content { + cursor: pointer; +} + +.icon-plus { + font-size: .8rem; +} + +.search-form { + display: inline-block; +} + +.form-inline { + position: relative; +} + +.form-control-expandable { + padding-right: 1.5rem; +} + +.expand-search { + @include absolute(8px, 8px, auto, auto); + color: $color-border-dark !important; + font-size: .9rem; + font-weight: normal; + cursor: pointer !important; +} + +:host /deep/ .modal-body { + background: $color-background; +} + +:host /deep/ .modal-tabs { + background: $color-dark-foreground; +} \ No newline at end of file diff --git a/src/Squidex/app/features/content/shared/contents-selector.component.ts b/src/Squidex/app/features/content/shared/contents-selector.component.ts new file mode 100644 index 000000000..0a75b01ca --- /dev/null +++ b/src/Squidex/app/features/content/shared/contents-selector.component.ts @@ -0,0 +1,147 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { FormControl } from '@angular/forms'; + +import { + AppsState, + ContentDto, + ContentsService, + DialogService, + FieldDto, + ImmutableArray, + ModalView, + Pager, + SchemaDetailsDto, + LanguageDto +} from '@app/shared'; + +@Component({ + selector: 'sqx-contents-selector', + styleUrls: ['./contents-selector.component.scss'], + templateUrl: './contents-selector.component.html' +}) +export class ContentsSelectorComponent implements OnInit { + @Input() + public language: LanguageDto[]; + + @Input() + public schema: SchemaDetailsDto; + + @Input() + public schemaFields: FieldDto[]; + + @Output() + public selected = new EventEmitter(); + + public searchModal = new ModalView(); + + public contentItems: ImmutableArray; + public contentsFilter = new FormControl(); + public contentsQuery = ''; + public contentsPager = new Pager(0); + + public selectedItems: { [id: string]: ContentDto; } = {}; + public selectionCount = 0; + + public isAllSelected = false; + + constructor( + private readonly appsState: AppsState, + private readonly contentsService: ContentsService, + private readonly dialogs: DialogService + ) { + } + + public ngOnInit() { + this.load(); + } + + public load(showInfo = false) { + this.contentsService.getContents(this.appsState.appName, this.schema.name, this.contentsPager.pageSize, this.contentsPager.skip, this.contentsQuery, undefined, false) + .finally(() => { + this.selectedItems = {}; + + this.updateSelectionSummary(); + }) + .subscribe(dtos => { + this.contentItems = ImmutableArray.of(dtos.items); + this.contentsPager = this.contentsPager.setCount(dtos.total); + + if (showInfo) { + this.dialogs.notifyInfo('Contents reloaded.'); + } + }, error => { + this.dialogs.notifyError(error); + }); + } + + public search() { + this.contentsQuery = this.contentsFilter.value; + this.contentsPager = new Pager(0); + + this.load(); + } + + public goNext() { + this.contentsPager = this.contentsPager.goNext(); + + this.load(); + } + + public goPrev() { + this.contentsPager = this.contentsPager.goPrev(); + + this.load(); + } + + public isItemSelected(content: ContentDto) { + return this.selectedItems[content.id]; + } + + public complete() { + this.selected.emit([]); + } + + public select() { + this.selected.emit(Object.values(this.selectedItems)); + } + + public selectAll(isSelected: boolean) { + this.selectedItems = {}; + + if (isSelected) { + for (let content of this.contentItems.values) { + this.selectedItems[content.id] = content; + } + } + + this.updateSelectionSummary(); + } + + public onContentSelected(content: ContentDto) { + if (this.selectedItems[content.id]) { + delete this.selectedItems[content.id]; + } else { + this.selectedItems[content.id] = content; + } + + this.updateSelectionSummary(); + } + + private updateSelectionSummary() { + this.selectionCount = Object.keys(this.selectedItems).length; + + this.isAllSelected = this.selectionCount === this.contentItems.length; + } + + public trackByContent(content: ContentDto): string { + return content.id; + } +} + 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 07147915e..7e3ab1743 100644 --- a/src/Squidex/app/features/content/shared/references-editor.component.html +++ b/src/Squidex/app/features/content/shared/references-editor.component.html @@ -1,7 +1,7 @@
-
- Drop content here to add a reference. +
+ click here to link a content.
@@ -14,8 +14,8 @@ @@ -23,4 +23,13 @@ -
\ No newline at end of file +
+ + + + + \ No newline at end of file diff --git a/src/Squidex/app/features/content/shared/references-editor.component.scss b/src/Squidex/app/features/content/shared/references-editor.component.scss index ae939e2d1..2cdce8038 100644 --- a/src/Squidex/app/features/content/shared/references-editor.component.scss +++ b/src/Squidex/app/features/content/shared/references-editor.component.scss @@ -32,7 +32,7 @@ font-size: 1.2rem; font-weight: normal; text-align: center; - padding: 2rem; + padding: 1rem; cursor: pointer; color: darken($color-border, 30%); } diff --git a/src/Squidex/app/features/content/shared/references-editor.component.ts b/src/Squidex/app/features/content/shared/references-editor.component.ts index bcacd9f5f..6db99c283 100644 --- a/src/Squidex/app/features/content/shared/references-editor.component.ts +++ b/src/Squidex/app/features/content/shared/references-editor.component.ts @@ -11,13 +11,14 @@ import { Component, forwardRef, Input, OnInit } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { - AppContext, AppLanguageDto, + AppsState, ContentDto, ContentsService, FieldDto, ImmutableArray, MathHelper, + ModalView, SchemaDetailsDto, SchemasService, Types @@ -32,7 +33,6 @@ export const SQX_REFERENCES_EDITOR_CONTROL_VALUE_ACCESSOR: any = { styleUrls: ['./references-editor.component.scss'], templateUrl: './references-editor.component.html', providers: [ - AppContext, SQX_REFERENCES_EDITOR_CONTROL_VALUE_ACCESSOR ] }) @@ -46,15 +46,18 @@ export class ReferencesEditorComponent implements ControlValueAccessor, OnInit { @Input() public language: AppLanguageDto; + public selectorModal = new ModalView(); + public schema: SchemaDetailsDto; + public schemaFields: FieldDto[]; public contentItems = ImmutableArray.empty(); - public contentFields: FieldDto[]; public isDisabled = false; public isInvalidSchema = false; - constructor(public readonly ctx: AppContext, + constructor( + private readonly appsState: AppsState, private readonly contentsService: ContentsService, private readonly schemasService: SchemasService ) { @@ -65,7 +68,7 @@ export class ReferencesEditorComponent implements ControlValueAccessor, OnInit { return; } - this.schemasService.getSchema(this.ctx.appName, this.schemaId) + this.schemasService.getSchema(this.appsState.appName, this.schemaId) .subscribe(dto => { this.schema = dto; @@ -81,7 +84,7 @@ export class ReferencesEditorComponent implements ControlValueAccessor, OnInit { if (Types.isArrayOfString(value) && value.length > 0) { const contentIds: string[] = value; - this.contentsService.getContents(this.ctx.appName, this.schemaId, 10000, 0, undefined, contentIds) + this.contentsService.getContents(this.appsState.appName, this.schemaId, 10000, 0, undefined, contentIds) .subscribe(dtos => { this.contentItems = ImmutableArray.of(contentIds.map(id => dtos.items.find(c => c.id === id)).filter(r => !!r).map(r => r!)); }); @@ -100,20 +103,16 @@ export class ReferencesEditorComponent implements ControlValueAccessor, OnInit { this.callTouched = fn; } - public canDrop() { - const component = this; - - return (dragData: any) => { - return dragData.content instanceof ContentDto && dragData.schemaId === component.schemaId && !component.contentItems.find(c => c.id === dragData.content.id); - }; - } - - public onContentDropped(content: ContentDto) { - if (content) { - this.contentItems = this.contentItems.pushFront(content); + public onContentsSelected(contents: ContentDto[]) { + for (let content of contents) { + this.contentItems = this.contentItems.push(content); + } + if (contents.length > 0) { this.updateValue(); } + + this.selectorModal.hide(); } public onContentRemoving(content: ContentDto) { @@ -144,14 +143,14 @@ export class ReferencesEditorComponent implements ControlValueAccessor, OnInit { } private loadFields() { - this.contentFields = this.schema.fields.filter(x => x.properties.isListField); + this.schemaFields = this.schema.fields.filter(x => x.properties.isListField); - if (this.contentFields.length === 0 && this.schema.fields.length > 0) { - this.contentFields = [this.schema.fields[0]]; + if (this.schemaFields.length === 0 && this.schema.fields.length > 0) { + this.schemaFields = [this.schema.fields[0]]; } - if (this.contentFields.length === 0) { - this.contentFields = [{}]; + if (this.schemaFields.length === 0) { + this.schemaFields = [{}]; } } } \ No newline at end of file diff --git a/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html b/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html index 4d501a310..ead1fbeff 100644 --- a/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html +++ b/src/Squidex/app/features/schemas/pages/schemas/schema-form.component.html @@ -28,12 +28,12 @@ diff --git a/src/Squidex/app/framework/angular/modals/modal-dialog.component.html b/src/Squidex/app/framework/angular/modals/modal-dialog.component.html index db0cd1511..953c127c7 100644 --- a/src/Squidex/app/framework/angular/modals/modal-dialog.component.html +++ b/src/Squidex/app/framework/angular/modals/modal-dialog.component.html @@ -13,11 +13,11 @@ -