Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Search highlight #327

Merged
merged 12 commits into from
Jun 22, 2021
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions frontend/src/search/SearchEntryConverter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export default function convertSearchResultToSearchResultList(
{formatEntry(n)}
</div>
),
entity: n,
entity: { ...n, interfaceType: 'NodeDescriptor' as const },
})),
},
{
Expand All @@ -67,7 +67,7 @@ export default function convertSearchResultToSearchResultList(
&nbsp;{formatEntry(e)}
</div>
),
entity: e,
entity: { ...e, interfaceType: 'EdgeDescriptor' as const },
})),
},
{
Expand All @@ -81,7 +81,7 @@ export default function convertSearchResultToSearchResultList(
color={styleProvider.getStyle({ id: -1, types: [t.name] }).color}
/>
),
entity: t,
entity: { ...t, interfaceType: 'NodeTypeDescriptor' as const },
})),
},
{
Expand All @@ -98,7 +98,7 @@ export default function convertSearchResultToSearchResultList(
}
/>
),
entity: t,
entity: { ...t, interfaceType: 'EdgeTypeDescriptor' as const },
})),
},
]
Expand Down
9 changes: 2 additions & 7 deletions frontend/src/search/SearchResultList.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import { EdgeDescriptor, NodeDescriptor } from '../shared/entities';
import { EdgeTypeDescriptor, NodeTypeDescriptor } from '../shared/schema';
import { SelectedSearchResult } from '../stores/SearchSelectionStore';

export default interface SearchResultList {
key: number | string;
header: string;
elements: {
key: number | string;
element: JSX.Element;
entity:
| EdgeDescriptor
| EdgeTypeDescriptor
| NodeDescriptor
| NodeTypeDescriptor;
entity: SelectedSearchResult;
}[];
}
29 changes: 22 additions & 7 deletions frontend/src/stores/SearchSelectionStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,34 @@ import SimpleStore from './SimpleStore';
import { EdgeDescriptor, NodeDescriptor } from '../shared/entities';
import { EdgeTypeDescriptor, NodeTypeDescriptor } from '../shared/schema';

// enables discriminated union types https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#discriminating-unions
interface TypedEdgeDescriptor extends EdgeDescriptor {
interfaceType: 'EdgeDescriptor';
}
interface TypedEdgeTypeDescriptor extends EdgeTypeDescriptor {
interfaceType: 'EdgeTypeDescriptor';
}
interface TypedNodeDescriptor extends NodeDescriptor {
interfaceType: 'NodeDescriptor';
}
interface TypedNodeTypeDescriptor extends NodeTypeDescriptor {
interfaceType: 'NodeTypeDescriptor';
}

export type SelectedSearchResult =
| EdgeDescriptor
| EdgeTypeDescriptor
| NodeDescriptor
| NodeTypeDescriptor
| undefined;
| TypedEdgeDescriptor
| TypedEdgeTypeDescriptor
| TypedNodeDescriptor
| TypedNodeTypeDescriptor;

