\n);\n\nDatasetMessage.propTypes = {\n message: PropTypes.string,\n};\n\nDatasetMessage.defaultProps = {\n message: '',\n};\n\nexport default DatasetMessage;\n","import React from 'react';\nimport videojs from 'video.js';\nimport 'video.js/dist/video-js.css';\nimport PropTypes from 'prop-types';\n\nconst VideoJS = (props) => {\n const videoRef = React.useRef(null);\n const { options } = props;\n\n // This seperate functional component fixes the removal of the videoelement\n // from the DOM when calling the dispose() method on a player\n const VideoHtml = () => (\n
\n \n
\n );\n\n React.useEffect(() => {\n const videoElement = videoRef.current;\n let player;\n if (videoElement) {\n player = videojs(videoElement, options, () => {\n });\n }\n return () => {\n if (player) {\n player.dispose();\n }\n };\n }, [options]);\n\n return ();\n};\n\nVideoJS.propTypes = {\n options: PropTypes.object,\n};\n\nVideoJS.defaultProps = {\n options: {},\n};\n\nexport default VideoJS;\n","import React from 'react';\nimport VideoJS from '../components/video-js';\n\nconst videoJsOptions = (mediaURL) => (\n {\n autoplay: false,\n controls: true,\n responsive: true,\n fluid: true,\n sources: [{\n src: mediaURL,\n type: 'video/mp4',\n }],\n }\n);\n\nexport const videoOrImage = (mediaURL, isPopUp, isMobile = true) => {\n const urlSplit = mediaURL.split('.');\n const urlExt = urlSplit[urlSplit.length - 1].toLowerCase();\n if (urlExt === 'png' || urlExt === 'jpg' || urlExt === 'gif') {\n if (isPopUp) {\n return (\n \n \n \n );\n }\n return (\n \n \n \n );\n }\n return (\n \n );\n};\n","import React, { memo } from 'react';\nimport PropTypes from 'prop-types';\nimport { Statistic } from 'antd';\nimport {\n CaretUpFilled,\n ClockCircleOutlined,\n SafetyCertificateFilled,\n} from '@ant-design/icons';\nimport moment from 'moment';\nimport DegreesToCardinal from './DegreesToCardinal';\nimport { brandingConfig } from '../../config';\nimport './styles.scss';\nimport ParameterColors from './parameterColors';\nimport { getParameterGroupIcon } from '../../utils';\n\nconst QualifiedParameter = (props) => {\n const {\n icon,\n date,\n minimized,\n name,\n nameEllipsis,\n value,\n unit,\n meta,\n style,\n metricVisible,\n dataVerifiedVisible,\n onClick,\n isMobile,\n isSnapshot,\n pinned,\n standardName,\n parameterName,\n } = props;\n\n // ESP Qualifiers\n // I do note that ESP data and metadata are incredibly difficult to create\n // a general interface for, and do invite proposals for how to achieve it.\n // That said, having a qualified parameter object is my proposal to begin\n // development of a solution to those and similar aims. If there are other\n // unique parameters that come up with special qualifiers, then we can\n // codify the logic of them here.\n // -Joe Smith (GLOS)\n\n const espIsEstimate = meta?.length > 0 ? meta.filter((q) => q.standard_name === 'microcystin_is_estimate') : null;\n const espLLOD = meta?.length > 0 ? meta.filter((q) => q.standard_name === 'microcystin_llod') : null;\n const espLLOQ = meta?.length > 0 ? meta.filter((q) => q.standard_name === 'microcystin_lloq') : null;\n const espIsGtULOQ = meta?.length > 0 ? meta.filter((q) => q.standard_name === 'microcystin_lt_llod') : null;\n const espIsLtLLOD = meta?.length > 0 ? meta.filter((q) => q.standard_name === 'microcystin_gt_uloq') : null;\n\n const isTop = parameterName.includes('top_');\n const isBottom = parameterName.includes('bottom_');\n const wantDepthParamAdjective = false;\n\n // End ESP Qualifiers\n\n const getShortName = (longName) => {\n if (longName === 'Microcystin concentration') {\n return 'Microcystin';\n }\n return longName;\n };\n\n const IconComponent = () => (\n icon ? (\n \n {icon}\n \n \n ) : ''\n );\n\n const formattedValue = DegreesToCardinal(value, unit);\n\n return (\n minimized\n ? (\n
\n Check out our brand new\n \n 'omics data\n \n about harmful algal blooms in the Great Lakes.\n
\n )\n : (\n
\n Check out our brand new\n \n 'omics data\n \n about harmful algal blooms in the Great Lakes.\n
\n )}\n \n \n )}\n {/*
\n
\n {!isLarge && ()}\n
\n
\n \n We'd like to hear from you!\n \n \n Tell us how you interact with Seagull and stand a chance to win a Yeti cooler! Your feedback is incredibly valuable to us.\n {' '}\n \n Start Survey\n \n \n
\n Welcome to Seagull, your Great Lakes discovery platform for information and data! Explore current conditions, forecasts, and more. Plan your next adventure with confidence — Seagull puts Great Lakes data at your fingertips. Navigate with ease and discover the rich resources of these magnificent waters.\n
\n \n
\n \n
\n >\n \n );\n};\n\nexport default SocialShareRecipientModal;\n","import React, { useEffect, useState, useContext } from 'react';\nimport { useQuery } from 'react-query';\nimport moment from 'moment';\nimport 'mapbox-gl/dist/mapbox-gl.css';\nimport mapboxgl from 'mapbox-gl';\nimport { GeolocateControl, NavigationControl, ScaleControl } from 'react-map-gl';\n\nimport { useHistory, useLocation } from 'react-router';\nimport lodash from 'lodash';\nimport ReactGA from 'react-ga4';\nimport { Spin } from 'antd';\nimport MediaQuery from 'react-responsive';\n\nimport MapControls from './components/MapControls';\nimport MapFilterPopup from './components/MapFilterPopup';\nimport CustomMap from '../../components/custom-map';\nimport Landing from '../landing/index';\nimport appConfig, { brandingConfig } from '../../config/index';\n\nimport { MapEvent, MapFilterEvent } from '../../constant/google-analytics/constant';\nimport {\n platformTags, waterTags, weatherTags, orgTags, overlayTags,\n} from './constant';\nimport {\n datasets,\n getObsDatasetsOrgGeojson,\n getDatasetSummaries,\n getObsLatestData,\n parameterConfigurations,\n datasetTrajectoriesGeojson,\n getRegisteredParameters,\n getDatasetsLatest,\n} from '../../services/dataset.service';\nimport { getOrganizations } from '../../services/organization.service';\nimport MapQueryTypes from '../../services/query-types/map';\nimport { getLatestParameterDate, platformGroupings } from '../../utils';\nimport ProfileQueryTypes from '../../services/query-types/profile';\nimport { getUserFavoritePlatforms } from '../../services/profile.service';\nimport UserContext from '../../contexts/UserContext';\n\nimport './styles.scss';\nimport { useData } from '../../contexts/DataContext';\nimport SocialShareRecipientModal from './components/SocialShareRecipientModal';\n\n// This was recommended as a fix for an issue with the basemap from Mapbox not loading\n// https://github.com/mapbox/mapbox-gl-js/issues/10173#issuecomment-799627909\n// eslint-disable-next-line import/no-webpack-loader-syntax\nmapboxgl.workerClass = require('worker-loader!mapbox-gl/dist/mapbox-gl-csp-worker').default;\n\nconst scaleControlStyle = {\n bottom: 36,\n left: 0,\n padding: '10px',\n};\n\nconst Dashboard = () => {\n const history = useHistory();\n const location = useLocation();\n\n const { cognitoUser } = useContext(UserContext);\n\n const [filterVisible, setFilterVisible] = useState(false);\n const [searchDrawer, toggleSearchDrawer] = useState(true);\n const [editing, setEditing] = useState(false);\n const [selectedPlatform, setSelectedPlatform] = useState();\n const [selectedOverlay, setSelectedOverlay] = useState('surface_water_temperature');\n const [showPaths, setShowPaths] = useState(true);\n const [omicsFilters, setOmicsFilters] = useState({\n selectedFilter: 'All',\n selectedStartDate: moment('2010-01-01'),\n selectedEndDate: moment(),\n });\n const [showShareModal, setShowShareModal] = useState(false);\n\n const {\n useGlobalData,\n } = useData();\n\n const { observationalPlatformsResult } = useGlobalData();\n\n const handleLandingChange = () => {\n toggleSearchDrawer(false);\n history.replace('/map');\n };\n\n useEffect(() => {\n if (location.pathname === '/landing') {\n toggleSearchDrawer(true);\n } else {\n toggleSearchDrawer(false);\n }\n }, [location.pathname]);\n\n // List of all platform tags\n const [tagsList, setTagsList] = useState({\n platforms: [],\n weather: [],\n org: [],\n water: [],\n });\n\n const defaultPlatformTags = brandingConfig.filters.isNest\n ? platformTags.filter((platform) => platform.key === 'buoy' || platform.key === 'tower' || platform.key === 'nest')\n : platformTags.filter((platform) => platform.key === 'buoy' || platform.key === 'tower');\n\n // Tags displayed on the map\n // Filter by buoys by default\n // const [filterTags, setFilterTags] = useState(platformTags.filter((platform) => platform.key === 'buoy'));\n // *** Make sure what you have for filterTags here matches what's in selectedTags below ***\n const [filterTags, setFilterTags] = useState(\n defaultPlatformTags.concat(\n overlayTags.filter((overlay) => overlay.key === 'surface_water_temperature'),\n ),\n );\n const [selectedLake, setSelectedLake] = useState(brandingConfig.filters.defaultLake);\n\n const [selectedTags, setSelectedTags] = useState({\n platforms: defaultPlatformTags,\n weather: weatherTags.filter((weather) => weather.key === 'surface_water_temperature'),\n org: [],\n water: [],\n });\n const [platformEvents, setPlatformEvents] = useState(['activated', 'unavailable']);\n const [filterFavorites, setFilterFavorites] = useState(false);\n const [filteredPlatforms, setFilteredPlatforms] = useState(\n platformTags.filter((platform) => platform.key === 'buoy').concat(\n platformTags.filter((platform) => platform.key === 'tower'),\n ),\n );\n\n const {\n data: omicsObsDatasetsGeojson,\n isLoading: omicsObsDatasetsGeojsonIsLoading,\n } = useQuery(\n [MapQueryTypes.REST_OBSERVATIONAL_PLATFORMS_BY_ORG],\n () => getObsDatasetsOrgGeojson(parseInt(process.env.REACT_APP_OMICS_ORG_ID, 10)),\n { refetchOnWindowFocus: false },\n );\n\n const {\n data: organizationsResult,\n isLoading: organizationLoading,\n } = useQuery(MapQueryTypes.REST_ORGANIZATIONS, getOrganizations, { refetchOnWindowFocus: false });\n\n const {\n data: datasetsResult,\n isLoading: datasetsLoading,\n } = useQuery(MapQueryTypes.REST_OBS_DATASETS, datasets, { refetchOnWindowFocus: false });\n\n const {\n data: obsDatasetSummariesResult,\n isLoading: summariesLoading,\n } = useQuery(MapQueryTypes.REST_OBS_DATASET_SUMMARIES, () => getDatasetSummaries(), { refetchOnWindowFocus: false });\n\n const {\n data: obsLatest,\n isLoading: obsLatestIsLoading,\n } = useQuery(MapQueryTypes.REST_OBSERVATIONAL_LATEST_DATA, () => getObsLatestData(), { refetchOnWindowFocus: false });\n\n const {\n data: parameterConfigurationsResult,\n } = useQuery(MapQueryTypes.REST_PARAMETER_CONFIGURATIONS, parameterConfigurations, { refetchOnWindowFocus: false });\n\n const {\n data: registeredParametersResult,\n isLoading: registeredParametersAreLoading,\n } = useQuery(MapQueryTypes.REST_PARAMETERS_REGISTERED_DASHBOARD, getRegisteredParameters, { refetchOnWindowFocus: false });\n\n const {\n data: datasetTrajectoriesGeojsonResult,\n } = useQuery(\n MapQueryTypes.REST_OBSERVATIONAL_DATASET_TRAJECTORIES,\n datasetTrajectoriesGeojson,\n {\n refetchOnWindowFocus: false,\n enabled: filterTags?.some((t) => t.key === 'moving'),\n },\n );\n\n const {\n data: favoritePlatformData,\n } = useQuery(\n ProfileQueryTypes.REST_PROFILE_FAVORITE_PLATFORMS,\n getUserFavoritePlatforms,\n {\n refetchOnWindowFocus: false,\n enabled: Boolean(cognitoUser),\n },\n );\n\n const [omicsPlatformIds, setOmicsPlatformIds] = useState([]);\n const [omicsParameterIds, setOmicsParameterIds] = useState([]);\n\n const {\n data: obsDatasetsLatest,\n refetch: obsDatasetsLatestRefetch,\n isInitialLoading: obsDatasetsLatestIsInitialLoading,\n } = useQuery(\n [MapQueryTypes.REST_OBS_DATASETS_LATEST, omicsFilters.startDate, omicsFilters.endDate, omicsPlatformIds, omicsParameterIds],\n () => getDatasetsLatest({\n platformId: omicsPlatformIds,\n parameterId: omicsParameterIds,\n startDate: omicsFilters.startDate.format('YYYY-MM-DD'),\n endDate: omicsFilters.endDate.format('YYYY-MM-DD'),\n includeObservations: true,\n }),\n {\n refetchOnWindowFocus: false,\n enabled: (!!omicsFilters.startDate && !!omicsFilters.endDate && !!omicsPlatformIds?.length && !!omicsParameterIds?.length),\n },\n );\n\n useEffect(() => {\n filterTags.forEach((tag) => {\n if (tag.overlay && tag.key === selectedOverlay) {\n const nonOverlay = filterTags.filter((o) => o.key !== 'surface_water_temperature' && o.key !== 'water_current' && o.key !== 'wind_current' && o.key !== 'omics');\n setFilterTags([...nonOverlay, tag]);\n }\n });\n\n if (selectedOverlay === 'omics') {\n setOmicsFilters({\n selectedFilter: 'All',\n startDate: moment('2010-01-01'),\n endDate: moment(),\n });\n } else {\n setOmicsFilters({\n selectedFilter: 'All',\n startDate: '',\n endDate: '',\n });\n }\n }, [selectedOverlay]);\n\n const setOmicsQueryParams = () => {\n if (obsDatasetSummariesResult) {\n // TODO when the time comes: generalize to other Omics orgs\n const omicsSummaries = obsDatasetSummariesResult.filter((summary) => summary?.obs_dataset_platform_assignment?.platform?.external_id_type === 'omics_sample_site');\n const uniquePlatformIds = lodash.uniq(omicsSummaries.map((summary) => summary.obs_dataset_platform_assignment?.platform?.platform_id));\n const uniqueParamIds = lodash.uniq(omicsSummaries.flatMap((summary) => summary.obs_dataset_platform_assignment?.platform?.parameters?.map((parameter) => parameter.parameter_id)));\n setOmicsPlatformIds(uniquePlatformIds);\n setOmicsParameterIds(uniqueParamIds);\n }\n };\n\n const getAndFormatTagsList = (filteredData) => {\n const tagsListData = {};\n\n tagsListData.platformTags = platformTags.map((tag) => {\n const tagCount = filteredData.filter((feature) => feature.properties.platform_type.includes(tag.key)).length;\n return { ...tag, text: `${tag.text} ${tagCount ? ` (${tagCount})` : ''}`, count: tagCount };\n });\n\n // only show nest tags in nests\n if (!brandingConfig.filters.isNest) {\n tagsListData.platformTags = tagsListData.platformTags.filter((tag) => tag.type !== 'nest');\n }\n\n tagsListData.orgTags = orgTags.map((tag) => ({ ...tag, text: `${tag.text}` }));\n tagsListData.weatherTags = weatherTags.map((tag) => ({ ...tag, text: `${tag.text}` }));\n tagsListData.waterTags = waterTags.map((tag) => ({ ...tag, text: `${tag.text}` }));\n\n setTagsList(tagsListData);\n return tagsListData;\n };\n\n const filterPlatforms = () => {\n if (observationalPlatformsResult) {\n let filteredData = observationalPlatformsResult;\n if (datasetTrajectoriesGeojsonResult) {\n filteredData.concat(datasetTrajectoriesGeojsonResult.features);\n }\n\n if (selectedLake) {\n const selectedLakeStr = `lake-${selectedLake.toLowerCase()}`;\n filteredData = filteredData.filter((o) => o.properties.body_of_water === selectedLakeStr);\n }\n\n // For each platform, if platform_event==='activated' but there is no recent data,\n // set platform_event='unavailable'\n if (obsLatest && obsDatasetSummariesResult) {\n for (let i = 0; i < filteredData.length; i++) {\n // Getting platform external_id_type from dataset-summaries,\n // but could in the future potentially be included in the obs-datasets.geojson\n // Since we are re-assigning platform_event later in this loop, eslint considers any access of filteredData[i] unsafe,\n // but should be ok for now.\n\n // eslint-disable-next-line no-loop-func\n const datasetSummary = obsDatasetSummariesResult.find((o) => o.obs_dataset_id === filteredData[i].properties.obs_dataset_id);\n const platform = datasetSummary?.obs_dataset_platform_assignment?.platform;\n\n // add the platform to the properties so we can access it later\n filteredData[i].properties.platform = platform;\n\n const latestDate = getLatestParameterDate(filteredData[i].properties, obsLatest, true);\n const timeSince = moment().diff(moment(latestDate), 'hours');\n filteredData[i].properties.last_updated_parameter_in_hours = timeSince;\n\n // timeSince will be NaN if latestDate is null, so check for this first.\n // Exclude ESP sites from this event re-assignment. In the future this may be a metadata configuration.\n if (\n (Number.isNaN(timeSince) || timeSince > appConfig.oldDataCutoffInHours)\n && filteredData[i].properties?.platform_event === 'activated'\n && platform?.external_id_type !== 'esp_site'\n ) {\n filteredData[i].properties.platform_event = 'unavailable';\n }\n }\n }\n\n if (platformEvents && platformEvents.length > 0) {\n filteredData = filteredData.filter((o) => platformEvents.indexOf(o.properties.platform_event) > -1);\n\n /* Hide Decommissioned platforms by default */\n if (platformEvents.indexOf('decommissioned') === -1) {\n filteredData = filteredData.filter((o) => o.properties.platform_event !== 'decommissioned');\n }\n }\n\n const formattedTagsList = getAndFormatTagsList(filteredData);\n\n if (obsDatasetSummariesResult && brandingConfig.filters.isNest) {\n // filter platforms data by selected nest tags\n const nestFilter = filterTags.some((t) => t.key === 'nest');\n const seagullFilter = filterTags.some((t) => t.key === 'seagull');\n const nestDatasetIds = obsDatasetSummariesResult\n .filter((o) => o?.organization_id === brandingConfig.filters.organizationId)\n .map((o) => o.obs_dataset_id);\n\n // if filtering by nest platforms and seagull platforms have not been added,\n // only show platforms within the nest organization\n if (nestFilter && !seagullFilter) {\n filteredData = filteredData.filter((o) => nestDatasetIds.includes(o.properties.obs_dataset_id));\n }\n\n // if nest filter is not selected, remove nest platforms\n if (!nestFilter) {\n filteredData = filteredData.filter((o) => !nestDatasetIds.includes(o.properties.obs_dataset_id));\n }\n }\n\n // filter platforms data by selected platform_type tags\n const filterTagsNew = filterTags.filter((o) => o.key !== 'surface_water_temperature' && o.key !== 'water_current' && o.key !== 'wind_current' && o.key !== 'omics' && o.key !== 'sampling_location');\n if (filterTagsNew) {\n if (filterTagsNew.length > 0) {\n filteredData = filteredData.filter((o) => filterTagsNew.length > 0\n && filterTagsNew.some((tag) => platformGroupings[tag.key]?.includes(o.properties.platform_type)));\n } else {\n filteredData = [];\n }\n } else {\n filteredData = [];\n }\n\n // Get/Isolate the omics data/sampling data first. TODO: Generalize\n // Namely because the date filter needs to run ONLY through omics/sampling data, not the rest\n if (omicsObsDatasetsGeojson) {\n const omicsData = omicsObsDatasetsGeojson.features;\n if (formattedTagsList?.orgTags?.some((t) => t.key === 'omics')) {\n if (obsDatasetsLatest?.length) {\n // remove platforms that aren't in obsDatasetsLatest\n const visibleDatasetIds = obsDatasetsLatest.map((o) => o.obs_dataset_id);\n const visibleSamplingPlatforms = omicsData\n .filter((p) => visibleDatasetIds.includes(p.properties.obs_dataset_id) && platformGroupings.sampling_location.includes(p.properties.platform_type))\n .reduce((acc, obj) => {\n const coordinates = obj.geometry.coordinates.join(',');\n const existingObj = acc.find((o) => o.geometry.coordinates.join(',') === coordinates);\n if (!existingObj || obj.properties.last_updated_parameter_in_hours < existingObj.properties.last_updated_parameter_in_hours) {\n return [...acc.filter((o) => o.geometry.coordinates.join(',') !== coordinates), obj];\n }\n return acc;\n }, []);\n filteredData = visibleSamplingPlatforms.concat(filteredData);\n }\n }\n }\n\n setFilteredPlatforms(filteredData);\n }\n };\n\n useEffect(() => {\n if (obsDatasetSummariesResult) {\n setOmicsQueryParams();\n }\n }, [obsDatasetSummariesResult]);\n\n useEffect(() => {\n if (!registeredParametersResult?.length || !parameterConfigurationsResult?.length) {\n return;\n }\n\n if (omicsFilters.filter === 'All') {\n setOmicsQueryParams();\n } else {\n const parameter = parameterConfigurationsResult?.find((p) => p.display_name.en.includes(omicsFilters.filter));\n if (parameter) {\n const parameterIds = registeredParametersResult\n .filter((p) => p?.standard_name === parameter.standard_name)\n .map((p) => p.parameter_id);\n\n setOmicsParameterIds(parameterIds);\n } else {\n // eslint-disable-next-line no-console\n console.warn('No parameter found for selected filter');\n setOmicsQueryParams();\n }\n }\n }, [omicsFilters.filter]);\n\n const urlSearchQuery = new URLSearchParams(location.search);\n\n if (urlSearchQuery.get('lake') && urlSearchQuery.get('lake') !== selectedLake) {\n setSelectedLake(urlSearchQuery.get('lake'));\n }\n\n if (urlSearchQuery.get('status') && !lodash.isEqual(urlSearchQuery.get('status').split(','), platformEvents)) {\n setPlatformEvents(urlSearchQuery.get('status').split(','));\n }\n\n if (urlSearchQuery.get('tags')) {\n const tagsQuery = {};\n urlSearchQuery.get('tags').split(',').forEach((o) => {\n const [tag, val] = o.split(':');\n switch (tag) {\n case 'platforms':\n tagsQuery.platforms = platformTags.filter((p) => val.includes(p.key));\n break;\n case 'overlay':\n tagsQuery.overlay = overlayTags.filter((p) => val.includes(p.key));\n break;\n case 'water':\n tagsQuery.water = waterTags.filter((p) => val.includes(p.key));\n break;\n case 'org':\n tagsQuery.org = orgTags.filter((p) => val.includes(p.key));\n break;\n case 'weather':\n tagsQuery.weather = weatherTags.filter((p) => val.includes(p.key));\n break;\n default:\n tagsQuery.favorite = [];\n }\n });\n\n if (tagsQuery.water?.length) {\n if (tagsQuery.water[0].key !== selectedOverlay) {\n setSelectedOverlay(tagsQuery.water[0].key);\n }\n }\n\n if (tagsQuery.weather?.length) {\n if (tagsQuery.weather[0].key !== selectedOverlay) {\n setSelectedOverlay(tagsQuery.weather[0].key);\n }\n }\n\n if (tagsQuery.org?.length) {\n if (tagsQuery.org[0].key !== selectedOverlay) {\n setSelectedOverlay(tagsQuery.org[0].key);\n }\n }\n\n if (!lodash.isEqual(selectedTags, tagsQuery)) {\n setSelectedTags(tagsQuery);\n setFilterTags(Object.keys(tagsQuery).reduce(\n (prev, cur) => [...prev, ...tagsQuery[cur]],\n [],\n ));\n }\n }\n\n useEffect(() => {\n if (omicsParameterIds?.length && omicsPlatformIds?.length) {\n obsDatasetsLatestRefetch();\n }\n }, [omicsPlatformIds, omicsParameterIds, obsDatasetSummariesResult]);\n\n useEffect(() => {\n if (urlSearchQuery.has('editing') && urlSearchQuery.get('editing') === '1') {\n setEditing(true);\n }\n }, [urlSearchQuery]);\n\n useEffect(() => {\n if (urlSearchQuery.has('favorite_platform') && observationalPlatformsResult) {\n const platform = observationalPlatformsResult.features.find((o) => o.properties.org_platform_id === (urlSearchQuery.get('favorite_platform')));\n setFilterVisible(true);\n\n if (platform) {\n setSelectedPlatform(platform.properties);\n }\n }\n }, [urlSearchQuery, observationalPlatformsResult]);\n\n useEffect(() => {\n filterTags.forEach((tag) => {\n if (tag.overlay && tag.key === selectedOverlay) {\n const nonOverlay = filterTags.filter((o) => (\n o.key !== 'surface_water_temperature'\n && o.key !== 'water_current'\n && o.key !== 'wind_current'\n && o.key !== 'omics'\n && o.key !== 'bathy'\n ));\n setFilterTags([...nonOverlay, tag]);\n }\n });\n }, [selectedOverlay]);\n\n useEffect(() => {\n filterPlatforms();\n }, [\n observationalPlatformsResult,\n platformEvents,\n selectedTags,\n selectedLake,\n datasetTrajectoriesGeojsonResult,\n obsDatasetSummariesResult,\n obsDatasetsLatest,\n obsLatest,\n ]);\n\n const pushToHistory = (key, value) => {\n let newParams = '';\n // location from useLocation is out of date, so opted to use window.location\n if (window.location.search !== '') {\n const params = new URLSearchParams(window.location.search);\n\n if (!value) {\n params.delete(key);\n } else {\n params.set(key, value);\n }\n\n newParams = params.toString();\n } else if (value) {\n const params = new URLSearchParams({ [key]: value });\n newParams = params.toString();\n }\n\n history.replace({\n pathname: '/map',\n search: `?${newParams}`,\n });\n };\n\n const onLakeSelect = (lake) => {\n setSelectedLake(lake);\n // don't reset tags if the user is selecting a lake from the map for now\n // setFilterTags([]);\n // setSelectedTags({\n // platforms: [selectedTags.platforms],\n // weather: [selectedTags.weather],\n // org: [selectedTags.org],\n // water: [],\n // });\n pushToHistory('lake', lake);\n ReactGA.event({ ...MapFilterEvent.LakeFilterSelect, label: `${lake}` });\n };\n\n const showFilter = () => {\n setFilterVisible(true);\n };\n\n const onClose = () => {\n setFilterVisible(false);\n };\n\n const onStatusSelect = (statusList) => {\n const selectedStatuses = statusList.map((stat) => stat.toLowerCase().split(' ').join('_'));\n setPlatformEvents(selectedStatuses);\n\n pushToHistory('status', selectedStatuses.join(','));\n ReactGA.event({ ...MapFilterEvent.PlatformStatusFilter, label: `${statusList}` });\n };\n\n const handleClearFilters = () => {\n setSelectedTags({\n platforms: [],\n weather: [],\n org: [],\n water: [],\n });\n setFilterTags([]);\n setSelectedLake('');\n setSelectedOverlay('');\n setPlatformEvents([]);\n\n history.replace('/map');\n ReactGA.event(MapFilterEvent.ClearFilters);\n };\n\n const handleClearLakeFilters = () => {\n const newHistory = history.location.search.replace(`lake=${selectedLake}`, '');\n history.replace(newHistory);\n setSelectedLake('');\n ReactGA.event(MapFilterEvent.ClearLakeFilters);\n };\n\n const onTagSelect = (tag, checked, type) => {\n if (tag.overlay) {\n if (tag.forecast && tag.key === selectedOverlay) {\n setSelectedOverlay('');\n setSelectedTags({\n platforms: [selectedTags.platforms],\n weather: [tag.key],\n org: [],\n water: [],\n });\n } else if (tag.key === selectedOverlay) {\n setSelectedOverlay('');\n setSelectedTags({\n platforms: [selectedTags.platforms],\n weather: [],\n org: [tag.key],\n water: [],\n });\n } else {\n setSelectedTags({\n platforms: [selectedTags.platforms],\n weather: [],\n org: [],\n water: [],\n });\n setSelectedOverlay('');\n setSelectedOverlay(tag.key);\n }\n }\n\n const addTags = type === 'water' || type === 'weather' || type === 'org' ? [tag] : [...selectedTags[type], tag];\n\n if (!selectedTags) {\n // eslint-disable-next-line no-console\n console.warn('need to hanlde no tags selected');\n }\n\n const nextSelectedTags = checked\n ? addTags\n : selectedTags[type].filter((t) => t.key !== tag.key);\n\n const newSelectedTags = type === 'weather'\n ? { ...selectedTags, org: [], [type]: nextSelectedTags }\n : type === 'org'\n ? { ...selectedTags, weather: [], [type]: nextSelectedTags }\n : { ...selectedTags, [type]: nextSelectedTags };\n\n if (tag.key === 'omics') {\n const samplingLocationTag = tagsList.platformTags.find((t) => t.key === 'sampling_location');\n newSelectedTags.platforms.push(samplingLocationTag);\n }\n\n const tags = Object.keys(newSelectedTags).reduce(\n (prev, cur) => [...prev, ...newSelectedTags[cur]],\n [],\n );\n\n setFilterTags(tags);\n setSelectedTags(newSelectedTags);\n pushToHistory('tags', Object.keys(newSelectedTags).map((key) => `${key}:${newSelectedTags[key].map((o) => o.key).join('-')}`).join(','));\n ReactGA.event({ ...MapFilterEvent.TagSelect, label: `${tag.key}` });\n };\n\n const onTagRemove = (tag) => {\n if (tag.overlay) {\n setSelectedOverlay('');\n }\n const newSelectedTags = { ...selectedTags }; // OK as ref\n const keys = Object.keys(newSelectedTags);\n for (let i = 0; i < keys.length; i++) {\n const tagIdx = newSelectedTags[keys[i]] && newSelectedTags[keys[i]].length > 0\n ? newSelectedTags[keys[i]].map((item) => item.key).indexOf(tag.key)\n : -1;\n\n if (tagIdx > -1) {\n setSelectedTags({ ...selectedTags }[keys[i]].splice(tagIdx, 1));\n\n setFilterTags(filterTags.filter((item) => item.key !== tag.key));\n pushToHistory('tags', Object.keys(newSelectedTags).map((key) => `${key}:${newSelectedTags[key].map((o) => o.key).join('-')}`).join(','));\n ReactGA.event({ ...MapFilterEvent.TagRemoved, label: `${tag.key}` });\n break;\n }\n }\n };\n\n const trackUserDidGeolocate = () => {\n ReactGA.event(MapEvent.UserDidGeolocate);\n };\n\n const isLoading = omicsObsDatasetsGeojsonIsLoading || organizationLoading || summariesLoading || obsLatestIsLoading || datasetsLoading || obsDatasetsLatestIsInitialLoading || registeredParametersAreLoading;\n\n const onClickShare = () => {\n setShowShareModal(true);\n };\n\n return (\n isLoading\n ? (\n
\n {\n row.dataset.platform.platform_event\n ? `This platform has been ${row.dataset.platform.platform_event?.event.split('_').join(' ')} and is not reporting data`\n : 'There is no reporting data'\n }\n \n )}\n \n
\n );\n};\n\nexport default Announcements;\n","import React, { useState } from 'react';\nimport {\n Badge, Card, Col, List, Row, Skeleton, Table, Typography, Tabs,\n} from 'antd';\nimport { RightOutlined } from '@ant-design/icons';\nimport { Link as RouterLink, useHistory, useLocation } from 'react-router-dom';\nimport moment from 'moment';\nimport PropTypes from 'prop-types';\nimport PreferenceForm from '../../preferences/components/PreferenceForm';\nimport { notificationTypeOptions, notificationFrequencyOptions } from '../../preferences/constants';\nimport { brandingConfig } from '../../../../config';\nimport { columns } from '../config';\nimport Announcements from './Announcements';\n\nconst { Title } = Typography;\n\nexport const AlertTabs = (props) => {\n const {\n isMobile, onShowDetail, setHistoryDetail, alertInitLoading, loadMore, alertHistoryList, getAlertIcon, alertsData,\n } = props;\n const history = useHistory();\n const { hash } = useLocation();\n\n const defaultActiveKey = hash ? hash.replace('#', '') : 'settings';\n const [detailData, setDetailData] = useState();\n\n const onTabChange = (key) => {\n history.push(`${history.location.pathname}#${key}`);\n };\n\n const tabsContent = [\n {\n label: 'Notification Settings',\n key: 'settings',\n children: (\n \n \n Notification Settings\n \n
These settings will apply to all alerts and notifications. You can personalize your notification settings within each alert in the 'Manage Alerts' tab above.
\n {parameter?.description}\n {parameterDepths.length > 1 && (\n <>\n {' '}\n Click on the depths on the right of the graph to see the data at those depths.\n >\n )}\n
\n )}\n
\n Show data from last available:\n {isMobile\n ? (\n \n ) : (\n \n )}\n {selectedPeriod === 'Custom'\n && (\n }\n theme=\"dark\"\n defaultOpen={pickerOpen}\n open={pickerOpen}\n onOpenChange={(open) => {\n if (open) {\n setPickerOpen(true);\n } else {\n setPickerOpen(false);\n }\n }}\n />\n )}\n {!isMobile && ( // Only show workspace link on desktop\n
\n onClick(parameter)}\n onClickCompare={(parameter) => onClickCompare(parameter)}\n comparisonParameters={comparisonParameters}\n categorySelected={categorySelected}\n categoriesPresent={categoryArray}\n handleSetCategoryArray={handleSetCategoryArray}\n isSamplingLocation={isPlatformsDataConsole}\n // canAddToGraph={isPlatformsDataConsole}\n // parameterOnGraph=\"\" // I don't love this but working for now...\n currentParameter={parameter}\n />\n \n \n
\n {parameter?.description}\n {parameterDepths.length > 1 && (\n <>\n {' '}\n Click on the depths on the right of the graph to see the data at those depths.\n >\n )}\n
\n )}\n {!isPlatformsDataConsole\n ? (\n
\n Show data from last available:\n {isMobile\n ? (\n \n ) : (\n \n )}\n {selectedPeriod === 'Custom'\n && (\n }\n theme=\"dark\"\n defaultOpen={pickerOpen}\n open={pickerOpen}\n onOpenChange={(open) => {\n if (open) {\n setPickerOpen(true);\n } else {\n setPickerOpen(false);\n }\n }}\n />\n )}\n {!isMobile && ( // Only show workspace link on desktop\n
\n )}\n onClick(parameter)}\n onClickCompare={(parameter) => onClickCompare(parameter)}\n comparisonParameters={comparisonParameters}\n categorySelected={categorySelected}\n categoriesPresent={categoryArray}\n handleSetCategoryArray={handleSetCategoryArray}\n isSamplingLocation={isPlatformsDataConsole}\n canAddToGraph={isPlatformsDataConsole}\n parameterOnGraph={environmentalDataOverlayName} // I don't love this but working for now...\n currentParameter={parameter}\n />\n \n \n
\n );\n};\n\nGroupDetail.propTypes = {\n};\n\nGroupDetail.defaultProps = {\n};\n\nexport default GroupDetail;\n","import {\n FacebookFilled, LinkedinFilled, MinusCircleOutlined, PlusCircleFilled, UploadOutlined,\n} from '@ant-design/icons';\nimport {\n Form, Input, Select, Upload, Typography,\n} from 'antd';\nimport React from 'react';\nimport BraftEditor from 'braft-editor';\nimport 'braft-editor/dist/index.css';\nimport PropTypes from 'prop-types';\n\nimport { MEMBER_ROLES } from '../../constant';\nimport { brandingConfig } from '../../../../config';\nimport { featureFlag } from '../../../../constant';\n\nconst { Link } = Typography;\n\nconst normFile = (e) => {\n if (Array.isArray(e)) {\n return e;\n }\n return e && e.fileList;\n};\n\nconst FirstStep = (props) => {\n const editorControls = ['bold', 'italic', 'link'];\n\n const { hidden } = props;\n\n return (\n
\n \n \n \n\n \n \n \n \n
Group Media
\n
Optional Add up to 10 images to your group. These images could be of your platforms, facilities, leadership, or anything you want. These images will be viewable to the public.
\n You will automatically become a group admin. A group can have multiple admins, all of whom will be able to accept and manage new members, edit datasets and platforms, and request data be available on public Seagull. Contributors are allowed to add new data and platforms and can collaborate on datasets. Members can view the data in your group, but cannot contribute.\n
\n \n )}\n >\n
\n \n {' '}\n Add Contacts from Linkedin\n {' '}\n
\n
\n \n {' '}\n Add Contacts from Facebook\n {' '}\n
\n );\n};\n\nSecondStep.propTypes = {\n hidden: PropTypes.bool,\n};\n\nSecondStep.defaultProps = {\n hidden: true,\n};\n\nexport default SecondStep;\n","import {\n ArrowRightOutlined,\n LeftOutlined,\n SearchOutlined,\n} from '@ant-design/icons';\nimport {\n Avatar, Card, Form, Row, Col, Input, List, Skeleton, Typography, Button, Breadcrumb, notification,\n} from 'antd';\nimport React, { useEffect, useState } from 'react';\nimport 'braft-editor/dist/index.css';\nimport { useMutation, useQuery } from 'react-query';\nimport { useMediaQuery } from 'react-responsive';\nimport { NavLink, useHistory } from 'react-router-dom';\n\nimport './styles.scss';\nimport MapQueryTypes from '../../../services/query-types/map';\nimport { UserAddFilledIcon, UsersFilledIcon } from '../../../components/icons';\nimport FirstStep from './components/FirstStep';\nimport SecondStep from './components/SecondStep';\nimport { createOrganization, joinOrganization, getOrganizations } from '../../../services/organization.service';\nimport { brandingConfig } from '../../../config';\n\nconst { Link } = Typography;\n\nconst GroupCreate = () => {\n const history = useHistory();\n const isMobile = useMediaQuery({ maxWidth: 760 });\n\n const [orgForm] = Form.useForm();\n\n const [organizationsList, setOrganizationsList] = useState([]);\n const [organizationSearch, setOrganizationSearch] = useState();\n const [step, setStep] = useState(1);\n const [headerText, setHeaderText] = useState({\n title: '',\n description: 'Create a group in Seagull to keep all of your colleagues, classmates, or family member\\'s data in one place. Collaborate on projects together, add platforms that are associated with your group, add custom metadata that Seagull may not support, and even get a unique URL with your brand.',\n });\n const [selectedOrganization, setSelectedOrganization] = useState();\n\n const createMutation = useMutation((data) => createOrganization(data));\n const joinOrgMutation = useMutation((data) => joinOrganization(data));\n\n const {\n data: organizationsResult,\n refetch: organizationsRefetch,\n } = useQuery(MapQueryTypes.REST_ORGANIZATIONS, getOrganizations, { refetchOnWindowFocus: false });\n\n if (!organizationsResult) {\n organizationsRefetch();\n }\n\n useEffect(() => {\n if (organizationsResult) {\n let filteredOrganizations = organizationsResult;\n\n if (organizationSearch) {\n filteredOrganizations = filteredOrganizations.filter((org) => org.name.toLowerCase().includes(organizationSearch.toLowerCase()));\n }\n\n setOrganizationsList(filteredOrganizations);\n }\n }, [organizationsResult, organizationSearch]);\n\n useEffect(() => {\n if (createMutation.isSuccess) {\n setStep(3);\n\n notification.success(\n {\n description: (\n
\n
Group Created!
\n
\n Your Group\n {' '}\n \n {createMutation.data.name}\n \n {' '}\n has successfully been created.\n
\n
\n ),\n className: 'create-group-message',\n duration: 5,\n },\n );\n createMutation.reset();\n }\n }, [createMutation]);\n\n useEffect(() => {\n if (step === 2) {\n setHeaderText({\n title: 'Apply to be verified',\n description: 'Become a verified organization to gain access to special group features, such as adding custom metadata, get a unique URL just for your group, and even publish your data to public Seagull. You can apply to verified at any point.',\n });\n } else if (step === 3) {\n setHeaderText({\n title: 'Your group has been created! ',\n description: (\n
\n
\n Your application to be verified has been submitted and representative from GLOS will review your request. If you don't receive an email from us within 48 hours, please reach out to us at help@glos.org\n
\n
\n While you wait for your members to accept their invites, start adding platforms or data to\n {' '}\n \n {orgForm?.getFieldsValue()?.groupName}\n \n .\n
\n Verified organizations have access to more features on Seagull, such as custom data upload and a unique branded experience. Verification means that your group's data is trusted and accurate so other Seagull users can rely on you.\n
\n \n {\n !verifyMutation.isIdle\n ? 'Verification Requested'\n : 'Request to become verified'\n }\n \n
\n \n \n
\n );\n};\n\nGeneralTab.propTypes = {\n form: PropTypes.object,\n isMobile: PropTypes.bool,\n organization: PropTypes.object,\n onSave: PropTypes.func,\n};\n\nGeneralTab.defaultProps = {\n form: null,\n isMobile: false,\n organization: {},\n onSave: () => {},\n};\n\nexport default GeneralTab;\n","export const joinRequests = [\n {\n name: 'Sadie Harmon ',\n email: 'Howard5@yahoo.com',\n summary: 'Sadie is requesting to join the group RAEON',\n pending_organization_role: 'member',\n },\n {\n name: 'Sadie Harmon ',\n email: 'Howard5@yahoo.com',\n summary: 'Sadie is requesting to join the group RAEON',\n pending_organization_role: 'member',\n },\n];\n\nexport const members = [\n {\n name: 'Tim Smith ',\n email: 'Howard5@yahoo.com',\n summary: 'Sadie is requesting to join the group RAEON',\n pending_organization_role: null,\n organization_role: 'admin',\n },\n];\n\nexport const membersSort = [\n { name: 'Sort By', value: '' },\n { name: 'Name A-Z', value: 'name_asc' },\n { name: 'Name Z-A', value: 'name_desc' },\n { name: 'Permission Highest to Lowest', value: 'permission_desc' },\n { name: 'Permission Lowest to Highest', value: 'permission_asc' },\n];\n","import React, { memo, useContext, useState } from 'react';\nimport {\n Input, Select, Col, Row, Typography, Button,\n} from 'antd';\nimport {\n CaretDownFilled, DownOutlined, UpOutlined, SearchOutlined, PlusCircleFilled,\n} from '@ant-design/icons';\nimport PropTypes from 'prop-types';\nimport { useHistory } from 'react-router';\n\nimport { FilterIcon } from '../../../../../components/icons';\nimport { membersSort } from '../constant';\nimport { brandingConfig } from '../../../../../config';\nimport UserContext from '../../../../../contexts/UserContext';\n\nconst { Link } = Typography;\nconst { Option } = Select;\n\nconst MembersFilter = (props) => {\n const {\n isMobile,\n handleSearch,\n handleSort,\n } = props;\n\n const history = useHistory();\n\n const [visible, setVisible] = useState(!isMobile);\n\n const sortByEl = () => (\n
`Total ${total} parameters found`,\n position: ['bottomLeft'],\n }}\n locale={{\n emptyText: This platform doesn't appear to be sending us data right now.}\n />,\n }}\n />\n
\n );\n};\n\nexport default Sentry.withProfiler(App);\n","import React from 'react';\n// eslint-disable-next-line import/extensions\nimport { useInitial } from './useInitial';\n\nconst ComponentPreviews = React.lazy(() => import('./previews'));\n\nexport {\n ComponentPreviews,\n useInitial,\n};\n","import React from 'react';\nimport ReactDOM from 'react-dom';\nimport {\n BrowserRouter as Router,\n} from 'react-router-dom';\nimport { QueryClient, QueryClientProvider } from 'react-query';\nimport { ConfigProvider } from 'antd';\nimport enUS from 'antd/lib/locale/en_US';\nimport { Helmet } from 'react-helmet';\nimport * as Sentry from '@sentry/react';\nimport { CaptureConsole as CaptureConsoleIntegration } from '@sentry/integrations';\nimport { BrowserTracing } from '@sentry/tracing';\nimport { DevSupport } from '@react-buddy/ide-toolbox';\nimport ErrorBoundary from 'bizcharts/lib/boundary/ErrorBoundary';\nimport * as serviceWorker from './serviceWorker';\nimport { brandingConfig } from './config';\n\nimport 'antd/dist/antd.css';\n\nimport App from './App';\n\n// eslint-disable-next-line import/extensions\nimport { ComponentPreviews, useInitial } from './dev';\nimport ErrorPage from './pages/error';\n\nSentry.init({\n dsn: 'https://64aee4e96c5d4ca9a920655dc3aa123e@o4504664366841856.ingest.sentry.io/4504664369725440',\n // This sets the sample rate to be 10%. You may want this to be 100% while\n // in development and sample at a lower rate in production\n replaysSessionSampleRate: 1.0,\n // If the entire session is not sampled, use the below sample rate to sample\n // sessions when an error occurs.\n replaysOnErrorSampleRate: 1.0,\n\n autoSessionTracking: true,\n\n integrations: [new BrowserTracing(), new Sentry.Replay(), new CaptureConsoleIntegration({ levels: ['error'] })],\n\n // Set tracesSampleRate to 1.0 to capture 100%\n // of transactions for performance monitoring.\n // We recommend adjusting this value in production\n tracesSampleRate: 1.0,\n enabled: process.env.NODE_ENV !== 'development',\n attachStacktrace: true,\n include: {\n cookies: true,\n data: true,\n headers: true,\n ip: true,\n query_string: true,\n url: true,\n user: {\n id: true,\n username: true,\n email: true,\n },\n },\n});\n\nconst queryClient = new QueryClient({\n defaultOptions: {\n queries: {\n refetchOnWindowFocus: false,\n staleTime: 0,\n cacheTime: Infinity,\n },\n },\n});\n\nReactDOM.render(\n <>\n \n \n {brandingConfig.labels.siteTitle}\n {brandingConfig.fonts.body && }\n \n \n \n \n \n \n
\n \n
\n >\n )}\n >\n \n \n \n \n \n \n \n >,\n document.getElementById('root'),\n);\n\n// If you want your app to work offline and load faster, you can change\n// unregister() to register() below. Note this comes with some pitfalls.\n// Learn more about service workers: https://bit.ly/CRA-PWA\nserviceWorker.unregister();\n","import { useState } from 'react';\nimport { InitialHookStatus } from '@react-buddy/ide-toolbox';\n\nexport const useInitial: () => InitialHookStatus = () => {\n // eslint-disable-next-line no-unused-vars\n const [status, setStatus] = useState({\n loading: false,\n error: false,\n });\n /*\n Implement hook functionality here.\n If you need to execute async operation, set loading to true and when it's over, set loading to false.\n If you caught some errors, set error status to true.\n Initial hook is considered to be successfully completed if it will return {loading: false, error: false}.\n */\n return status;\n};\n","import React from 'react';\nimport Icon from '@ant-design/icons';\n\nconst CloseSvg = () => (\n \n);\n\nconst CloseIcon = (props) => ;\n\nexport default CloseIcon;\n","import React from 'react';\nimport Icon from '@ant-design/icons';\n\nconst WaterTempSvg = () => (\n \n);\n\nconst WaterTempIcon = (props) => ;\n\nexport default WaterTempIcon;\n","import * as React from 'react';\n\nconst SearchingIcon = (props) => (\n \n);\nexport default SearchingIcon;\n","import React from 'react';\nimport Icon from '@ant-design/icons';\n\nconst CurrentSvg = () => (\n \n);\n\nconst CurrentIcon = (props) => ;\n\nexport default CurrentIcon;\n","import React from 'react';\nimport Icon from '@ant-design/icons';\n\nconst DatasetsSvg = () => (\n \n);\n\nconst DatasetsIcon = (props) => ;\n\nexport default DatasetsIcon;\n","import React from 'react';\nimport Icon from '@ant-design/icons';\n\nconst EditSvg = () => (\n \n);\n\nconst EditIcon = (props) => ;\n\nexport default EditIcon;\n","import React from 'react';\nimport Icon from '@ant-design/icons';\n\nconst MapViewInfoSvg = () => (\n \n\n);\n\nconst MapViewInfoIcon = (props) => ;\n\nexport default MapViewInfoIcon;\n","import * as React from 'react';\nimport Icon from '@ant-design/icons';\n\nconst SvgPinClicked = (props) => (\n \n);\nconst SvgPinClickedIcon = (props) => ;\n\nexport default SvgPinClickedIcon;\n","import React from 'react';\nimport Icon from '@ant-design/icons';\n\nconst NoDataSvg = () => (\n \n);\n\nconst NoDataInRangeIcon = (props) => ;\n\nexport default NoDataInRangeIcon;\n"],"sourceRoot":""}