Skip to content

Commit

Permalink
Merge pull request #296 from performant-software/feature/basira291_se…
Browse files Browse the repository at this point in the history
…arch_history

BASIRA #291 - Session search history
  • Loading branch information
dleadbetter authored Dec 24, 2024
2 parents 10498ee + 2d2aae6 commit 8bfaa27
Show file tree
Hide file tree
Showing 13 changed files with 285 additions and 35 deletions.
6 changes: 6 additions & 0 deletions client/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import PhysicalComponent from './pages/PhysicalComponent';
import Place from './pages/Place';
import Search from './pages/Search';
import SearchContextProvider from './components/SearchContextProvider';
import SearchHistory from './pages/SearchHistory';
import VisualContext from './pages/VisualContext';
import './App.css';

Expand All @@ -25,6 +26,11 @@ const App = () => (
component={Search}
exact
/>
<Route
path='/search_history'
component={SearchHistory}
exact
/>
<Route
path='/artworks/:id'
component={Artwork}
Expand Down
3 changes: 3 additions & 0 deletions client/src/components/NavBar.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.nav-bar.ui.menu {
border-radius: 0;
}
28 changes: 28 additions & 0 deletions client/src/components/NavBar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// @flow

import React from 'react';
import { Header, Menu } from 'semantic-ui-react';
import LinksMenu from './LinksMenu';
import './NavBar.css';

const NavBar = () => {
return (
<Menu
className='nav-bar'
inverted
size='large'
>
<Menu.Item>
<Header
content='BASIRA'
inverted
/>
</Menu.Item>
<LinksMenu
position='right'
/>
</Menu>
);
};

export default NavBar;
4 changes: 3 additions & 1 deletion client/src/components/RecordPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,9 @@ const RecordPage = (props: Props) => {
<Menu.Item
position='right'
>
<SearchLink />
<SearchLink
inverted
/>
</Menu.Item>
</Menu>
</Ref>
Expand Down
6 changes: 1 addition & 5 deletions client/src/components/SearchFacets.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,7 @@
import { FacetSlider, FacetToggle, LinkButton } from '@performant-software/semantic-components';
import React, { useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import {
useRange,
useRefinementList,
useToggleRefinement
} from 'react-instantsearch-hooks-web';
import { useRange, useRefinementList, useToggleRefinement } from 'react-instantsearch-hooks-web';
import { Header, Segment } from 'semantic-ui-react';
import _ from 'underscore';
import SearchFacet from './SearchFacet';
Expand Down
45 changes: 45 additions & 0 deletions client/src/components/SearchHistory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// @flow

import { useCallback, useEffect, useMemo } from 'react';
import { useCurrentRefinements, useSearchBox } from 'react-instantsearch-hooks-web';
import { useLocation } from 'react-router-dom';
import _ from 'underscore';
import SearchHistoryService from '../services/SearchHistory';
import useFacetLabels from '../hooks/FacetLabels';

const SearchHistory = () => {
const currentRefinements = useCurrentRefinements();
const { query } = useSearchBox();

const location = useLocation();
const { getLabel } = useFacetLabels();

const { search: url } = location;

/**
* Retrieves the label and facet value from the current refinements.
*
* @type {function(*): *}
*/
const transformItem = useCallback((item) => (
_.map(item.refinements, (r) => `${getLabel(r.attribute)}: ${r.label}`)
), [getLabel]);

/**
* Transforms the list of items into an array of label/values.
*/
const items = useMemo(() => _.flatten(_.map(currentRefinements.items, transformItem)), [currentRefinements]);

/**
* Saves the search whenever the URL is changed.
*/
useEffect(() => {
if (!(_.isEmpty(query) && _.isEmpty(items))) {
SearchHistoryService.saveSearch({ url, query, items, created: Date.now() });
}
}, [location.search]);

return null;
};

export default SearchHistory;
8 changes: 6 additions & 2 deletions client/src/components/SearchLink.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ import { Link } from 'react-router-dom';
import { Button } from 'semantic-ui-react';
import SearchContext from '../context/Search';

const SearchLink = () => {
type Props = {
inverted?: boolean
};

const SearchLink = (props: Props) => {
const { search } = useContext(SearchContext);
const { t } = useTranslation();

Expand All @@ -16,7 +20,7 @@ const SearchLink = () => {
basic
content={t('SearchLink.buttons.back')}
icon='arrow alternate circle left outline'
inverted
inverted={props.inverted}
to={`/${search || ''}`}
/>
);
Expand Down
16 changes: 16 additions & 0 deletions client/src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,9 @@
"subjectCulturalContext": "Subject Cultural Context"
}
},
"labels": {
"viewRecent": "View Recent"
},
"sort": {
"artworkDate": {
"label": "Artwork Date",
Expand All @@ -558,6 +561,19 @@
"expand": "Expand All"
}
},
"SearchHistory": {
"buttons": {
"clear": "Clear"
},
"columns": {
"created": "Created",
"facets": "Facets",
"query": "Query"
},
"messages": {
"copy": "Copied URL to clipboard"
}
},
"SearchLink": {
"buttons": {
"back": "Back to Search"
Expand Down
6 changes: 1 addition & 5 deletions client/src/pages/Search.css
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
.search > .ui.menu {
border-radius: 0;
}

.search .item-collection {
padding-bottom: 1em;
}
Expand All @@ -21,6 +17,6 @@

.search .stats-container {
display: flex;
justify-content: flex-end;
justify-content: space-between;
margin-top: 0.5em;
}
33 changes: 11 additions & 22 deletions client/src/pages/Search.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,17 @@ import {
useStats
} from 'react-instantsearch-hooks-web';
import { Link, useHistory, useLocation } from 'react-router-dom';
import {
Container,
Grid,
Header,
Menu
} from 'semantic-ui-react';
import { Container, Grid } from 'semantic-ui-react';
import _ from 'underscore';
import Banner from '../components/Banner';
import LinksMenu from '../components/LinksMenu';
import NavBar from '../components/NavBar';
import PageFooter from '../components/PageFooter';
import SearchContext from '../context/Search';
import SearchFacets from '../components/SearchFacets';
import SearchResultDescription from '../components/SearchResultDescription';
import SearchThumbnail from '../components/SearchThumbnail';
import searchClient from '../config/Search';
import SearchHistory from '../components/SearchHistory';
import useFacetLabels from '../hooks/FacetLabels';
import './Search.css';

Expand All @@ -58,7 +54,7 @@ const Search = () => {
*/
const transformCurrentFacets = useCallback((items) => (
_.map(items, (item) => ({ ...item, label: getLabel(item.label) }))
), []);
), [getLabel]);

/**
* Set the search in the context when the location.search attribute changes.
Expand All @@ -70,20 +66,7 @@ const Search = () => {
className='search'
fluid
>
<Menu
inverted
size='large'
>
<Menu.Item>
<Header
content='BASIRA'
inverted
/>
</Menu.Item>
<LinksMenu
position='right'
/>
</Menu>
<NavBar />
<Banner />
<InstantSearch
indexName={process.env.REACT_APP_TYPESENSE_COLLECTION_NAME}
Expand All @@ -97,6 +80,7 @@ const Search = () => {
}}
searchClient={searchClient}
>
<SearchHistory />
<Container>
<Grid
relaxed
Expand All @@ -113,6 +97,11 @@ const Search = () => {
<div
className='stats-container'
>
<Link
to='search_history'
>
{ t('Search.labels.viewRecent') }
</Link>
<SearchStats
useStats={useStats}
/>
Expand Down
3 changes: 3 additions & 0 deletions client/src/pages/SearchHistory.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.search-history > .ui.container > .search-list > .table > tbody > tr > td:first-child {
white-space: nowrap;
}
122 changes: 122 additions & 0 deletions client/src/pages/SearchHistory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// @flow

import { CurrentFacetLabels, EmbeddedList } from '@performant-software/semantic-components';
import { useTimer } from '@performant-software/shared-components';
import React, { useCallback, useState } from 'react';
import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Button, Container, Popup } from 'semantic-ui-react';
import _ from 'underscore';
import { getDateTimeView } from '../utils/Date';
import NavBar from '../components/NavBar';
import SearchHistoryService from '../services/SearchHistory';
import SearchLink from '../components/SearchLink';
import './SearchHistory.css';

const TOOLTIP_DELAY_MS = 1500;

const SearchHistory = () => {
const [items, setItems] = useState(_.sortBy(SearchHistoryService.getHistory(), 'created').reverse());
const [copyItem, setCopyItem] = useState();

const { t } = useTranslation();
const { clearTimer, setTimer } = useTimer();

/**
* Clears the search history.
*
* @type {(function(): void)|*}
*/
const onClear = useCallback(() => {
// Clear the items on the state
setItems([]);

// Clear the items in session storage
SearchHistoryService.clearHistory();
}, []);

/**
* Copies the URL for the passed item and sets the selected item on the state.
*
* @type {(function(*): void)|*}
*/
const onCopy = useCallback((item) => {
const url = `${window.location.origin}/${item.url}`;
navigator.clipboard.writeText(url);

setCopyItem(item);

clearTimer();
setTimer(() => setCopyItem(null), TOOLTIP_DELAY_MS);
}, []);

return (
<Container
className='search-history'
fluid
>
<NavBar />
<Container>
<EmbeddedList
className='search-list'
actions={[{
as: Link,
asProps: (item) => ({
to: `/${item.url}`
}),
icon: 'arrow alternate circle right outline',
name: 'link'
}, {
name: 'copy',
render: (item) => (
<Popup
content={t('SearchHistory.messages.copy')}
hoverable
inverted
on='click'
onOpen={() => onCopy(item)}
open={copyItem === item}
trigger={(
<Button
basic
icon='copy outline'
size='small'
/>
)}
/>
)
}]}
buttons={[{
render: () => <SearchLink />
}, {
color: 'red',
content: t('SearchHistory.buttons.clear'),
icon: 'trash',
onClick: onClear
}]}
columns={[{
name: 'created',
label: t('SearchHistory.columns.created'),
resolve: (item) => getDateTimeView(item.created)
}, {
name: 'query',
label: t('SearchHistory.columns.query')
}, {
name: 'items',
label: t('SearchHistory.columns.facets'),
render: (item) => (
<CurrentFacetLabels
items={_.map(item.items, (label) => ({ label }))}
/>
)
}]}
defaultSort='created'
defaultSortDirection='descending'
items={items}
/>
</Container>
</Container>
);
};

export default SearchHistory;
Loading

0 comments on commit 8bfaa27

Please sign in to comment.