Merge pull request #4424 from FinnStutzenstein/reviveGlobalSearch

Revive the global search.
This commit is contained in:
Finn Stutzenstein 2019-02-28 09:54:01 +01:00 committed by GitHub
commit 77e192719d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 139 additions and 85 deletions

View File

@ -19,6 +19,7 @@ export interface ModelEntry extends BaseModelEntry {
export interface SearchableModelEntry extends BaseModelEntry {
viewModel: ViewModelConstructor<BaseViewModel & Searchable>;
searchOrder: number;
openInNewTab?: boolean;
}
/**

View File

@ -79,7 +79,12 @@ export class AppLoadService {
repository
);
if (this.isSearchableModelEntry(entry)) {
this.searchService.registerModel(entry.collectionString, entry.viewModel, entry.searchOrder);
this.searchService.registerModel(
entry.collectionString,
repository,
entry.searchOrder,
entry.openInNewTab
);
}
});
}

View File

@ -54,6 +54,10 @@ export class ItemRepositoryService extends BaseRepository<ViewItem, Item> {
super(DS, mapperService, viewModelStoreService, Item);
}
public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Items' : 'Item');
};
protected setupDependencyObservation(): void {
this.DS.secondaryModelChangeSubject.subscribe(model => {
const viewModel = this.viewModelStoreService.get(model.collectionString, model.id);
@ -75,9 +79,7 @@ export class ItemRepositoryService extends BaseRepository<ViewItem, Item> {
public createViewModel(item: Item): ViewItem {
const contentObject = this.getContentObject(item);
const viewItem = new ViewItem(item, contentObject);
viewItem.getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Items' : 'Item');
};
viewItem.getVerboseName = this.getVerboseName;
viewItem.getTitle = () => {
if (viewItem.contentObject) {
return viewItem.contentObject.getAgendaTitle();

View File

@ -7,12 +7,7 @@ import { BaseRepository } from './base-repository';
export function isBaseAgendaContentObjectRepository(obj: any): obj is BaseAgendaContentObjectRepository<any, any> {
const repo = obj as BaseAgendaContentObjectRepository<any, any>;
return (
!!obj &&
repo.getVerboseName !== undefined &&
repo.getAgendaTitle !== undefined &&
repo.getAgendaTitleWithType !== undefined
);
return !!obj && repo.getAgendaTitle !== undefined && repo.getAgendaTitleWithType !== undefined;
}
export abstract class BaseAgendaContentObjectRepository<
@ -21,7 +16,6 @@ export abstract class BaseAgendaContentObjectRepository<
> extends BaseRepository<V, M> {
public abstract getAgendaTitle: (model: Partial<M> | Partial<V>) => string;
public abstract getAgendaTitleWithType: (model: Partial<M> | Partial<V>) => string;
public abstract getVerboseName: (plural?: boolean) => string;
/**
*/

View File

@ -46,6 +46,8 @@ export abstract class BaseRepository<V extends BaseViewModel, M extends BaseMode
return this._collectionString;
}
public abstract getVerboseName: (plural?: boolean) => string;
/**
* Construction routine for the base repository
*

View File

@ -21,11 +21,13 @@ export class ChatMessageRepositoryService extends BaseRepository<ViewChatMessage
super(DS, mapperService, viewModelStoreService, ChatMessage);
}
protected createViewModel(message: ChatMessage): ViewChatMessage {
const viewChatMessage = new ViewChatMessage(message);
viewChatMessage.getVerboseName = (plural: boolean = false) => {
public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Chatmessages' : 'Chatmessage');
};
protected createViewModel(message: ChatMessage): ViewChatMessage {
const viewChatMessage = new ViewChatMessage(message);
viewChatMessage.getVerboseName = this.getVerboseName;
return viewChatMessage;
}

View File

@ -112,15 +112,17 @@ export class ConfigRepositoryService extends BaseRepository<ViewConfig, Config>
});
}
public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Configs' : 'Config');
};
/**
* Creates a new ViewConfig of a given Config object
* @param config
*/
public createViewModel(config: Config): ViewConfig {
const viewConfig = new ViewConfig(config);
viewConfig.getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Configs' : 'Config');
};
viewConfig.getVerboseName = this.getVerboseName;
return viewConfig;
}