/**
* Contains the selected search result (if any).
* It's the search result that was clicked on in the search bar.
*/
export default class SearchSelectionStore extends SimpleStore<SelectedSearchResult> {
protected getInitialValue(): SelectedSearchResult {
export default class SearchSelectionStore extends SimpleStore<
SelectedSearchResult | undefined
> {
protected getInitialValue(): SelectedSearchResult | undefined {
return undefined;
}
}
45 changes: 42 additions & 3 deletions frontend/src/stores/colors/EntityStyleProviderImpl.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import 'reflect-metadata';
import { common } from '@material-ui/core/colors';
import { NodeStyle, EdgeStyle } from './EntityStyle';
import { EdgeStyle, NodeStyle } from './EntityStyle';
import { EntityStyleProvider } from './EntityStyleProvider';
import getNthColor from './getNthColor';
import { EdgeDescriptor, NodeDescriptor } from '../../shared/entities';
import { ArgumentError } from '../../shared/errors';
import { QueryNodeResult, QueryEdgeResult } from '../../shared/queries';
import { QueryEdgeResult, QueryNodeResult } from '../../shared/queries';
import getTextColor from './getTextColor';
import EntityStyleStore from './EntityStyleStore';

Expand Down Expand Up @@ -36,7 +36,7 @@ export class EntityStyleProviderImpl implements EntityStyleProvider {
color: common.black,
text: { color: common.black },
stroke: {
width: 1,
width: this.isSelected(entity) ? 5 : 1,
dashes: false,
color: common.black,
},
Expand Down Expand Up @@ -121,6 +121,45 @@ export class EntityStyleProviderImpl implements EntityStyleProvider {
private isVirtual(entity: QueryEntityResult): boolean {
return entity.virtual === true;
}

/**
* Returns true if entity corresponds to the entity that is selected via search.
* @param entity - is compared with the selection
* @private
*/
private isSelected(entity: QueryEntityResult): boolean {
const selection = this.entityStyleStore.getEntitySelectionInfo();
if (selection === undefined) {
return false;
}

// check entity type
if (
!(
(selection.kind === 'EDGE' && isEdgeDescriptor(entity)) ||
(selection.kind === 'NODE' && isNodeDescriptor(entity))
)
) {
return false;
}

if ('id' in selection) {
// single entity selected => check if entity is that entity
return entity.id === selection.id;
}

if ('type' in selection) {
// type selected => check if entity is of that type
if (isNodeDescriptor(entity)) {
return entity.types.some((type) => type === selection.type);
}
if (isEdgeDescriptor(entity)) {
return entity.type === selection.type;
}
}

return false;
}
}

export default EntityStyleProviderImpl;
88 changes: 86 additions & 2 deletions frontend/src/stores/colors/EntityStyleStore.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,40 @@
import 'reflect-metadata';
import { injectable } from 'inversify';
import { BehaviorSubject, Observable } from 'rxjs';
import { inject, injectable } from 'inversify';
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
import { EntityStyleProvider } from './EntityStyleProvider';
import { EntityStyleProviderImpl } from './EntityStyleProviderImpl';
import SearchSelectionStore, {
SelectedSearchResult,
} from '../SearchSelectionStore';
import { ArgumentError } from '../../shared/errors';

export type SelectionInfo =
| { kind: 'NODE' | 'EDGE'; id: number }
| { kind: 'NODE' | 'EDGE'; type: string };

/**
* Converts a {@link SelectedSearchResult} to a {@link SelectionInfo}
* @param selectionResult - {@link SelectedSearchResult} that is converted
* @private
*/
function createSelectionInfo(
selectionResult?: SelectedSearchResult
): SelectionInfo | undefined {
if (selectionResult === undefined) return undefined;

switch (selectionResult.interfaceType) {
case 'EdgeDescriptor':
return { kind: 'EDGE', id: selectionResult.id };
case 'NodeDescriptor':
return { kind: 'NODE', id: selectionResult.id };
case 'EdgeTypeDescriptor':
return { kind: 'EDGE', type: selectionResult.name };
case 'NodeTypeDescriptor':
return { kind: 'NODE', type: selectionResult.name };
default:
throw new ArgumentError(`Unknown selection`);
}
}

/**
* A simple store contains a single state.
Expand All @@ -11,6 +43,11 @@ import { EntityStyleProviderImpl } from './EntityStyleProviderImpl';
*/
@injectable()
export class EntityStyleStore {
private searchSelectionStoreSubscription?: Subscription;

@inject(SearchSelectionStore)
private readonly searchSelectionStore!: SearchSelectionStore;

/**
* Contains the state.
* Returns the current state immediately after subscribing.
Expand All @@ -20,28 +57,53 @@ export class EntityStyleStore {
this.getEntityColorizer()
);

/**
* Contains information about the current selected entity.
* @private
*/
private entitySelectionInfo?: SelectionInfo;

public getEntitySelectionInfo(): SelectionInfo | undefined {
return this.entitySelectionInfo;
}

/**
* Returns the entity colorizing function.
* @protected
*/
protected getEntityColorizer(): EntityStyleProvider {
return new EntityStyleProviderImpl(this);
}
/**
* Updates the current filter by replacing it completely.
*/
public setState(newState: EntityStyleProvider): void {
this.storeSubject.next(newState);
}

/**
* Returns an observable that outputs the stored value.
*/
public getState(): Observable<EntityStyleProvider> {
this.ensureInit();
return this.storeSubject.pipe();
}

/**
* Returns the current value of the stored value.
*/
public getValue(): EntityStyleProvider {
this.ensureInit();
return this.storeSubject.value;
}

protected ensureInit(): void {
if (this.searchSelectionStoreSubscription == null) {
this.searchSelectionStoreSubscription =
this.subscribeToSearchSelectionStore();
}
}

private readonly greyScaleEdges = new BehaviorSubject<boolean>(false);

/**
Expand All @@ -60,6 +122,28 @@ export class EntityStyleStore {
this.greyScaleEdges.next(greyScale);
this.storeSubject.next(this.getEntityColorizer());
}

/**
* Updates the store using a {@link SelectedSearchResult}
* @param selectionResult - the {@link SelectedSearchResult} that is used for updating
* @private
*/
private nextSelectionResult(selectionResult?: SelectedSearchResult) {
this.entitySelectionInfo = createSelectionInfo(selectionResult);
this.storeSubject.next(this.getEntityColorizer());
}

/**
* Subscribes to the state of the {@link searchSelectionStore} so that the state
* of this store is updated when the {@link searchSelectionStore} updates.
* @returns subscription of the {@link searchSelectionStore} state
* @private
*/
private subscribeToSearchSelectionStore(): Subscription {
return this.searchSelectionStore.getState().subscribe({
next: (selectionResult) => this.nextSelectionResult(selectionResult),
});
}
}

export default EntityStyleStore;