Browse Source

Improved search form.

pull/313/head
Sebastian Stehle 8 years ago
parent
commit
e9a5e5c2ae
  1. 42
      src/Squidex/app/features/assets/pages/assets-page.component.html
  2. 28
      src/Squidex/app/features/assets/pages/assets-page.component.ts
  3. 1
      src/Squidex/app/features/content/declarations.ts
  4. 6
      src/Squidex/app/features/content/module.ts
  5. 52
      src/Squidex/app/features/content/pages/contents/contents-page.component.html
  6. 6
      src/Squidex/app/features/content/pages/contents/contents-page.component.ts
  7. 4
      src/Squidex/app/features/content/shared/assets-editor.component.ts
  8. 4
      src/Squidex/app/features/content/shared/contents-selector.component.html
  9. 20
      src/Squidex/app/shared/components/assets-selector.component.html
  10. 9
      src/Squidex/app/shared/components/assets-selector.component.ts
  11. 36
      src/Squidex/app/shared/components/search-form.component.html
  12. 0
      src/Squidex/app/shared/components/search-form.component.scss
  13. 20
      src/Squidex/app/shared/components/search-form.component.ts
  14. 1
      src/Squidex/app/shared/declarations.ts
  15. 2
      src/Squidex/app/shared/internal.ts
  16. 5
      src/Squidex/app/shared/module.ts
  17. 4
      src/Squidex/app/shared/state/assets.state.ts
  18. 24
      src/Squidex/app/shared/state/queries.spec.ts
  19. 10
      src/Squidex/app/shared/state/queries.ts

42
src/Squidex/app/features/assets/pages/assets-page.component.html

@ -6,16 +6,25 @@
</ng-container>
<ng-container menu>
<button class="btn btn-link btn-secondary mr-1" (click)="reload()" title="Refresh Assets (CTRL + SHIFT + R)">
<i class="icon-reset"></i> Refresh
</button>
<div class="row no-gutters pl-1">
<div class="col col-auto offset-xl-4">
<sqx-shortcut keys="ctrl+shift+r" (trigger)="reload()"></sqx-shortcut>
<sqx-shortcut keys="ctrl+shift+r" (trigger)="reload()"></sqx-shortcut>
<sqx-shortcut keys="ctrl+shift+f" (trigger)="inputFind.focus()"></sqx-shortcut>
<button class="btn btn-link btn-secondary" (click)="reload()" title="Refresh Contents (CTRL + SHIFT + R)">
<i class="icon-reset"></i> Refresh
</button>
</div>
<div class="col pl-1">
<sqx-shortcut keys="ctrl+shift+r" (trigger)="reload()"></sqx-shortcut>
<form class="form-inline" (ngSubmit)="search()">
<input class="form-control" #inputFind [formControl]="assetsFilter" placeholder="Search for assets" />
</form>
<sqx-search-form formClass="form" placeholder="Search for assets" fieldExample="fileSize"
(queryChanged)="search($event)"
[query]="assetsState.assetsQuery | async"
[queries]="queries"
enableShortcut="true">
</sqx-search-form>
</div>
</div>
</ng-container>
<ng-container content>
@ -26,7 +35,7 @@
<div class="sidebar-section">
<a class="row sidebar-item" (click)="resetTags()" [class.active]="assetsState.isTagSelectionEmpty()">
<div class="col">
All Assets
All tags
</div>
</a>
</div>
@ -43,5 +52,20 @@
</div>
</a>
</div>
<div class="sidebar-section">
<h3>Saved queries</h3>
<a class="row sidebar-item" *ngFor="let query of queries.queries | async" (click)="search(query.filter)" [class.active]="isSelectedQuery(query.filter)">
<div class="col truncate">
{{query.name}}
</div>
<div class="col col-auto">
<a class="sidebar-item-remove" (click)="queries.remove(query.name)">
<i class="icon-close"></i>
</a>
</div>
</a>
</div>
</ng-container>
</sqx-panel>

28
src/Squidex/app/features/assets/pages/assets-page.component.ts