View File

@ -41,6 +41,10 @@ export class HistoryRepositoryService extends BaseRepository<ViewHistory, Histor
super(DS, mapperService, viewModelStoreService, History, [User]);
}
public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Histories' : 'History');
};
/**
* Creates a new ViewHistory objects out of a historyObject
*
@ -50,9 +54,7 @@ export class HistoryRepositoryService extends BaseRepository<ViewHistory, Histor
public createViewModel(history: History): ViewHistory {
const user = this.viewModelStoreService.get(ViewUser, history.user_id);
const viewHistory = new ViewHistory(history, user);
viewHistory.getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Histories' : 'History'); // Whats about the plural case??
};
viewHistory.getVerboseName = this.getVerboseName;
return viewHistory;
}

View File

@ -39,6 +39,10 @@ export class MediafileRepositoryService extends BaseRepository<ViewMediafile, Me
super(DS, mapperService, viewModelStoreService, Mediafile, [User]);
}
public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Files' : 'File');
};
/**
* Creates mediafile ViewModels out of given mediafile objects
*
@ -48,9 +52,7 @@ export class MediafileRepositoryService extends BaseRepository<ViewMediafile, Me
public createViewModel(file: Mediafile): ViewMediafile {
const uploader = this.viewModelStoreService.get(ViewUser, file.uploader_id);
const viewMediafile = new ViewMediafile(file, uploader);
viewMediafile.getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Files' : 'File');
};
viewMediafile.getVerboseName = this.getVerboseName;
return viewMediafile;
}

View File

@ -52,11 +52,13 @@ export class CategoryRepositoryService extends BaseRepository<ViewCategory, Cate
super(DS, mapperService, viewModelStoreService, Category);
}
protected createViewModel(category: Category): ViewCategory {
const viewCategory = new ViewCategory(category);
viewCategory.getVerboseName = (plural: boolean = false) => {
public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Categories' : 'Category');
};
protected createViewModel(category: Category): ViewCategory {
const viewCategory = new ViewCategory(category);
viewCategory.getVerboseName = this.getVerboseName;
return viewCategory;
}

View File

@ -53,6 +53,10 @@ export class ChangeRecommendationRepositoryService extends BaseRepository<
super(DS, mapperService, viewModelStoreService, MotionChangeRecommendation, [Category, User, Workflow]);
}
public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Change recommendations' : 'Change recommendation');
};
/**
* Creates this view wrapper based on an actual Change Recommendation model
*
@ -60,9 +64,7 @@ export class ChangeRecommendationRepositoryService extends BaseRepository<
*/
protected createViewModel(model: MotionChangeRecommendation): ViewMotionChangeRecommendation {
const viewMotionChangeRecommendation = new ViewMotionChangeRecommendation(model);
viewMotionChangeRecommendation.getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Change recommendations' : 'Change recommendation');
};
viewMotionChangeRecommendation.getVerboseName = this.getVerboseName;
return viewMotionChangeRecommendation;
}

View File

