{"version":3,"sources":["components/api-authorization/AuthorizeService.js","hooks/pressatlas-api/fetchJsonApi.js","hooks/pressatlas-api/useJsonApi.js","hooks/pressatlas-api/useBoundedNews.js","hooks/search.js","hooks/useMapBoundsSearch.js","hooks/useSearchNews.js","hooks/useSelection.js","hooks/useThemedBasemap.js","components/ads/PressAtlasAd.js","components/news/ArticleActions.js","components/social/facebook/FacebookShare.jsx","components/social/linkedin/LinkedinShare.jsx","components/social/twitter/TwitterShare.jsx","components/social/EmailShare.jsx","components/social/Share.jsx","components/mapping/ArticlePopupContent.js","components/mapping/PressAtlasMap.js","components/PoweredBy.js","components/news/NewsCard.js","components/news/ResultsCount.js","components/news/NewsResults.js","components/search/AdvancedSearch.js","components/search/Search.js","components/search/utils.js","components/DateRangeSlider.js","components/SidePane.js","pages/PressAtlas.js"],"names":["AuthorizeService","_callbacks","_nextSubscriptionId","_user","_isAuthenticated","_popUpDisabled","a","this","getUser","user","profile","ensureUserManagerInitialized","userManager","access_token","state","signinSilent","createArguments","silentUser","updateState","success","console","log","Error","signinPopup","popUpUser","message","error","signinRedirect","redirect","url","signinCallback","signoutPopup","undefined","signoutRedirect","signoutCallback","response","data","notifySubscribers","callback","push","subscription","subscriptionId","subscriptionIndex","map","element","index","found","filter","length","splice","i","useReplaceToNavigate","status","AuthenticationResultStatus","Fail","Success","Redirect","fetch","ApplicationPaths","ApiAuthorizationClientConfigurationUrl","ok","ApplicationName","json","settings","automaticSilentRenew","includeIdTokenInSilentRenew","userStore","WebStorageStateStore","prefix","UserManager","events","addUserSignedOut","removeUser","authService","fetchJsonApi","init","authenticate","attemptSignin","getAccessToken","token","requestHeaders","headers","signIn","EMPTY_RESULT","useBoundedNews","search","start","rows","useMemo","request","extent","lonMin","latMin","lonMax","latMax","dateFrom","from","dateTo","to","query","diseaseOnly","maxResults","URLSearchParams","toString","useState","requestKey","setRequestKey","useEffect","Date","toISOString","results","setResults","history","useHistory","cancelled","result","debug","link","document","createElement","href","location","pathname","hash","returnUrl","protocol","host","redirectUrl","Login","QueryParameterNames","ReturnUrl","encodeURIComponent","responseJson","useJsonApi","news","articles","totalCount","DEFAULT_SEARCH","useSearchReducer","initial","useReducer","searchReducer","initSearchReducerWithUrl","queryParamsToSearch","action","type","payload","oldSearch","newSearch","fullNewSearch","isSearchUpdate","queryString","queryParams","has","get","isNaN","parseFloat","parseInt","searchToQueryParams","params","extentToBounds","useSearchNews","searchDispatch","apiSearch","setApiSearch","newsResults","useSelection","selection","setSelection","selectPlace","useCallback","place","id","lat","lon","selected","move","useThemedBasemap","mode","useContext","ThemeContext","TileLayer","attribution","PressAtlasAd","responsive","className","target","rel","ArticleActions","React","memo","article","icon","faNewspaper","facebookShareUrl","text","hashtags","app_id","quote","forEach","tag","append","FacebookShare","shareType","shareUrl","faFacebook","role","linkedinShareUrl","LinkedinShare","faLinkedinIn","twitterShareUrl","via","related","relation","TwitterShare","faTwitter","emailShareUrl","subject","body","recipient","subjectEnc","bodyEnc","EmailShare","mailto","faEnvelope","articleSocialShareInfo","articleEmailShareInfo","title","places","Share","emailSubject","emailBody","ArticleShare","primaryPlaces","name","SearchShare","ArticlePopupContent","articlePlace","publisher","style","overflow","maxHeight","faMapMarkerAlt","faClock","rssDate","summary","PressAtlasMap","whenCreated","basemap","heatmapFn","popupContentsFn","popupTemplate","zoom","zoomControl","layersControl","position","PoweredBy","NewsCard","selectArticle","selectArticlePlace","Card","CardHeader","onClick","Collapse","isOpen","timeout","cursor","CardBody","marginRight","format_date","Places","placesOpen","setPlacesOpen","ListGroup","ListGroupItem","ResultsCount","displayed","total","indexToAdIndex","adPerN","Math","floor","adIndexToIndex","NewsResults","virtuoso","useRef","mounted","findIndex","current","scrollToIndex","behavior","window","flex","ref","itemContent","AdvancedSearch","now","dateFromDefault","startOfDay","dateToDefault","endOfDay","SearchDateTimePicker","label","initialFocusedDate","value","onChange","dateStr","FormGroup","Label","for","Slider","event","val","min","max","props","row","xs","Col","DateTimePicker","autoOk","clearable","disableFuture","ampm","format","date","getFullYear","getMonth","getDate","Search","children","localSearchFields","localSearch","localSearchDispatch","setIsOpen","filtered","isSearching","Form","onSubmit","preventDefault","InputGroup","Input","placeholder","InputGroupAddon","addonType","FilterButton","SearchButton","SearchError","filterButtonClass","faFilter","Button","color","faCircleNotch","faSearch","spin","local","SearchContext","createContext","DateRangeSlider","ValueLabelComponent","CustomValueLabelComponent","valueLabelFormat","x","Array","isArray","checkValue","labelFormat","apply","convertNumberToIso8601","convertIso8601ToNumber","open","Tooltip","enterTouchDelay","placement","dateString","parseISO","getTime","dateVal","func","item","SidePane","width","hide","paneStyle","display","transition","marginOffset","marginLeft","contentsStyle","flexDirection","direction","SidePaneToggle","borderRadius","buttonStyle","backgroundColor","border","outline","padding","height","margin","zIndex","borderTopRightRadius","borderBottomRightRadius","openIcon","faAngleLeft","closedIcon","faAngleRight","borderTopLeftRadius","borderBottomLeftRadius","PressAtlas","replaceDefault","useLocation","useUrlSyncedSearchReducer","setMap","initialBounds","fitBounds","bounds","getBounds","getWest","getSouth","getEast","getNorth","useMapBoundsSearch","reduce","acc","cur","dateMin","dateMax","filterDateRange","setFilterDateRange","filteredNews","flatMap","Provider","Helmet","content","valueLabelDisplay"],"mappings":"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","file":"static/js/5.4a8f6175.chunk.js","sourcesContent":["import { UserManager, WebStorageStateStore } from 'oidc-client';\r\nimport { ApplicationPaths, ApplicationName } from './ApiAuthorizationConstants';\r\n\r\nexport class AuthorizeService {\r\n _callbacks = [];\r\n _nextSubscriptionId = 0;\r\n _user = null;\r\n _isAuthenticated = false;\r\n\r\n // By default pop ups are disabled because they don't work properly on Edge.\r\n // If you want to enable pop up authentication simply set this flag to false.\r\n _popUpDisabled = true;\r\n\r\n async isAuthenticated() {\r\n const user = await this.getUser();\r\n return !!user;\r\n }\r\n\r\n async getUser() {\r\n if (this._user && this._user.profile) {\r\n return this._user.profile;\r\n }\r\n\r\n await this.ensureUserManagerInitialized();\r\n const user = await this.userManager.getUser();\r\n return user && user.profile;\r\n }\r\n\r\n async getAccessToken() {\r\n await this.ensureUserManagerInitialized();\r\n const user = await this.userManager.getUser();\r\n return user && user.access_token;\r\n }\r\n\r\n // We try to authenticate the user in three different ways:\r\n // 1) We try to see if we can authenticate the user silently. This happens\r\n // when the user is already logged in on the IdP and is done using a hidden iframe\r\n // on the client.\r\n // 2) We try to authenticate the user using a PopUp Window. This might fail if there is a\r\n // Pop-Up blocker or the user has disabled PopUps.\r\n // 3) If the two methods above fail, we redirect the browser to the IdP to perform a traditional\r\n // redirect flow.\r\n async signIn(state) {\r\n await this.ensureUserManagerInitialized();\r\n try {\r\n const silentUser = await this.userManager.signinSilent(this.createArguments());\r\n this.updateState(silentUser);\r\n return this.success(state);\r\n } catch (silentError) {\r\n // User might not be authenticated, fallback to popup authentication\r\n console.log(\"Silent authentication error: \", silentError);\r\n\r\n try {\r\n if (this._popUpDisabled) {\r\n throw new Error('Popup disabled. Change \\'AuthorizeService.js:AuthorizeService._popupDisabled\\' to false to enable it.')\r\n }\r\n\r\n const popUpUser = await this.userManager.signinPopup(this.createArguments());\r\n this.updateState(popUpUser);\r\n return this.success(state);\r\n } catch (popUpError) {\r\n if (popUpError.message === \"Popup window closed\") {\r\n // The user explicitly cancelled the login action by closing an opened popup.\r\n return this.error(\"The user closed the window.\");\r\n } else if (!this._popUpDisabled) {\r\n console.log(\"Popup authentication error: \", popUpError);\r\n }\r\n\r\n // PopUps might be blocked by the user, fallback to redirect\r\n try {\r\n await this.userManager.signinRedirect(this.createArguments(state));\r\n return this.redirect();\r\n } catch (redirectError) {\r\n console.log(\"Redirect authentication error: \", redirectError);\r\n return this.error(redirectError);\r\n }\r\n }\r\n }\r\n }\r\n\r\n async completeSignIn(url) {\r\n try {\r\n await this.ensureUserManagerInitialized();\r\n const user = await this.userManager.signinCallback(url);\r\n this.updateState(user);\r\n return this.success(user && user.state);\r\n } catch (error) {\r\n console.log('There was an error signing in: ', error);\r\n return this.error('There was an error signing in.');\r\n }\r\n }\r\n\r\n // We try to sign out the user in two different ways:\r\n // 1) We try to do a sign-out using a PopUp Window. This might fail if there is a\r\n // Pop-Up blocker or the user has disabled PopUps.\r\n // 2) If the method above fails, we redirect the browser to the IdP to perform a traditional\r\n // post logout redirect flow.\r\n async signOut(state) {\r\n await this.ensureUserManagerInitialized();\r\n try {\r\n if (this._popUpDisabled) {\r\n throw new Error('Popup disabled. Change \\'AuthorizeService.js:AuthorizeService._popupDisabled\\' to false to enable it.')\r\n }\r\n\r\n await this.userManager.signoutPopup(this.createArguments());\r\n this.updateState(undefined);\r\n return this.success(state);\r\n } catch (popupSignOutError) {\r\n console.log(\"Popup signout error: \", popupSignOutError);\r\n try {\r\n await this.userManager.signoutRedirect(this.createArguments(state));\r\n return this.redirect();\r\n } catch (redirectSignOutError) {\r\n console.log(\"Redirect signout error: \", redirectSignOutError);\r\n return this.error(redirectSignOutError);\r\n }\r\n }\r\n }\r\n\r\n async completeSignOut(url) {\r\n await this.ensureUserManagerInitialized();\r\n try {\r\n const response = await this.userManager.signoutCallback(url);\r\n this.updateState(null);\r\n return this.success(response && response.data);\r\n } catch (error) {\r\n console.log(`There was an error trying to log out '${error}'.`);\r\n return this.error(error);\r\n }\r\n }\r\n\r\n updateState(user) {\r\n this._user = user;\r\n this._isAuthenticated = !!this._user;\r\n this.notifySubscribers();\r\n }\r\n\r\n subscribe(callback) {\r\n this._callbacks.push({ callback, subscription: this._nextSubscriptionId++ });\r\n return this._nextSubscriptionId - 1;\r\n }\r\n\r\n unsubscribe(subscriptionId) {\r\n const subscriptionIndex = this._callbacks\r\n .map((element, index) => element.subscription === subscriptionId ? { found: true, index } : { found: false })\r\n .filter(element => element.found === true);\r\n if (subscriptionIndex.length !== 1) {\r\n throw new Error(`Found an invalid number of subscriptions ${subscriptionIndex.length}`);\r\n }\r\n\r\n this._callbacks.splice(subscriptionIndex[0].index, 1);\r\n }\r\n\r\n notifySubscribers() {\r\n for (let i = 0; i < this._callbacks.length; i++) {\r\n const callback = this._callbacks[i].callback;\r\n callback();\r\n }\r\n }\r\n\r\n createArguments(state) {\r\n return { useReplaceToNavigate: true, data: state };\r\n }\r\n\r\n error(message) {\r\n return { status: AuthenticationResultStatus.Fail, message };\r\n }\r\n\r\n success(state) {\r\n return { status: AuthenticationResultStatus.Success, state };\r\n }\r\n\r\n redirect() {\r\n return { status: AuthenticationResultStatus.Redirect };\r\n }\r\n\r\n async ensureUserManagerInitialized() {\r\n if (this.userManager !== undefined) {\r\n return;\r\n }\r\n\r\n let response = await fetch(ApplicationPaths.ApiAuthorizationClientConfigurationUrl);\r\n if (!response.ok) {\r\n throw new Error(`Could not load settings for '${ApplicationName}'`);\r\n }\r\n\r\n let settings = await response.json();\r\n settings.automaticSilentRenew = true;\r\n settings.includeIdTokenInSilentRenew = true;\r\n settings.userStore = new WebStorageStateStore({\r\n prefix: ApplicationName\r\n });\r\n\r\n this.userManager = new UserManager(settings);\r\n\r\n this.userManager.events.addUserSignedOut(async () => {\r\n await this.userManager.removeUser();\r\n this.updateState(undefined);\r\n });\r\n }\r\n\r\n static get instance() { return authService }\r\n}\r\n\r\nconst authService = new AuthorizeService();\r\n\r\nexport default authService;\r\n\r\nexport const AuthenticationResultStatus = {\r\n Redirect: 'redirect',\r\n Success: 'success',\r\n Fail: 'fail'\r\n};\r\n","// Copyright 2021 Geospatial Intelligence Pty Ltd\r\nimport authService, {\r\n AuthenticationResultStatus\r\n} from '../../components/api-authorization/AuthorizeService';\r\n\r\n/**\r\n * Perform a request. May also redirect to login page on unauthorized.\r\n * NOTE: This isn't actually a hook.\r\n * \r\n * @param {string} url Url of request passed to `fetch`.\r\n * @param {!Object} init Init passed to `fetch`.\r\n * @param {boolean} authenticate Whether to authenticate.\r\n * @param {boolean} attemptSignin Whether to attempt sign in on 401 (if authenticate is true).\r\n * @returns {Promise} The response returned from fetch.\r\n */\r\nexport async function fetchJsonApi(url, init, authenticate, attemptSignin) {\r\n const token = authenticate ? await authService.getAccessToken() : null;\r\n // Combine init with authorization header.\r\n const requestHeaders = { ...(init?.headers ?? {}) };\r\n if (token) {\r\n requestHeaders['Authorization'] = `Bearer ${token}`;\r\n }\r\n //const user = await authService.getUser();\r\n //console.log({ user });\r\n const response = await fetch(url, { ...init, headers: requestHeaders });\r\n // Check if we should attempt sign on.\r\n if (response.status === 401 && authenticate && attemptSignin) {\r\n const signinResult = await authService.signIn();\r\n if (signinResult.status === AuthenticationResultStatus.Success) {\r\n // Can perform the request again.\r\n return await fetchJsonApi(url, init, authenticate, false);\r\n }\r\n }\r\n return response;\r\n}\r\n\r\nexport default fetchJsonApi\r\n","// Copyright 2021 Geospatial Intelligence Pty Ltd\r\nimport { useEffect, useState } from 'react';\r\nimport { useHistory } from 'react-router-dom';\r\nimport {\r\n ApplicationPaths, QueryParameterNames\r\n} from '../../components/api-authorization/ApiAuthorizationConstants';\r\nimport fetchJsonApi from './fetchJsonApi';\r\n\r\nconst EMPTY_RESULT = { json: null, error: null };\r\n\r\n/**\r\n * Custom hook to perform basic json api requests.\r\n * \r\n * @param {string} url The url to use.\r\n * @param {!Object} options\r\n * @param {!Object} options.init Passed to `window.fetch`.\r\n * @param {?any} options.requestKey Some object to force refresh changes.\r\n * @param {boolean=} options.authenticate Whether to use authentication.\r\n */\r\nexport function useJsonApi(url, { init, requestKey = null, authenticate = true } = {}) {\r\n const [results, setResults] = useState({ ...EMPTY_RESULT });\r\n const history = useHistory();\r\n \r\n useEffect(() => {\r\n let cancelled = false;\r\n (async () => {\r\n //const params = new URLSearchParams(request);\r\n //const url = `/api/news?${params.toString()}`;\r\n const result = { ...EMPTY_RESULT };\r\n try {\r\n const response = await fetchJsonApi(url, init, authenticate, true);\r\n // Check if we need to force logout.\r\n if (response.status === 401 && !cancelled) {\r\n console.debug('Not authenticated, logging off.');\r\n // TODO: Probably shouldn't use history for location, as it\r\n // is mutable and this might have changed. Generally if it\r\n // is a problematic change, this hook should have been\r\n // unmounted and the !cancelled check will protect us. In\r\n // other cases, we probably want the newer value.\r\n const link = document.createElement(\"a\");\r\n link.href = `${history.location.pathname}${history.location.search}${history.location.hash}`;\r\n console.debug(history.location);\r\n const returnUrl = `${link.protocol}//${link.host}${link.pathname}${link.search}${link.hash}`;\r\n console.debug({ returnUrl });\r\n const redirectUrl = `${ApplicationPaths.Login}?${QueryParameterNames.ReturnUrl}=${encodeURIComponent(returnUrl)}`;\r\n history.push(redirectUrl);\r\n }\r\n // TODO: May not have response json on error state. Handle better.\r\n const responseJson = await response.json();\r\n if (response.ok) {\r\n result.json = responseJson;\r\n console.debug(result);\r\n } else {\r\n result.error = responseJson;\r\n console.error(responseJson);\r\n }\r\n }\r\n catch (error) {\r\n console.error(error);\r\n result.error = error;\r\n }\r\n finally {\r\n if (!cancelled) {\r\n setResults(result);\r\n }\r\n }\r\n })();\r\n\r\n // Prevent updates if a newer request comes through.\r\n return () => { cancelled = true; };\r\n }, [url, init, authenticate, history, requestKey]);\r\n\r\n return results;\r\n}\r\n","// Copyright 2021 Geospatial Intelligence Pty Ltd\r\nimport { useEffect, useMemo, useState } from 'react';\r\nimport { useJsonApi } from './useJsonApi';\r\n\r\n/**\r\n * Custom hook to fetch news based on search.\r\n */\r\nexport function useBoundedNews({ search, start = 0, rows = 100 }) {\r\n const url = useMemo(() => {\r\n // TODO: May need to coordinate better with search, as rows and\r\n // maxResults are for the same thing.\r\n const request = { start, rows };\r\n if (search.extent) {\r\n const extent = search.extent;\r\n request.lonMin = extent.lonMin;\r\n request.latMin = extent.latMin;\r\n request.lonMax = extent.lonMax;\r\n request.latMax = extent.latMax;\r\n }\r\n if (search.dateFrom) {\r\n request.from = search.dateFrom;\r\n }\r\n if (search.dateTo) {\r\n request.to = search.dateTo;\r\n }\r\n if (search.query) {\r\n request.query = search.query;\r\n }\r\n if (search.diseaseOnly != null) {\r\n request.diseaseOnly = search.diseaseOnly;\r\n }\r\n if (search.maxResults != null) {\r\n request.rows = search.maxResults;\r\n }\r\n return `/api/search?${new URLSearchParams(request).toString()}`;\r\n }, [start, rows, search]);\r\n\r\n // Need something to trigger new requests even if the URL hasn't changed.\r\n const [requestKey, setRequestKey] = useState(null);\r\n useEffect(() => setRequestKey(new Date().toISOString()), [search]);\r\n\r\n const results = useJsonApi(url, { requestKey });\r\n\r\n const newsResults = useMemo(() => ({\r\n news: results.json?.articles ?? [],\r\n totalCount: results.json?.totalCount ?? -1,\r\n error: results.error,\r\n }), [results]);\r\n\r\n return newsResults;\r\n}\r\n","// Copyright 2021 Geospatial Intelligence Pty Ltd\r\nimport { useEffect, useReducer } from 'react';\r\nimport { useHistory, useLocation } from 'react-router-dom';\r\n\r\n\r\n// Default search params.\r\nconst DEFAULT_SEARCH = {\r\n query: '',\r\n dateFrom: '',\r\n dateTo: '',\r\n state: 'INIT',\r\n};\r\n\r\n/**\r\n * Search reducer with added hooks for keeping search in sync with URL.\r\n * \r\n * @param {any} initial\r\n */\r\nexport function useUrlSyncedSearchReducer(initial = {}, replaceDefault = false) {\r\n const location = useLocation();\r\n const history = useHistory();\r\n const [search, searchDispatch] = useSearchReducer({\r\n initial: {\r\n ...(replaceDefault ? {} : DEFAULT_SEARCH),\r\n ...initial,\r\n },\r\n location,\r\n }, initSearchReducerWithUrl);\r\n\r\n // Update the search on changes to the URL. Note that the reducer will bail\r\n // out if not an actual change.\r\n useEffect(() => {\r\n searchDispatch({\r\n type: 'URL',\r\n payload: queryParamsToSearch(location.search),\r\n });\r\n }, [location.search, searchDispatch]);\r\n\r\n // Update the URL on changes to search - this should only happen if the\r\n // change is triggered by the API state being activated.\r\n useEffect(() => {\r\n if (search.state === 'API') {\r\n history.push(`${location.pathname}?${searchToQueryParams(search)}`);\r\n }\r\n // TODO: Check whether these are triggering too many updates.\r\n }, [search, history, location.pathname]);\r\n\r\n return [search, searchDispatch];\r\n}\r\n\r\n/**\r\n * Reducer for search object to support state updates with bailout.\r\n * \r\n * @param {any} initial\r\n * @param {any} init\r\n */\r\nexport function useSearchReducer(initial = DEFAULT_SEARCH, init = undefined) {\r\n return useReducer(searchReducer, initial, init);\r\n}\r\n\r\n/**\r\n * Initialiser for the search reducer with url. Takes initial params and adds\r\n * to them from url.\r\n * \r\n * @param {any} param0\r\n */\r\nfunction initSearchReducerWithUrl({ initial, location }) {\r\n return {\r\n ...initial,\r\n ...queryParamsToSearch(location.search),\r\n };\r\n}\r\n\r\n/**\r\n * Reducer to update search state.\r\n * \r\n * @param {Search} state The current search state.\r\n * @param {{type: string, payload: ?Object}} action The action to perform.\r\n * @returns {Search} The updated search.\r\n */\r\nfunction searchReducer(state, action) {\r\n switch (action.type) {\r\n case 'SEARCH':\r\n case 'MAP':\r\n // Return a new object with updated states - will trigger next changes.\r\n return { ...state, ...action.payload, state: action.type };\r\n case 'UPDATE':\r\n case 'URL':\r\n if (isSearchUpdate(state, action.payload)) {\r\n return { ...state, ...action.payload, state: action.type };\r\n }\r\n // Bail out if there is no change.\r\n return state;\r\n case 'API':\r\n case 'IDLE':\r\n // Update the state only.\r\n return { ...state, state: action.type };\r\n default:\r\n throw new Error();\r\n }\r\n}\r\n\r\n/**\r\n * Check if any of the fields in a search object would be updated.\r\n * \r\n * @param {Search} oldSearch The search object to check.\r\n * @param {!Object} newSearch Object with fields to update.\r\n * @returns {boolean} True if any fields would be updated, false otherwise.\r\n */\r\nfunction isSearchUpdate(oldSearch, newSearch) {\r\n if (oldSearch === newSearch) {\r\n return false;\r\n }\r\n const fullNewSearch = { ...oldSearch, ...newSearch };\r\n if (oldSearch.query !== fullNewSearch.query) {\r\n return true;\r\n }\r\n if (oldSearch.dateFrom !== fullNewSearch.dateFrom) {\r\n return true;\r\n }\r\n if (oldSearch.dateTo !== fullNewSearch.dateTo) {\r\n return true;\r\n }\r\n if (oldSearch.extent && oldSearch.extent.lonMin !== fullNewSearch.extent.lonMin\r\n && oldSearch.extent.latMin !== fullNewSearch.extent.latMin\r\n && oldSearch.extent.lonMax !== fullNewSearch.extent.lonMax\r\n && oldSearch.extent.latMax !== fullNewSearch.extent.latMax)\r\n {\r\n return true;\r\n }\r\n if (oldSearch.maxResults !== fullNewSearch.maxResults) {\r\n return true;\r\n }\r\n return false;\r\n}\r\n\r\n/**\r\n * Convert URL query params to search object.\r\n * \r\n * @param {string} queryString The query string.\r\n * @returns {Search} The search object.\r\n */\r\nfunction queryParamsToSearch(queryString) {\r\n const queryParams = new URLSearchParams(queryString);\r\n const search = { ...DEFAULT_SEARCH };\r\n if (queryParams.has('query')) {\r\n search.query = queryParams.get('query');\r\n }\r\n if (queryParams.has('dateFrom')) {\r\n search.dateFrom = queryParams.get('dateFrom');\r\n }\r\n if (queryParams.has('dateTo')) {\r\n search.dateTo = queryParams.get('dateTo');\r\n }\r\n // TODO: May need to do something smarter here - this doesn't get reset if\r\n // the url changes while on the tab (e.g. clicking the nav while on news\r\n // already).\r\n const lonMin = queryParams.get('lonMin') || 'NaN';\r\n const latMin = queryParams.get('latMin') || 'NaN';\r\n const lonMax = queryParams.get('lonMax') || 'NaN';\r\n const latMax = queryParams.get('latMax') || 'NaN';\r\n if (!(isNaN(lonMin) || isNaN(latMin) || isNaN(lonMax) || isNaN(latMax))) {\r\n search.extent = {\r\n lonMin: isNaN(lonMin) ? -180 : parseFloat(lonMin),\r\n latMin: isNaN(latMin) ? -90 : parseFloat(latMin),\r\n lonMax: isNaN(lonMax) ? 180 : parseFloat(lonMax),\r\n latMax: isNaN(latMax) ? 90 : parseFloat(latMax),\r\n }\r\n }\r\n if (queryParams.has('maxResults')) {\r\n const maxResults = parseInt(queryParams.get('maxResults'));\r\n if (!isNaN(maxResults)) {\r\n search.maxResults = maxResults;\r\n }\r\n }\r\n return search;\r\n}\r\n\r\n/**\r\n * Converts a search object to query string.\r\n * \r\n * @param {Search} search The search object.\r\n * @returns {string} The query string, including ? or empty if no relevant parts.\r\n */\r\nfunction searchToQueryParams(search) {\r\n const params = {};\r\n if (search.query) {\r\n params.query = search.query;\r\n }\r\n if (search.dateFrom) {\r\n params.dateFrom = search.dateFrom;\r\n }\r\n if (search.dateTo) {\r\n params.dateTo = search.dateTo;\r\n }\r\n if (search.extent) {\r\n params.lonMin = search.extent.lonMin;\r\n params.latMin = search.extent.latMin;\r\n params.lonMax = search.extent.lonMax;\r\n params.latMax = search.extent.latMax;\r\n }\r\n if (search.maxResults) {\r\n params.maxResults = search.maxResults;\r\n }\r\n return new URLSearchParams(params).toString();\r\n}\r\n","// Copyright 2021 Geospatial Intelligence Pty Ltd\r\nimport { useEffect, useState } from 'react';\r\n\r\nexport function useMapBoundsSearch(map, search, searchDispatch) {\r\n // TODO: Find a better way to do this.\r\n // Move map to the initial bounds.\r\n const [initialBounds,] = useState(search.extent);\r\n useEffect(() => {\r\n if (map && initialBounds) {\r\n map.fitBounds(extentToBounds(initialBounds));\r\n }\r\n }, [initialBounds, map]);\r\n useEffect(() => {\r\n if (search.state !== 'SEARCH' || !map) {\r\n return;\r\n }\r\n const bounds = map.getBounds();\r\n const extent = {\r\n lonMin: bounds.getWest(),\r\n latMin: bounds.getSouth(),\r\n lonMax: bounds.getEast(),\r\n latMax: bounds.getNorth(),\r\n };\r\n // TODO: May have to normalise to (-180, 180].\r\n searchDispatch({ type: 'MAP', payload: { extent } });\r\n }, [map, search.state, searchDispatch]);\r\n // Sync map bounds to url.\r\n useEffect(() => {\r\n if (!map || !search.extent || search.state !== 'URL') {\r\n return;\r\n }\r\n map.fitBounds(extentToBounds(search.extent));\r\n }, [map, search, searchDispatch]);\r\n}\r\n\r\n/**\r\n * Convert an extent object into the correct format for leaflet bounds, i.e.\r\n * [[latMin, lonMin], [latMax, lonMax]].\r\n * \r\n * @param {any} param0\r\n */\r\nfunction extentToBounds({ lonMin, latMin, lonMax, latMax }) {\r\n return [[latMin, lonMin], [latMax, lonMax]];\r\n}\r\n","// Copyright 2021 Geospatial Intelligence Pty Ltd\r\nimport { useEffect, useState } from 'react';\r\nimport { useBoundedNews } from './pressatlas-api';\r\n\r\n/**\r\n * Hook to wrap the search an it's dispatch to manage the API side of things.\r\n * \r\n * @param {any} search\r\n * @param {any} searchDispatch\r\n */\r\nexport function useSearchNews(search, searchDispatch) {\r\n const [apiSearch, setApiSearch] = useState(search);\r\n const newsResults = useBoundedNews({ search: apiSearch, rows: 1000 });\r\n\r\n // Watch search, and if coming from adding MAP coords or changing URL, then\r\n // update the local 'api' copy of search to trigger a request. Also update\r\n // the search state if coming from MAP (i.e. not URL) to trigger a URL\r\n // update.\r\n useEffect(() => {\r\n // Only search on bbox or url change.\r\n if (search.state !== 'MAP' && search.state !== 'URL') {\r\n return;\r\n }\r\n if (search.state === 'MAP') {\r\n // This is a fresh search, so dispatch to cause update to url (if\r\n // using the url synced search reducer). Also causes busy search\r\n // spinner, etc.\r\n searchDispatch({ type: 'API' });\r\n }\r\n // Update the search to trigger the actual request.\r\n setApiSearch(search);\r\n }, [search, searchDispatch]);\r\n\r\n // Watch the results to see when the request returns, and update search\r\n // state accordingly.\r\n useEffect(() => {\r\n searchDispatch({ type: 'IDLE' });\r\n }, [newsResults, searchDispatch]);\r\n\r\n return newsResults;\r\n}\r\n\r\nexport default useSearchNews;\r\n","// Copyright 2021 Geospatial Intelligence Pty Ltd\r\nimport { useCallback, useState } from 'react';\r\n\r\nexport function useSelection() {\r\n const [selection, setSelection] = useState(null);\r\n const selectPlace = useCallback((place) => setSelection({\r\n id: place.id,\r\n lat: place.lat,\r\n lon: place.lon,\r\n selected: place,\r\n move: false\r\n }), [setSelection]);\r\n return { selection, setSelection, selectPlace };\r\n}\r\n\r\nexport default useSelection;\r\n","// Copyright 2021 Geospatial Intelligence Pty Ltd\r\nimport { useContext, useMemo } from 'react';\r\nimport { TileLayer } from 'react-leaflet';\r\nimport { ThemeContext } from '../components/context';\r\n\r\nexport function useThemedBasemap() {\r\n const { mode } = useContext(ThemeContext);\r\n const basemap = useMemo(() => {\r\n if (mode === 'light') {\r\n return (\r\n \r\n \r\n );\r\n }\r\n return (\r\n \r\n \r\n );\r\n }, [mode]);\r\n return basemap;\r\n}\r\n\r\nexport default useThemedBasemap;\r\n","// Copyright 2021 SpatialApps Pty Ltd\r\nimport './PressAtlasAd.css';\r\n\r\n/**\r\n * React component for a pressatlas ad.\r\n */\r\nexport function PressAtlasAd({ responsive }) {\r\n const className = `pa-ad${responsive ? ' pa-ad--responsive' : ''}`;\r\n return (\r\n \r\n
\r\n \r\n );\r\n}\r\n\r\nexport default PressAtlasAd;\r\n","// Copyright 2021 Geospatial Intelligence Pty Ltd\r\nimport { faNewspaper } from '@fortawesome/free-solid-svg-icons';\r\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\r\nimport React from 'react';\r\n\r\nimport './ArticleActions.css';\r\n\r\n/**\r\n * React component for displaying a single news card.\r\n * \r\n * @param {!Object} props\r\n * @param {!Object} props.article News article.\r\n */\r\nexport const ArticleActions = React.memo(({ article }) => (\r\n
\r\n \r\n Read\r\n \r\n
\r\n));\r\n\r\nexport default ArticleActions;\r\n","// Copyright 2020 SpatialApps Pty Ltd\r\nimport React from 'react';\r\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\r\nimport { faFacebook } from '@fortawesome/free-brands-svg-icons';\r\n\r\n/**\r\n * Get a facebook share link.\r\n * \r\n * @param {{url: string}} param0 Must contain url. Only the last hashtag will be used.\r\n * @returns {string} URL to share via facebook.\r\n */\r\nexport function facebookShareUrl({ url, text, hashtags }) {\r\n const params = new URLSearchParams({\r\n app_id: '326877125203185',\r\n href: url,\r\n quote: text,\r\n });\r\n // Treat hashtags differently as it is an array.\r\n hashtags.forEach((tag) => params.append('hashtag', `#${tag}`));\r\n return `https://www.facebook.com/dialog/share?${params.toString()}`;\r\n}\r\n\r\n/**\r\n * React component for facebook share button.\r\n * \r\n * @param {!Object} param0 Props. Must include url.\r\n */\r\nexport function FacebookShare({ url, text, hashtags, shareType }) {\r\n const shareUrl = facebookShareUrl({ url, text, hashtags });\r\n return (\r\n \r\n \r\n \r\n );\r\n}\r\n","// Copyright 2020 SpatialApps Pty Ltd\r\nimport React from 'react';\r\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\r\nimport { faLinkedinIn } from '@fortawesome/free-brands-svg-icons';\r\n\r\n/**\r\n * Get a url to share via linkedin.\r\n * \r\n * @param {{url: string}} param0 Must contain url.\r\n * @returns {string} URL to share via linkedin.\r\n */\r\nexport function linkedinShareUrl({ url }) {\r\n const params = new URLSearchParams({\r\n url: url,\r\n });\r\n return `https://www.linkedin.com/sharing/share-offsite/?${params.toString()}`;\r\n}\r\n\r\n/**\r\n * React component for linkedin share button.\r\n * \r\n * @param {!Object} param0 Props. Must include url.\r\n */\r\nexport function LinkedinShare({ url, shareType }) {\r\n const shareUrl = linkedinShareUrl({ url });\r\n return (\r\n \r\n \r\n \r\n );\r\n}\r\n","// Copyright 2020 SpatialApps Pty Ltd\r\nimport React from 'react';\r\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\r\nimport { faTwitter } from '@fortawesome/free-brands-svg-icons';\r\n\r\n/**\r\n * Get a url to share on twitter.\r\n * \r\n * @param {{url: string}} param0 Must include url to share. Optionally also text, hashtags, via and related.\r\n * @returns {string} URL to share via twitter.\r\n */\r\nexport function twitterShareUrl(\r\n {\r\n url,\r\n text,\r\n hashtags,\r\n via = 'PressAtlas_app',\r\n related = ['PressAtlas_app', 'SpatialApps'],\r\n }) {\r\n const params = new URLSearchParams({ text, url, via });\r\n // Treat hashtags and related differently as they are arrays.\r\n hashtags.forEach((tag) => params.append('hashtags', tag));\r\n related.forEach((relation) => params.append('related', relation));\r\n return `https://twitter.com/intent/tweet?${params.toString()}`;\r\n}\r\n\r\n/**\r\n * Create a twitter share button React component.\r\n * \r\n * @param {!Object} param0 Props. Must include URL to share.\r\n * @returns {JSX} Twitter share button.\r\n */\r\nexport function TwitterShare(\r\n {\r\n url,\r\n text,\r\n hashtags,\r\n shareType,\r\n via = 'PressAtlas_app',\r\n related = ['PressAtlas_app', 'SpatialApps'],\r\n }) {\r\n const shareUrl = twitterShareUrl({ url, text, hashtags, via, related });\r\n return (\r\n \r\n \r\n \r\n );\r\n}\r\n","// Copyright 2020 SpatialApps Pty Ltd\r\nimport React from 'react';\r\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\r\nimport { faEnvelope } from '@fortawesome/free-solid-svg-icons';\r\n\r\n/**\r\n * URL for an email share button. Uses mailto at the moment.\r\n * \r\n * @param {{subject: string, body: string, recipient: string}} param0 Subject and body - recipient may be empty.\r\n * @returns {string} The url for the email share.\r\n */\r\nexport function emailShareUrl({ subject, body, recipient = '' }) {\r\n const subjectEnc = encodeURIComponent(subject);\r\n const bodyEnc = encodeURIComponent(body);\r\n return `mailto:${recipient}?subject=${subjectEnc}&body=${bodyEnc}`;\r\n}\r\n\r\n/**\r\n * Component for an email share button. Uses mailto at the moment.\r\n * \r\n * @param {{subject: string, body: string, recipient: string}} param0 Subject and body - recipient may be empty.\r\n * @returns {JSX} The JSX for the email share button.\r\n */\r\nexport function EmailShare({ subject, body, shareType, recipient = '' }) {\r\n const mailto = emailShareUrl({ subject, body, recipient });\r\n return (\r\n \r\n \r\n \r\n );\r\n}\r\n","// Copyright 2020 SpatialApps Pty Ltd\r\nimport React from 'react';\r\nimport { FacebookShare, facebookShareUrl } from './facebook/FacebookShare.jsx';\r\nimport { LinkedinShare, linkedinShareUrl } from './linkedin/LinkedinShare.jsx';\r\nimport { TwitterShare, twitterShareUrl } from './twitter/TwitterShare.jsx';\r\nimport { EmailShare, emailShareUrl } from './EmailShare.jsx';\r\n\r\nimport './Share.css';\r\n\r\n/**\r\n * Function to convert article into share info to create link.\r\n * \r\n * @param {{url: string}} article Must contain URL for the article.\r\n * @returns {{url: string, text: string, hashtags: !Array}} Info object for sharing on social.\r\n */\r\nfunction articleSocialShareInfo(article) {\r\n return {\r\n url: article.url,\r\n text: 'I found this article using PressAtlas, the free geospatial '\r\n + 'news platform. Powered by Mercury - Making better decisions',\r\n hashtags: ['news', 'tech', 'unlockingwhere', 'geospatial'],\r\n };\r\n}\r\n\r\n/**\r\n * Function to convert article into email subject and body text.\r\n * \r\n * @param {{url: string, title: string, places: ?Array}} article The article to share via email.\r\n * @returns {!Array} Subject, body strings for email.\r\n */\r\nfunction articleEmailShareInfo({ url, title, places }) {\r\n let body = 'I thought you might be interested in this article: ${title} (${url})\\n'\r\n + 'Found using < a href = \"https://pressatlas.com/\" > PressAtlas (https://pressatlas.com/)'\r\n + ', the free geospatial news platform from Spatial Apps. Powered by Mercury - Making better decisions';\r\n if (places) {\r\n body += 'The article is talking about: '\r\n + `${places.map(place => `\\n\\t- ${place}`)}`;\r\n }\r\n const subject = `I think this might be of interest - ${title}`;\r\n return { subject, body };\r\n}\r\n\r\n/**\r\n * Html only (non-react) share component for an article.\r\n * \r\n * @param {{url: string, title: string, places: ?Array}} article The article to share.\r\n * @returns {string} HTML for the share component.\r\n */\r\nexport function shareArticleHtml(article) {\r\n // This is used for all the socials (but not email).\r\n const socialShare = articleSocialShareInfo(article);\r\n // Text used for emails.\r\n const emailShareInfo = articleEmailShareInfo({\r\n url: article.url,\r\n title: article.title,\r\n places: article.primaryPlaces,\r\n });\r\n // Link style builder.\r\n const articleShareLink = (shareUrl, iconClass) => {\r\n return ``\r\n + ``\r\n + '';\r\n };\r\n console.log(article);\r\n return '
'\r\n + 'Share'\r\n + articleShareLink(facebookShareUrl(socialShare), \"fab fa-facebook\")\r\n + articleShareLink(linkedinShareUrl(socialShare), \"fab fa-linkedin-in\")\r\n + articleShareLink(twitterShareUrl(socialShare), \"fab fa-twitter\")\r\n + articleShareLink(emailShareUrl(emailShareInfo), \"fas fa-envelope\")\r\n + '
';\r\n}\r\n\r\n/**\r\n * Component for Share - contains each of the main social medias.\r\n * @param {!Object} param0 Props.\r\n * @returns {JSX} Share buttons for the major socials.\r\n */\r\nexport function Share({ url, text, hashtags, emailSubject, emailBody, shareType = '' }) {\r\n // TODO: Include summary text where applicable?\r\n return (\r\n
\r\n Share\r\n \r\n \r\n \r\n \r\n
\r\n );\r\n}\r\n\r\n/**\r\n * React component for sharing an article.\r\n * \r\n * @param {!Object} param0 Props.\r\n */\r\nexport function ArticleShare({ article }) {\r\n const { url, text, hashtags } = articleSocialShareInfo(article);\r\n const { subject, body } = articleEmailShareInfo({\r\n url: article.url,\r\n title: article.title,\r\n places: article.primaryPlaces.map((place) => place.name),\r\n });\r\n return (\r\n \r\n );\r\n}\r\n\r\n/**\r\n * React component for sharing a search.\r\n * \r\n * @param {{url: string}} param0 Props, must contain url of the search to share.\r\n */\r\nexport function SearchShare({ url }) {\r\n const text = \"I'm using PressAtlas to redefine my news search.\";\r\n const hashtags = ['news', 'tech', 'unlockingwhere', 'spatialnews'];\r\n const subject = \"I'm using PressAtlas to redefine how I search for news\";\r\n const body = \"I'm using PressAtlas (https://pressatlas.com/), the free \"\r\n + 'geospatial news platform. I thought you might be interested in the '\r\n + `news articles I found: ${ url }`;\r\n return (\r\n \r\n );\r\n}\r\n","// Copyright 2021 SpatialApps Pty Ltd\r\nimport { faClock, faMapMarkerAlt } from '@fortawesome/free-solid-svg-icons';\r\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\r\nimport { ArticleActions } from '../news/ArticleActions';\r\nimport { ArticleShare } from '../social/Share';\r\n\r\nexport const ArticlePopupContent = ({ articlePlace }) => {\r\n return (\r\n <>\r\n
{articlePlace.title}
\r\n {articlePlace.publisher}\r\n
\r\n

\r\n {articlePlace.name}\r\n
\r\n {articlePlace.rssDate}\r\n

\r\n

\r\n {articlePlace.summary}\r\n

\r\n \r\n
\r\n \r\n \r\n );\r\n};\r\n\r\nexport default ArticlePopupContent;\r\n","// Copyright 2021 SpatialApps Pty Ltd\r\nimport { HeatedFeatureMap, ReactControl } from 'gi-react-leaflet';\r\nimport { useThemedBasemap } from '../../hooks';\r\nimport PressAtlasAd from '../ads/PressAtlasAd';\r\nimport ArticlePopupContent from './ArticlePopupContent';\r\n\r\nimport './PressAtlasMap.css';\r\n\r\nexport function PressAtlasMap({ places, selection, selectPlace, whenCreated }) {\r\n const basemap = useThemedBasemap();\r\n\r\n return (\r\n [place.lat, place.lon, 1000]}\r\n popupContentsFn={popupTemplate}\r\n whenCreated={whenCreated}\r\n zoom={3}\r\n zoomControl=\"topright\"\r\n layersControl\r\n >\r\n \r\n \r\n \r\n \r\n );\r\n}\r\n\r\nexport default PressAtlasMap;\r\n\r\nconst popupTemplate = (articlePlace) => (\r\n \r\n);\r\n","// Copyright 2021 SpatialApps Pty Ltd\r\nimport './PoweredBy.css';\r\n\r\n/**\r\n * Renders a \"Powered By\" message\r\n * \r\n * @param none.\r\n * @returns\r\n */\r\nexport function PoweredBy() {\r\n return (\r\n
\r\n \r\n Powered By Mercury.\r\n \r\n
\r\n );\r\n}\r\n\r\nexport default PoweredBy;\r\n","// Copyright 2021 Geospatial Intelligence Pty Ltd\r\nimport React, { useState } from 'react';\r\nimport { format as format_date } from 'date-fns';\r\nimport { faClock, faMapMarkerAlt } from '@fortawesome/free-solid-svg-icons';\r\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\r\nimport {\r\n Card, CardBody, CardHeader, Collapse, ListGroup, ListGroupItem\r\n} from 'reactstrap';\r\nimport { ArticleShare } from '../social/Share';\r\n\r\nimport './NewsCard.css';\r\nimport ArticleActions from './ArticleActions';\r\n\r\n/**\r\n * React component for displaying a single news card.\r\n * \r\n * @param {!Object} props\r\n * @param {!Object} props.article News article.\r\n */\r\nexport const NewsCard = React.memo(({\r\n article,\r\n selectArticle,\r\n selectArticlePlace,\r\n selected = false\r\n}) => {\r\n return (\r\n \r\n selectArticle(article)}>\r\n {article.title}\r\n \r\n \r\n \r\n {selected && (\r\n <>\r\n \r\n {article.publisher}\r\n

