Commit ced7aafd authored by Magnus Westergaard's avatar Magnus Westergaard
Browse files

DEICH-5621: Add related content based on agents. Refactored DynamicList and friends in the process.

parent c31d394b
import React, { Fragment } from "react";
import "isomorphic-unfetch";
import React from "react";
import PropTypes from "prop-types";
import { connect } from "react-redux";
import autoBind from "auto-bind";
import { withRouter } from "next/router";
import { inlineSearch, inlineResultsSelector } from "../../store/search";
import { toggleFavourites } from "../../store/application";
import { Block, Loader } from "@digibib/deichman-ui";
import { shuffleWithSeed } from "../../utilities/array";
import WorkGrid from "../WorkGrid";
import InspirationGrid from "../InspirationGrid";
import "./styles.css";
import ScrollIndicator from "../Grid/ScrollIndicator";
class DynamicList extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: true
};
autoBind(this);
}
componentDidMount() {
this.search();
}
componentDidUpdate(prevProps) {
const { router } = this.props;
// Only search if something has changed
if (
router.query !== prevProps.router.query || // Topbar filter change
this.props.data.listQuery !== prevProps.data.listQuery // Inline filter change
) {
this.search();
}
}
handleFavourite(recordId) {
const data = {
recordId
};
this.props.toggleFavourites(data);
}
async search() {
const { data = {}, router, randomizeHits } = this.props;
const { listQuery = "https://deichman.no/sok/*" } = data;
// if randomizing, fetch more publications to pick randomly from
const pageSize = randomizeHits ? "50" : "";
this.setState({
isLoading: true
});
await this.props.inlineSearch(listQuery, router.query, pageSize);
this.setState({ isLoading: false });
}
render() {
const {
type,
data = {},
results,
favourites,
randomSeed,
randomizeHits,
children
} = this.props;
const { isLoading } = this.state;
const { title = "Tittel" } = data;
const { hits = [], totalHits = 0 } = results;
const noHits = !isLoading && hits.length === 0;
// Randomize hits if set
const preparedHits = randomizeHits
? shuffleWithSeed(hits, randomSeed)
: hits;
return (
<Fragment>
<h2 className="dynamic-list__title">
{title}
<ScrollIndicator
svgClass={`dynamic-list__scroll-indicator${
type === "highlight"
? " dynamic-list__scroll-indicator--highlight"
: ""
}`}
/>
</h2>
{children}
import "./styles.css";
{/* Show loader, and avoid content jump */}
{isLoading && !hits.length && (
<Block top={6} responsive>
<div className="dynamic-list__spacer">
<Loader />
</div>
</Block>
)}
const DEFAULT_SIZES = {
standard: 6,
highlight: 4
};
{noHits ? (
<Block top={6} responsive>
<p>Ingen resultater...</p>
</Block>
) : (
<Block top={6} responsive>
{type === "standard" && (
<WorkGrid
items={preparedHits.slice(0, 6)}
totalCount={totalHits}
favourites={favourites}
onFavourite={this.handleFavourite}
isHorizontal
hideFlagsList={["recommended", "liked"]}
/>
)}
{type === "highlight" && (
<InspirationGrid
items={preparedHits.slice(0, 4)}
totalCount={totalHits}
favourites={favourites}
onFavourite={this.handleFavourite}
/>
)}
</Block>
)}
</Fragment>
);
}
}
const DynamicList = ({
type,
title,
items,
totalCount,
noHits,
isLoading,
favourites,
onFavourite,
size,
children
}) => {
const numberOfItems = size > 0 ? size : DEFAULT_SIZES[type];
const itemsToDisplay = items.slice(0, numberOfItems);
return (
<>
<h2 className="dynamic-list__title">
{title}
<ScrollIndicator
svgClass={`dynamic-list__scroll-indicator${
type === "highlight"
? " dynamic-list__scroll-indicator--highlight"
: ""
}`}
/>
</h2>
{children}
{/* Show loader, and avoid content jump */}
{isLoading && !items.length && (
<Block top={6} responsive>
<div className="dynamic-list__spacer">
<Loader />
</div>
</Block>
)}
{noHits ? (
<Block top={6} responsive>
<p>Ingen resultater...</p>
</Block>
) : (
<Block top={6} responsive>
{type === "standard" && (
<WorkGrid
items={itemsToDisplay}
totalCount={totalCount}
favourites={favourites}
onFavourite={onFavourite}
isHorizontal
hideFlagsList={["recommended", "liked"]}
/>
)}
{type === "highlight" && (
<InspirationGrid
items={itemsToDisplay}
totalCount={totalCount}
favourites={favourites}
onFavourite={onFavourite}
/>
)}
</Block>
)}
</>
);
};
DynamicList.defaultProps = {
type: "standard",
results: {},
items: [],
favourites: [],
randomSeed: "1",
randomizeHits: false,
size: undefined,
children: []
};
DynamicList.propTypes = {
type: PropTypes.string,
data: PropTypes.object.isRequired,
router: PropTypes.object.isRequired,
inlineSearch: PropTypes.func.isRequired,
results: PropTypes.object,
title: PropTypes.string.isRequired,
isLoading: PropTypes.bool.isRequired,
items: PropTypes.array,
totalCount: PropTypes.number.isRequired,
noHits: PropTypes.bool.isRequired,
favourites: PropTypes.array,
toggleFavourites: PropTypes.func.isRequired,
randomSeed: PropTypes.number,
randomizeHits: PropTypes.bool,
onFavourite: PropTypes.func.isRequired,
size: PropTypes.number,
children: PropTypes.node
};
function mapDispatchToProps(dispatch) {
return {
inlineSearch: (listQuery, query, pageSize) =>
dispatch(inlineSearch(listQuery, false, query, pageSize)),
toggleFavourites: data => dispatch(toggleFavourites(data))
};
}
function mapStateToProps(state, ownProps) {
const { biblioNumbers } = state.favourites;
const { seed } = state.random;
const { listQuery } = ownProps.data;
return {
favourites: biblioNumbers,
results: inlineResultsSelector(state, listQuery),
randomSeed: seed
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(withRouter(DynamicList));
export default DynamicList;
import React from "react";
import PropTypes from "prop-types";
import Link from "next/link";
import DynamicList from "../DynamicList";
import InlineSearch from "../InlineSearch";
import { fullName } from "../../utilities/name";
import { pageLinkFromUri } from "../../utilities/pageLink";
import { translations } from "../../constants/translations";
import {
ACTOR,
DIRECTOR,
FEATURING,
PRODUCTION_COMPANY
} from "../../constants/roles";
import { Block, Flex, Icon } from "@digibib/deichman-ui";
const roleSearchField = {
mainEntry: "work.mainEntryInfo.id", // pseudo-role
[ACTOR]: "work.agentsInfo.actors.id",
[DIRECTOR]: "work.agentsInfo.directors.id",
[FEATURING]: "work.agentsInfo.featuring.id",
[PRODUCTION_COMPANY]: "work.agentsInfo.productionCompanies.id"
};
const DynamicListFromAgent = ({
agent,
agentRole,
mediaType,
randomizeHits,
size
}) => {
const agentLabel = fullName(agent);
const agentUri = agent.id;
const pLink = pageLinkFromUri(agentUri, agentLabel);
const agentId = agentUri.substring(agentUri.lastIndexOf("/") + 1);
const agentQuery = `${roleSearchField[agentRole]}:${agentId}`;
const mediaTypeQuery = mediaType
? `?mediatype=mediaType_${translations[mediaType]}`
: "";
return (
<InlineSearch
query={`${agentQuery}${mediaTypeQuery}`}
inheritRouterQuery={false}
randomizeHits={randomizeHits}
>
{({ data, isLoading, favourites, onFavourite }) => {
const displayLink = !isLoading && data.hits.length > size;
return (
<>
<DynamicList
type="standard"
size={size}
title={`Mer fra ${agentLabel}`}
isLoading={isLoading}
items={data.hits}
noHits={data.noHits}
totalCount={data.totalHits}
favourites={favourites}
onFavourite={onFavourite}
/>
{displayLink && (
<Flex align="center">
<Icon type="arrow-large-right" size="20" />
<Block left={2}>
<Link href={pLink.href} as={pLink.as}>
<a>Se alt fra {agentLabel}</a>
</Link>
</Block>
</Flex>
)}
</>
);
}}
</InlineSearch>
);
};
DynamicListFromAgent.defaultProps = {
mediaType: undefined,
randomizeHits: false,
size: 6
};
DynamicListFromAgent.propTypes = {
agent: PropTypes.object.isRequired,
agentRole: PropTypes.string.isRequired,
mediaType: PropTypes.string,
randomizeHits: PropTypes.bool,
size: PropTypes.number
};
export default DynamicListFromAgent;
import DynamicListFromAgent from "./DynamicListFromAgent";
export default DynamicListFromAgent;
import React from "react";
import PropTypes from "prop-types";
import DynamicList from "../DynamicList";
import InlineSearch from "../InlineSearch";
const DynamicListFromQuery = ({
type,
query,
title,
inheritRouterQuery,
randomizeHits,
children
}) => (
<InlineSearch
query={query}
inheritRouterQuery={inheritRouterQuery}
randomizeHits={randomizeHits}
>
{({ data, isLoading, favourites, onFavourite }) => (
<DynamicList
type={type}
title={title}
isLoading={isLoading}
items={data.hits}
noHits={data.noHits}
totalCount={data.totalHits}
favourites={favourites}
onFavourite={onFavourite}
>
{children}
</DynamicList>
)}
</InlineSearch>
);
DynamicListFromQuery.defaultProps = {
type: "standard",
inheritRouterQuery: true,
randomizeHits: false,
children: []
};
DynamicListFromQuery.propTypes = {
type: PropTypes.string,
inheritRouterQuery: PropTypes.bool,
query: PropTypes.string.isRequired,
randomizeHits: PropTypes.bool,
title: PropTypes.string.isRequired,
children: PropTypes.node
};
export default DynamicListFromQuery;
import DynamicListFromQuery from "./DynamicListFromQuery";
export default DynamicListFromQuery;
import React, { useState, useEffect, Fragment } from "react";
import React, { useState, useEffect } from "react";
import PropTypes from "prop-types";
import { Block, ButtonGroup, Select } from "@digibib/deichman-ui";
import DynamicList from "../DynamicList";
import DynamicListFromQuery from "../DynamicListFromQuery";
import ArrowLink from "../ArrowLink";
import "./styles.css";
......@@ -40,14 +40,8 @@ const DynamicListWithInlineFilter = ({
};
return (
<Fragment>
<DynamicList
type={listType}
data={{
listQuery: searchString,
title: listTitle
}}
>
<>
<DynamicListFromQuery type={listType} query={searchString} title={listTitle}>
<div className="dynamic-list-with-inline-filter__button-group">
<Block top={4} bottom={4} responsive>
<ButtonGroup
......@@ -69,7 +63,7 @@ const DynamicListWithInlineFilter = ({
/>
</Block>
</div>
</DynamicList>
</DynamicListFromQuery>
{dynamicLink && (
<ArrowLink
......@@ -78,7 +72,7 @@ const DynamicListWithInlineFilter = ({
text={dynamicLink.text}
/>
)}
</Fragment>
</>
);
};
......
......@@ -22,11 +22,11 @@ class InlineSearch extends React.Component {
}
componentDidUpdate(prevProps) {
const { router, query } = this.props;
const { router, query, inheritRouterQuery } = this.props;
// Only search if something has changed
if (
router.query !== prevProps.router.query || // Topbar filter change
(inheritRouterQuery && router.query !== prevProps.router.query) || // Topbar filter change (router query)
query !== prevProps.query // Inline filter change
) {
this.search();
......@@ -41,13 +41,19 @@ class InlineSearch extends React.Component {
}
async search() {
const { query = "https://deichman.no/sok/*", router } = this.props;
const {
query,
router,
inheritRouterQuery,
randomizeHits,
randomPoolSize
} = this.props;
this.setState({
isLoading: true
});
this.setState({ isLoading: true });
await this.props.inlineSearch(query, router.query);
const extraQueries = inheritRouterQuery ? router.query : {};
const size = randomizeHits ? randomPoolSize : undefined;
await this.props.inlineSearch(query, extraQueries, size);
this.setState({ isLoading: false });
}
......@@ -78,29 +84,31 @@ class InlineSearch extends React.Component {
InlineSearch.defaultProps = {
results: {},
limited: false,
favourites: [],
randomSeed: "1",
randomizeHits: false
randomizeHits: false,
randomPoolSize: 100,
randomSeed: 1,
inheritRouterQuery: true
};
InlineSearch.propTypes = {
query: PropTypes.string.isRequired,
router: PropTypes.object.isRequired,
inlineSearch: PropTypes.func.isRequired,
inheritRouterQuery: PropTypes.bool,
results: PropTypes.object,
limited: PropTypes.bool,
favourites: PropTypes.array,
toggleFavourites: PropTypes.func.isRequired,
randomSeed: PropTypes.number,
randomizeHits: PropTypes.bool,
randomPoolSize: PropTypes.number,
randomSeed: PropTypes.number,
children: PropTypes.any.isRequired
};
function mapDispatchToProps(dispatch) {
return {
inlineSearch: (query, routerQuery) =>
dispatch(inlineSearch(query, false, routerQuery)),
inlineSearch: (query, routerQuery, size) =>
dispatch(inlineSearch(query, false, routerQuery, size)),
toggleFavourites: data => dispatch(toggleFavourites(data))
};
}
......
import React from "react";
import PropTypes from "prop-types";
import DynamicList from "../DynamicList";
import DynamicListFromAgent from "../DynamicListFromAgent";
import DynamicListFromQuery from "../DynamicListFromQuery";
import { translations } from "../../constants/translations";
import { FILM, GAME } from "../../constants/mediaTypes";
import {
ACTOR,
DIRECTOR,
FEATURING,
PRODUCTION_COMPANY
} from "../../constants/roles";
import { Block } from "@digibib/deichman-ui";
// ordered lists of fallback roles per mediatype (used if mainEntry does not exist)
const fallbackRoles = {
[FILM]: [DIRECTOR, FEATURING, PRODUCTION_COMPANY, ACTOR]
};
// roles to show content for per mediatype (in addition to mainEntry)
const additionalRoles = {
[GAME]: [PRODUCTION_COMPANY]
};
const findFallbackContributor = pub => {