@ -51,6 +51,10 @@ export class MotionCommentSectionRepositoryService extends BaseRepository<
super(DS, mapperService, viewModelStoreService, MotionCommentSection, [Group]);
}
public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Comment sections' : 'Comment section');
};
/**
* Creates the ViewModel for the MotionComment Section
*
@ -61,9 +65,7 @@ export class MotionCommentSectionRepositoryService extends BaseRepository<
const readGroups = this.viewModelStoreService.getMany(ViewGroup, section.read_groups_id);
const writeGroups = this.viewModelStoreService.getMany(ViewGroup, section.write_groups_id);
const viewMotionCommentSection = new ViewMotionCommentSection(section, readGroups, writeGroups);
viewMotionCommentSection.getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Comment sections' : 'Comment section');
};
viewMotionCommentSection.getVerboseName = this.getVerboseName;
return viewMotionCommentSection;
}

View File

@ -40,11 +40,13 @@ export class StatuteParagraphRepositoryService extends BaseRepository<ViewStatut
super(DS, mapperService, viewModelStoreService, StatuteParagraph);
}
protected createViewModel(statuteParagraph: StatuteParagraph): ViewStatuteParagraph {
const viewStatuteParagraph = new ViewStatuteParagraph(statuteParagraph);
viewStatuteParagraph.getVerboseName = (plural: boolean = false) => {
public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Statute paragraphs' : 'Statute paragraph');
};
protected createViewModel(statuteParagraph: StatuteParagraph): ViewStatuteParagraph {
const viewStatuteParagraph = new ViewStatuteParagraph(statuteParagraph);
viewStatuteParagraph.getVerboseName = this.getVerboseName;
return viewStatuteParagraph;
}

View File

@ -52,6 +52,10 @@ export class WorkflowRepositoryService extends BaseRepository<ViewWorkflow, Work
super(DS, mapperService, viewModelStoreService, Workflow);
}
public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Workflows' : 'Workflow');
};
/**
* Creates a ViewWorkflow from a given Workflow
*
@ -59,9 +63,7 @@ export class WorkflowRepositoryService extends BaseRepository<ViewWorkflow, Work
*/
protected createViewModel(workflow: Workflow): ViewWorkflow {
const viewWorkflow = new ViewWorkflow(workflow);
viewWorkflow.getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Workflows' : 'Workflow');
};
viewWorkflow.getVerboseName = this.getVerboseName;
return viewWorkflow;
}

View File

