@imerss/inat-components 1.1.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/PUBLISH.md +13 -0
  2. package/README.md +20 -0
  3. package/dist/index.d.ts +118 -73
  4. package/dist/index.js +322 -389
  5. package/dist/standalone.min.js +3 -0
  6. package/package.json +17 -5
  7. package/public/standalone.html +55 -0
  8. package/rollup.config.js +15 -4
  9. package/src/__shared/api/common-taxa.ts +3 -3
  10. package/src/__shared/api/favourites.ts +6 -11
  11. package/src/__shared/api/recent-observations.ts +3 -3
  12. package/src/__shared/api/summary.ts +1 -0
  13. package/src/__shared/constants.ts +3 -1
  14. package/src/__shared/index.ts +1 -1
  15. package/src/__shared/typings.ts +58 -44
  16. package/src/__shared/utils/configUtils.ts +10 -6
  17. package/src/__shared/utils/dateUtils.ts +1 -1
  18. package/src/components/common-taxa/common-taxa.tsx +34 -60
  19. package/src/components/error/error.tsx +1 -1
  20. package/src/components/favourites/favourites.tsx +35 -60
  21. package/src/components/hooks/hooks.tsx +20 -10
  22. package/src/components/loader/loader.tsx +1 -1
  23. package/src/components/observation/observation.tsx +5 -4
  24. package/src/components/recent-observations/recent-observations.tsx +43 -60
  25. package/src/components/shared/css/general.module.scss +5 -1
  26. package/src/components/summary/ObserverList.tsx +21 -15
  27. package/src/components/summary/summary.tsx +59 -51
  28. package/src/components/tabs/tabs.tsx +48 -27
  29. package/src/components/taxon-panel/taxon-panel.tsx +83 -77
  30. package/src/components/year-dropdown/year-dropdown.tsx +11 -7
  31. package/src/hooks/useLoadSourceData.ts +81 -0
  32. package/src/index.ts +1 -1
  33. package/src/types/index.d.ts +17 -0
  34. package/src/types/standalone.d.ts +6 -0
@@ -1,100 +1,106 @@
1
- import React, {useState} from "react";
2
- import {getSourceFile, Tab, DataSource, Feature} from "../../__shared";
3
- import {RecentObservations, RecentObservationsProps} from '../recent-observations/recent-observations';
4
- import {CommonTaxa} from '../common-taxa/common-taxa';
5
- import {Favourites} from "../favourites/favourites"
6
- import {Summary} from "../summary/summary";
1
+ import React, { useMemo, useState } from "react";
2
+ import { C, getSourceFile, DataSource, DataSourceEnum, Feature, GeneralClasses, TaxonPanelFeatures } from "../../__shared";
3
+ import { RecentObservations } from '../recent-observations/recent-observations';
4
+ import { CommonTaxa } from '../common-taxa/common-taxa';
5
+ import { Favourites } from "../favourites/favourites"
6
+ import { Summary } from "../summary/summary";
7
7
  import Years from "../year-dropdown/year-dropdown";
8
8
  import Tabs from "../tabs/tabs";
9
- import {useFeatureTitles} from "../hooks/hooks";
10
- import styles from '../shared/css/general.module.scss';
9
+ import { convertFeaturesToObj, useFeatureTitles } from "../hooks/hooks";
10
+ import styles from "../shared/css/general.module.scss";
11
11
 
