Skip to content

Commit

Permalink
Merge pull request #10 from weegeekps/search_by_title
Browse files Browse the repository at this point in the history
Added title substring search.
  • Loading branch information
Adam Morris authored Oct 29, 2019
2 parents 1940b2a + c0d37a7 commit 8c967f7
Show file tree
Hide file tree
Showing 11 changed files with 165 additions and 18 deletions.
2 changes: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from "react";
import "./App.css";
import ProjectList from "./components/ProjectList";
import FilterBar from "./components/FilterBar";
import SearchBar from "./components/SearchBar";

const App: React.FC = () => {
return (
Expand All @@ -10,6 +11,7 @@ const App: React.FC = () => {
<h1>glTF Project Explorer</h1>
</div>
<div className="content">
<SearchBar></SearchBar>
<FilterBar></FilterBar>
<ProjectList></ProjectList>
</div>
Expand Down
24 changes: 24 additions & 0 deletions src/components/SearchBar.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
.search-bar {
background-color: #fcfcfc;
border-radius: 3px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.25);
margin: 1rem;
padding: 1rem;
display: flex;
flex-direction: column;
align-items: stretch;
}

.search-bar h1 {
font-size: 1.5rem;
margin: 0;
padding: 0;
}

.search-bar input {
margin: 0.5rem 0 0 0;
padding: 0.25rem 0.5rem;
height: 2rem;
line-height: 1.5rem;
font-size: 1rem;
}
52 changes: 52 additions & 0 deletions src/components/SearchBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React, { useCallback } from "react";
import { connect } from "react-redux";
import { IAppState } from "../interfaces/IAppState";
import { updateTitleSubstringFilter } from "../store/filters/Actions";
import "./SearchBar.css";

export interface ISearchBarProps {
titleSubstring: string;
updateTitleSubstringFilter: typeof updateTitleSubstringFilter;
}

const SearchBar: React.FC<ISearchBarProps> = props => {
const { titleSubstring, updateTitleSubstringFilter } = props;

const handleSearch = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
const newTitleSubstring = event.target.value;
updateTitleSubstringFilter(newTitleSubstring);
},
[updateTitleSubstringFilter]
);

return (
<div className="search-bar">
<h1>Search by Title</h1>
<input
placeholder="Type to search"
value={titleSubstring}
onChange={handleSearch}
></input>
</div>
);
};

function mapStateToProps(state: IAppState) {
const {
filters: { titleSubstring }
} = state;

return {
titleSubstring
};
}

const mapDispatchToProps = {
updateTitleSubstringFilter
};

export default connect(
mapStateToProps,
mapDispatchToProps
)(SearchBar);
1 change: 1 addition & 0 deletions src/interfaces/IAppState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export interface IFiltersState {
types: IFilter[];
languages: IFilter[];
licenses: IFilter[];
titleSubstring: string;
selected: Set<IFilter>;
}

Expand Down
18 changes: 15 additions & 3 deletions src/store/filters/Actions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
IUpdateFiltersAction,
IUpdateSelectedFiltersAction
IUpdateSelectedFiltersAction,
IUpdateTitleSubstringFilterAction
} from "./Interfaces";
import { FilterActionTypes } from "./Types";
import { IFilter } from "../../interfaces/IFilter";
Expand All @@ -9,14 +10,16 @@ export function updateFilters(
tasks: IFilter[],
types: IFilter[],
licenses: IFilter[],
languages: IFilter[]
languages: IFilter[],
titleSubstring: string
): IUpdateFiltersAction {
return {
type: FilterActionTypes.UPDATE_FILTERS,
tasks,
types,
licenses,
languages
languages,
titleSubstring
};
}

Expand All @@ -30,3 +33,12 @@ export function updateSelectedFilters(
selected
};
}

export function updateTitleSubstringFilter(
titleSubstring: string
): IUpdateTitleSubstringFilterAction {
return {
type: FilterActionTypes.UPDATE_TITLE_SUBSTRING_FILTER,
titleSubstring
};
}
9 changes: 8 additions & 1 deletion src/store/filters/Interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,24 @@ import { FilterActionTypes } from "./Types";

export type FiltersActions =
| IUpdateFiltersAction
| IUpdateSelectedFiltersAction;
| IUpdateSelectedFiltersAction
| IUpdateTitleSubstringFilterAction;

export interface IUpdateFiltersAction {
readonly type: FilterActionTypes.UPDATE_FILTERS;
readonly tasks: IFilter[];
readonly types: IFilter[];
readonly languages: IFilter[];
readonly licenses: IFilter[];
readonly titleSubstring: string;
}

export interface IUpdateSelectedFiltersAction {
readonly type: FilterActionTypes.UPDATE_SELECTED_FILTERS;
readonly selected: Set<IFilter>;
}