@ -25,11 +25,13 @@ export class CountdownRepositoryService extends BaseRepository<ViewCountdown, Co
super(DS, mapperService, viewModelStoreService, Countdown);
}
protected createViewModel(countdown: Countdown): ViewCountdown {
const viewCountdown = new ViewCountdown(countdown);
viewCountdown.getVerboseName = (plural: boolean = false) => {
public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Countdowns' : 'Countdown');
};
protected createViewModel(countdown: Countdown): ViewCountdown {
const viewCountdown = new ViewCountdown(countdown);
viewCountdown.getVerboseName = this.getVerboseName;
return viewCountdown;
}

View File

@ -23,11 +23,13 @@ export class ProjectorMessageRepositoryService extends BaseRepository<ViewProjec
super(DS, mapperService, viewModelStoreService, ProjectorMessage);
}
protected createViewModel(message: ProjectorMessage): ViewProjectorMessage {
const viewProjectorMessage = new ViewProjectorMessage(message);
viewProjectorMessage.getVerboseName = (plural: boolean = false) => {
public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Messages' : 'Message');
};
protected createViewModel(message: ProjectorMessage): ViewProjectorMessage {
const viewProjectorMessage = new ViewProjectorMessage(message);
viewProjectorMessage.getVerboseName = this.getVerboseName;
return viewProjectorMessage;
}

View File

@ -46,11 +46,13 @@ export class ProjectorRepositoryService extends BaseRepository<ViewProjector, Pr
super(DS, mapperService, viewModelStoreService, Projector);
}
public createViewModel(projector: Projector): ViewProjector {
const viewProjector = new ViewProjector(projector);
viewProjector.getVerboseName = (plural: boolean = false) => {
public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Projectors' : 'Projector');
};
public createViewModel(projector: Projector): ViewProjector {
const viewProjector = new ViewProjector(projector);
viewProjector.getVerboseName = this.getVerboseName;
return viewProjector;
}

View File

@ -43,11 +43,13 @@ export class TagRepositoryService extends BaseRepository<ViewTag, Tag> {
super(DS, mapperService, viewModelStoreService, Tag);
}
protected createViewModel(tag: Tag): ViewTag {
const viewTag = new ViewTag(tag);
viewTag.getVerboseName = (plural: boolean = false) => {
public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Tags' : 'Tag');
};
protected createViewModel(tag: Tag): ViewTag {
const viewTag = new ViewTag(tag);
viewTag.getVerboseName = this.getVerboseName;
return viewTag;
}

View File

@ -60,11 +60,13 @@ export class GroupRepositoryService extends BaseRepository<ViewGroup, Group> {
this.sortPermsPerApp();
}
public createViewModel(group: Group): ViewGroup {
const viewGroup = new ViewGroup(group);
viewGroup.getVerboseName = (plural: boolean = false) => {
public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Groups' : 'Group');
};
public createViewModel(group: Group): ViewGroup {
const viewGroup = new ViewGroup(group);
viewGroup.getVerboseName = this.getVerboseName;
return viewGroup;
}

View File

@ -28,11 +28,13 @@ export class PersonalNoteRepositoryService extends BaseRepository<ViewPersonalNo
super(DS, mapperService, viewModelStoreService, PersonalNote);
}
protected createViewModel(personalNote: PersonalNote): ViewPersonalNote {
const viewPersonalNote = new ViewPersonalNote();
viewPersonalNote.getVerboseName = (plural: boolean = false) => {
public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Personal notes' : 'Personal note');
};
protected createViewModel(personalNote: PersonalNote): ViewPersonalNote {
const viewPersonalNote = new ViewPersonalNote();
viewPersonalNote.getVerboseName = this.getVerboseName;
return viewPersonalNote;
}

View File

@ -51,13 +51,14 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User> {
super(DS, mapperService, viewModelStoreService, User, [Group]);
}
public createViewModel(user: User): ViewUser {
const groups = this.viewModelStoreService.getMany(ViewGroup, user.groups_id);
const viewUser = new ViewUser(user, groups);
viewUser.getVerboseName = (plural: boolean = false) => {
public getVerboseName = (plural: boolean = false) => {
return this.translate.instant(plural ? 'Participants' : 'Participant');
};
public createViewModel(user: User): ViewUser {
const groups = this.viewModelStoreService.getMany(ViewGroup, user.groups_id);
const viewUser = new ViewUser(user, groups);
viewUser.getVerboseName = this.getVerboseName;
viewUser.getNumberForName = (nr: number) => {
return `${this.translate.instant('No.')} ${nr}`;
};

View File

@ -1,6 +1,8 @@
import { Injectable } from '@angular/core';
import { Searchable } from '../../site/base/searchable';
import { BaseViewModel } from 'app/site/base/base-view-model';
import { BaseRepository } from '../repositories/base-repository';
import { ViewModelStoreService } from '../core-services/view-model-store.service';
/**
* The representation every searchable model should use to represent their data.
@ -25,6 +27,11 @@ export interface SearchModel {
* The plural verbose name of the model.
*/
verboseNamePlural: string;
/**
* Whether to open the detail page in a new tab.
*/
openInNewTab: boolean;
}
/**
@ -42,6 +49,11 @@ export interface SearchResult {
*/
verboseName: string;
/**
* Whether to open the detail page in a new tab.
*/
openInNewTab: boolean;
/**
* All matched models.
*/
@ -63,12 +75,13 @@ export class SearchService {
verboseNameSingular: string;
verboseNamePlural: string;
displayOrder: number;
openInNewTab: boolean;
}[] = [];
/**
* @param DS The DataStore to search in.
* @param viewModelStore The store to search in.
*/
public constructor() {}
public constructor(private viewModelStore: ViewModelStoreService) {}
/**
* Registers a model by the given attributes.
@ -79,15 +92,17 @@ export class SearchService {
*/
public registerModel(
collectionString: string,
ctor: new (...args: any[]) => Searchable & BaseViewModel,
displayOrder: number
repo: BaseRepository<any, any>,
displayOrder: number,
openInNewTab: boolean = false
): void {
// const instance = new ctor();
this.searchModels.push({
collectionString: collectionString,
verboseNameSingular: 'TODO', // instance.getVerboseName(),
verboseNamePlural: 'TODO', // instance.getVerboseName(true),
displayOrder: displayOrder
verboseNameSingular: repo.getVerboseName(),
verboseNamePlural: repo.getVerboseName(true),
displayOrder: displayOrder,
openInNewTab: openInNewTab
});
this.searchModels.sort((a, b) => a.displayOrder - b.displayOrder);
}
@ -99,7 +114,8 @@ export class SearchService {
return this.searchModels.map(searchModel => ({
collectionString: searchModel.collectionString,
verboseNameSingular: searchModel.verboseNameSingular,
verboseNamePlural: searchModel.verboseNamePlural
verboseNamePlural: searchModel.verboseNamePlural,
openInNewTab: searchModel.openInNewTab
}));
}
@ -112,19 +128,19 @@ export class SearchService {
*/
public search(query: string, inCollectionStrings: string[]): SearchResult[] {
query = query.toLowerCase();
/*return this.searchModels
return this.searchModels
.filter(s => inCollectionStrings.includes(s.collectionString))
.map(searchModel => {
const results = this.viewModelStore.filter(searchModel.collectionString, model =>
model.formatForSearch().some(text => text.toLowerCase().includes(query))
);
const results = this.viewModelStore
.getAll(searchModel.collectionString)
.map(x => x as (BaseViewModel & Searchable))
.filter(model => model.formatForSearch().some(text => text.toLowerCase().includes(query)));
return {
collectionString: searchModel.collectionString,
verboseName: results.length === 1 ? searchModel.verboseNameSingular : searchModel.verboseNamePlural,
openInNewTab: searchModel.openInNewTab,
models: results
};
});*/
throw new Error('Todo');
return [];
});
}
}

View File

@ -10,7 +10,7 @@ export const AssignmentsAppConfig: AppConfig = {
collectionString: 'assignments/assignment',
model: Assignment,
viewModel: ViewAssignment,
searchOrder: 3,
// searchOrder: 3, // TODO: enable, if there is a detail page and so on.
repository: AssignmentRepositoryService
}
],

View File

@ -76,7 +76,7 @@ export class ViewAssignment extends BaseAgendaViewModel {
};
public formatForSearch(): SearchRepresentation {
throw new Error('TODO');
return [this.title];
}
public getDetailStateURL(): string {

View File

@ -44,10 +44,10 @@
<mat-card-content>
<mat-list>
<mat-list-item *ngFor="let model of searchResult.models">
<a
[routerLink]="model.getDetailStateURL()"
target="{{ searchResult.collectionString === 'mediafiles/mediafile' ? '_blank' : '' }}"
>
<a *ngIf="!searchResult.openInNewTab" [routerLink]="model.getDetailStateURL()">
{{ model.getTitle() }}
</a>
<a *ngIf="searchResult.openInNewTab" [routerLink]="model.getDetailStateURL()" target="_blank" >
{{ model.getTitle() }}
</a>
</mat-list-item>

View File

@ -114,7 +114,9 @@ export class SearchComponent extends BaseViewComponent implements OnInit {
this.searchResults = this.searchService.search(this.query, collectionStrings);
// Because the results are per model, we need to accumulate the total number of all search results.
this.searchResultCount = this.searchResults.map(sr => sr.models.length).reduce((acc, current) => acc + current);
this.searchResultCount = this.searchResults
.map(sr => sr.models.length)
.reduce((acc, current) => acc + current, 0);
// Update the URL.
this.router.navigate([], {

View File

@ -11,6 +11,7 @@ export const MediafileAppConfig: AppConfig = {
model: Mediafile,
viewModel: ViewMediafile,
searchOrder: 5,
openInNewTab: true,
repository: MediafileRepositoryService
}
],

View File

@ -85,7 +85,7 @@ export class ViewMediafile extends BaseProjectableViewModel implements Searchabl
}
public getDetailStateURL(): string {
throw new Error('TODO');
return this.downloadUrl;
}
public getSlide(): ProjectorElementBuildDeskriptor {

View File

@ -46,7 +46,7 @@ export class ViewTag extends BaseViewModel implements Searchable {
}
public getDetailStateURL(): string {
throw new Error('TODO');
return `/tags`;
}
/**

View File

@ -189,7 +189,7 @@ export class ViewUser extends BaseProjectableViewModel implements Searchable {
}
public getDetailStateURL(): string {
throw new Error('TODO');
return `/users/${this.id}`;
}
public getSlide(): ProjectorElementBuildDeskriptor {