@ -11,7 +11,12 @@ import { Component, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import { onErrorResumeNext } from 'rxjs/operators';
import { AppsState, AssetsState } from '@app/shared';
import {
AppsState,
AssetsState,
Queries,
UIState
} from '@app/shared';
@Component({
selector: 'sqx-assets-page',
@ -21,9 +26,12 @@ import { AppsState, AssetsState } from '@app/shared';
export class AssetsPageComponent implements OnInit {
public assetsFilter = new FormControl();
public queries = new Queries(this.uiState, 'assets');
constructor(
public readonly appsState: AppsState,
public readonly assetsState: AssetsState
public readonly assetsState: AssetsState,
private readonly uiState: UIState
) {
}
@ -35,18 +43,18 @@ export class AssetsPageComponent implements OnInit {
this.assetsState.load(true).pipe(onErrorResumeNext()).subscribe();
}
public search() {
this.assetsState.search(this.assetsFilter.value).pipe(onErrorResumeNext()).subscribe();
}
public resetTags() {
this.assetsState.resetTags().pipe(onErrorResumeNext()).subscribe();
public search(query: string) {
this.assetsState.search(query).pipe(onErrorResumeNext()).subscribe();
}
public toggleTag(tag: string) {
this.assetsState.toggleTag(tag).pipe(onErrorResumeNext()).subscribe();
}
public resetTags() {
this.assetsState.resetTags().pipe(onErrorResumeNext()).subscribe();
}
public goNext() {
this.assetsState.goNext().pipe(onErrorResumeNext()).subscribe();
}
@ -54,5 +62,9 @@ export class AssetsPageComponent implements OnInit {
public goPrev() {
this.assetsState.goPrev().pipe(onErrorResumeNext()).subscribe();
}
public isSelectedQuery(query: string) {
return query === this.assetsState.snapshot.assetsQuery || (!query && !this.assetsState.assetsQuery);
}
}

1
src/Squidex/app/features/content/declarations.ts

@ -9,7 +9,6 @@ export * from './pages/content/content-field.component';
export * from './pages/content/content-history.component';
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/array-editor.component';

6
src/Squidex/app/features/content/module.ts

@ -32,8 +32,7 @@ import {
DueTimeSelectorComponent,
FieldEditorComponent,
ReferencesEditorComponent,
SchemasPageComponent,
SearchFormComponent
SchemasPageComponent
} from './declarations';
const routes: Routes = [
@ -100,8 +99,7 @@ const routes: Routes = [
DueTimeSelectorComponent,
FieldEditorComponent,
ReferencesEditorComponent,
SchemasPageComponent,
SearchFormComponent
SchemasPageComponent
]
})
export class SqxFeatureContentModule { }

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

@ -14,19 +14,21 @@
<ng-container menu>
<div class="row no-gutters pl-1">
<div class="col col-auto offset-xl-4">
<sqx-shortcut keys="ctrl+shift+r" (trigger)="reload()"></sqx-shortcut>
<button class="btn btn-link btn-secondary" (click)="reload()" title="Refresh Contents (CTRL + SHIFT + R)">
<i class="icon-reset"></i> Refresh
</button>
</div>
<div class="col pl-1">
<sqx-shortcut keys="ctrl+shift+r" (trigger)="reload()"></sqx-shortcut>
<sqx-search-form formClass="form"
<sqx-search-form formClass="form" placeholder="Search for content" fieldExample="data/[MY_FIELD]/iv"
(queryChanged)="search($event)"
[query]="contentsState.contentsQuery | async"
[queries]="schemaQueries"
(archivedChanged)="goArchive($event)"
[archived]="contentsState.isArchive | async"
expandable="true"
enableArchive="true"
enableShortcut="true">
</sqx-search-form>
</div>
@ -122,32 +124,28 @@
</ng-container>
<ng-container sidebar>
<ng-container *ngIf="schemaQueries.queries | async; let queries">
<div class="sidebar-section">
<h3>Queries</h3>
<a class="row sidebar-item" *ngFor="let query of schemaQueries.defaultQueries" (click)="search(query.filter)" [class.active]="isSelectedQuery(query.filter)">
<div class="col truncate">
{{query.name}}
</div>
</a>
</div>
<div class="sidebar-section">
<a class="row sidebar-item" *ngFor="let query of schemaQueries.defaultQueries" (click)="search(query.filter)" [class.active]="isSelectedQuery(query.filter)">
<div class="col truncate">
{{query.name}}
</div>
</a>
</div>
<div class="sidebar-section" *ngIf="queries.length > 0">
<h3>Bookmarks</h3>
<div class="sidebar-section">
<h3>Saved queries</h3>
<a class="row sidebar-item" *ngFor="let query of queries" (click)="search(query.filter)" [class.active]="isSelectedQuery(query.filter)">
<div class="col truncate">
{{query.name}}
</div>
<div class="col col-auto">
<a class="sidebar-item-remove" (click)="schemaQueries.remove(query.name)">
<i class="icon-close"></i>
</a>
</div>
</a>
</div>
</ng-container>
<a class="row sidebar-item" *ngFor="let query of schemaQueries.queries | async" (click)="search(query.filter)" [class.active]="isSelectedQuery(query.filter)">
<div class="col truncate">
{{query.name}}
</div>
<div class="col col-auto">
<a class="sidebar-item-remove" (click)="schemaQueries.remove(query.name)">
<i class="icon-close"></i>
</a>
</div>
</a>
</div>
</ng-container>
</sqx-panel>

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

@ -17,8 +17,8 @@ import {
ImmutableArray,
LanguagesState,
ModalModel,
Queries,
SchemaDetailsDto,
SchemaQueries,
SchemasState,
UIState
} from '@app/shared';
@ -36,7 +36,7 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
private selectedSchemaSubscription: Subscription;
public schema: SchemaDetailsDto;
public schemaQueries: SchemaQueries;
public schemaQueries: Queries;
public searchModal = new ModalModel();
@ -76,7 +76,7 @@ export class ContentsPageComponent implements OnDestroy, OnInit {
this.resetSelection();
this.schema = schema!;
this.schemaQueries = new SchemaQueries(this.uiState, this.schema.name);
this.schemaQueries = new Queries(this.uiState, `schemas.${this.schema.name}`);
this.contentsState.init().pipe(onErrorResumeNext()).subscribe();
});

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

@ -14,8 +14,8 @@ import {
AppsState,
AssetDto,
AssetsService,
DialogModel,
ImmutableArray,
ModalModel,
Types
} from '@app/shared';
@ -35,7 +35,7 @@ export class AssetsEditorComponent implements ControlValueAccessor {
private callChange = (v: any) => { /* NOOP */ };
private callTouched = () => { /* NOOP */ };
public assetsDialog = new ModalModel();
public assetsDialog = new DialogModel();
public newAssets = ImmutableArray.empty<File>();
public oldAssets = ImmutableArray.empty<AssetDto>();

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

@ -11,10 +11,10 @@
</button>
</div>
<div class="col pl-1">
<sqx-search-form formClass="form"
<sqx-search-form formClass="form" placeholder="Search for content" fieldExample="data/[MY_FIELD]/iv"
[query]="contentsState.contentsQuery | async"
(queryChanged)="search($event)"
[canArchive]="false">
expandable="true">
</sqx-search-form>
<ng-container *ngIf="languages.length > 1">

20
src/Squidex/app/shared/components/assets-selector.component.html

@ -4,13 +4,19 @@
</ng-container>
<ng-container tabs>
<button class="float-right btn btn-link btn-secondary" (click)="reload()">
<i class="icon-reset"></i> Refresh
</button>
<form class="float-left form-inline" (ngSubmit)="search()">
<input class="form-control" [formControl]="assetsFilter" placeholder="Search for assets" />
</form>
<div class="row no-gutters">
<div class="col col-auto offset-lg-4">
<button class="btn btn-link btn-secondary" (click)="reload()">
<i class="icon-reset"></i> Refresh
</button>
</div>
<div class="col pl-1">
<sqx-search-form formClass="form" placeholder="Search for assets" fieldExample="fileSize"
[query]="state.assetsQuery | async"
(queryChanged)="search($event)">
</sqx-search-form>
</div>
</div>
</ng-container>
<ng-container content>

9
src/Squidex/app/shared/components/assets-selector.component.ts

@ -8,7 +8,6 @@
// tslint:disable:prefer-for-of
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
import { FormControl } from '@angular/forms';
import { onErrorResumeNext } from 'rxjs/operators';
import {
@ -32,8 +31,6 @@ export class AssetsSelectorComponent implements OnInit {
@Output()
public selected = new EventEmitter<AssetDto[]>();
public assetsFilter = new FormControl('');
constructor(
public readonly state: AssetsDialogState
) {
@ -41,16 +38,14 @@ export class AssetsSelectorComponent implements OnInit {
public ngOnInit() {
this.state.load().pipe(onErrorResumeNext()).subscribe();
this.assetsFilter.setValue(this.state.snapshot.assetsQuery);
}
public reload() {
this.state.load(true).pipe(onErrorResumeNext()).subscribe();
}
public search() {
this.state.search(this.assetsFilter.value).pipe(onErrorResumeNext()).subscribe();
public search(query: string) {
this.state.search(query).pipe(onErrorResumeNext()).subscribe();
}
public complete() {

36
src/Squidex/app/features/content/pages/contents/search-form.component.html → src/Squidex/app/shared/components/search-form.component.html

@ -3,23 +3,27 @@
</ng-container>
<form [class]="formClass" (ngSubmit)="search()">
<input class="form-control form-control-expandable" #inputFind [formControl]="contentsFilter" placeholder="Search for content" />
<input class="form-control form-control-expandable" #inputFind [formControl]="contentsFilter" [placeholder]="placeholder" />
<a class="expand-search" (click)="searchModal.toggle()" #archive>
<i class="icon-caret-down"></i>
</a>
<ng-container *ngIf="saveKey | async; else notBookmarked; let key">
<a class="save-search" (click)="queries.remove(key)">
<i class="icon-star-full"></i>
<ng-container *ngIf="expandable">
<a class="expand-search" (click)="searchModal.toggle()" #archive>
<i class="icon-caret-down"></i>
</a>
</ng-container>
<ng-template #notBookmarked>
<a class="save-search" (click)="saveQuery()" *ngIf="contentsFilterValue | async">
<i class="icon-star-empty"></i>
</a>
</ng-template>
<ng-container *ngIf="queries">
<ng-container *ngIf="saveKey | async; else notBookmarked; let key">
<a class="save-search" (click)="queries.remove(key)">
<i class="icon-star-full"></i>
</a>
</ng-container>
<ng-template #notBookmarked>
<a class="save-search" (click)="saveQuery()" *ngIf="contentsFilterValue | async">
<i class="icon-star-empty"></i>
</a>
</ng-template>
</ng-container>
</form>
<sqx-onboarding-tooltip id="contentArchive" [for]="archive" position="bottomRight" after="60000">
@ -45,7 +49,7 @@
<label class="col col-2 col-form-label" for="filter">Filter</label>
<div class="col col-10">
<input type="input" class="form-control" id="filter" (blur)="updateQuery()" formControlName="odataFilter" placeholder="data/[MY_FIELD]/iv eq 100" />
<input type="input" class="form-control" id="filter" (blur)="updateQuery()" formControlName="odataFilter" placeholder="{{fieldExample}} eq [VALUE]" />
</div>
</div>
@ -53,12 +57,12 @@
<label class="col col-2 col-form-label" for="orderBy">Order</label>
<div class="col col-10">
<input type="input" class="form-control" id="orderBy" (blur)="updateQuery()" formControlName="odataOrderBy" placeholder="data/[MY_FIELD]/iv desc" />
<input type="input" class="form-control" id="orderBy" (blur)="updateQuery()" formControlName="odataOrderBy" placeholder="{{fieldExample}} desc" />
</div>
</div>
</div>
<div class="form-check" *ngIf="canArchive">
<div class="form-check" *ngIf="enableArchive">
<input class="form-check-input" type="checkbox" id="archivedItems" [ngModel]="archived" (ngModelChange)="archivedChanged.emit($event)" />
<label class="form-check-label" for="archivedItems">
Archived items

0
src/Squidex/app/features/content/pages/contents/search-form.component.scss → src/Squidex/app/shared/components/search-form.component.scss

20
src/Squidex/app/features/content/pages/contents/search-form.component.ts → src/Squidex/app/shared/components/search-form.component.ts

@ -8,7 +8,12 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core';
import { FormBuilder, FormControl } from '@angular/forms';
import { ModalModel, SaveQueryForm, SchemaQueries } from '@app/shared';
import {
ModalModel,
Queries,
SaveQueryForm
} from '@app/shared/internal';
import { Observable } from 'rxjs';
import { shareReplay } from 'rxjs/operators';
@ -20,7 +25,16 @@ import { shareReplay } from 'rxjs/operators';
})
export class SearchFormComponent implements OnChanges, OnInit {
@Input()
public queries: SchemaQueries;
public queries: Queries;
@Input()
public placeholder = '';
@Input()
public fieldExample = '[MY_FIELD]';
@Input()
public expandable = false;
@Input()
public query = '';
@ -38,7 +52,7 @@ export class SearchFormComponent implements OnChanges, OnInit {
public schemaName = '';
@Input()
public canArchive = true;
public enableArchive = false;
@Input()
public enableShortcut = false;

1
src/Squidex/app/shared/declarations.ts

@ -18,5 +18,6 @@ export * from './components/markdown-editor.component';
export * from './components/pipes';
export * from './components/rich-editor.component';
export * from './components/schema-category.component';
export * from './components/search-form.component';
export * from './internal';

2
src/Squidex/app/shared/internal.ts

@ -57,11 +57,11 @@ export * from './state/languages.state';
export * from './state/patterns.forms';
export * from './state/patterns.state';
export * from './state/plans.state';
export * from './state/queries';
export * from './state/rule-events.state';
export * from './state/rules.state';
export * from './state/schemas.forms';
export * from './state/schemas.state';
export * from './state/schema-queries';
export * from './state/ui.state';
export * from './utils/messages';

5
src/Squidex/app/shared/module.ts

@ -66,6 +66,7 @@ import {
SchemaMustExistPublishedGuard,
SchemasService,
SchemasState,
SearchFormComponent,
UIService,
UIState,
UnsetAppGuard,
@ -108,7 +109,8 @@ import {
UserNameRefPipe,
UserPicturePipe,
UserPictureRefPipe,
RichEditorComponent
RichEditorComponent,
SearchFormComponent
],
exports: [
AppFormComponent,
@ -126,6 +128,7 @@ import {
MarkdownEditorComponent,
RouterModule,
SchemaCategoryComponent,
SearchFormComponent,
UserDtoPicture,
UserIdPicturePipe,
UserNamePipe,

4
src/Squidex/app/shared/state/assets.state.ts

@ -45,6 +45,10 @@ export class AssetsState extends State<Snapshot> {
this.changes.pipe(map(x => x.assets),
distinctUntilChanged());
public assetsQuery =
this.changes.pipe(map(x => x.assetsQuery),
distinctUntilChanged());
public assetsPager =
this.changes.pipe(map(x => x.assetsPager),
distinctUntilChanged());

24
src/Squidex/app/shared/state/schema-queries.spec.ts → src/Squidex/app/shared/state/queries.spec.ts

@ -8,35 +8,35 @@
import { BehaviorSubject } from 'rxjs';
import { IMock, Mock, Times } from 'typemoq';
import { Query, SchemaQueries } from './schema-queries';
import { Queries, Query } from './queries';
import { UIState } from './ui.state';
describe('SchemaQueries', () => {
const schema = 'my-schema';
describe('Queries', () => {
const prefix = 'schemas.my-schema';
let uiState: IMock<UIState>;
let filter = new BehaviorSubject('');
let queries = new BehaviorSubject({});
let schemaQueries: SchemaQueries;
let queries$ = new BehaviorSubject({});
let queries: Queries;
beforeEach(() => {
uiState = Mock.ofType<UIState>();
uiState.setup(x => x.get('schemas.my-schema.queries', {}))
.returns(() => queries);
.returns(() => queries$);
queries.next({
queries$.next({
key1: 'query1',
key2: 'query2'
});
schemaQueries = new SchemaQueries(uiState.object, schema);
queries = new Queries(uiState.object, prefix);
});
it('should load queries', () => {
let converted: Query[];
schemaQueries.queries.subscribe(x => {
queries.queries.subscribe(x => {
converted = x;
});
@ -56,7 +56,7 @@ describe('SchemaQueries', () => {
it('should provide key', () => {
let key: string;
schemaQueries.getSaveKey(filter).subscribe(x => {
queries.getSaveKey(filter).subscribe(x => {
key = x!;
});
@ -66,13 +66,13 @@ describe('SchemaQueries', () => {
});
it('should forward add call to state', () => {
schemaQueries.add('key3', 'filter3');
queries.add('key3', 'filter3');
uiState.verify(x => x.set('schemas.my-schema.queries.key3', 'filter3'), Times.once());
});
it('should forward remove call to state', () => {
schemaQueries.remove('key3');
queries.remove('key3');
uiState.verify(x => x.remove('schemas.my-schema.queries.key3'), Times.once());
});

10
src/Squidex/app/shared/state/schema-queries.ts → src/Squidex/app/shared/state/queries.ts

@ -16,7 +16,7 @@ export interface Query {
filter: string;
}
export class SchemaQueries {
export class Queries {
public queries: Observable<Query[]>;
public defaultQueries: Query[] = [{
@ -27,9 +27,9 @@ export class SchemaQueries {
constructor(
private readonly uiState: UIState,
private readonly schemaName: string
private readonly prefix: string
) {
this.queries = this.uiState.get(`schemas.${this.schemaName}.queries`, {}).pipe(
this.queries = this.uiState.get(`${this.prefix}.queries`, {}).pipe(
map(x => {
let queries: Query[] = Object.keys(x).map(y => ({ name: y, filter: x[y] }));
@ -53,11 +53,11 @@ export class SchemaQueries {
}
public add(key: string, filter: string) {
this.uiState.set(`schemas.${this.schemaName}.queries.${key}`, filter);
this.uiState.set(`${this.prefix}.queries.${key}`, filter);
}
public remove(key: string) {
this.uiState.remove(`schemas.${this.schemaName}.queries.${key}`);
this.uiState.remove(`${this.prefix}.queries.${key}`);
}
public getSaveKey(filter$: Observable<string>): Observable<string | null> {
Loading…
Cancel
Save