Browse Source

Feature/contributors in search (#600)

* Fix for rerun rules.

* Tests fixed

* Contributors in search.
pull/604/head
Sebastian Stehle 5 years ago
committed by GitHub
parent
commit
71c864e71f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 8
      frontend/app/features/content/pages/contents/contents-page.component.ts
  2. 4
      frontend/app/features/settings/pages/contributors/contributor-add-form.component.html
  3. 2
      frontend/app/features/settings/pages/contributors/contributor-add-form.component.scss
  4. 20
      frontend/app/framework/angular/forms/editors/dropdown.component.html
  5. 19
      frontend/app/framework/angular/forms/editors/dropdown.component.scss
  6. 102
      frontend/app/framework/angular/forms/editors/dropdown.component.ts
  7. 36
      frontend/app/framework/utils/keys.ts
  8. 34
      frontend/app/shared/components/search/queries/filter-comparison.component.html
  9. 10
      frontend/app/shared/components/search/queries/filter-comparison.component.scss
  10. 16
      frontend/app/shared/components/search/queries/filter-comparison.component.ts
  11. 2
      frontend/app/shared/components/search/query-list.component.ts
  12. 15
      frontend/app/shared/components/search/search-form.component.ts
  13. 4
      frontend/app/shared/services/contributors.service.ts
  14. 7
      frontend/app/shared/state/contributors.state.spec.ts
  15. 10
      frontend/app/shared/state/contributors.state.ts
  16. 12
      frontend/app/shared/state/query.ts

8
frontend/app/features/content/pages/contents/contents-page.component.ts

@ -9,7 +9,7 @@
import { Component, OnInit, ViewChild } from '@angular/core'; import { Component, OnInit, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { AppLanguageDto, ContentDto, ContentsState, fadeAnimation, LanguagesState, ModalModel, Queries, Query, QueryModel, queryModelFromSchema, ResourceOwner, Router2State, SchemaDetailsDto, SchemasState, TableFields, TempService, UIState } from '@app/shared'; import { AppLanguageDto, AppsState, ContentDto, ContentsState, ContributorsState, fadeAnimation, LanguagesState, ModalModel, Queries, Query, QueryModel, queryModelFromSchema, ResourceOwner, Router2State, SchemaDetailsDto, SchemasState, TableFields, TempService, UIState } from '@app/shared';
import { combineLatest } from 'rxjs'; import { combineLatest } from 'rxjs';
import { distinctUntilChanged, onErrorResumeNext, switchMap, tap } from 'rxjs/operators'; import { distinctUntilChanged, onErrorResumeNext, switchMap, tap } from 'rxjs/operators';
import { DueTimeSelectorComponent } from './../../shared/due-time-selector.component'; import { DueTimeSelectorComponent } from './../../shared/due-time-selector.component';
@ -53,6 +53,8 @@ export class ContentsPageComponent extends ResourceOwner implements OnInit {
constructor( constructor(
public readonly contentsRoute: Router2State, public readonly contentsRoute: Router2State,
public readonly contentsState: ContentsState, public readonly contentsState: ContentsState,
private readonly appsState: AppsState,
private readonly contributorsState: ContributorsState,
private readonly route: ActivatedRoute, private readonly route: ActivatedRoute,
private readonly router: Router, private readonly router: Router,
private readonly languagesState: LanguagesState, private readonly languagesState: LanguagesState,
@ -64,6 +66,10 @@ export class ContentsPageComponent extends ResourceOwner implements OnInit {
} }
public ngOnInit() { public ngOnInit() {
if (this.appsState.snapshot.selectedApp?.canReadContributors) {
this.contributorsState.loadIfNotLoaded();
}
this.own( this.own(
combineLatest([ combineLatest([
this.schemasState.selectedSchema, this.schemasState.selectedSchema,

4
frontend/app/features/settings/pages/contributors/contributor-add-form.component.html

@ -5,9 +5,9 @@
<sqx-autocomplete [source]="usersDataSource" formControlName="user" inputName="contributor" placeholder="{{ 'contributors.emailPlaceholder' | sqxTranslate }}" displayProperty="displayName"> <sqx-autocomplete [source]="usersDataSource" formControlName="user" inputName="contributor" placeholder="{{ 'contributors.emailPlaceholder' | sqxTranslate }}" displayProperty="displayName">
<ng-template let-user="$implicit"> <ng-template let-user="$implicit">
<span class="autocomplete-user"> <span class="autocomplete-user">
<img class="user-picture autocomplete-user-picture" [src]="user | sqxUserDtoPicture"> <img class="user-picture" [src]="user | sqxUserDtoPicture">
<span class="user-name autocomplete-user-name">{{user.displayName}}</span> <span class="user-name">{{user.displayName}}</span>
</span> </span>
</ng-template> </ng-template>
</sqx-autocomplete> </sqx-autocomplete>

2
frontend/app/features/settings/pages/contributors/contributor-add-form.component.scss

@ -3,7 +3,7 @@
@include truncate; @include truncate;
} }
&-name { .user-name {
margin-left: .25rem; margin-left: .25rem;
} }
} }

20
frontend/app/framework/angular/forms/editors/dropdown.component.html

@ -1,13 +1,14 @@
<div class="selection"> <div class="selection">
<input type="text" class="form-control" [disabled]="snapshot.isDisabled" (click)="open()" readonly (keydown)="onKeyDown($event)" #input autocomplete="off" autocorrect="off" autocapitalize="off"> <input type="text" class="custom-select" [disabled]="snapshot.isDisabled" (click)="open()" readonly (keydown)="onKeyDown($event)" #input
autocomplete="off"
autocorrect="off"
autocapitalize="off">
<div class="control-dropdown-item" *ngIf="snapshot.selectedItem"> <div class="control-dropdown-item" *ngIf="selectedItem">
<span class="truncate" *ngIf="!templateSelection">{{snapshot.selectedItem}}</span> <span class="truncate" *ngIf="!templateSelection">{{selectedItem}}</span>
<ng-template *ngIf="templateSelection" [sqxTemplateWrapper]="templateSelection" [item]="snapshot.selectedItem"></ng-template> <ng-template *ngIf="templateSelection" [sqxTemplateWrapper]="templateSelection" [item]="selectedItem"></ng-template>
</div> </div>
<i class="icon-caret-down"></i>
</div> </div>
<div class="items-container"> <div class="items-container">
@ -18,7 +19,12 @@
</div> </div>
<div class="control-dropdown-items" #container> <div class="control-dropdown-items" #container>
<div *ngFor="let item of snapshot.suggestedItems; let i = index;" class="control-dropdown-item control-dropdown-item-selectable" [class.active]="i === snapshot.selectedIndex" [class.separated]="separated" (mousedown)="selectIndexAndClose(i)" [sqxScrollActive]="i === snapshot.selectedIndex" [sqxScrollContainer]="container"> <div *ngFor="let item of snapshot.suggestedItems; let i = index;" class="control-dropdown-item control-dropdown-item-selectable"
[class.active]="i === snapshot.selectedIndex"
[class.separated]="separated"
(mousedown)="selectIndexAndClose(i)"
[sqxScrollActive]="i === snapshot.selectedIndex"
[sqxScrollContainer]="container">
<ng-container *ngIf="!templateItem">{{item}}</ng-container> <ng-container *ngIf="!templateItem">{{item}}</ng-container>
<ng-template *ngIf="templateItem" [sqxTemplateWrapper]="templateItem" [item]="item" [index]="i" [context]="snapshot.query"></ng-template> <ng-template *ngIf="templateItem" [sqxTemplateWrapper]="templateItem" [item]="item" [index]="i" [context]="snapshot.query"></ng-template>

19
frontend/app/framework/angular/forms/editors/dropdown.component.scss

@ -1,16 +1,16 @@
$color-input-disabled: #eef1f4; $color-input-disabled: #eef1f4;
.form-control { .custom-select {
& { & {
width: 100%; cursor: default;
} }
&[readonly] { &[readonly] {
background: $color-input-background; background-color: $color-input-background;
} }
&:disabled { &:disabled {
background: $color-input-disabled; background-color: $color-input-disabled;
} }
} }
@ -32,25 +32,18 @@ $color-input-disabled: #eef1f4;
.selection { .selection {
& { & {
overflow: hidden;
position: relative; position: relative;
} }
.control-dropdown-item { .control-dropdown-item {
@include absolute(0, 1rem, 0, 0); @include absolute(0, 1.75rem, 0, 0);
line-height: 1.2rem; line-height: 1.2rem;
overflow: hidden;
padding-bottom: 0; padding-bottom: 0;
pointer-events: none; pointer-events: none;
position: absolute; position: absolute;
} }
.icon-caret-down {
@include absolute(30%, 5px, null, null);
font-size: .9rem;
font-weight: normal;
pointer-events: none;
}
.truncate { .truncate {
min-height: 1.2rem; min-height: 1.2rem;
} }

102
frontend/app/framework/angular/forms/editors/dropdown.component.ts

@ -5,6 +5,8 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
// tslint:disable: prefer-for-of
import { AfterContentInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChildren, forwardRef, Input, OnChanges, OnInit, QueryList, SimpleChanges, TemplateRef } from '@angular/core'; import { AfterContentInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChildren, forwardRef, Input, OnChanges, OnInit, QueryList, SimpleChanges, TemplateRef } from '@angular/core';
import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR } from '@angular/forms'; import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR } from '@angular/forms';
import { Keys, ModalModel, StatefulControlComponent, Types } from '@app/framework/internal'; import { Keys, ModalModel, StatefulControlComponent, Types } from '@app/framework/internal';
@ -18,9 +20,6 @@ interface State {
// The suggested item. // The suggested item.
suggestedItems: ReadonlyArray<any>; suggestedItems: ReadonlyArray<any>;
// The selected suggested item.
selectedItem: any;
// The selected suggested index. // The selected suggested index.
selectedIndex: number; selectedIndex: number;
@ -40,12 +39,17 @@ const NO_EMIT = { emitEvent: false };
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class DropdownComponent extends StatefulControlComponent<State, ReadonlyArray<any>> implements AfterContentInit, ControlValueAccessor, OnChanges, OnInit { export class DropdownComponent extends StatefulControlComponent<State, ReadonlyArray<any>> implements AfterContentInit, ControlValueAccessor, OnChanges, OnInit {
private value: any;
@Input() @Input()
public items: ReadonlyArray<any> = []; public items: ReadonlyArray<any> = [];
@Input() @Input()
public searchProperty = 'name'; public searchProperty = 'name';
@Input()
public valueProperty?: string;
@Input() @Input()
public canSearch = true; public canSearch = true;
@ -62,9 +66,12 @@ export class DropdownComponent extends StatefulControlComponent<State, ReadonlyA
public queryInput = new FormControl(); public queryInput = new FormControl();
public get selectedItem() {
return this.items[this.snapshot.selectedIndex];
}
constructor(changeDetector: ChangeDetectorRef) { constructor(changeDetector: ChangeDetectorRef) {
super(changeDetector, { super(changeDetector, {
selectedItem: undefined,
selectedIndex: -1, selectedIndex: -1,
suggestedItems: [] suggestedItems: []
}); });
@ -102,11 +109,13 @@ export class DropdownComponent extends StatefulControlComponent<State, ReadonlyA
public ngOnChanges(changes: SimpleChanges) { public ngOnChanges(changes: SimpleChanges) {
if (changes['items']) { if (changes['items']) {
this.items = this.items || [];
this.resetSearch(); this.resetSearch();
this.next(s => ({ this.next(s => ({
...s, ...s,
suggestedIndex: 0, suggestedIndex: this.getSelectedIndex(this.value),
suggestedItems: this.items || [] suggestedItems: this.items || []
})); }));
} }
@ -127,7 +136,9 @@ export class DropdownComponent extends StatefulControlComponent<State, ReadonlyA
} }
public writeValue(obj: any) { public writeValue(obj: any) {
this.selectIndex(this.items && obj ? this.items.indexOf(obj) : -1, false); this.value = obj;
this.selectIndex(this.getSelectedIndex(obj), false);
} }
public setDisabledState(isDisabled: boolean): void { public setDisabledState(isDisabled: boolean): void {
@ -141,21 +152,18 @@ export class DropdownComponent extends StatefulControlComponent<State, ReadonlyA
} }
public onKeyDown(event: KeyboardEvent) { public onKeyDown(event: KeyboardEvent) {
switch (event.keyCode) { if (Keys.isUp(event)) {
case Keys.UP: this.selectPrevIndex();
this.selectPrevIndex(); return false;
return false; } else if (Keys.isDown(event)) {
case Keys.DOWN: this.selectNextIndex();
this.selectNextIndex(); return false;
return false; } else if (Keys.isEnter(event)) {
case Keys.ENTER: this.selectIndexAndClose(this.snapshot.selectedIndex);
this.selectIndexAndClose(this.snapshot.selectedIndex); return false;
return false; } else if (Keys.isEscape(event) && this.dropdown.isOpen) {
case Keys.ESCAPE: this.close();
if (this.dropdown.isOpen) { return false;
this.close();
return false;
}
} }
return true; return true;
@ -194,25 +202,53 @@ export class DropdownComponent extends StatefulControlComponent<State, ReadonlyA
} }
public selectIndex(selectedIndex: number, fromUserAction: boolean) { public selectIndex(selectedIndex: number, fromUserAction: boolean) {
if (selectedIndex < 0 && fromUserAction) { if (fromUserAction) {
selectedIndex = 0; const items = this.snapshot.suggestedItems || [];
}
const items = this.snapshot.suggestedItems || []; if (selectedIndex < 0) {
selectedIndex = 0;
}
if (selectedIndex >= items.length && fromUserAction) { if (selectedIndex >= items.length) {
selectedIndex = items.length - 1; selectedIndex = items.length - 1;
} }
const value = items[selectedIndex]; const selectedItem = items[selectedIndex];
if (value !== this.snapshot.selectedItem) { let selectedValue = selectedItem;
if (fromUserAction) {
this.callChange(value); if (this.valueProperty && this.valueProperty.length > 0 && selectedValue) {
selectedValue = selectedValue[this.valueProperty];
}
if (this.value !== selectedValue) {
this.value = selectedValue;
this.callChange(selectedValue);
this.callTouched(); this.callTouched();
} }
}
this.next(s => ({ ...s, selectedIndex }));
}
this.next(s => ({ ...s, selectedIndex, selectedItem: value })); private getSelectedIndex(value: any) {
if (!value) {
return -1;
} }
if (this.valueProperty && this.valueProperty.length > 0) {
for (let i = 0; i < this.items.length; i++) {
const item = this.items[i];
if (item && item[this.valueProperty] === value) {
return i;
}
}
} else {
return this.items.indexOf(value);
}
return -1;
} }
} }

36
frontend/app/framework/utils/keys.ts

@ -12,4 +12,40 @@ export module Keys {
export const ESCAPE = 27; export const ESCAPE = 27;
export const DOWN = 40; export const DOWN = 40;
export const UP = 38; export const UP = 38;
export function isComma(event: KeyboardEvent) {
const key = event.key || event.keyCode;
return key === ',' || key === COMMA;
}
export function isDelete(event: KeyboardEvent) {
const key = event.key || event.keyCode;
return key === 'Delete' || key === DELETE;
}
export function isEnter(event: KeyboardEvent) {
const key = event.key || event.keyCode;
return key === 'ENTER' || key === ENTER;
}
export function isDown(event: KeyboardEvent) {
const key = event.key || event.keyCode;
return key === 'ArrowDown' || key === DOWN;
}
export function isUp(event: KeyboardEvent) {
const key = event.key || event.keyCode;
return key === 'ArrowUp' || key === UP;
}
export function isEscape(event: KeyboardEvent) {
const key = event.key || event.keyCode;
return key === 'Escape' || key === 'Esc' || key === UP;
}
} }

34
frontend/app/shared/components/search/queries/filter-comparison.component.html

@ -49,14 +49,40 @@
</ng-container> </ng-container>
<ng-container *ngSwitchCase="'status'"> <ng-container *ngSwitchCase="'status'">
<sqx-dropdown [items]="fieldModel.extra" <sqx-dropdown [items]="fieldModel.extra"
[ngModel]="getStatus(fieldModel.extra)" valueProperty="status"
(ngModelChange)="changeStatus($event)" [ngModel]="filter.value"
(ngModelChange)="changeValue($event)"
[canSearch]="false"> [canSearch]="false">
<ng-template let-target="$implicit"> <ng-template let-status="$implicit">
<i class="icon-circle" [style.color]="target.color"></i> {{target.status}} <i class="icon-circle" [style.color]="status.color"></i> {{status.status}}
</ng-template> </ng-template>
</sqx-dropdown> </sqx-dropdown>
</ng-container> </ng-container>
<ng-container *ngSwitchCase="'user'">
<ng-container *ngIf="contributorsState.isLoaded | async; else noPermission">
<sqx-dropdown [items]="contributorsState.contributors | async"
valueProperty="token"
[ngModel]="filter.value"
(ngModelChange)="changeValue($event)">
<ng-template let-user="$implicit">
<span class="dropdown-user">
<img class="user-picture" [src]="user | sqxUserDtoPicture">
<span class="user-name ">{{user.contributorName}}</span>
</span>
</ng-template>
<ng-template let-user="$implicit">
<span class="user-name">{{user.contributorName}}</span>
</ng-template>
</sqx-dropdown>
</ng-container>
<ng-template #noPermission>
<input type="text" class="form-control" *ngIf="!fieldModel.extra"
[ngModel]="filter.value"
(ngModelChange)="changeValue($event)"
/>
</ng-template>
</ng-container>
<ng-container *ngSwitchCase="'string'"> <ng-container *ngSwitchCase="'string'">
<input type="text" class="form-control" *ngIf="!fieldModel.extra" <input type="text" class="form-control" *ngIf="!fieldModel.extra"
[ngModel]="filter.value" [ngModel]="filter.value"

10
frontend/app/shared/components/search/queries/filter-comparison.component.scss

@ -5,3 +5,13 @@
.operator { .operator {
width: 10rem; width: 10rem;
} }
.dropdown-user {
& {
@include truncate;
}
.user-name {
margin-left: .25rem;
}
}

16
frontend/app/shared/components/search/queries/filter-comparison.component.ts

@ -6,7 +6,8 @@
*/ */
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { FilterComparison, LanguageDto, QueryFieldModel, QueryModel, StatusInfo } from '@app/shared/internal'; import { FilterComparison, LanguageDto, QueryFieldModel, QueryModel } from '@app/shared/internal';
import { ContributorsState } from '@app/shared/state/contributors.state';
@Component({ @Component({
selector: 'sqx-filter-comparison', selector: 'sqx-filter-comparison',
@ -34,6 +35,11 @@ export class FilterComparisonComponent implements OnChanges {
public noValue = false; public noValue = false;
constructor(
public readonly contributorsState: ContributorsState
) {
}
public ngOnChanges(changes: SimpleChanges) { public ngOnChanges(changes: SimpleChanges) {
if (changes['filter']) { if (changes['filter']) {
this.updatePath(false); this.updatePath(false);
@ -41,14 +47,6 @@ export class FilterComparisonComponent implements OnChanges {
} }
} }
public getStatus(statuses: ReadonlyArray<StatusInfo>) {
return statuses.find(x => x.status === this.filter.value);
}
public changeStatus(status: StatusInfo) {
this.changeValue(status.status);
}
public changeValue(value: any) { public changeValue(value: any) {
this.filter.value = value; this.filter.value = value;

2
frontend/app/shared/components/search/query-list.component.ts

@ -22,7 +22,7 @@ export class QueryListComponent {
public remove = new EventEmitter<SavedQuery>(); public remove = new EventEmitter<SavedQuery>();
@Input() @Input()
public queryUsed: Query | undefined; public queryUsed?: Query;
@Input() @Input()
public queries: ReadonlyArray<SavedQuery>; public queries: ReadonlyArray<SavedQuery>;

15
frontend/app/shared/components/search/search-form.component.ts

@ -7,7 +7,7 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { FormBuilder } from '@angular/forms'; import { FormBuilder } from '@angular/forms';
import { DialogModel, hasFilter, LanguageDto, Queries, Query, QueryModel, SaveQueryForm } from '@app/shared/internal'; import { DialogModel, equalsQuery, hasFilter, LanguageDto, Queries, Query, QueryModel, SaveQueryForm, Types } from '@app/shared/internal';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
@Component({ @Component({
@ -18,6 +18,7 @@ import { Observable } from 'rxjs';
}) })
export class SearchFormComponent implements OnChanges { export class SearchFormComponent implements OnChanges {
public readonly standalone = { standalone: true }; public readonly standalone = { standalone: true };
private previousQuery?: Query;
@Output() @Output()
public queryChange = new EventEmitter<Query>(); public queryChange = new EventEmitter<Query>();
@ -32,7 +33,7 @@ export class SearchFormComponent implements OnChanges {
public queryModel: QueryModel; public queryModel: QueryModel;
@Input() @Input()
public query: Query | undefined; public query?: Query;
@Input() @Input()
public queries: Queries; public queries: Queries;
@ -62,6 +63,8 @@ export class SearchFormComponent implements OnChanges {
} }
if (changes['query']) { if (changes['query']) {
this.previousQuery = Types.clone(this.query);
this.hasFilter = hasFilter(this.query); this.hasFilter = hasFilter(this.query);
} }
} }
@ -69,7 +72,13 @@ export class SearchFormComponent implements OnChanges {
public search(close = false) { public search(close = false) {
this.hasFilter = hasFilter(this.query); this.hasFilter = hasFilter(this.query);
this.queryChange.emit(this.query); if (!equalsQuery(this.query, this.previousQuery)) {
const clone = Types.clone(this.query);
this.queryChange.emit(clone);
this.previousQuery = this.query;
}
if (close) { if (close) {
this.searchDialog.hide(); this.searchDialog.hide();

4
frontend/app/shared/services/contributors.service.ts

@ -26,6 +26,10 @@ export class ContributorDto {
public readonly canUpdate: boolean; public readonly canUpdate: boolean;
public readonly canRevoke: boolean; public readonly canRevoke: boolean;
public get token() {
return `subject:${this.contributorId}`;
}
constructor( constructor(
links: ResourceLinks, links: ResourceLinks,
public readonly contributorId: string, public readonly contributorId: string,

7
frontend/app/shared/state/contributors.state.spec.ts

@ -71,6 +71,13 @@ describe('ContributorsState', () => {
expect(contributorsState.snapshot.isLoading).toBeFalsy(); expect(contributorsState.snapshot.isLoading).toBeFalsy();
}); });
it('should not load if already loaded', () => {
contributorsState.load(true).subscribe();
contributorsState.loadIfNotLoaded().subscribe();
expect().nothing();
});
it('should only show current page of contributors', () => { it('should only show current page of contributors', () => {
contributorsState.load().subscribe(); contributorsState.load().subscribe();

10
frontend/app/shared/state/contributors.state.ts

@ -7,7 +7,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { DialogService, ErrorDto, Pager, shareMapSubscribed, shareSubscribed, State, StateSynchronizer, Types, Version } from '@app/framework'; import { DialogService, ErrorDto, Pager, shareMapSubscribed, shareSubscribed, State, StateSynchronizer, Types, Version } from '@app/framework';
import { Observable, throwError } from 'rxjs'; import { EMPTY, Observable, throwError } from 'rxjs';
import { catchError, finalize, tap } from 'rxjs/operators'; import { catchError, finalize, tap } from 'rxjs/operators';
import { AssignContributorDto, ContributorDto, ContributorsPayload, ContributorsService } from './../services/contributors.service'; import { AssignContributorDto, ContributorDto, ContributorsPayload, ContributorsService } from './../services/contributors.service';
import { AppsState } from './apps.state'; import { AppsState } from './apps.state';
@ -97,6 +97,14 @@ export class ContributorsState extends State<Snapshot> {
.build(); .build();
} }
public loadIfNotLoaded(): Observable<any> {
if (this.snapshot.isLoaded) {
return EMPTY;
}
return this.loadInternal(false);
}
public load(isReload = false): Observable<any> { public load(isReload = false): Observable<any> {
if (!isReload) { if (!isReload) {
const contributorsPager = this.snapshot.contributorsPager.reset(); const contributorsPager = this.snapshot.contributorsPager.reset();

12
frontend/app/shared/state/query.ts

@ -21,7 +21,8 @@ export type QueryValueType =
'reference' | 'reference' |
'status' | 'status' |
'string' | 'string' |
'tags'; 'tags' |
'user';
export interface FilterOperator { export interface FilterOperator {
// The optional display value. // The optional display value.
@ -255,6 +256,11 @@ const TypeStatus: QueryFieldModel = {
operators: EqualOperators operators: EqualOperators
}; };
const TypeUser: QueryFieldModel = {
type: 'user',
operators: EqualOperators
};
const TypeString: QueryFieldModel = { const TypeString: QueryFieldModel = {
type: 'string', type: 'string',
operators: [...EqualOperators, ...CompareOperator, ...StringOperators, ...ArrayOperators] operators: [...EqualOperators, ...CompareOperator, ...StringOperators, ...ArrayOperators]
@ -272,7 +278,7 @@ const DEFAULT_FIELDS: QueryModelFields = {
description: 'i18n:contents.createFieldDescription' description: 'i18n:contents.createFieldDescription'
}, },
createdBy: { createdBy: {
...TypeString, ...TypeUser,
displayName: 'meta.createdBy', displayName: 'meta.createdBy',
description: 'i18n:contents.createdByFieldDescription' description: 'i18n:contents.createdByFieldDescription'
}, },
@ -282,7 +288,7 @@ const DEFAULT_FIELDS: QueryModelFields = {
description: 'i18n:contents.lastModifiedFieldDescription' description: 'i18n:contents.lastModifiedFieldDescription'
}, },
lastModifiedBy: { lastModifiedBy: {
...TypeString, ...TypeUser,
displayName: 'meta.lastModifiedBy', displayName: 'meta.lastModifiedBy',
description: 'i18n:contents.lastModifiedByFieldDescription' description: 'i18n:contents.lastModifiedByFieldDescription'
}, },

Loading…
Cancel
Save