12
- const TaxonPanel = ({ taxonId, placeId, dataSource, config, baseURL }: any) => {
13
- const [tab, setTab] = useState(Tab.recent);
12
+ export type TaxonPanelProps = {
13
+ taxon: {
14
+ id: number;
15
+ str: string;
16
+ };
17
+ place: {
18
+ id: number;
19
+ str: string;
20
+ }
21
+ dataSourceBaseUrl: string;
22
+ dataSource: DataSource;
23
+ features: TaxonPanelFeatures;
24
+ generalClasses: GeneralClasses;
25
+ itemWidth?: number;
26
+ }
27
+
28
+ const TaxonPanel = ({ taxon, place, features, dataSource, dataSourceBaseUrl, itemWidth, generalClasses }: TaxonPanelProps) => {
29
+ const [tab, setTab] = useState(features[0].feature);
14
30
  const [year, setYear] = useState<string>("all");
15
- const titles = useFeatureTitles(config.features);
16
- const [taxonInfo, setTaxonInfo] = useState(() => config.taxa.find((i) => i.taxonId === taxonId));
17
- const [placeInfo, setPlaceInfo] = useState(() => config.places.find((i) => i.placeId === placeId));
31
+ const featuresObj = useMemo(() => {
32
+ return convertFeaturesToObj(features);
33
+ }, [features]);
34
+ const titles = useFeatureTitles(featuresObj);
35
+
36
+ const getProps = (feature: Feature): any => {
37
+ const props: any = {
38
+ source: dataSource,
39
+ itemWidth,
40
+ tabDesc: featuresObj[feature]?.desc,
41
+ generalClasses,
42
+ className: featuresObj[feature]?.className
43
+ };
44
+ if (dataSource === DataSourceEnum.url) {
45
+ props.dataUrl = dataSourceBaseUrl + '/' + getSourceFile(feature, taxon, place, year);
46
+ } else {
47
+ props.placeId = place.id;
48
+ props.taxonId = taxon.id;
49
+ }
50
+
51
+ if (feature !== Feature.stats) {
52
+ props.numResults = featuresObj[feature]?.numResults;
53
+ }
54
+
55
+ if (feature !== Feature.recentObservations) {
56
+ props.year = year;
57
+ }
58
+
59
+ return props;
60
+ };
18
61
 
19
62
  const getCurrentTab = () => {
20
63
  switch (tab) {
21
- case Tab.recent: {
22
- const props: Partial<RecentObservationsProps> = {
23
- source: dataSource
24
- };
25
- if (dataSource === DataSource.url) {
26
- props.dataUrl = baseURL + '/' + getSourceFile(Feature.recentObservations, taxonInfo, placeInfo);
27
- } else {
28
- props.placeId = placeId;
29
- props.taxonId = taxonId;
30
- }
31
- return (
32
- <RecentObservations {...props} />
33
- );
34
- }
64
+ case Feature.recentObservations:
65
+ return <RecentObservations {...getProps(Feature.recentObservations)} />;
66
+ case Feature.commonTaxa:
67
+ return <CommonTaxa {...getProps(Feature.commonTaxa)} />;
68
+ case Feature.favourites:
69
+ return <Favourites {...getProps(Feature.favourites)} />;
35
70
 
36
- case Tab.commonTaxa: {
37
- const props: any = {
38
- source: dataSource,
39
- year
40
- };
41
- if (dataSource === DataSource.url) {
42
- props.dataUrl = baseURL + '/' + getSourceFile(Feature.commonTaxa, taxonInfo, placeInfo, year);
43
- } else {
44
- props.placeId = placeId;
45
- props.taxonId = taxonId;
46
- }
71
+ case Feature.stats:
47
72
  return (
48
- <CommonTaxa {...props} />
73
+ <Summary
74
+ {...getProps(Feature.stats)}
75
+ observersListClass={featuresObj[Feature.stats]?.observersListClass}
76
+ statsCountSummaryClass={featuresObj[Feature.stats]?.statsCountSummaryClass}
77
+ />
49
78
  );
50
- }
51
-
52
- case Tab.favourites: {
53
- const props: any = {
54
- source: dataSource,
55
- year
56
- };
57
- if (dataSource === DataSource.url) {
58
- props.dataUrl = baseURL + '/' + getSourceFile(Feature.favourites, taxonInfo, placeInfo, year);
59
- } else {
60
- props.placeId = placeId;
61
- props.taxonId = taxonId;
62
- }
63
- return (
64
- <Favourites {...props} />
65
- );
66
- }
67
-
68
- case Tab.stats: {
69
- const props: any = {
70
- source: dataSource,
71
- year
72
- };
73
- if (dataSource === DataSource.url) {
74
- props.dataUrl = baseURL + '/' + getSourceFile(Feature.stats, taxonInfo, placeInfo, year);
75
- } else {
76
- props.placeId = placeId;
77
- props.taxonId = taxonId;
78
- }
79
- return <Summary {...props} />;
80
- }
81
79
  }
82
80
  };
83
81
 
82
+ const tabsClass = generalClasses?.tabsElement || '';
83
+ const yearDropdownClass = generalClasses?.yearsDropdown || '';
84
+
84
85
  return (
85
86
  <div className={styles.page}>
86
- <Tabs
87
+ {features.length > 1 && <Tabs
87
88
  selectedTab={tab}
88
89
  onChangeTab={setTab}
89
- features={config.features}
90
- />
90
+ features={features}
91
+ className={tabsClass}
92
+ />}
91
93
  <div>
92
- {tab !== Tab.recent && (
93
- <div style={{ float: "right" }}>
94
- <Years value={year} onChange={setYear} />
94
+ {tab !== Feature.recentObservations && (
95
+ <div style={{ float: "right" }} className={yearDropdownClass}>
96
+ <Years
97
+ value={year}
98
+ numYears={featuresObj[tab]?.numYears || C.DEFAULT_NUM_YEARS}
99
+ onChange={setYear}
100
+ />
95
101
  </div>
96
102
  )}
97
- <h1>{titles[tab]}</h1>
103
+ <h1 className={generalClasses?.pageHeadings}>{titles[tab]}</h1>
98
104
  </div>
99
105
  {getCurrentTab()}
100
106
  </div>
@@ -1,27 +1,31 @@
1
- import React, {useState} from "react";
2
- import {getCurrentYear} from "../../__shared";
1
+ import React, { useEffect, useState } from "react";
2
+ import { getCurrentYear } from "../../__shared";
3
3
  import styles from "./year-dropdown.module.scss";
4
4
 
5
5
  interface YearsProps {
6
6
  value: string;
7
7
  onChange: (year: string) => void;
8
+ numYears: number;
8
9
  className?: string;
9
10
  }
10
11
 
11
- const Years = ({ value, onChange, className }: YearsProps) => {
12
- const [years] = useState(() => {
12
+ const Years = ({ value, onChange, numYears, className }: YearsProps) => {
13
+ const [years, setYears] = useState([]);
14
+
15
+ useEffect(() => {
13
16
  const currentYear = getCurrentYear();
14
17
  const years: any = [];
15
- for (let i = currentYear; i>=currentYear-10; i--) {
18
+ for (let i = currentYear; i>currentYear-numYears; i--) {
16
19
  years.push(i);
17
20
  }
18
- return years;
19
- });
21
+ setYears(years);
22
+ }, [numYears]);
20
23
 
21
24
  let classes = styles.dropdown;
22
25
  if (className) {
23
26
  classes += ` ${className}`;
24
27
  }
28
+
25
29
  return (
26
30
  <select className={classes} onChange={(e) => onChange(e.target.value)} defaultValue={value}>
27
31
  <option value="all">All years</option>
@@ -0,0 +1,81 @@
1
+ import { useEffect, useState } from "react";
2
+ import { DataSource, DataSourceEnum } from "../__shared";
3
+
4
+ type UseLoadSourceData = {
5
+ action: any;
6
+ taxonId?: number;
7
+ placeId?: number;
8
+ numResults?: number;
9
+ source?: DataSource;
10
+ dataUrl?: string;
11
+ year?: number | string,
12
+ isSummaryData?: boolean;
13
+ };
14
+
15
+ /**
16
+ * This hook handles loading the iNat data from whatever source has been specified (DataSource):
17
+ * nothing needs to be done.
18
+ * - "autoLoad": the user is developing locally and wants to just ping iNat directly for the source
19
+ * - "url": the user has generated the data sources at a URL somewhere via @imerss/inat-component-utils
20
+ */
21
+ export const useLoadSourceData = ({
22
+ taxonId,
23
+ placeId,
24
+ numResults,
25
+ source,
26
+ dataUrl,
27
+ year,
28
+ action,
29
+ isSummaryData = false
30
+ }: UseLoadSourceData) => {
31
+ const [loading, setLoading] = useState(false);
32
+ const [results, setResults] = useState<any>([]);
33
+
34
+ useEffect(() => {
35
+ if (source === DataSourceEnum.autoLoad) {
36
+ if (!taxonId) {
37
+ console.error("Please supply a `taxonId` prop for the `autoLoad` source prop option.");
38
+ return;
39
+ }
40
+ if (!placeId) {
41
+ console.error("Please supply a `placeId` prop for the `autoLoad` source prop option.");
42
+ return;
43
+ }
44
+
45
+ (async () => {
46
+ setLoading(true);
47
+
48
+ // note that `year` isn't used for recentObservations, but we pass it anyway to get more reusability out
49
+ // of this hook
50
+ const obs = await action({ taxonId, placeId, numResults, year });
51
+ setResults(obs.results);
52
+ setLoading(false);
53
+ })();
54
+ }
55
+
56
+ if (source === DataSourceEnum.url) {
57
+ if (!dataUrl) {
58
+ console.error("Please supply a `dataUrl` prop for the `url` source prop option.");
59
+ return;
60
+ }
61
+
62
+ (async () => {
63
+ setLoading(true);
64
+ const obs = await fetch(dataUrl);
65
+ const json = await obs.json();
66
+
67
+ // hack workaround. Better solution would have been to standardize the JSON structure at the top-level
68
+ if (isSummaryData) {
69
+ setResults(json);
70
+ } else {
71
+ setResults(json.results);
72
+ }
73
+ setLoading(false);
74
+ })();
75
+ }
76
+ }, [source, dataUrl, taxonId, placeId, year, numResults, isSummaryData]);
77
+
78
+ return {
79
+ loading, results
80
+ };
81
+ }
package/src/index.ts CHANGED
@@ -12,4 +12,4 @@ export * from "./components/summary/summary";
12
12
  // all typings
13
13
  export * from "./__shared";
14
14
 
15
- export default TaxonPanel;
15
+ export default TaxonPanel;
@@ -0,0 +1,17 @@
1
+ import { TaxonPanelProps } from "../components/taxon-panel/taxon-panel";
2
+
3
+ export {};
4
+ declare global {
5
+ interface Window {
6
+ inatComponents: {
7
+ initTaxonPanel: (domId: string, taxonPanelProps: TaxonPanelProps) => void;
8
+ }
9
+ }
10
+ }
11
+
12
+ window.initStandalone = window.initStandalone || {};
13
+
14
+ export enum DataSource {
15
+ autoLoad = "autoLoad",
16
+ url = "url"
17
+ }
@@ -0,0 +1,6 @@
1
+ interface InitStandaloneProps {
2
+ taxonId: number;
3
+ placeId: number;
4
+ }
5
+
6
+ export type InitStandalone = (id: string, props: InitStandaloneProps) => void;