export interface IUpdateTitleSubstringFilterAction {
readonly type: FilterActionTypes.UPDATE_TITLE_SUBSTRING_FILTER;
readonly titleSubstring: string;
}
9 changes: 8 additions & 1 deletion src/store/filters/Reducers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export function filters(
types: [],
licenses: [],
languages: [],
titleSubstring: "",
selected: new Set()
},
action: FiltersActions
Expand All @@ -19,13 +20,19 @@ export function filters(
tasks: action.tasks,
types: action.types,
licenses: action.licenses,
languages: action.languages
languages: action.languages,
titleSubstring: action.titleSubstring
};
case FilterActionTypes.UPDATE_SELECTED_FILTERS:
return {
...state,
selected: action.selected
};
case FilterActionTypes.UPDATE_TITLE_SUBSTRING_FILTER:
return {
...state,
titleSubstring: action.titleSubstring
};
default:
return state;
}
Expand Down
12 changes: 11 additions & 1 deletion src/store/filters/Sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { ProjectsActionTypes } from "../projects/Types";
import * as projectSelectors from "../projects/Selectors";
import * as actions from "./Actions";

const DEFAULT_FULL_TEXT_TITLE_VALUE = "";

export function calculateTaskFilters(projects: IProjectInfo[]) {
const tasks = [
...new Set(projects.flatMap(p => p.task).filter(x => x))
Expand Down Expand Up @@ -41,7 +43,15 @@ export function* calculateFilters() {
call(calculateLicenseFilters, projects),
call(calculateLanguageFilters, projects)
]);
yield put(actions.updateFilters(tasks, types, licenses, languages));
yield put(
actions.updateFilters(
tasks,
types,
licenses,
languages,
DEFAULT_FULL_TEXT_TITLE_VALUE
)
);
yield put(actions.updateSelectedFilters(new Set()));
}

Expand Down
5 changes: 5 additions & 0 deletions src/store/filters/Selectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,8 @@ export const getSelectedFilters = createSelector(
getFilters,
filters => filters.selected
);

export const getTitleSubstring = createSelector(
getFilters,
filters => filters.titleSubstring
);
1 change: 1 addition & 0 deletions src/store/filters/Types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export enum FilterActionTypes {
UPDATE_FILTERS = "UPDATE_FILTERS",
UPDATE_SELECTED_FILTERS = "UPDATE_SELECTED_FILTERS",
UPDATE_TITLE_SUBSTRING_FILTER = "UPDATE_TITLE_SUBSTRING_FILTER",
PERFORM_SEARCH = "PERFORM_SEARCH"
}
50 changes: 38 additions & 12 deletions src/store/results/Sagas.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { takeEvery, all, put, select } from "redux-saga/effects";
import { takeEvery, all, put, select, debounce } from "redux-saga/effects";
import * as projectSelectors from "../projects/Selectors";
import * as filterSelectors from "../filters/Selectors";
import { IProjectInfo } from "../../interfaces/IProjectInfo";
Expand All @@ -10,17 +10,12 @@ interface IGroupedFilters {
[dimension: string]: IFilter[];
}

export function* applyFilters() {
const [projects, selectedFilters]: [IProjectInfo[], Set<IFilter>] = yield all(
[
select(projectSelectors.getProjects),
select(filterSelectors.getSelectedFilters)
]
);

function applyTagFilters(
projects: IProjectInfo[],
selectedFilters: Set<IFilter>
): IProjectInfo[] {
if (selectedFilters.size < 1) {
yield put(actions.storeResults(projects));
return;
return projects;
}

const dimensions = Object.values(FilterDimension);
Expand All @@ -37,7 +32,7 @@ export function* applyFilters() {
{}
);

const results = projects.filter(project => {
return projects.filter(project => {
let match = false;

for (const dimension of dimensions) {
Expand All @@ -59,10 +54,41 @@ export function* applyFilters() {

return match;
});
}

function applyTitleSearchFilter(
projects: IProjectInfo[],
titleSubstring?: string
): IProjectInfo[] {
if (!titleSubstring) {
return projects;
}

return projects.filter(p => p.name.includes(titleSubstring));
}

export function* applyFilters() {
const [projects, selectedFilters, titleSubstring]: [
IProjectInfo[],
Set<IFilter>,
string
] = yield all([
select(projectSelectors.getProjects),
select(filterSelectors.getSelectedFilters),
select(filterSelectors.getTitleSubstring)
]);

const interimResults = applyTagFilters(projects, selectedFilters);
const results = applyTitleSearchFilter(interimResults, titleSubstring);

yield put(actions.storeResults(results));
}

export function* watchForResultUpdates() {
yield takeEvery(FilterActionTypes.UPDATE_SELECTED_FILTERS, applyFilters);
yield debounce(
500,
FilterActionTypes.UPDATE_TITLE_SUBSTRING_FILTER,
applyFilters
);
}

0 comments on commit 8c967f7

Please sign in to comment.