\r\n \r\n \r\n

\r\n

\r\n {article.summary}\r\n

\r\n selectArticlePlace(article, place)}\r\n />\r\n \r\n \r\n )}\r\n
\r\n
\r\n
\r\n );\r\n});\r\n\r\nexport default NewsCard;\r\n\r\nconst Places = React.memo(({ places, selectPlace }) => {\r\n const [placesOpen, setPlacesOpen] = useState(true);\r\n\r\n return (\r\n <>\r\n setPlacesOpen(!placesOpen)}\r\n >\r\n Places\r\n

\r\n \r\n \r\n {places.map((place) =>\r\n selectPlace(place)}\r\n >\r\n {place.name}\r\n \r\n )}\r\n \r\n \r\n \r\n );\r\n});\r\n","// Copyright 2021 SpatialApps Pty Ltd\r\nimport './ResultsCount.css';\r\n\r\n/**\r\n * Component for results count. Renders the count out of total matched, with a\r\n * tooltip for more info.\r\n * \r\n * @param {{displayed: number, total: number}} param0 The displayed and total counts.\r\n * @returns\r\n */\r\nexport function ResultsCount({ displayed, total }) {\r\n return (\r\n <>\r\n {total >= 0 &&\r\n (\r\n
\r\n \r\n {displayed} of {total} articles displayed.\r\n \r\n \r\n The most recent articles matching your keywords are\r\n displayed. To see others, add filters to limit the\r\n dates or increase the maximum number of articles.\r\n \r\n
\r\n )}\r\n \r\n );\r\n}\r\n\r\nexport default ResultsCount;\r\n","// Copyright 2021 Geospatial Intelligence Pty Ltd\r\nimport React, { useCallback, useEffect, useMemo, useRef } from 'react';\r\nimport { Virtuoso } from 'react-virtuoso';\r\nimport PressAtlasAd from '../ads/PressAtlasAd';\r\nimport { SearchShare } from '../social/Share';\r\nimport { PoweredBy } from '../PoweredBy';\r\nimport NewsCard from './NewsCard';\r\nimport ResultsCount from './ResultsCount';\r\n\r\nimport './NewsResults.css';\r\n\r\nconst AD_PER_N = 5;\r\nconst indexToAdIndex = (index, adPerN = AD_PER_N) => index + Math.floor(index / adPerN);\r\nconst adIndexToIndex = (index, adPerN = AD_PER_N) => index - Math.floor(index / adPerN);\r\n\r\n/**\r\n * React component for a list of places.\r\n * \r\n * @param {!Object} props\r\n * @param {!Array} props.news\r\n */\r\nexport const NewsResults = React.memo(({\r\n news,\r\n totalCount,\r\n selection,\r\n setSelection\r\n}) => {\r\n const virtuoso = useRef(null);\r\n news = useMemo(() => news ?? [], [news]);\r\n\r\n const selectArticlePlace = useCallback((article, place) => setSelection({\r\n id: article.id,\r\n lat: place.lat,\r\n lon: place.lon,\r\n selected: article,\r\n move: true\r\n }), [setSelection]);\r\n\r\n const selectArticle = useCallback((article) => setSelection({\r\n id: article.id,\r\n lat: article.primaryPlaces[0].lat,\r\n lon: article.primaryPlaces[0].lon,\r\n selected: article,\r\n move: false,\r\n }), [setSelection]);\r\n\r\n useEffect(() => {\r\n let mounted = true;\r\n if (selection?.id) {\r\n const index = news.findIndex(\r\n (article) => article.id === selection.id);\r\n console.log({ article: index >= 0 ? news[index] : null });\r\n if (index >= 0 && mounted) {\r\n virtuoso.current?.scrollToIndex({\r\n index: indexToAdIndex(index),\r\n behavior: news.length > 100 ? 'auto' : 'smooth',\r\n });\r\n }\r\n }\r\n return () => { mounted = false; };\r\n }, [news, selection?.id]);\r\n\r\n return (\r\n
\r\n \r\n \r\n \r\n
\r\n (index == 0 || index % 5 !== 0 ? (\r\n \r\n ) : (\r\n \r\n ))}\r\n />\r\n
\r\n
\r\n );\r\n});\r\n\r\nexport default NewsResults;\r\n","// Copyright 2021 Geospatial Intelligence Pty Ltd\r\nimport { Slider } from '@material-ui/core';\r\nimport { DateTimePicker } from '@material-ui/pickers';\r\nimport { Card, Col, FormGroup, Label } from 'reactstrap';\r\n\r\n/**\r\n * AdvancedSearch component.\r\n * \r\n * @param {{search: !Object, searchDispatch: function(!Object)}} param0 Props.\r\n */\r\nexport function AdvancedSearch({ search, searchDispatch }) {\r\n const now = new Date();\r\n const dateFromDefault = startOfDay(now);\r\n const dateToDefault = endOfDay(now);\r\n\r\n function updateDateFrom(dateStr) {\r\n const payload = { dateFrom: dateStr };\r\n if (dateStr && search.dateTo && dateStr >= search.dateTo) {\r\n // We need to adjust search.dateTo.\r\n payload.dateTo = endOfDay(new Date(dateStr)).toISOString();\r\n }\r\n searchDispatch({ type: 'UPDATE', payload: payload });\r\n }\r\n\r\n function updateDateTo(dateStr) {\r\n const payload = { dateTo: dateStr };\r\n if (dateStr && search.dateFrom && dateStr <= search.dateFrom) {\r\n // We need to adjust search.dateTo.\r\n payload.dateFrom = startOfDay(new Date(dateStr)).toISOString();\r\n }\r\n searchDispatch({ type: 'UPDATE', payload: payload });\r\n }\r\n\r\n return (\r\n \r\n
\r\n \r\n \r\n {search.maxResults != null && (\r\n \r\n \r\n searchDispatch({\r\n type: 'UPDATE',\r\n payload: { maxResults: val },\r\n })}\r\n min={1}\r\n max={1000}\r\n />\r\n \r\n )}\r\n
\r\n
\r\n );\r\n}\r\n\r\nexport default AdvancedSearch;\r\n\r\n/**\r\n * DateTime picker Component including inline label.\r\n * \r\n * @param {{value: string|Date, onChange: function(Date), label: string}} param0 Props - anything extra is passed to DateTimePicker.\r\n * @returns {JSX} Component for date time picking.\r\n */\r\nfunction SearchDateTimePicker({ value, onChange, label, ...props }) {\r\n return (\r\n \r\n \r\n \r\n onChange(date ? date.toISOString() : '')}\r\n {...props}\r\n />\r\n \r\n \r\n );\r\n}\r\n\r\nfunction startOfDay(date) {\r\n return new Date(\r\n date.getFullYear(), date.getMonth(), date.getDate(), 0, 0);\r\n}\r\n\r\nfunction endOfDay(date) {\r\n return new Date(\r\n date.getFullYear(), date.getMonth(), date.getDate(), 23, 59);\r\n}\r\n","// Copyright 2021 Geospatial Intelligence Pty Ltd\r\nimport {\r\n faCircleNotch, faFilter, faSearch,\r\n} from '@fortawesome/free-solid-svg-icons';\r\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\r\nimport { useEffect, useState } from 'react';\r\nimport {\r\n Button, Collapse, Form, FormGroup, Input, InputGroup, InputGroupAddon\r\n} from 'reactstrap';\r\nimport { useSearchReducer } from '../../hooks';\r\nimport AdvancedSearch from './AdvancedSearch';\r\nimport './Search.css'\r\n\r\n/**\r\n * Search bar component.\r\n * \r\n * @param {{search: !Object, searchDispatch: function(!Object, !Object), error: ?Object} param0 Props.\r\n */\r\nexport function Search({\r\n search,\r\n searchDispatch,\r\n error,\r\n children,\r\n}) {\r\n // TODO: May want to look at https://react-leaflet.js.org/docs/example-react-control\r\n // for adding this as a proper leaftlet control.\r\n // Keep track of edits internally until the button is pressed.\r\n const [localSearch, localSearchDispatch] = useSearchReducer(\r\n localSearchFields(search));\r\n\r\n useEffect(() => {\r\n localSearchDispatch({\r\n type: 'UPDATE',\r\n payload: localSearchFields(search),\r\n })\r\n }, [search, localSearchDispatch]);\r\n\r\n function submit(event) {\r\n event.preventDefault();\r\n searchDispatch({ type: 'SEARCH', payload: localSearch });\r\n }\r\n\r\n const [isOpen, setIsOpen] = useState(false);\r\n\r\n const filtered = localSearch.dateFrom || localSearch.dateTo;\r\n const isSearching = search.state === 'API' || search.state === 'URL';\r\n\r\n return (\r\n
\r\n
\r\n \r\n \r\n \r\n localSearchDispatch({\r\n type: 'UPDATE',\r\n payload: { query: event.target.value }\r\n })}\r\n />\r\n \r\n setIsOpen(!isOpen)}\r\n />\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
\r\n \r\n {children}\r\n
\r\n );\r\n}\r\n\r\nexport default Search;\r\n\r\n/**\r\n * React component for filter button.\r\n * @param {{filtered: boolean, onClick: function(event)}} param0 Props.\r\n */\r\nfunction FilterButton({ filtered, onClick }) {\r\n let filterButtonClass = 'btn filter-button';\r\n if (filtered) {\r\n filterButtonClass += ' filter-button--filtered';\r\n }\r\n return (\r\n \r\n \r\n \r\n );\r\n}\r\n\r\n/**\r\n * React component for search button.\r\n * @param {{isSearching: boolean}} param0 Props.\r\n */\r\nfunction SearchButton({ isSearching }) {\r\n return (\r\n \r\n );\r\n}\r\n\r\n/**\r\n * React component to display search error.\r\n * \r\n * @param {{isSearching: boolean, error: !Object}} props Props.\r\n */\r\nfunction SearchError({ isSearching, error }) {\r\n return (\r\n <>\r\n {(!isSearching && error) && (\r\n
\r\n An error occurred.\r\n
\r\n )}\r\n \r\n );\r\n}\r\n\r\n/**\r\n * Extract relevant fields from the object to maintain a local copy.\r\n * \r\n * @param {!Object} search The search object.\r\n */\r\nfunction localSearchFields(search) {\r\n const local = {\r\n query: search.query,\r\n dateFrom: search.dateFrom,\r\n dateTo: search.dateTo,\r\n };\r\n\r\n if (search.maxResults != null) {\r\n local.maxResults = search.maxResults;\r\n }\r\n\r\n return local;\r\n}\r\n","// Copyright 2021 Geospatial Intelligence Pty Ltd\r\nimport React from 'react';\r\n\r\nexport const SearchContext = React.createContext({\r\n search: {},\r\n searchDispatch: () => { },\r\n});\r\n","// Copyright 2021 Geospatial Intelligence Pty Ltd\r\nimport React from 'react';\r\nimport { parseISO } from 'date-fns';\r\nimport { Slider, Tooltip } from '@material-ui/core';\r\n\r\n/**\r\n * React component to display a date range with icon.\r\n * \r\n * @param {!Object} props\r\n * @param {string|Array} props.value The date value, or range, in ISO8601 format.\r\n * @param {string} props.min Min value for range, in ISO8601 format.\r\n * @param {string} props.max Max value for range, in ISO8601 format.\r\n * @param {function(!Object, string|Array)} props.onChange Change handler.\r\n * \r\n * @returns {?JSX} A date range slider widget, or null if either dates is null. \r\n */\r\nexport function DateRangeSlider({\r\n value,\r\n min,\r\n max,\r\n onChange,\r\n ValueLabelComponent = CustomValueLabelComponent,\r\n valueLabelFormat = (x) => x,\r\n ...props\r\n}) {\r\n // Only display if we have both dates.\r\n if (!min || !max) {\r\n return null;\r\n }\r\n value = checkValue(value, min, max);\r\n if (typeof valueLabelFormat === 'function') {\r\n const labelFormat = valueLabelFormat;\r\n valueLabelFormat = (val) => labelFormat(apply(convertNumberToIso8601, val));\r\n }\r\n return (\r\n onChange?.(event, apply(convertNumberToIso8601, val))}\r\n ValueLabelComponent={ValueLabelComponent}\r\n valueLabelFormat={valueLabelFormat}\r\n {...props} />\r\n );\r\n}\r\n\r\nexport default DateRangeSlider;\r\n\r\nfunction CustomValueLabelComponent({ children, open, value }) {\r\n return (\r\n \r\n {children}\r\n \r\n );\r\n}\r\n\r\n/**\r\n * Convert an ISO8601 string to ms since epoch.\r\n * \r\n * TODO: Should probably use date-fns.\r\n * \r\n * @param {string} dateString Date string in ISO8601 format.\r\n * @returns {number} Ms since epoch.\r\n */\r\nfunction convertIso8601ToNumber(dateString) {\r\n return parseISO(dateString).getTime();\r\n}\r\n\r\n/**\r\n * Convert ms since epoch to ISO8601 string.\r\n *\r\n * TODO: Should probably use date-fns.\r\n *\r\n * @param {number} dateVal Ms since epoch.\r\n * @returns {string} Date string in ISO8601 format.\r\n */\r\nfunction convertNumberToIso8601(dateVal) {\r\n return new Date(dateVal).toISOString();\r\n}\r\n\r\n/**\r\n * Apply function to single value or array of values.\r\n * \r\n * @param {function(any): any} func Function to apply.\r\n * @param {any|Array} value Single value or array. Func is mapped if array.\r\n * @returns {any|Array} Function over value or mapped over array.\r\n */\r\nfunction apply(func, value) {\r\n if (Array.isArray(value)) {\r\n return value.map((item) => func(item));\r\n }\r\n return func(value);\r\n}\r\n\r\n/**\r\n * Checks value is not null. If null, clamps to [min, max].\r\n * \r\n * NOTE: Does not actually clamp to range.\r\n * TODO: Clamp to range.\r\n * \r\n * @param {any} value Single value or array of [minVal, maxVal].\r\n * @param {any} min Min of valid range.\r\n * @param {any} max Max of valid range.\r\n * @return {any} Replaced null with max or min.\r\n */\r\nfunction checkValue(value, min, max) {\r\n if (Array.isArray(value)) {\r\n return [value[0] || min, value[1] || max];\r\n }\r\n return value || max;\r\n}\r\n","// Copyright 2021 Geospatial Intelligence Pty Ltd\r\nimport React, { useState } from 'react';\r\nimport { FontAwesomeIcon } from '@fortawesome/react-fontawesome';\r\nimport { faAngleLeft, faAngleRight } from '@fortawesome/free-solid-svg-icons';\r\n\r\n\r\nexport default function SidePane({\r\n children,\r\n width = 320,\r\n hide = 'none',\r\n ...props\r\n}) {\r\n const [isOpen, setIsOpen] = useState(true);\r\n const paneStyle = {\r\n display: 'flex',\r\n flex: '0 0',\r\n transition: '300ms ease-out',\r\n };\r\n // Adjust margins to hide/show pane, and to include toggle.\r\n const marginOffset = isOpen ? 0 : -width;\r\n const buttonOffset = -14;\r\n if (hide === 'left') {\r\n paneStyle.marginLeft = marginOffset;\r\n paneStyle.marginRight = buttonOffset;\r\n } else if (hide === 'right') {\r\n paneStyle.marginRight = marginOffset;\r\n paneStyle.marginLeft = buttonOffset;\r\n }\r\n const contentsStyle = {\r\n display: 'flex',\r\n flexDirection: 'column',\r\n maxHeight: '100%',\r\n overflow: 'auto',\r\n width,\r\n };\r\n return (\r\n
\r\n {hide === 'right' &&\r\n \r\n }\r\n
\r\n {children}\r\n
\r\n {hide === 'left' &&\r\n \r\n }\r\n
\r\n );\r\n}\r\n\r\nconst SidePaneToggle = ({\r\n isOpen,\r\n setIsOpen,\r\n direction = 'left',\r\n borderRadius = '.5rem',\r\n}) => {\r\n const buttonStyle = {\r\n backgroundColor: 'var(--nav-background',\r\n color: 'var(--nav-color)',\r\n border: 'none',\r\n outline: 'none',\r\n padding: '1px 3px',\r\n height: '3rem',\r\n margin: 'auto',\r\n zIndex: 1000,\r\n // Default to left, but override with right later.\r\n borderTopRightRadius: borderRadius,\r\n borderBottomRightRadius: borderRadius,\r\n };\r\n let openIcon = faAngleLeft;\r\n let closedIcon = faAngleRight;\r\n if (direction === 'right') {\r\n buttonStyle.borderTopLeftRadius = borderRadius;\r\n buttonStyle.borderBottomLeftRadius = borderRadius;\r\n openIcon = faAngleRight;\r\n closedIcon = faAngleLeft;\r\n }\r\n return (\r\n setIsOpen(!isOpen)}\r\n style={buttonStyle}\r\n aria-label=\"Side pane toggle.\"\r\n >\r\n \r\n \r\n );\r\n}\r\n","import React, { useEffect, useMemo, useState } from 'react';\r\nimport { Helmet } from 'react-helmet';\r\nimport PressAtlasMap from '../components/mapping/PressAtlasMap';\r\nimport NewsResults from '../components/news/NewsResults';\r\nimport { Search, SearchContext } from '../components/search';\r\nimport DateRangeSlider from '../components/DateRangeSlider';\r\nimport SidePane from '../components/SidePane';\r\nimport {\r\n useMapBoundsSearch, useSearchNews, useSelection, useUrlSyncedSearchReducer,\r\n} from '../hooks';\r\n\r\nexport function PressAtlas() {\r\n const [search, searchDispatch] = useUrlSyncedSearchReducer({\r\n maxResults: 100,\r\n });\r\n const { selection, setSelection, selectPlace } = useSelection();\r\n // Make sure the map bounds are added to search when appropriate.\r\n // TODO: This originally comes from using ArcGIS where getting bounds was an\r\n // async operation, it may be possible to do this more synchronously.\r\n const [map, setMap] = useState();\r\n useMapBoundsSearch(map, search, searchDispatch);\r\n\r\n const newsResults = useSearchNews(search, searchDispatch);\r\n\r\n // If the news changes, reset the selection.\r\n useEffect(() => setSelection(null), [newsResults.news, setSelection]);\r\n\r\n const [dateMin, dateMax] = useMemo(() => {\r\n if (search.dateFrom && search.dateTo) {\r\n return [search.dateFrom, search.dateTo];\r\n }\r\n const newsDateMin = search.dateFrom || newsResults.news.reduce(\r\n (acc, cur) => acc && acc < cur.rssDate ? acc : cur.rssDate, null);\r\n const newsDateMax = search.dateTo || newsResults.news.reduce(\r\n (acc, cur) => acc && acc > cur.rssDate ? acc : cur.rssDate, null);\r\n // TODO: Do something about same dates.\r\n return [newsDateMin, newsDateMax];\r\n }, [search.dateFrom, search.dateTo, newsResults.news]);\r\n\r\n const [filterDateRange, setFilterDateRange] = useState([dateMin, dateMax]);\r\n\r\n useEffect(() => {\r\n setFilterDateRange([dateMin, dateMax]);\r\n }, [dateMin, dateMax]);\r\n\r\n // Filter by filter dates. TODO: May need to do something smarter for performance.\r\n const filteredNews = useMemo(() =>\r\n (newsResults.news\r\n // TODO: Should use a proper date comparison - likely to cut equal matches.\r\n .filter(article =>\r\n article.rssDate >= filterDateRange[0]\r\n && article.rssDate <= filterDateRange[1])\r\n ?? []),\r\n [newsResults.news, filterDateRange]);\r\n\r\n // Create places based on filtered news.\r\n const places = useMemo(\r\n () => filteredNews.flatMap(\r\n (article) => article.primaryPlaces.map((place) => ({ ...place, ...article }))),\r\n [filteredNews]);\r\n\r\n return (\r\n \r\n \r\n Search News - PressAtlas\r\n \r\n \r\n
\r\n \r\n \r\n \r\n
\r\n \r\n \r\n setFilterDateRange(val)}\r\n valueLabelDisplay=\"auto\"\r\n valueLabelFormat={(x) => x}\r\n />\r\n \r\n
\r\n
\r\n
\r\n );\r\n}\r\n\r\nexport default PressAtlas;\r\n"],"sourceRoot":""}