Advertisement
haddy315

concepts/pages/viewConceptsPage.tsx

May 7th, 2021
50
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 13.50 KB | None | 0 0
  1. import React, { useEffect, useState } from "react";
  2. import { includes } from "lodash";
  3. import { createStyles, Grid, makeStyles, Theme } from "@material-ui/core";
  4. import { ConceptsTable, AddConceptsIcon } from "../components";
  5. import { connect } from "react-redux";
  6. import {
  7. removeConceptsFromDictionaryLoadingSelector,
  8. retrieveConceptsAction,
  9. viewConceptsLoadingSelector,
  10. viewConceptsErrorsSelector
  11. } from "../redux";
  12. import { AppState } from "../../../redux";
  13. import { APIConcept, OptionalQueryParams as QueryParams } from "../types";
  14. import { useHistory, useLocation, useParams } from "react-router";
  15. import { useQueryParams } from "../../../utils";
  16. import qs from "qs";
  17. import { ProgressOverlay } from "../../../utils/components";
  18. import FilterOptions from "../components/FilterOptions";
  19. import {
  20. APIOrg,
  21. APIProfile,
  22. canModifyContainer,
  23. profileSelector
  24. } from "../../authentication";
  25. import { orgsSelector } from "../../authentication/redux/reducer";
  26. import {
  27. DICTIONARY_CONTAINER,
  28. FILTER_SOURCE_IDS,
  29. SOURCE_CONTAINER,
  30. SOURCE_VERSION_CONTAINER
  31. } from "../constants";
  32. import {
  33. dictionarySelector,
  34. recursivelyAddConceptsToDictionaryAction,
  35. removeReferencesFromDictionaryAction,
  36. makeRetrieveDictionaryAction,
  37. retrieveDictionaryLoadingSelector
  38. } from "../../dictionaries/redux";
  39. import { canModifyConcept, getContainerIdFromUrl } from "../utils";
  40. import { APIDictionary } from "../../dictionaries";
  41. import {
  42. sourceSelector,
  43. retrieveSourceLoadingSelector,
  44. retrieveSourceAndDetailsAction,
  45. retrievePublicSourcesAction
  46. } from "../../sources/redux";
  47. import { APISource } from "../../sources";
  48. import ViewConceptsHeader from "../components/ViewConceptsHeader";
  49. import { PUBLIC_SOURCES_ACTION_INDEX } from "../../sources/redux/constants";
  50.  
  51. export interface StateProps {
  52. concepts?: APIConcept[];
  53. modifiedConcepts?: APIConcept[];
  54. dictionary?: APIDictionary;
  55. source?: APISource;
  56. sources?:APISource[];
  57. loading: boolean;
  58. errors?: {};
  59. meta?: { num_found?: number };
  60. profile?: APIProfile;
  61. usersOrgs?: APIOrg[];
  62. }
  63.  
  64. export type ActionProps = {
  65. retrieveConcepts: (
  66. ...args: Parameters<typeof retrieveConceptsAction>
  67. ) => void;
  68. retrieveDictionary: (
  69. ...args: Parameters<ReturnType<typeof makeRetrieveDictionaryAction>>
  70. ) => void;
  71. addConceptsToDictionary: (
  72. ...args: Parameters<typeof recursivelyAddConceptsToDictionaryAction>
  73. ) => void;
  74. removeConceptsFromDictionary: (
  75. ...args: Parameters<typeof removeReferencesFromDictionaryAction>
  76. ) => void;
  77. retrieveSource: (
  78. ...args: Parameters<typeof retrieveSourceAndDetailsAction>
  79. ) => void;
  80. retrieveSources: (
  81. ...args: Parameters<typeof retrievePublicSourcesAction>
  82. ) => void;
  83. };
  84.  
  85. export interface OwnProps {
  86. containerType: string;
  87. viewDictConcepts?: boolean;
  88. }
  89.  
  90. type Props = StateProps & ActionProps & OwnProps;
  91.  
  92. const useStyles = makeStyles((theme: Theme) =>
  93. createStyles({
  94. link: {
  95. textDecoration: "none",
  96. color: "inherit",
  97. width: "100%"
  98. },
  99. largerTooltip: {
  100. fontSize: "larger"
  101. },
  102. content: {
  103. height: "100%"
  104. }
  105. })
  106. );
  107.  
  108. const INITIAL_LIMIT = 10; // todo get limit from settings
  109.  
  110. const ViewConceptsPage: React.FC<Props> = ({
  111. concepts,
  112. modifiedConcepts,
  113. dictionary,
  114. source,
  115. retrieveSources,
  116. sources,
  117. loading,
  118. errors,
  119. retrieveConcepts,
  120. retrieveDictionary,
  121. retrieveSource,
  122. meta = {},
  123. profile,
  124. usersOrgs,
  125. containerType,
  126. viewDictConcepts,
  127. addConceptsToDictionary,
  128. removeConceptsFromDictionary
  129. }) => {
  130. const classes = useStyles();
  131.  
  132. const { replace: goTo } = useHistory(); // replace because we want to keep the back button useful
  133. const { pathname: url } = useLocation();
  134. const containerUrl = url.replace("/concepts", "");
  135. const { ownerType, owner } = useParams<{
  136. ownerType: string;
  137. owner: string;
  138. }>();
  139.  
  140. // only relevant with the collection container
  141. const preferredSource = dictionary?.preferred_source || "Public Sources";
  142. const linkedSource =
  143. containerType === SOURCE_CONTAINER ||
  144. containerType === SOURCE_VERSION_CONTAINER
  145. ? source?.url
  146. : dictionary?.extras?.source;
  147. // end only relevant with the collection container
  148.  
  149. const queryParams: QueryParams = useQueryParams();
  150. const {
  151. page = 1,
  152. sortDirection = "sortAsc",
  153. sortBy = "id",
  154. limit = INITIAL_LIMIT,
  155. q: initialQ = "",
  156. classFilters: initialClassFilters = [],
  157. dataTypeFilters: initialDataTypeFilters = [],
  158. generalFilters: initialGeneralFilters = [],
  159. sourceFilters: initialSourceFilters = [],
  160. addToDictionary: dictionaryToAddTo
  161. } = queryParams;
  162.  
  163. console.log(url, 'URL');
  164. console.log(sources, 'SOURCES');
  165. const sourceUrl = '/sources/';
  166. const sourcesLimit = 0;
  167. useEffect(() => {
  168. retrieveSources(sourceUrl, initialQ, sourcesLimit, page);
  169. }, []);
  170. // This useEffect is to fetch the dictionary while on the concepts page,
  171. // before when one would refresh the page the would lose the dictionary.
  172. useEffect(() => {
  173. if (dictionary === undefined && dictionaryToAddTo) {
  174. retrieveDictionary(dictionaryToAddTo);
  175. }
  176. }, [dictionary, dictionaryToAddTo]); // eslint-disable-line react-hooks/exhaustive-deps
  177.  
  178. const [showOptions, setShowOptions] = useState(true);
  179. // why did he put the filtered state here and not inside the component, you ask?
  180. // consistency my friend, consistency. The key thing here is one can trigger a requery by changing
  181. // the page count/ number and if the state is not up here then, we query with stale options
  182. const [classFilters, setClassFilters] = useState<string[]>(
  183. initialClassFilters
  184. );
  185. const [dataTypeFilters, setInitialDataTypeFilters] = useState<string[]>(
  186. initialDataTypeFilters
  187. );
  188. const [generalFilters, setGeneralFilters] = useState(initialGeneralFilters);
  189. const [sourceFilters, setSourceFilters] = useState<string[]>(
  190. initialSourceFilters
  191. );
  192. const [q, setQ] = useState(initialQ);
  193.  
  194. const gimmeAUrl = (params: QueryParams = {}, conceptsUrl: string = url) => {
  195. const newParams: QueryParams = {
  196. ...queryParams,
  197. ...{
  198. classFilters: classFilters,
  199. dataTypeFilters: dataTypeFilters,
  200. generalFilters: generalFilters,
  201. sourceFilters: sourceFilters,
  202. page: 1,
  203. q
  204. },
  205. ...params
  206. };
  207. return `${conceptsUrl}?${qs.stringify(newParams)}`;
  208. };
  209.  
  210. useEffect(() => {
  211. // we don't make this reactive(only depend on the initial values), because the requirement
  212. // was only trigger queries on user search(enter or apply filters, or change page)
  213. containerType === SOURCE_CONTAINER ||
  214. containerType === SOURCE_VERSION_CONTAINER
  215. ? retrieveSource(containerUrl)
  216. : retrieveDictionary(containerUrl);
  217.  
  218. retrieveConcepts({
  219. conceptsUrl: url,
  220. page: page,
  221. limit: limit,
  222. q: initialQ,
  223. sortDirection: sortDirection,
  224. sortBy: sortBy,
  225. dataTypeFilters: initialDataTypeFilters,
  226. classFilters: initialClassFilters,
  227. sourceFilters: initialSourceFilters,
  228. includeRetired: initialGeneralFilters.includes("Include Retired")
  229. });
  230. // i don't know how the comparison algorithm works, but for these arrays, it fails.
  231. // stringify the arrays to work around that
  232. // eslint-disable-next-line react-hooks/exhaustive-deps
  233. }, [
  234. retrieveConcepts,
  235. url,
  236. page,
  237. limit,
  238. initialQ,
  239. sortDirection,
  240. sortBy,
  241. // eslint-disable-next-line react-hooks/exhaustive-deps
  242. initialDataTypeFilters.toString(),
  243. // eslint-disable-next-line react-hooks/exhaustive-deps
  244. initialClassFilters.toString(),
  245. // eslint-disable-next-line react-hooks/exhaustive-deps
  246. initialSourceFilters.toString(),
  247. // eslint-disable-next-line react-hooks/exhaustive-deps
  248. initialGeneralFilters.toString()
  249. ]);
  250.  
  251. const canModifyDictionary =
  252. containerType === DICTIONARY_CONTAINER &&
  253. canModifyContainer(ownerType, owner, profile, usersOrgs);
  254.  
  255. const canModifySource =
  256. containerType === SOURCE_CONTAINER &&
  257. canModifyContainer(ownerType, owner, profile, usersOrgs) &&
  258. !dictionaryToAddTo;
  259.  
  260. return (
  261. <ViewConceptsHeader
  262. containerType={containerType}
  263. containerUrl={containerUrl}
  264. gimmeAUrl={gimmeAUrl}
  265. addConceptToDictionary={dictionaryToAddTo}
  266. sources={sources}
  267. >
  268. <Grid
  269. container
  270. className={classes.content}
  271. component="div"
  272. justify="space-around"
  273. alignItems="flex-start"
  274. >
  275. <ProgressOverlay
  276. loading={loading}
  277. error={
  278. errors
  279. ? "Could not fetch concepts. Refresh this page to retry"
  280. : undefined
  281. }
  282. >
  283. <Grid
  284. id="viewConceptsPage"
  285. item
  286. xs={showOptions ? 9 : 12}
  287. component="div"
  288. >
  289. <ConceptsTable
  290. concepts={(viewDictConcepts ? concepts : modifiedConcepts) ?? []}
  291. buttons={{
  292. edit: canModifyDictionary || canModifySource, // relevant for DICTIONARY_CONTAINER, condition already includes isDictionary condition
  293. addToDictionary:
  294. containerType === SOURCE_CONTAINER && !!dictionaryToAddTo // relevant for SOURCE_CONTAINER
  295. }}
  296. q={q}
  297. setQ={setQ}
  298. page={page}
  299. sortDirection={sortDirection}
  300. sortBy={sortBy}
  301. limit={Number(limit)}
  302. buildUrl={gimmeAUrl}
  303. goTo={goTo}
  304. count={meta.num_found || concepts?.length || 0}
  305. toggleShowOptions={() => setShowOptions(!showOptions)}
  306. addConceptsToDictionary={(concepts: APIConcept[]) =>
  307. dictionaryToAddTo &&
  308. addConceptsToDictionary(
  309. containerUrl,
  310. dictionaryToAddTo,
  311. concepts
  312. )
  313. }
  314. dictionaryToAddTo={dictionaryToAddTo}
  315. linkedDictionary={containerUrl}
  316. linkedSource={linkedSource}
  317. canModifyConcept={(concept: APIConcept) =>
  318. canModifyConcept(concept.url, profile, usersOrgs)
  319. }
  320. removeConceptsFromDictionary={(conceptVersionUrls: string[]) =>
  321. removeConceptsFromDictionary(containerUrl, conceptVersionUrls)
  322. }
  323. />
  324. </Grid>
  325. {!showOptions ? (
  326. ""
  327. ) : (
  328. <Grid item xs={2} component="div">
  329. <FilterOptions
  330. checkedClasses={classFilters}
  331. setCheckedClasses={setClassFilters}
  332. checkedDataTypes={dataTypeFilters}
  333. setCheckedDataTypes={setInitialDataTypeFilters}
  334. checkedGeneral={generalFilters}
  335. setCheckedGeneral={setGeneralFilters}
  336. checkedSources={sourceFilters}
  337. setCheckedSources={setSourceFilters}
  338. showSources={containerType !== SOURCE_CONTAINER}
  339. // interesting how we generate these, isn't it? yeah well, this is an important feature, so there :)
  340. sourceOptions={
  341. [
  342. getContainerIdFromUrl(linkedSource),
  343. ...FILTER_SOURCE_IDS
  344. ].filter(source => source !== undefined) as string[]
  345. }
  346. url={gimmeAUrl()}
  347. />
  348. </Grid>
  349. )}
  350. </ProgressOverlay>
  351. </Grid>
  352.  
  353. <AddConceptsIcon
  354. canModifyDictionary={canModifyDictionary}
  355. canModifySource={canModifySource}
  356. containerUrl={containerUrl}
  357. gimmeAUrl={gimmeAUrl}
  358. linkedSource={linkedSource}
  359. preferredSource={preferredSource}
  360. />
  361. </ViewConceptsHeader>
  362. );
  363. };
  364.  
  365. const mapStateToProps = (state: AppState) => {
  366. const dictionary = dictionarySelector(state);
  367. const concepts = state.concepts.concepts.items || [];
  368. const dictionaryConcepts = dictionary?.references.map(r => r.expression);
  369. const modifiedConcepts = concepts?.map(c =>
  370. includes(dictionaryConcepts, c.version_url)
  371. ? { ...c, added: true }
  372. : { ...c }
  373. );
  374. return {
  375. profile: profileSelector(state),
  376. usersOrgs: orgsSelector(state),
  377. concepts: state.concepts.concepts
  378. ? state.concepts.concepts.items
  379. : undefined,
  380. modifiedConcepts: modifiedConcepts,
  381. dictionary: dictionarySelector(state),
  382. source: sourceSelector(state),
  383. sources: state.sources.sources[PUBLIC_SOURCES_ACTION_INDEX]?.items,
  384. meta: state.concepts.concepts
  385. ? state.concepts.concepts.responseMeta
  386. : undefined,
  387. loading:
  388. viewConceptsLoadingSelector(state) ||
  389. retrieveDictionaryLoadingSelector(state) ||
  390. removeConceptsFromDictionaryLoadingSelector(state) ||
  391. retrieveSourceLoadingSelector(state),
  392. errors: viewConceptsErrorsSelector(state)
  393. };
  394. };
  395.  
  396. const mapActionsToProps = {
  397. retrieveConcepts: retrieveConceptsAction,
  398. retrieveDictionary: makeRetrieveDictionaryAction(true),
  399. retrieveSource: retrieveSourceAndDetailsAction,
  400. addConceptsToDictionary: recursivelyAddConceptsToDictionaryAction,
  401. removeConceptsFromDictionary: removeReferencesFromDictionaryAction,
  402. retrieveSources: retrievePublicSourcesAction
  403. };
  404.  
  405. export default connect<StateProps, ActionProps, OwnProps, AppState>(
  406. mapStateToProps,
  407. mapActionsToProps
  408. )(ViewConceptsPage);
  409.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement