setHovered(true)}\n onMouseLeave={() => setHovered(false)}\n >\n {hovered && (\n
\n \n Layers \n {mainLayers.map(createLayerCheckbox)}\n \n\n
\n\n \n Basemap \n {basemaps.map(createBasemapOption)}\n \n \n )}\n\n
\n \n \n
\n );\n};\n\nexport default MapControlLayers;\n","import React from 'react';\n\nexport default function DCBoundary({ className }) {\n return (\n \n {view3D && view3D.ready && (\n
\n )}\n\n {\n // no results modal\n showNoResults && (\n
\n
\n
No Results
\n
setShowNoResults(false)} />\n
\n
\n )\n }\n\n
\n\n
setSplitPercentage(size)}\n size={compareToolActive ? splitPercentage || '50%' : '100%'}\n resizerStyle={compareToolActive ? { width: '5px' } : { width: '0px' }}\n >\n \n\n \n \n
\n );\n};\n\nexport default MapView;\n","import Tooltip from '@material-ui/core/Tooltip';\nimport { withStyles } from '@material-ui/core/styles';\n\nconst StyledTooltip = withStyles(() => ({\n tooltip: {\n backgroundColor: '#202432',\n color: 'white',\n maxWidth: 220,\n fontSize: 15,\n position: 'relative',\n right: '5px',\n },\n arrow: {\n color: '#202432',\n margin: '50px',\n },\n}))(Tooltip);\n\nexport default StyledTooltip;\n","import { useEffect, useState } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { Tab, Tabs } from '@material-ui/core';\nimport UploadIcon from '@material-ui/icons/CloudUpload';\nimport DeleteIcon from '@material-ui/icons/Delete';\nimport ListIcon from '@material-ui/icons/List';\nimport LightIcon from '@material-ui/icons/BrightnessMedium';\nimport CompareIcon from '@material-ui/icons/StarHalf';\nimport LineOfSightIcon from '@material-ui/icons/LinearScale';\nimport SketchIcon from '@material-ui/icons/Create';\nimport ReportIcon from '@material-ui/icons/GetApp';\nimport HelpIcon from '@material-ui/icons/Help';\nimport MeasurementIcon from '@material-ui/icons/SquareFoot';\nimport StyledTooltip from '../shared/StyledTooltip';\nimport {\n ActiveTool,\n activeToolState,\n currentBuildingState,\n setActiveTool,\n setSiteExpanded,\n siteExpandedState,\n} from '../../../store/slices/appSlice';\nimport {\n resetSubmitWorkflow,\n selectedFileState,\n setStep,\n uploadedFilesState,\n uploadingState,\n} from '../../../store/slices/uploadSlice';\nimport { selectedSketchState, sketchesState } from '../../../store/slices/sketchSlice';\nimport sketchController from '../../controllers/SketchController';\nimport './SiteControls.scss';\n\ninterface ITab {\n tool: ActiveTool;\n label: string;\n visible: boolean;\n icon: JSX.Element;\n tooltip?: string;\n className?: string;\n callbackAction?: () => void;\n}\n\nconst SiteControls = () => {\n const dispatch = useDispatch();\n const activeTool = useSelector(activeToolState);\n const uploadedFiles = useSelector(uploadedFilesState);\n const sketches = useSelector(sketchesState);\n const selectedSketch = useSelector(selectedSketchState);\n const currentBuilding = useSelector(currentBuildingState);\n const selectedFile = useSelector(selectedFileState);\n const uploading = useSelector(uploadingState);\n const siteExpanded = useSelector(siteExpandedState);\n const [selectedTab, setSelectedTab] = useState
();\n\n const displayCompare =\n activeTool === 'compare' ||\n (activeTool === 'uploaded-files' && selectedFile?.geometry && selectedFile?.geometry?.hasOwnProperty('type')) ||\n (activeTool === 'sketch' && sketches.length) ||\n (activeTool === 'light' && (!!selectedSketch || !!selectedFile));\n const displaySubmitNewFile = activeTool !== 'submit' && activeTool !== 'default' && Object.keys(uploadedFiles).length;\n const displayDelete =\n activeTool === 'delete' ||\n (!selectedSketch && activeTool !== 'sketch' && currentBuilding && currentBuilding.hasOwnProperty('attributes'));\n const displayReport =\n ((activeTool === 'uploaded-files' || activeTool === 'light') &&\n selectedFile?.geometry &&\n selectedFile?.geometry.hasOwnProperty('type') &&\n !uploading) ||\n selectedSketch ||\n activeTool === 'shadow-report';\n\n const TABS: ITab[] = [\n {\n tool: 'submit',\n label: displaySubmitNewFile ? 'Submit New 3D File' : 'Submit 3D File',\n visible: true,\n icon: ,\n tooltip:\n 'Submit a 3D KMZ file to render on the map to visualize new buildings or features. Once uploaded, edit the position and orientation as needed.',\n callbackAction: () => dispatch(resetSubmitWorkflow()),\n },\n {\n tool: 'uploaded-files',\n label: 'Uploaded Files',\n visible: !!Object.keys(uploadedFiles).length,\n icon: ,\n callbackAction: () => dispatch(setStep(1)),\n },\n {\n tool: 'delete',\n label: 'Delete Building',\n visible: displayDelete,\n icon: ,\n },\n {\n tool: 'light',\n label: 'Light Study',\n visible: true,\n icon: ,\n tooltip:\n 'Select time of year and start time. Click start to progress through the day from selected start time to observe light and shadows for the study area. Click stop to stop cycling through times.',\n },\n {\n tool: 'compare',\n label: 'Before & After',\n visible: displayCompare,\n icon: ,\n },\n {\n tool: 'measurement',\n label: 'Measurement',\n visible: true,\n icon: ,\n },\n {\n tool: 'line-of-sight',\n label: 'Line of Sight',\n visible: true,\n icon: ,\n tooltip: 'Click a point on the map and determine what is visible from that point.',\n },\n {\n tool: 'sketch',\n label: 'Sketch',\n visible: true,\n icon: ,\n tooltip:\n 'Sketch 3D shapes directly on the map and manipulate the object’s height to reflect proposed changes to a building or feature.',\n },\n {\n tool: 'shadow-report',\n label: 'Shadow Report',\n visible: displayReport,\n icon: ,\n },\n ];\n\n useEffect(() => {\n if (activeTool === 'default') {\n setSelectedTab(undefined);\n } else if (selectedTab !== activeTool) {\n setSelectedTab(activeTool);\n }\n }, [activeTool]);\n\n useEffect(() => {\n if (activeTool !== 'sketch' && activeTool !== 'light' && activeTool !== 'shadow-report') {\n sketchController.cancelSketch();\n }\n }, [activeTool]);\n\n const handleChange = (event: React.ChangeEvent<{}>, selectedTool: ActiveTool) => {\n setSelectedTab(selectedTool);\n dispatch(setActiveTool(selectedTool));\n\n if (selectedTool === 'compare') {\n dispatch(setSiteExpanded(false));\n } else if (!siteExpanded) {\n dispatch(setSiteExpanded(true));\n }\n\n const tab = TABS.find((tab) => tab.tool === selectedTool);\n\n if (tab?.callbackAction) {\n tab.callbackAction();\n }\n };\n\n const HELP_TAB: ITab = {\n tool: 'help',\n label: 'Help',\n visible: true,\n icon: ,\n className: 'SiteControls__tab--help',\n };\n\n const currentTabs = TABS.filter((tab) => tab.visible);\n\n return (\n \n
\n
\n {currentTabs.map((tab) => (\n (tab.callbackAction ? tab.callbackAction() : undefined)}\n className={`SiteControls__tab ${tab.className ?? ''}`}\n value={tab.tool}\n label={\n \n {tab.label} \n \n }\n icon={{tab.icon} }\n />\n ))}\n\n
\n\n \n {HELP_TAB.label} \n \n }\n icon={{HELP_TAB.icon} }\n />\n \n
\n );\n};\n\nexport default SiteControls;\n","import { useEffect } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport api from '../../../../utils/api';\nimport {\n setActiveTool,\n currentBuildingState,\n removedBuildingsState,\n setCurrentBuilding,\n} from '../../../../../store/slices/appSlice';\nimport mapController from '../../../../controllers/MapController';\nimport { addRemovedBuilding } from '../../../../../store/slices/appSlice';\nimport { popRemovedBuilding } from '../../../../../store/slices/appSlice';\nimport { LAYER_TITLES } from '../../../../configs/config.layerTitles';\nimport './DeleteControl.scss';\n\nconst DeleteControl = () => {\n const dispatch = useDispatch();\n const removedBuildings = useSelector(removedBuildingsState);\n const currentBuilding = useSelector(currentBuildingState);\n\n useEffect(() => {\n // Create new definition expression off of new list\n const definition = api.generateBuildingDefinitionExpression(removedBuildings);\n // Set the definition expression\n const layer = mapController.map.allLayers.items.find((l) => l.title === LAYER_TITLES.BUILDINGS);\n layer.definitionExpression = definition;\n\n if (!currentBuilding && !removedBuildings.length) {\n dispatch(setActiveTool('default'));\n }\n }, [removedBuildings]);\n\n const cancelDelete = (event) => {\n dispatch(setActiveTool('default'));\n };\n\n const removeBuilding = (event) => {\n if (currentBuilding) {\n // Push the new building to be removed to the list\n dispatch(addRemovedBuilding(currentBuilding.attributes.OBJECTID));\n\n // Clear out current building\n dispatch(setCurrentBuilding());\n mapController.highlightHandler?.remove();\n }\n };\n\n const undoRemoval = (event) => {\n event.preventDefault();\n\n if (removedBuildings.length > 0) {\n // Removed the last pushed building from array\n dispatch(popRemovedBuilding());\n\n // Clear out current building\n dispatch(setCurrentBuilding());\n }\n };\n\n // if there removedBuildings in the list, do not disable the undo function\n return (\n \n {currentBuilding && (currentBuilding?.attributes || !removedBuildings.length) ? (\n
\n \n Delete Building\n \n\n \n Cancel\n \n
\n ) : (\n
\n
Building Deleted
\n
\n Undo\n \n
\n )}\n
\n );\n};\n\nexport default DeleteControl;\n","import { useEffect, useState } from 'react';\nimport { urls } from '../../config';\nimport { useSelector } from 'react-redux';\n\nimport { Card, CardMedia } from 'material-ui/Card';\nimport { materialStyle } from '../../../style/material';\n\nimport DeleteControl from './controls/DeleteControl/DeleteControl';\nimport { loadModules } from 'esri-loader';\nimport { activeToolState, siteState } from '../../../store/slices/appSlice';\nimport mapController from '../../controllers/MapController';\n\nconst SiteInformation = () => {\n const site = useSelector(siteState);\n const activeTool = useSelector(activeToolState);\n const [address, setAddress] = useState();\n const [zoneInfo, setZoneInfo] = useState();\n const [googleStreetViewImage, setGoogleStreetViewImage] = useState();\n\n const getAddress = () => {\n return site?.address ? site.address : `${site?.latlon[0].toFixed(3)}, ${site?.latlon[1].toFixed(3)}`;\n };\n\n useEffect(() => {\n const noCurrentSite = site?.latlon?.length === 0 ? true : false;\n const addressTemp = noCurrentSite ? '' : getAddress();\n const zoneInfoTemp = noCurrentSite ? '' : `${site?.zoneCode} ${site?.zoneDistrict}`;\n const googleStreetViewImageTemp = site?.address ? `url(${urls.googleStreetView(site?.address)})` : '';\n\n setAddress(addressTemp);\n setZoneInfo(zoneInfoTemp);\n setGoogleStreetViewImage(googleStreetViewImageTemp);\n }, [site]);\n\n if (!site) {\n return null;\n }\n\n const handleAdditionalZoning = (event) => {\n event.preventDefault();\n window.open(site.zoneWebUrl, '_blank');\n };\n\n const handleOfficialZoning = async (event) => {\n event.preventDefault();\n const [webMercatorUtils] = await loadModules(['esri/geometry/support/webMercatorUtils']);\n\n const view3D = mapController.view3D;\n const [x, y] = webMercatorUtils.lngLatToXY(site.latlon[1], site.latlon[0]);\n const zoom = Math.round(view3D.zoom) > 18 ? 18 : Math.round(view3D.zoom);\n const url = urls.zr16(x, y, zoom);\n window.open(url, '_blank');\n };\n\n const getZoomLevel = () => {\n return mapController.view3D.zoom;\n };\n\n const openGoogleMaps = () => {\n if (Array.isArray(site.latlon) && site.latlon.length === 2) {\n // add 2 to zoom since zoom scale on google maps is larger\n const url = urls.googleMap(site.latlon, getZoomLevel() + 2);\n window.open(url, '_blank');\n }\n };\n\n return (\n \n );\n};\n\nexport default SiteInformation;\n","import React from 'react';\nimport './Button.scss';\n\nconst Button = React.forwardRef(function Button(props, ref) {\n return (\n \n {props.icon && {props.icon}
}\n {props.label && `${props.label}`}\n \n );\n});\n\nexport default Button;\n","import shortid from 'shortid';\nimport { useSelector, useDispatch } from 'react-redux';\n\nimport HelpIcon from 'material-ui/svg-icons/action/help';\nimport InfoIcon from 'material-ui/svg-icons/action/info';\nimport Keyboard from 'material-ui/svg-icons/hardware/keyboard';\nimport Comment from 'material-ui/svg-icons/communication/chat-bubble';\nimport Folder from 'material-ui/svg-icons/file/folder-open';\nimport TutorialIcon from '@material-ui/icons/MenuBook';\nimport { red500, fullWhite } from 'material-ui/styles/colors';\nimport Button from '../../shared/Button';\nimport {\n enableHelpState,\n setEnableHelp,\n setDisplayNavigation,\n setDisplayDisclaimer,\n} from '../../../../store/slices/appSlice';\nimport './HelpControl.scss';\n\nconst helpOptions = [\n {\n id: 'keyboard-shortcuts',\n label: 'Navigation',\n icon: ,\n },\n {\n id: 'tutorials',\n label: 'Tutorials',\n icon: ,\n },\n {\n id: 'about',\n label: 'About DCOZ',\n icon: ,\n },\n {\n id: 'disclaimer',\n label: 'Disclaimer',\n icon: ,\n },\n {\n id: 'feedback',\n label: 'Feedback',\n icon: ,\n },\n];\n\nconst HelpControl = () => {\n const dispatch = useDispatch();\n const enableHelp = useSelector(enableHelpState);\n\n const handleHelpOption = (event) => {\n const { id } = event.currentTarget;\n switch (id) {\n case 'interactive-help':\n dispatch(setEnableHelp(!enableHelp));\n break;\n case 'keyboard-shortcuts':\n dispatch(setDisplayNavigation(true));\n break;\n case 'about':\n window.open('http://maps.dcoz.dc.gov', '_blank');\n break;\n case 'disclaimer':\n dispatch(setDisplayDisclaimer(true));\n break;\n case 'feedback':\n window.open('https://app.dcoz.dc.gov/Contact/Home.aspx', '_blank');\n break;\n case 'tutorials':\n window.open('https://maps.dcoz.dc.gov/?page=3D-Zoning-Map-Tutorial', '_blank');\n break;\n default:\n break;\n }\n };\n\n const helpStyle = enableHelp ? red500 : fullWhite;\n\n return (\n \n }\n className='HelpControl-item__button'\n onClick={handleHelpOption}\n />\n {helpOptions.map((option) => (\n \n ))}\n
\n );\n};\n\nexport default HelpControl;\n","import React from 'react';\n\nexport default function Stepper(props) {\n const { step } = props;\n return (\n \n
Submit a File \n\n
\n
0 ? 'completed-step' : ''}`}>\n
\n
\n
\n\n
\n
\n
0 ? 'active' : ''}`}>
\n
\n\n
1 ? 'completed-step' : ''}`}>\n
\n
\n
\n\n
\n
\n
1 ? 'active' : ''}`}>
\n
\n\n
2 ? 'completed-step' : ''}`}>\n
\n
\n
\n
\n
\n );\n}\n","import * as React from 'react';\n\nconst FileIcon = (props) => (\n \n \n \n \n);\n\nexport default FileIcon;\n","const KmzFileIcon = (props) => (\n \n \n \n \n);\n\nexport default KmzFileIcon;\n","const SlpkFileIcon = (props) => (\n \n \n \n \n);\n\nexport default SlpkFileIcon;\n","import FileIcon from '../components/shared/icons/FileIcon';\nimport KmzFileIcon from '../components/shared/icons/KmzFileIcon';\nimport SlpkFileIcon from '../components/shared/icons/SlpkFileIcon';\n\nexport const getFileType = (file: File) => {\n return file.name.split('.')[file.name.split('.').length - 1];\n};\n\nexport const getFileIcon = (file: File) => {\n const type = getFileType(file);\n\n switch (type) {\n case 'kmz':\n return ;\n case 'slpk':\n return ;\n default:\n return ;\n }\n};\n","import LinearProgress from 'material-ui/LinearProgress';\nimport { useSelector } from 'react-redux';\nimport { selectedFileIdState, uploadedFilesState } from '../../../store/slices/uploadSlice';\nimport { getFileIcon } from '../../utils/file.utils';\n\nconst LinearProgressBar = (props) => {\n const { value, mode, min, max, message, file } = props;\n const messageFormatted = message.replace('informative -', '').replace('UPDATE:', '');\n const uploadedFiles = useSelector(uploadedFilesState);\n const selectedFileId = useSelector(selectedFileIdState);\n const selectedFile = uploadedFiles[selectedFileId];\n\n return (\n \n {(file || selectedFile?.file) && getFileIcon(file ?? selectedFile.file)}\n
\n {messageFormatted && messageFormatted.length ? messageFormatted : 'Uploading...'}\n
\n
\n
\n );\n};\n\nexport default LinearProgressBar;\n","import { useSelector, useDispatch } from 'react-redux';\nimport api from '../../../../utils/api';\nimport mapController from '../../../../controllers/MapController';\nimport { selectedFileIdState, setSelectedFileId, uploadedFilesState } from '../../../../../store/slices/uploadSlice';\nimport { view3DState } from '../../../../../store/slices/appSlice';\nimport { getFileIcon } from '../../../../utils/file.utils';\n\nconst FileCard = ({ hasFinishedUploading, itemId, location, file, showEditSearch, toggleShowEditSearch }) => {\n const dispatch = useDispatch();\n const uploadedFiles = useSelector(uploadedFilesState);\n const selectedFileId = useSelector(selectedFileIdState);\n const currentFile = uploadedFiles[itemId];\n const view3D = useSelector(view3DState);\n\n const handleClick = (e) => {\n if (currentFile) {\n const geometry = currentFile.geometry;\n\n if (geometry) {\n view3D.goTo({ target: geometry, tilt: 45 });\n if (itemId !== selectedFileId) {\n mapController.highlightHandler?.remove();\n if (showEditSearch) {\n toggleShowEditSearch();\n }\n }\n } else {\n const sceneLayerId = currentFile.sceneLayerId;\n const selectedFileSceneLayer = mapController.map.findLayerById(sceneLayerId);\n\n if (selectedFileSceneLayer && selectedFileSceneLayer.fullExtent) {\n view3D.goTo({ target: selectedFileSceneLayer.fullExtent, tilt: 45 });\n }\n }\n\n dispatch(setSelectedFileId(itemId));\n }\n };\n\n const handleToggleShowEditSearch = (e) => {\n e.stopPropagation();\n toggleShowEditSearch();\n };\n\n const isSelectedFile = selectedFileId && selectedFileId === itemId;\n\n return !file ? null : (\n \n
\n {file && getFileIcon(file)}\n
\n
{file.name || 'Undefined'}
\n
{api.bytesToSize(file.size)}
\n
\n
\n\n
\n\n
\n
\n
Location:
\n {location?.hasOwnProperty('FULLADDRESS') && isSelectedFile && (\n
\n Edit\n
\n )}\n
\n
\n {location?.hasOwnProperty('FULLADDRESS') ? location.FULLADDRESS : 'No location attached to the file'}\n
\n
\n
\n );\n};\n\nexport default FileCard;\n","import React from 'react';\nimport Dialog from '@material-ui/core/Dialog';\nimport DialogTitle from '@material-ui/core/DialogTitle';\nimport DialogContent from '@material-ui/core/DialogContent';\nimport DialogActions from '@material-ui/core/DialogActions';\nimport DialogContentText from '@material-ui/core/DialogContentText';\nimport IconButton from '@material-ui/core/IconButton';\nimport CloseIcon from '@material-ui/icons/Close';\n\nconst DynamicModal = ({ children, id, title, open, handleClose }) => {\n return (\n \n \n {title && {title} }\n \n \n \n \n \n
\n\n \n {children} \n \n \n );\n};\n\nexport default DynamicModal;\n","import { useState } from 'react';\nimport { useSelector } from 'react-redux';\nimport { errorState } from '../../../../../store/slices/uploadSlice';\nimport DynamicModal from '../../../modals/DynamicModal';\nimport Button from '../../../shared/Button';\n\nconst ErrorMessagesDetailsButton = () => {\n const error = useSelector(errorState);\n const [modalOpen, setModalOpen] = useState(false);\n\n const handleCloseModal = () => setModalOpen(false);\n const handleOpenModal = () => setModalOpen(true);\n\n return error?.errorMessages?.length ? (\n <>\n \n \n \n {error.errorMessages.map((error: any) => (\n {error.description} \n ))}\n \n \n >\n ) : null;\n};\n\nexport default ErrorMessagesDetailsButton;\n","import { useState } from 'react';\nimport { useSelector } from 'react-redux';\nimport LinearProgressBar from '../../../shared/LinearProgressBar';\nimport {\n errorState,\n uploadedFilesState,\n uploadingState,\n selectedFileIdState,\n} from '../../../../../store/slices/uploadSlice';\nimport { activeToolState } from '../../../../../store/slices/appSlice';\nimport FileCard from './FileCard';\nimport ErrorMessagesDetailsButton from './ErrorMessagesDetailsButton';\n\nconst EditView = ({ showEditSearch, toggleShowEditSearch, message }) => {\n const error = useSelector(errorState);\n const uploading = useSelector(uploadingState);\n const activeTool = useSelector(activeToolState);\n const uploadedFiles = useSelector(uploadedFilesState);\n const selectedFileId = useSelector(selectedFileIdState);\n const selectedFile = uploadedFiles[selectedFileId];\n\n const [modalOpen, setModalOpen] = useState(false);\n\n const hasFinishedUploading = Object.values(uploadedFiles).every(\n (file) => !!file.file && file.position && file.location && file.itemId\n );\n const handleCloseModal = () => setModalOpen(false);\n const handleOpenModal = () => setModalOpen(true);\n\n const renderErrorContent = () => {\n return (\n \n
{error?.message ?? 'There was an error processing your request'}
\n\n
\n
\n );\n };\n\n return (\n \n {uploading ? (\n \n ) : error ? (\n renderErrorContent()\n ) : (\n <>\n {/* If it has finished uploading new file, display cards for all uploaded files */}\n {hasFinishedUploading || activeTool === 'uploaded-files' ? (\n Object.values(uploadedFiles).map((file) => (\n \n ))\n ) : (\n \n )}\n >\n )}\n
\n );\n};\n\nexport default EditView;\n","import React from 'react';\n\nexport default function CompassIcon(props) {\n return (\n \n \n \n \n \n \n \n \n \n N\n \n \n \n \n S\n \n \n \n \n E\n \n \n \n \n W\n \n \n \n \n );\n}\n","import React, { Component } from 'react';\n\nimport Up from 'material-ui/svg-icons/navigation/arrow-drop-up';\nimport Down from 'material-ui/svg-icons/navigation/arrow-drop-down';\n\nimport '../../../css/components/NumberCounter.scss';\n\nexport default class NumberCounter extends Component {\n constructor(props) {\n super(props);\n this.input = React.createRef();\n }\n\n render() {\n const { id, value, onInputChange, onIncrement, onDecrement } = this.props;\n return (\n \n
\n
\n onIncrement(this.input.current)} />\n onDecrement(this.input.current)} />\n {/* onIncrement(this.input.current.value)} />\n onDecrement(this.input.current.value)} /> */}\n
\n
\n );\n }\n}\n","export const GP_SERVICE_REQUEST_TIMEOUT = 150000;\n","import { loadModules } from 'esri-loader';\nimport store from '../../store';\nimport { setActiveTool } from '../../store/slices/appSlice';\nimport {\n incrementStep,\n setError,\n setSelectedFileId,\n setUploadedFile,\n setUploading,\n UploadedFile,\n} from '../../store/slices/uploadSlice';\nimport { gp as gpConfig } from '../config';\nimport { GP_SERVICE_REQUEST_TIMEOUT } from '../configs/config.gpService';\nimport { addUploadLayer } from './esri';\n\ninterface IGPServiceExecuteResponse {\n messages: __esri.GPMessage[];\n results: __esri.ParameterValue[];\n}\n\nexport const reverseGeocodeKMZ = async (itemID: string, file: File) => {\n try {\n const [geoprocessor] = await loadModules(['esri/rest/geoprocessor']);\n const parameters = { KMZ: JSON.stringify({ itemID }) };\n let results: __esri.ParameterValue[] | undefined;\n\n if (process.env.REACT_APP_GP_SERVER_TYPE === 'async') {\n const jobInfo = await geoprocessor.submitJob(gpConfig.reverseGeocodeKMZ, parameters);\n await jobInfo.waitForJobCompletion();\n\n if (jobInfo.jobStatus === 'job-succeeded') {\n const dataParameters = ['Address', 'kmzID'];\n const gpDataPromises = dataParameters.map((param) => jobInfo.fetchResultData(param));\n\n results = await Promise.all(gpDataPromises);\n } else {\n handleError(jobInfo, 'reverseGeocodeKMZ');\n }\n } else {\n const response: IGPServiceExecuteResponse = await geoprocessor.execute(\n gpConfig.reverseGeocodeKMZ,\n parameters,\n undefined,\n {\n timeout: GP_SERVICE_REQUEST_TIMEOUT,\n }\n );\n\n results = response.results;\n }\n\n if (results) {\n await handleReverseGeocodeKMZSuccess(itemID, results, file);\n } else {\n handleError(results, 'reverseGeocodeKMZ');\n }\n } catch (error) {\n handleError(error, 'reverseGeocodeKMZ');\n }\n};\n\nconst handleReverseGeocodeKMZSuccess = async (itemId: string, results: __esri.ParameterValue[], file: File) => {\n const [addressResponse, kmzIDResponse] = results;\n\n const addressValue = addressResponse.value;\n const { FULLADDRESS, LATITUDE, LONGITUDE } = JSON.parse(JSON.stringify(addressValue));\n\n const kmzId = kmzIDResponse.value as string;\n\n store.dispatch(setSelectedFileId(itemId));\n store.dispatch(\n setUploadedFile({\n itemId,\n location: { FULLADDRESS, LATITUDE, LONGITUDE },\n kmzId,\n file,\n })\n );\n};\n\nexport const publishSLPK = async (itemID: string, file: File) => {\n try {\n const [geoprocessor] = await loadModules(['esri/rest/geoprocessor']);\n const parameters = { file: JSON.stringify({ itemID }) };\n\n let results: __esri.ParameterValue[] | undefined;\n\n if (process.env.REACT_APP_GP_SERVER_TYPE === 'async') {\n const jobInfo = await geoprocessor.submitJob(gpConfig.publishSLPK, parameters);\n await jobInfo.waitForJobCompletion();\n\n if (jobInfo.jobStatus === 'job-succeeded') {\n const dataParameters = ['service', 'slpk'];\n const gpDataPromises = dataParameters.map((param) => jobInfo.fetchResultData(param));\n results = await Promise.all(gpDataPromises);\n } else {\n handleError(jobInfo, 'publishSLPK');\n }\n } else {\n const response: IGPServiceExecuteResponse = await geoprocessor.execute(\n gpConfig.publishSLPK,\n parameters,\n undefined,\n {\n timeout: GP_SERVICE_REQUEST_TIMEOUT,\n }\n );\n\n results = response.results;\n }\n\n if (results) {\n await handlePublishSLPKSuccess(itemID, results, file);\n } else {\n handleError(results, 'publishSLPK');\n }\n } catch (error) {\n handleError(error, 'publishSLPK');\n }\n};\n\nconst handlePublishSLPKSuccess = async (itemId: string, results: __esri.ParameterValue[], file: File) => {\n const [serviceResponse, slpkResponse] = results;\n const sceneLayerUrl = `${serviceResponse.value}/layers/0`;\n const slpk = slpkResponse.value;\n\n store.dispatch(setSelectedFileId(itemId));\n await addUploadLayer(sceneLayerUrl, undefined, { itemId, slpk, file }, slpk);\n\n store.dispatch(setActiveTool('uploaded-files'));\n store.dispatch(incrementStep());\n};\n\nconst handleKMZToMultipatchSuccess = async (results: __esri.ParameterValue[], selectedFile: UploadedFile) => {\n const [serviceResponse, slpkResponse, footprintResponse] = results;\n\n const sceneLayerUrl = `${serviceResponse.value}/layers/0`;\n await addUploadLayer(sceneLayerUrl, footprintResponse.value, selectedFile, slpkResponse.value);\n\n store.dispatch(setActiveTool('uploaded-files'));\n store.dispatch(incrementStep());\n};\n\nexport const kmzToMultipatch = async () => {\n const { uploadedFiles, selectedFileId } = store.getState().uploadReducer;\n if (selectedFileId) {\n const selectedFile = uploadedFiles[selectedFileId];\n\n if (selectedFile) {\n const { itemId, location } = selectedFile;\n\n const parameters = {\n file: JSON.stringify({ itemID: itemId }),\n latitude: location.LATITUDE,\n longitude: location.LONGITUDE,\n };\n\n store.dispatch(setUploading(true));\n\n try {\n const [geoprocessor] = await loadModules(['esri/rest/geoprocessor']);\n let results: __esri.ParameterValue[] | undefined;\n\n if (process.env.REACT_APP_GP_SERVER_TYPE === 'async') {\n const jobInfo = await geoprocessor.submitJob(gpConfig.kmzToMultipatch, parameters);\n await jobInfo.waitForJobCompletion();\n\n if (jobInfo.jobStatus === 'job-succeeded') {\n const dataParameters = ['service', 'slpk', 'footprint'];\n const gpDataPromises = dataParameters.map((param) => jobInfo.fetchResultData(param));\n results = await Promise.all(gpDataPromises);\n } else {\n handleError(jobInfo, 'kmzMultipatch');\n }\n } else {\n const response: any = await geoprocessor.execute(gpConfig.kmzToMultipatch, parameters, undefined, {\n timeout: GP_SERVICE_REQUEST_TIMEOUT,\n });\n\n results = response.results;\n }\n\n if (results) {\n await handleKMZToMultipatchSuccess(results, selectedFile);\n } else {\n handleError(results, 'kmzMultipatch');\n }\n } catch (error) {\n handleError(error, 'kmzMultipatch');\n } finally {\n store.dispatch(setUploading(false));\n }\n }\n }\n};\n\nexport const handleError = (response: any, processName: string) => {\n let message;\n\n if (response?.message === 'Timeout exceeded') {\n message = `${processName} process time for this file exceeded the limit`;\n } else {\n message = `There was an error processing ${processName} job`;\n }\n\n const errorMessages = response?.messages?.filter((message: ErrorEvent) => message.type === 'error');\n\n store.dispatch(\n setError({ message: message ?? 'There was an error processing your request', errorMessages: errorMessages })\n );\n};\n","import { loadModules } from 'esri-loader';\nimport store from '../../store';\nimport { incrementStep, Position, setUploading, UploadedFile } from '../../store/slices/uploadSlice';\nimport { getUpdatedFilePosition } from './geometry.utils';\nimport api from './api';\nimport { gp as gpConfig } from '../config';\nimport { replaceUploadLayer } from './esri';\nimport { handleError } from './gpUpload.utils';\nimport { GP_SERVICE_REQUEST_TIMEOUT } from '../configs/config.gpService';\n\ninterface IGPServiceExecuteResponse {\n messages: __esri.GPMessage[];\n results: __esri.ParameterValue[];\n}\n\nconst handleEditBuildingLocationSuccess = async (\n results: __esri.ParameterValue[],\n selectedFile: UploadedFile,\n updatedPosition: Position\n) => {\n const [serviceResponse, slpkResponse, footprintResponse] = results;\n const sceneLayerUrl = `${serviceResponse.value}/layers/0`;\n await replaceUploadLayer(sceneLayerUrl, footprintResponse.value, selectedFile, slpkResponse.value, updatedPosition);\n};\n\nexport const editBuildingLocation = async (newPosition: Position) => {\n const { uploadedFiles, selectedFileId } = store.getState().uploadReducer;\n\n if (selectedFileId) {\n const selectedFile = uploadedFiles[selectedFileId];\n\n if (selectedFile) {\n const { kmzId } = selectedFile;\n\n const updatedPosition = getUpdatedFilePosition(newPosition);\n\n // if any value has changed, run through edit building process, else go to next step\n if (newPosition.x || newPosition.y || newPosition.z) {\n // convert meters to feet for service\n const parameters: { [key: string]: any } = {};\n Object.entries(updatedPosition).forEach((obj) => {\n const [key, value] = obj;\n parameters[key] = api.convertFeetToMeters(Number(value));\n });\n // assign kmz id\n parameters.id = kmzId;\n\n try {\n store.dispatch(setUploading(true));\n const [geoprocessor] = await loadModules(['esri/rest/geoprocessor']);\n const args = { config: JSON.stringify(parameters) };\n let results: __esri.ParameterValue[] | undefined;\n\n if (process.env.REACT_APP_GP_SERVER_TYPE === 'async') {\n const jobInfo = await geoprocessor.submitJob(gpConfig.editBuildingLocation, args);\n await jobInfo.waitForJobCompletion();\n\n if (jobInfo.jobStatus === 'job-succeeded') {\n const dataParameters = ['service', 'slpk', 'footprint'];\n const gpDataPromises = dataParameters.map((param) => jobInfo.fetchResultData(param));\n results = await Promise.all(gpDataPromises);\n } else {\n handleError(jobInfo, 'editBuildingLocation');\n }\n } else {\n const response: IGPServiceExecuteResponse = await geoprocessor.execute(\n gpConfig.editBuildingLocation,\n args,\n undefined,\n {\n timeout: GP_SERVICE_REQUEST_TIMEOUT,\n }\n );\n\n results = response.results;\n }\n\n if (results) {\n await handleEditBuildingLocationSuccess(results, selectedFile, updatedPosition);\n } else {\n handleError(results, 'editBuildingLocation');\n }\n } catch (error) {\n handleError(error, 'editBuildingLocation');\n } finally {\n store.dispatch(setUploading(false));\n }\n } else {\n store.dispatch(incrementStep());\n }\n }\n }\n};\n","import { useEffect, useState } from 'react';\nimport { useSelector } from 'react-redux';\nimport Button from '../../../shared/Button';\nimport CompassIcon from '../../../shared/CompassIcon';\nimport NumberCounter from '../../../shared/NumberCounter';\nimport { selectedFileIdState, uploadedFilesState } from '../../../../../store/slices/uploadSlice';\nimport '../../../../../css/components/EditPosition.scss';\nimport LinearProgressBar from '../../../shared/LinearProgressBar';\nimport { editBuildingLocation } from '../../../../utils/gpEditBuildingLocation.utills';\nimport mapController from '../../../../controllers/MapController';\nimport { INITIAL_EDIT_POSITION } from '../../../../utils/geometry.utils';\n\nconst EditPosition = () => {\n const selectedFileId = useSelector(selectedFileIdState);\n const uploadedFiles = useSelector(uploadedFilesState);\n const selectedFile = uploadedFiles[selectedFileId];\n\n const [tempPosition, setTempPosition] = useState(INITIAL_EDIT_POSITION);\n\n useEffect(() => {\n setTempPosition(INITIAL_EDIT_POSITION);\n mapController.replaceSelectedFileFootprintGraphic(INITIAL_EDIT_POSITION);\n }, [selectedFile]);\n\n const deriveNewPosition = (event) => {\n const { x, y, z } = tempPosition;\n const { id, value } = event.target;\n const number = Number(value);\n\n const newPosition = { x, y, z };\n switch (id) {\n case 'north':\n newPosition.y = number;\n break;\n case 'south':\n newPosition.y = number * -1;\n break;\n case 'west':\n newPosition.x = number * -1;\n break;\n case 'east':\n newPosition.x = number;\n break;\n case 'up':\n newPosition.z = number;\n break;\n case 'down':\n newPosition.z = number * -1;\n break;\n default:\n break;\n }\n\n setTempPosition(newPosition);\n mapController.replaceSelectedFileFootprintGraphic(newPosition);\n };\n\n const increment = (element) => {\n const { id, value } = element;\n // increment because method.\n const number = Number(value) + 1;\n const fakeEvent = {\n target: {\n id,\n value: number,\n },\n };\n deriveNewPosition(fakeEvent);\n };\n\n const decrement = (element) => {\n const { id, value } = element;\n // decrement because method.\n const number = Number(value) - 1;\n const fakeEvent = {\n target: {\n id,\n value: number,\n },\n };\n deriveNewPosition(fakeEvent);\n };\n\n const clear = () => {\n setTempPosition(INITIAL_EDIT_POSITION);\n mapController.replaceSelectedFileFootprintGraphic(INITIAL_EDIT_POSITION);\n };\n\n const handleSaveClick = () => {\n editBuildingLocation(tempPosition);\n };\n\n if (selectedFile && !selectedFile.geometry) return null;\n\n return selectedFile && tempPosition ? (\n \n
Edit Position
\n\n
\n
\n
0 ? tempPosition.y : 0}\n onIncrement={increment}\n onDecrement={decrement}\n onInputChange={deriveNewPosition}\n />\n
\n\n \n \n 0 ? tempPosition.x : 0}\n onIncrement={increment}\n onDecrement={decrement}\n onInputChange={deriveNewPosition}\n />\n\n
\n \n
\n \n\n
\n \n \n
\n
\n ) : (\n \n );\n};\n\nexport default EditPosition;\n","import { useEffect, useState } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { loadModules } from 'esri-loader';\nimport shortid from 'shortid';\nimport LocationOnIcon from '@material-ui/icons/LocationOn';\nimport Search from '../../../../shared/Search';\nimport GenericSearchConfig from '../../../../../configs/config.search';\nimport {\n IBuildingLocation,\n Position,\n setIsSelectLocationOnMapActive,\n isSelectLocationOnMapActiveState,\n selectedFileIdState,\n uploadedFilesState,\n} from '../../../../../../store/slices/uploadSlice';\nimport { setSite, siteState } from '../../../../../../store/slices/appSlice';\nimport mapController from '../../../../../controllers/MapController';\nimport { getXYDistanceBetweenPoints, INITIAL_EDIT_POSITION } from '../../../../../utils/geometry.utils';\nimport { editBuildingLocation } from '../../../../../utils/gpEditBuildingLocation.utills';\nimport './EditPositionByAddress.scss';\n\nconst EditPositionByAddress = () => {\n const dispatch = useDispatch();\n const uploadedFiles = useSelector(uploadedFilesState);\n const selectedFileId = useSelector(selectedFileIdState);\n const [noResults, setNoResults] = useState(false);\n const [tempPosition, setTempPosition] = useState();\n const [tempLocation, setTempLocation] = useState();\n const selectedFile = selectedFileId ? uploadedFiles[selectedFileId] : undefined;\n const isSelectLocationOnMapActive = useSelector(isSelectLocationOnMapActiveState);\n const site = useSelector(siteState);\n\n useEffect(() => {\n dispatch(setSite(undefined));\n }, []);\n\n useEffect(() => {\n if (selectedFile) {\n if (tempPosition) {\n mapController.replaceSelectedFileFootprintGraphic(tempPosition);\n } else {\n mapController.replaceSelectedFileFootprintGraphic(INITIAL_EDIT_POSITION);\n }\n }\n }, [selectedFile, tempPosition]);\n\n useEffect(() => {\n if (!tempPosition) {\n // map controller: enable map click\n dispatch(setIsSelectLocationOnMapActive(true));\n mapController.view3D.surface.style.cursor = 'crosshair';\n } else {\n // map controller: disable map click\n dispatch(setIsSelectLocationOnMapActive(false));\n mapController.view3D.surface.style.cursor = '';\n }\n\n return () => {\n // map controller: disable map click\n dispatch(setIsSelectLocationOnMapActive(false));\n mapController.view3D.surface.style.cursor = '';\n };\n }, [tempPosition]);\n\n useEffect(() => {\n if (isSelectLocationOnMapActive && site) {\n const { address, latlon } = site;\n const [LATITUDE, LONGITUDE] = latlon;\n\n const newLocation = {\n FULLADDRESS: address,\n LATITUDE,\n LONGITUDE,\n };\n\n handleNewLocation(newLocation);\n }\n }, [site]);\n\n const confirmNewPosition = () => {\n if (selectedFile?.geometry && tempPosition) {\n editBuildingLocation(tempPosition);\n }\n };\n\n const cancelNewPosition = () => {\n setTempPosition(undefined);\n setTempLocation(undefined);\n };\n\n const handleNewLocation = async (newLocation: IBuildingLocation) => {\n if (selectedFileId) {\n const selectedFile = uploadedFiles[selectedFileId];\n if (selectedFile?.location && selectedFile?.position) {\n const [Point] = (await loadModules(['esri/geometry/Point'])) as [typeof __esri.Point];\n\n const prevLocation = selectedFile?.location;\n\n if (newLocation && prevLocation) {\n const newLocationPoint = new Point({\n latitude: newLocation.LATITUDE,\n longitude: newLocation.LONGITUDE,\n });\n\n const prevLocationPoint = new Point({\n latitude: prevLocation.LATITUDE,\n longitude: prevLocation.LONGITUDE,\n });\n\n const xyDistance = await getXYDistanceBetweenPoints(prevLocationPoint, newLocationPoint);\n const newPosition = { x: xyDistance.x, y: xyDistance.y, z: selectedFile.position.z };\n\n setTempPosition(newPosition);\n setTempLocation(newLocation);\n }\n }\n }\n };\n\n const selectResult = async (response: any) => {\n const { feature } = response.result;\n const FULLADDRESS = feature.attributes.Match_addr;\n const { latitude: LATITUDE, longitude: LONGITUDE } = feature.geometry;\n\n const newLocation = {\n FULLADDRESS,\n LATITUDE,\n LONGITUDE,\n };\n\n handleNewLocation(newLocation);\n };\n\n const searchComplete = (response: any) => {\n const { numResults } = response;\n setNoResults(!numResults);\n };\n\n return (\n \n {tempPosition ? (\n
\n
Confirm new position?
\n
\n {tempLocation?.FULLADDRESS}\n
\n\n
\n Confirm\n \n
\n Cancel\n \n
\n ) : (\n <>\n
\n Where would you like to place your 3D building file?\n
\n
\n
\n
\n
or select a location on the map
\n
\n >\n )}\n
\n );\n};\n\nexport default EditPositionByAddress;\n","import { useSelector, useDispatch } from 'react-redux';\nimport Button from '../../../shared/Button';\nimport EditPosition from './EditPosition';\nimport {\n errorState,\n resetSubmitWorkflow,\n selectedFileIdState,\n uploadedFilesState,\n uploadingState,\n} from '../../../../../store/slices/uploadSlice';\nimport { activeToolState } from '../../../../../store/slices/appSlice';\nimport EditPositionByAddress from './EditPositionByAddress/EditPositionByAddress';\n\nconst EditActions = ({ showEditSearch, showEditPosition }) => {\n const dispatch = useDispatch();\n const uploading = useSelector(uploadingState);\n const error = useSelector(errorState);\n const activeTool = useSelector(activeToolState);\n const uploadedFiles = useSelector(uploadedFilesState);\n const selectedFileId = useSelector(selectedFileIdState);\n const selectedFile = selectedFileId ? uploadedFiles[selectedFileId] : undefined;\n\n const reset = () => {\n dispatch(resetSubmitWorkflow());\n };\n\n const renderEditActions = () => {\n if (uploading) {\n return null;\n } else if (error) {\n return ;\n } else if (!selectedFile?.location) {\n return Selected file doesn't support editing
;\n } else if (showEditSearch) {\n return ;\n } else if (showEditPosition || activeTool === 'uploaded-files') {\n return ;\n }\n };\n\n return {renderEditActions()}
;\n};\n\nexport default EditActions;\n","import { useState } from 'react';\nimport { useSelector } from 'react-redux';\nimport EditView from './EditView';\nimport EditActions from './EditActions';\nimport { selectedFileIdState, uploadingState } from '../../../../../store/slices/uploadSlice';\nimport '../../../../../css/components/Edit.scss';\n\nconst Edit = () => {\n const [showEditSearch, setShowEditSearch] = useState(false);\n const [showEditPosition, setShowEditPosition] = useState(false);\n const [message, setMessage] = useState('');\n const selectedFileId = useSelector(selectedFileIdState);\n const uploading = useSelector(uploadingState);\n\n // const location = useSelector(locationState);\n\n // useEffect(() => {\n // deriveShowEditSearch(location);\n // }, [location]);\n\n const deriveShowEditSearch = (location) =>\n setShowEditSearch(location && location.hasOwnProperty('FULLADDRESS') ? false : true);\n\n const toggleShowEditSearch = () => {\n setShowEditSearch(!showEditSearch);\n setShowEditPosition(showEditSearch);\n };\n\n return (\n \n \n\n {selectedFileId && !uploading && (\n \n )}\n
\n );\n};\n\nexport default Edit;\n","const KmzSlpkFileIcon = (props) => (\n \n \n \n \n);\n\nexport default KmzSlpkFileIcon;\n","import React from 'react';\n\nexport default function ErrorIcon() {\n return (\n \n \n \n \n \n \n \n );\n}\n","import { useState } from 'react';\nimport { useSelector } from 'react-redux';\nimport KmzSlpkFileIcon from '../../../shared/icons/KmzSlpkFileIcon';\nimport LinearProgressBar from '../../../shared/LinearProgressBar';\nimport ErrorTriangleIcon from '../../../shared/ErrorTriangleIcon';\nimport ErrorMessagesDetailsButton from './ErrorMessagesDetailsButton';\nimport {\n errorState,\n selectedFileIdState,\n uploadedFilesState,\n uploadingState,\n} from '../../../../../store/slices/uploadSlice';\nimport { getFileIcon } from '../../../../utils/file.utils';\n\nconst UploadView = (props) => {\n const [dragFile, setDragFile] = useState(false);\n const error = useSelector(errorState);\n const uploadedFiles = useSelector(uploadedFilesState);\n const selectedFileId = useSelector(selectedFileIdState);\n const selectedFile = uploadedFiles[selectedFileId];\n const uploading = useSelector(uploadingState);\n\n const onDragFile = (event) => {\n event.preventDefault();\n setDragFile(!dragFile);\n };\n\n const handleDrop = (event) => {\n event.preventDefault();\n const dataTransfer = event.dataTransfer;\n const [file] = dataTransfer.files;\n if (file) {\n props.handleUpload(file);\n }\n };\n\n const renderUploadView = () => {\n const { message } = props;\n let jsx;\n\n if (error) {\n jsx = (\n \n
\n
{error.message}
\n
\n
\n );\n } else if (uploading) {\n jsx = ;\n } else if (selectedFile?.file && selectedFile.file instanceof File) {\n jsx = (\n \n {selectedFile?.file && getFileIcon(selectedFile.file)}\n
{selectedFile.file.name}
\n
\n );\n } else {\n jsx = (\n \n
\n
Drag & Drop File Here
\n
\n );\n }\n return jsx;\n };\n\n return {renderUploadView()}
;\n};\n\nexport default UploadView;\n","import { useSelector, useDispatch } from 'react-redux';\nimport { useRef } from 'react';\nimport {\n errorState,\n resetSubmitWorkflow,\n selectedFileIdState,\n uploadedFilesState,\n uploadingState,\n} from '../../../../../store/slices/uploadSlice';\nimport { kmzToMultipatch } from '../../../../utils/gpUpload.utils';\n\nconst UploadActions = (props) => {\n const dispatch = useDispatch();\n const uploading = useSelector(uploadingState);\n const selectedFileId = useSelector(selectedFileIdState);\n const uploadedFiles = useSelector(uploadedFilesState);\n const selectedFile = uploadedFiles[selectedFileId];\n const error = useSelector(errorState);\n const inputRef = useRef();\n\n const handleInput = (event) => {\n if (event.currentTarget.files.length) {\n const [file] = event.currentTarget.files;\n props.handleUpload(file);\n }\n };\n\n const handleNextClick = () => {\n kmzToMultipatch();\n };\n\n const reset = () => {\n dispatch(resetSubmitWorkflow());\n };\n\n const renderUploadActions = () => {\n let jsx;\n\n if (uploading) {\n jsx = null;\n } else {\n // const existingFile = file && file instanceof File;\n const existingFile = selectedFile?.file && selectedFile?.file instanceof File;\n jsx = error ? (\n \n {'Try again >'}\n \n ) : (\n \n {existingFile && (\n
\n Next\n \n )}\n\n
inputRef.current.click()}>\n Choose File\n \n \n\n {existingFile &&
Maximum file size is 15MB
}\n
\n );\n }\n return jsx;\n };\n\n return {renderUploadActions()}
;\n};\n\nexport default UploadActions;\n","import { useState } from 'react';\nimport { useSelector, useDispatch } from 'react-redux';\nimport { gp as gpConfig } from '../../../../config';\nimport api from '../../../../utils/api';\nimport UploadView from './UploadView';\nimport UploadActions from './UploadActions';\nimport { errorState, setError, setUploading, uploadingState } from '../../../../../store/slices/uploadSlice';\n\n// import EsriRequest from 'esri/request';\nimport { loadModules } from 'esri-loader';\nimport { removedBuildingsState, setRemovedBuildings, view3DState } from '../../../../../store/slices/appSlice';\nimport { LAYER_TITLES } from '../../../../configs/config.layerTitles';\nimport { handleError, publishSLPK, reverseGeocodeKMZ } from '../../../../utils/gpUpload.utils';\nimport { getFileType } from '../../../../utils/file.utils';\n\nconst Upload = () => {\n const dispatch = useDispatch();\n const [message, setMessage] = useState('');\n\n const removedBuildings = useSelector(removedBuildingsState);\n const view3D = useSelector(view3DState);\n const error = useSelector(errorState);\n const uploading = useSelector(uploadingState);\n const [currentFile, setCurrentFile] = useState();\n\n const uploadFile = (file) => {\n const fileSize = api.bytesToSize(file.size);\n const valid = validateSize(fileSize);\n if (valid) {\n // ga('send', 'event', '3D Map Events', 'Upload File', 'Upload File');\n const type = getFileType(file);\n dispatch(setError(false));\n setCurrentFile(file);\n\n switch (type) {\n case 'kmz':\n return kmzUpload(file);\n case 'csv':\n return csvUpload(file);\n case 'slpk':\n return slpkUpload(file);\n default:\n return;\n }\n } else {\n setCurrentFile();\n }\n };\n\n const kmzUpload = async (file) => {\n const fd = new FormData();\n fd.append('file', file);\n fd.append('description', '');\n fd.append('f', 'pjson');\n\n try {\n dispatch(setUploading(true));\n const [esriRequest] = await loadModules(['esri/request']);\n const response = await esriRequest(gpConfig.upload, { body: fd });\n\n if (response.data.success) {\n await reverseGeocodeKMZ(response.data.item.itemID, file);\n }\n } catch (error) {\n handleError(error, 'kmzUpload');\n } finally {\n dispatch(setUploading(false));\n }\n };\n\n const csvUpload = (file) => {\n const reader = FileReader();\n reader.onload = (event) => {\n const objectIds = event.currentTarget.result.split(',').map((objectid) => parseInt(objectid));\n // evaluate against already removed buildings\n let i = objectIds.length;\n while (i--) {\n if (removedBuildings.includes(objectIds[i])) {\n objectIds.splice(i, 1);\n }\n }\n const removedBuildingsList = removedBuildings;\n removedBuildingsList.push(...objectIds);\n dispatch(setRemovedBuildings(removedBuildingsList));\n const definition = api.generateBuildingDefinitionExpression(removedBuildings);\n const layer = view3D.map.allLayers.items.find((l) => l.title === LAYER_TITLES.BUILDINGS);\n layer.definitionExpression = definition;\n };\n reader.readAsText(file);\n };\n\n const slpkUpload = async (file) => {\n const formData = new FormData();\n formData.append('file', file);\n formData.append('description', '');\n formData.append('f', 'pjson');\n\n try {\n dispatch(setUploading(true));\n const [esriRequest] = await loadModules(['esri/request']);\n const response = await esriRequest(gpConfig.upload, { body: formData });\n\n if (response.data.success) {\n await publishSLPK(response.data.item.itemID, file);\n }\n } catch (error) {\n handleError(error, 'slpkUpload');\n } finally {\n dispatch(setUploading(false));\n }\n };\n\n const validateSize = (sizeString) => {\n const [number, unit] = sizeString.split(' ');\n switch (unit) {\n case 'TB':\n case 'GB':\n dispatch(setError({ message: 'File size is too large.' }));\n return false;\n case 'MB': {\n const value = parseFloat(number);\n if (value >= 15) {\n dispatch(setError({ message: 'File size is too large.' }));\n return false;\n } else {\n return true;\n }\n }\n default:\n return true;\n }\n };\n\n return (\n \n
\n\n {!uploading && !error &&
or
}\n\n {!uploading &&
}\n
\n );\n};\n\nexport default Upload;\n","import { useSelector, useDispatch } from 'react-redux';\nimport { saveAs } from 'file-saver';\nimport Button from '../../../shared/Button';\nimport '../../../../../css/components/Export.scss';\nimport { decrementStep, resetSubmitWorkflow, selectedFileIdState, slpkState, uploadedFilesState } from '../../../../../store/slices/uploadSlice';\nimport { removedBuildingsState, setActiveTool } from '../../../../../store/slices/appSlice';\n\nconst Export = () => {\n const uploadedFiles = useSelector(uploadedFilesState);\n const selectedFileId = useSelector(selectedFileIdState);\n const selectedFile = uploadedFiles[selectedFileId];\n const dispatch = useDispatch();\n const removedBuildings = useSelector(removedBuildingsState);\n\n const exportSLPK = () => {\n if (selectedFile?.slpk) {\n // save scene layer package as well as csv of removed buildings\n window.open(selectedFile?.slpk, '_blank');\n // saveAs(new Blob([removedBuildings.join(',')], { type: 'text/csv;charset=utf-8' }), 'buildings.csv');\n }\n };\n\n const resetSubmit = () => {\n dispatch(setActiveTool('submit'));\n dispatch(resetSubmitWorkflow());\n };\n\n const continueEditing = () => dispatch(decrementStep());\n\n return (\n \n \n \n \n
\n );\n};\n\nexport default Export;\n","import { useSelector } from 'react-redux';\nimport Stepper from '../shared/Stepper';\nimport Edit from '../site/controls/submitparts/Edit';\nimport Upload from '../site/controls/submitparts/Upload';\nimport Export from '../site/controls/submitparts/Export';\nimport { stepState } from '../../../store/slices/uploadSlice';\nimport '../../../css/components/_submit.scss';\n\nconst Submit = () => {\n const step = useSelector(stepState);\n\n const renderCurrentStep = (step) => {\n switch (step) {\n case 0:\n return ;\n case 1:\n return ;\n case 2:\n return ;\n default:\n return;\n }\n };\n\n return (\n \n
\n
{renderCurrentStep(step)}
\n
\n );\n};\n\nexport default Submit;\n","import moment from 'moment';\n\nconst seasonToEquinox = (season) => {\n const year = new Date().getFullYear();\n switch (season) {\n case 'Spring':\n return moment().year(year).month('March').date(20);\n case 'Summer':\n return moment().year(year).month('June').date(21);\n case 'Fall':\n return moment().year(year).month('September').date(22);\n case 'Winter':\n return moment().year(year).month('December').date(21);\n default:\n return moment();\n }\n};\n\nconst getTime = (hour, minute, period) => {\n const trueHour = period === 'PM' ? Number(hour) + 12 : Number(hour);\n return moment().hour(trueHour).minute(Number(minute));\n};\n\nconst getDate = (season, hour, minute, period) => {\n const seasonDate = seasonToEquinox(season);\n const time = getTime(hour, minute, period);\n return seasonDate.hour(time.hour()).minute(time.minute());\n};\n\nconst getSeason = (date = new Date()) => {\n const month = date.getMonth();\n const day = date.getDate();\n\n // Dec 21 - Mar 19\n const isWinter = month < 2 || (month === 2 && day < 20) || (month === 11 && day >= 21);\n\n // Mar 20 - Jun 20\n const isSpring = (month > 2 && month < 5) || (month === 2 && day >= 20) || (month === 5 && day < 21);\n\n // Jun 21 - Sep 21\n const isSummer = (month > 5 && month < 8) || (month === 5 && day >= 21) || (month === 8 && day < 22);\n\n // Sep 21 - Dec 20\n const isFall = (month > 8 && month < 11) || (month === 8 && day >= 22) || (month === 11 && day < 21);\n\n if (isWinter) {\n return 'Winter';\n } else if (isSpring) {\n return 'Spring';\n } else if (isSummer) {\n return 'Summer';\n } else if (isFall) {\n return 'Fall';\n }\n};\n\nconst formatDate = (date) => {\n return moment(date).format('dddd, MMMM Do YYYY');\n};\n\nconst dateUtils = {\n seasonToEquinox,\n getTime,\n getDate,\n formatDate,\n getSeason,\n};\n\nexport default dateUtils;\n","import { useEffect, useRef, useState } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\n//@ts-ignore\nimport MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';\n//@ts-ignore\nimport getMuiTheme from 'material-ui/styles/getMuiTheme';\n//@ts-ignore\nimport DatePicker from 'material-ui/DatePicker';\nimport Button from '@material-ui/core/Button';\nimport { IconButton } from '@material-ui/core';\nimport EditIcon from '@material-ui/icons/Edit';\nimport { calendarTheme } from '../../../../../../style/theme';\nimport { view3DReadyState } from '../../../../../../store/slices/appSlice';\nimport DateUtil from '../../../../../utils/date';\nimport { SeasonType, selectedDateState, setSelectedDate } from '../../../../../../store/slices/lightStudySlice';\nimport dateUtils from '../../../../../utils/date';\nimport { selectedFileIdState } from '../../../../../../store/slices/uploadSlice';\nimport { selectedSketchState } from '../../../../../../store/slices/sketchSlice';\nimport './LightStudyDateSection.scss';\n\n// To have black text on date/time pickers - must create a new theme provider which overwrites app themeprovider\nconst dateTimeTheme = getMuiTheme({ ...calendarTheme });\n\nconst LightStudyDateSection = () => {\n const datePickerRef = useRef();\n const dispatch = useDispatch();\n const view3DReady = useSelector(view3DReadyState);\n const selectedDate = useSelector(selectedDateState);\n const selectedFileId = useSelector(selectedFileIdState);\n const selectedSketch = useSelector(selectedSketchState);\n const [season, setSeason] = useState(DateUtil.getSeason(selectedDate));\n const smallButtons = selectedFileId || selectedSketch;\n\n useEffect(() => {\n if (selectedDate) {\n setSeason(DateUtil.getSeason(selectedDate));\n }\n }, [selectedDate]);\n\n const openDatePicker = () => {\n datePickerRef.current?.openDialog();\n };\n\n const changeSeason = (e: React.MouseEvent) => {\n const selectedSeason = e.currentTarget.id;\n\n const date = dateUtils.getDate(selectedSeason, selectedDate.getHours()).toDate();\n dispatch(setSelectedDate(date));\n };\n\n const datePickerChange = (event: any, date: any) => {\n if (view3DReady) {\n const updatedDate = date;\n\n // Passing over hours, minutes, and seconds from previous time\n updatedDate.setHours(selectedDate.getHours(), selectedDate.getMinutes(), selectedDate.getSeconds());\n dispatch(setSelectedDate(updatedDate));\n }\n };\n\n return (\n <>\n \n \n \n \n
\n
\n {DateUtil.formatDate(selectedDate)}\n
\n\n
\n \n \n {/*
\n edit\n */}\n
\n\n
\n
Select time of year
\n
\n \n Spring\n \n \n Summer\n \n \n Fall\n \n \n Winter\n \n
\n
\n
\n >\n );\n};\n\nexport default LightStudyDateSection;\n","import { useEffect, useState } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport moment from 'moment';\nimport { selectedDateState, setSelectedDate } from '../../../../../../store/slices/lightStudySlice';\nimport NumberCounter from '../../../../shared/NumberCounter';\nimport './LightStudyTimeSelection.scss';\n\nconst LightStudyTimeSelection = () => {\n const dispatch = useDispatch();\n const selectedDate = useSelector(selectedDateState);\n\n const [hours, setHours] = useState(selectedDate.getHours());\n const [minutes, setMinutes] = useState(selectedDate.getMinutes());\n\n useEffect(() => {\n if (hours !== selectedDate.getHours()) {\n setHours(selectedDate.getHours());\n }\n\n if (minutes !== selectedDate.getMinutes()) {\n setMinutes(selectedDate.getMinutes());\n }\n }, [selectedDate]);\n\n const updateStates = (date: Date) => {\n setHours(date.getHours());\n setMinutes(date.getMinutes());\n dispatch(setSelectedDate(date));\n };\n\n const onHourIncrement = () => {\n const currentDate = moment(selectedDate);\n let updatedDate = moment(selectedDate).add(1, 'hours');\n\n if (updatedDate.date() !== currentDate.date()) {\n updatedDate.subtract(1, 'days');\n }\n\n updateStates(updatedDate.toDate());\n };\n\n const onHourDecrement = () => {\n const currentDate = moment(selectedDate);\n let updatedDate = moment(selectedDate).subtract(1, 'hours');\n\n if (updatedDate.date() !== currentDate.date()) {\n updatedDate.add(1, 'days');\n }\n\n updateStates(updatedDate.toDate());\n };\n\n const onHourInputChange = (e: React.ChangeEvent) => {\n const value = e.target.value;\n const number = Number(value);\n if (number >= 0 && number <= 12) {\n let updatedDate;\n\n if (hours < 12) {\n updatedDate = moment(selectedDate).hours(number);\n } else {\n updatedDate = moment(selectedDate).hours(number + 12);\n }\n\n updateStates(updatedDate.toDate());\n }\n };\n\n const onMinuteIncrement = () => {\n const currentDate = moment(selectedDate);\n let updatedDate = moment(selectedDate).add(1, 'minutes');\n\n if (updatedDate.date() !== currentDate.date()) {\n updatedDate.subtract(1, 'days');\n }\n\n updateStates(updatedDate.toDate());\n };\n\n const onMinuteDecrement = () => {};\n\n const onMinuteInputChange = (e: React.ChangeEvent) => {\n const value = e.target.value;\n const number = Number(value);\n \n if (number >= 0 && number <= 59) {\n let updatedDate = moment(selectedDate).minutes(number);\n updateStates(updatedDate.toDate());\n }\n };\n\n const changePeriod = () => {\n let updatedHour;\n\n if (hours >= 12) {\n updatedHour = hours - 12;\n } else {\n updatedHour = (hours + 12) % 24;\n }\n\n let updatedDate = moment(selectedDate).hour(updatedHour);\n updateStates(updatedDate.toDate());\n };\n\n const getFormattedHoursAMPM = (hours: number) => (hours % 12 ? hours % 12 : 12);\n\n return (\n \n
Select time of day
\n
\n
\n
\n
\n {/* {hours < 12 ? 'AM' : 'PM'} */}\n \n AM\n \n = 12 ? 'active' : ''}`}\n onClick={changePeriod}\n >\n PM\n \n
\n
\n
\n );\n};\n\nexport default LightStudyTimeSelection;\n","import { useEffect, useState } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport PlayArrowIcon from '@material-ui/icons/PlayArrowRounded';\nimport ShadowCastIcon from '@material-ui/icons/BrightnessMedium';\nimport StopIcon from '@material-ui/icons/StopRounded';\nimport Button from '@material-ui/core/Button';\nimport {\n isLightStudyActiveState,\n selectedDateState,\n setIsLightStudyActive,\n setIsShadowCastActive,\n setSelectedDate,\n} from '../../../../../../store/slices/lightStudySlice';\nimport './LightStudyAction.scss';\n\nconst LightStudyAction = () => {\n const dispatch = useDispatch();\n const selectedDate = useSelector(selectedDateState);\n const isLightStudyActive = useSelector(isLightStudyActiveState);\n const [lightStudyInterval, setLightStudyInterval] = useState(undefined);\n const INTERVAL_TIME = 500;\n\n useEffect(() => {\n return () => {\n if (lightStudyInterval) {\n stopLightStudy();\n }\n };\n }, [lightStudyInterval]);\n\n const startLightStudy = () => {\n let updatedDate = new Date(selectedDate);\n\n const interval = window.setInterval(() => {\n updatedDate = new Date(updatedDate);\n\n // increase the time by 30 minutes\n updatedDate.setMinutes(updatedDate.getMinutes() + 30);\n dispatch(setSelectedDate(updatedDate));\n }, INTERVAL_TIME);\n\n setLightStudyInterval(interval);\n dispatch(setIsLightStudyActive(true));\n };\n\n const stopLightStudy = () => {\n clearInterval(lightStudyInterval);\n setLightStudyInterval(undefined);\n dispatch(setIsLightStudyActive(false));\n };\n\n const startShadowCast = () => {\n dispatch(setIsShadowCastActive(true));\n };\n\n return (\n \n {isLightStudyActive ? (\n }\n onClick={stopLightStudy}\n >\n Stop Light Study\n \n ) : (\n }\n onClick={startLightStudy}\n >\n Start Light Study\n \n )}\n\n }\n onClick={startShadowCast}\n >\n Shadow Cast\n \n
\n );\n};\n\nexport default LightStudyAction;\n","import { ChangeEvent, useEffect, useState } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { FormControlLabel, Switch, Tooltip } from '@material-ui/core';\nimport ColorPicker from 'material-ui-color-picker';\nimport {\n displayShadowHighlightedState,\n highlightColorState,\n setDisplayShadowHighlighted,\n setHighlightColor,\n view3DState,\n} from '../../../../../../store/slices/appSlice';\nimport { selectedSketchState } from '../../../../../../store/slices/sketchSlice';\nimport { selectedFileIdState, selectedFileState } from '../../../../../../store/slices/uploadSlice';\nimport mapController from '../../../../../controllers/MapController';\nimport sketchController from '../../../../../controllers/SketchController';\nimport './LightStudyShadowControls.scss';\n\nconst LightStudyShadowControls = () => {\n const dispatch = useDispatch();\n const view3D = useSelector(view3DState);\n const selectedFileId = useSelector(selectedFileIdState);\n const selectedFile = useSelector(selectedFileState);\n const selectedSketch = useSelector(selectedSketchState);\n const displayShadowHighlighted = useSelector(displayShadowHighlightedState);\n const highlightShadowColor = useSelector(highlightColorState);\n\n const [shadowOn, setShadowOn] = useState(false);\n\n useEffect(() => {\n if (selectedFileId && view3D) {\n const selectedFileLayer = view3D?.map?.findLayerById(selectedFile?.sceneLayerId);\n if (selectedFileLayer) {\n const castShadows = selectedFileLayer?.renderer?.symbol?.symbolLayers?.getItemAt(0)?.castShadows;\n setShadowOn(castShadows);\n }\n } else if (selectedSketch) {\n const selectedSketchGraphic = sketchController.sketchGraphicsLayer?.graphics.find(\n // @ts-ignore\n (graphic) => graphic.uid === selectedSketch.uid\n );\n if (selectedSketchGraphic) {\n // @ts-ignore\n const castShadows = selectedSketchGraphic?.symbol?.symbolLayers?.getItemAt(0)?.castShadows;\n setShadowOn(castShadows);\n }\n }\n }, [selectedFileId, selectedSketch, view3D]);\n\n if (!selectedFileId && !selectedSketch) {\n return null;\n }\n\n const handleHighlightColorChange = (color: any) => {\n dispatch(setHighlightColor(color));\n mapController.setHighlightShadowColor(color);\n };\n\n const handleSelectedFileShadowToggle = (visible: boolean) => {\n mapController.setSelectedFileShadow(visible);\n setShadowOn(visible);\n\n if (!visible) {\n mapController.setSelectedFileShadowHighlight(false);\n dispatch(setDisplayShadowHighlighted(false));\n }\n };\n\n const handleSelectedSketchShadowToggle = (visible: boolean) => {\n sketchController.setSelectedSketchShadow(visible);\n setShadowOn(visible);\n\n if (!visible) {\n sketchController.setSelectedSketchShadowHighlight(false);\n dispatch(setDisplayShadowHighlighted(false));\n }\n };\n\n const handleShadowToggle = (event: ChangeEvent) => {\n const visible = event.target.checked;\n\n if (selectedFileId) {\n handleSelectedFileShadowToggle(visible);\n } else if (selectedSketch) {\n handleSelectedSketchShadowToggle(visible);\n }\n };\n\n const handleHighlightShadowToggle = (event: ChangeEvent) => {\n const highlighted = event.target.checked;\n\n if (selectedFileId) {\n mapController.setSelectedFileShadowHighlight(highlighted, highlightShadowColor);\n } else if (selectedSketch) {\n sketchController.setSelectedSketchShadowHighlight(highlighted, highlightShadowColor);\n }\n\n dispatch(setDisplayShadowHighlighted(highlighted));\n };\n\n return (\n \n
\n
\n }\n label={`New Building Shadow`}\n labelPlacement='end'\n />\n\n
\n \n }\n label='Highlight Shadow'\n labelPlacement='end'\n />\n {displayShadowHighlighted && (\n \n e.stopPropagation()}\n onCompositionUpdate={(e) => e.stopPropagation()}\n />\n \n )}\n
\n
\n
\n );\n};\n\nexport default LightStudyShadowControls;\n","import { useEffect, useRef } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport CircularProgress from '@material-ui/core/CircularProgress';\nimport { isShadowCastLoadedState } from '../../../../../store/slices/appSlice';\nimport mapController from '../../../../controllers/MapController';\nimport { setIsShadowCastActive } from '../../../../../store/slices/lightStudySlice';\nimport './ShadowCast.scss';\n\nconst ShadowCast = () => {\n const dispatch = useDispatch();\n const shadowCastRef = useRef(null);\n const isShadowCastLoaded = useSelector(isShadowCastLoadedState);\n\n useEffect(() => {\n if (shadowCastRef.current) {\n mapController.initializeShadowCastWidget(shadowCastRef);\n }\n\n return () => mapController.cancelShadowCast();\n }, [shadowCastRef]);\n\n const handleReturnButtonClick = () => {\n dispatch(setIsShadowCastActive(false));\n };\n\n return (\n \n
\n\n {isShadowCastLoaded ? (\n
handleReturnButtonClick()}>\n Return to Light Study\n \n ) : (\n
\n )}\n
\n );\n};\n\nexport default ShadowCast;\n","import { useEffect } from 'react';\nimport { useSelector } from 'react-redux';\nimport LightStudyDateSection from './LightStudyDateSection/LightStudyDateSection';\nimport LightStudyTimeSelection from './LightStudyTimeSelection/LightStudyTimeSelection';\nimport LightStudyAction from './LightStudyAction/LightStudyAction';\nimport LightStudyShadowControls from './LightStudyShadowControls/LightStudyShadowControls';\nimport { isShadowCastActiveState, selectedDateState } from '../../../../../store/slices/lightStudySlice';\nimport mapController from '../../../../controllers/MapController';\nimport ShadowCast from '../ShadowCast/ShadowCast';\nimport './LightStudy.scss';\n\nconst LightStudy = () => {\n const selectedDate = useSelector(selectedDateState);\n const isShadowCastActive = useSelector(isShadowCastActiveState);\n\n useEffect(() => {\n mapController.updateViewsLightning(selectedDate);\n }, [selectedDate]);\n\n return (\n \n {isShadowCastActive ? (\n \n ) : (\n <>\n \n \n \n \n >\n )}\n
\n );\n};\n\nexport default LightStudy;\n","import { StyleSheet } from '@react-pdf/renderer';\n\nconst width = 555;\nconst margin = 20;\nconst headerPadding = 15;\n\nconst headerDefault = {\n display: 'flex',\n height: 100,\n width: '100%',\n backgroundColor: '#202c3c',\n color: 'white',\n paddingLeft: headerPadding,\n paddingRight: headerPadding,\n};\n\nexport default StyleSheet.create({\n document: {\n display: 'flex',\n flexDirection: 'column',\n },\n page: {\n display: 'flex',\n flexDirection: 'column',\n },\n headerRow: {\n flexDirection: 'row',\n justifyContent: 'space-between',\n ...headerDefault,\n paddingBottom: headerPadding,\n paddingTop: headerPadding,\n },\n headerColumn: {\n flexDirection: 'column',\n },\n headerImagesColumn: {\n position: 'relative',\n display: 'flex',\n flexDirection: 'row',\n flexWrap: 'nowrap',\n justifyContent: 'flex-end',\n },\n headerText: {\n fontSize: 18,\n flexGrow: 2,\n alignSelf: 'center',\n },\n logo: {\n marginRight: 10,\n height: '100%',\n width: 'auto',\n },\n subSection: {\n display: 'flex',\n flexWrap: 'wrap',\n flexDirection: 'column',\n marginTop: 0,\n marginLeft: margin + 5,\n marginRight: margin + 5,\n marginBottom: margin + 5,\n },\n capitalize: {\n textTransform: 'capitalize',\n },\n subHeaderText: {\n fontSize: 12,\n flexGrow: 2,\n width: width,\n },\n devStandsRow: {\n display: 'flex',\n flexDirection: 'row',\n height: 'auto',\n },\n lotResultsWrapper: {\n display: 'flex',\n flexDirection: 'row',\n flexWrap: 'wrap',\n marginTop: margin,\n marginBottom: 0,\n },\n lotResultsSubwrapper: {\n display: 'flex',\n flexWrap: 'wrap',\n flexDirection: 'row',\n maxWidth: width,\n marginTop: margin / 2,\n marginBottom: margin / 2,\n },\n lotResultsText: {\n marginTop: 7.5,\n marginBottom: 7.5,\n fontSize: 11,\n width: '23%',\n },\n itemText: {\n marginRight: 20,\n marginBottom: 20,\n width: '23%',\n fontSize: 11,\n },\n noResultsText: {\n fontSize: 11,\n marginRight: 'auto',\n },\n detailText: {\n fontSize: 12,\n flexGrow: 2,\n marginTop: 15,\n },\n pageContent: {\n display: 'flex',\n flexDirection: 'column',\n marginTop: 0,\n marginLeft: margin,\n marginRight: margin,\n marginBottom: margin,\n maxWidth: width,\n },\n shadowSection: {\n paddingTop: 10,\n },\n screenShotWrapper: {\n flexDirection: 'column',\n justifyContent: 'center',\n },\n screenshot: {\n objectFit: 'cover',\n height: 310,\n width: width,\n marginTop: 10,\n fontSize: 11,\n maxWidth: '100%',\n maxHeight: '100%',\n borderBottom: '2pt solid black',\n },\n legendsContainer: {\n marginTop: 4,\n marginBottom: 10,\n },\n singleLegendContainer: {\n display: 'flex',\n flexDirection: 'row',\n flexWrap: 'nowrap',\n justifyContent: 'flex-start',\n alignItems: 'center',\n },\n legendText: {\n fontSize: 9,\n flexGrow: 1,\n marginLeft: 2,\n },\n});\n","import React from 'react';\nimport {\n Text,\n View\n} from '@react-pdf/renderer';\n\nimport style from './pdfStyle.config';\n\n\nconst LotResults = ({results, title}) => {\n const lots = results.map((result, index) => {result} );\n\n return (\n \n {title}: \n \n {lots}\n \n \n );\n};\n\nexport default LotResults;\n","export default __webpack_public_path__ + \"static/media/logo.f543fe65.png\";","import React from 'react';\nimport { Text, View, Image, Link } from '@react-pdf/renderer';\n\nimport dcozLogo from '../../../../../css/images/logo.png';\nimport style from './pdfStyle.config';\n\nconst Header = ({ location, insetScreenshotUrl, dcoz2DURL }) => {\n const date = new Date();\n const address = location && location.toLowerCase().substring(0, location.lastIndexOf(' '));\n const quadrant = location && location.substring(location.lastIndexOf(' ') + 1, location.length);\n\n const subHeaderTextCustom = {\n ...style.subHeaderText,\n width: 'auto',\n };\n\n return (\n \n \n \n DC Office of Zoning \n Shadow Report \n {date.toDateString()} \n {location ? (\n \n \n {address} {quadrant}\n \n \n ) : null}\n \n \n \n \n \n \n \n );\n};\n\nexport default Header;\n","import React from 'react';\nimport { Page, Text, View, Image } from '@react-pdf/renderer';\n\nimport Header from './Header';\nimport Color from 'color';\n\nimport style from './pdfStyle.config';\n\nconst ShadowPage = ({\n highlightShadow,\n shadowHighlightColor,\n screenshotData,\n time,\n location,\n insetScreenshotUrl,\n dcoz2DURL,\n}) => {\n const [buildingUpload, originalBuilding] = screenshotData;\n\n return (\n \n \n \n \n {time}: \n \n {buildingUpload && (\n <>\n \n\n {/* LEGENDS */}\n {highlightShadow && (\n \n \n \n NEW SHADOW CREATED BY PROJECT \n \n \n \n SHADOW CREATED BY EXISTING DEVELOPMENT \n \n \n \n \n AREAS OF OVERLAP BETWEEN EXISTING SHADOW AND NEW SHADOW CREATED BY PROJECT\n \n \n \n )}\n >\n )}\n {originalBuilding && (\n \n )}\n {!buildingUpload || !originalBuilding ? (\n Unable to render {time} screenshot(s) \n ) : null}\n \n \n \n \n );\n};\n\nexport default ShadowPage;\n","import React from 'react';\nimport { Page, View, Document } from '@react-pdf/renderer';\n\nimport LotResults from './LotResults';\nimport Header from './Header';\nimport ShadowPage from './ShadowPage';\n\nimport style from './pdfStyle.config';\nimport moment from 'moment-timezone';\n\nconst ShadowReport = ({\n highlightShadow,\n shadowHighlightColor,\n screenshotsData,\n location,\n sslResults,\n zoneResults,\n insetScreenshotUrl,\n dcoz2DURL,\n}) => {\n return (\n \n {Object.entries(screenshotsData).map(([stringDate, data]) => {\n const date = new Date(stringDate);\n\n return (\n \n );\n })}\n\n {(sslResults?.length || zoneResults?.length) && (\n \n \n \n {sslResults?.length && }\n {zoneResults?.length && }\n \n \n )}\n \n );\n};\n\nexport default ShadowReport;\n","import { useState, useEffect, useRef } from 'react';\nimport { useSelector, useDispatch } from 'react-redux';\nimport ReactGA from 'react-ga4';\nimport { BlobProvider } from '@react-pdf/renderer';\nimport { urls } from '../../../../config';\nimport ShadowReportDocument from './ShadowReportDocument';\nimport LinearProgressBar from '../../../shared/LinearProgressBar';\nimport api from '../../../../utils/api';\nimport {\n generateInsetMap,\n getLotData,\n generateCurrentBuildingGraphic,\n takeScreenshot,\n getInsetMapUrl,\n} from '../../../../utils/esri';\nimport { loadModules } from 'esri-loader';\nimport mapController from '../../../../controllers/MapController';\nimport sketchController from '../../../../controllers/SketchController';\nimport {\n addRemovedBuilding,\n displayShadowHighlightedState,\n highlightColorState,\n popRemovedBuilding,\n removedBuildingsState,\n setCurrentBuilding,\n siteState,\n} from '../../../../../store/slices/appSlice';\nimport { selectedFileState } from '../../../../../store/slices/uploadSlice';\nimport { selectedSketchState } from '../../../../../store/slices/sketchSlice';\nimport { LAYER_TITLES } from '../../../../configs/config.layerTitles';\nimport './ShadowReport.scss';\n\nconst ShadowReport = () => {\n const dispatch = useDispatch();\n const insetMapRef = useRef(null);\n const [reportReady, setReportReady] = useState(false);\n const [generatingReport, setGeneratingReport] = useState(false);\n const selectedFile = useSelector(selectedFileState);\n const selectedSketch = useSelector(selectedSketchState);\n const removedBuildings = useSelector(removedBuildingsState);\n const displayShadowHighlighted = useSelector(displayShadowHighlightedState);\n const highlightColor = useSelector(highlightColorState);\n const site = useSelector(siteState);\n\n const [reportData, setReportData] = useState<{\n screenshotsData: any;\n sslResults: any;\n zoneResults: any;\n insetScreenshotUrl: string;\n zr16Url: any;\n }>();\n\n useEffect(() => {\n setReportReady(!!reportData);\n }, [reportData]);\n\n const getInsetScreenshot = async (latitude: number, longitude: number) => {\n const { buildingGraphic, buildingCircle } = await generateCurrentBuildingGraphic(latitude, longitude);\n const insetMapView = await generateInsetMap(insetMapRef.current, buildingCircle.extent);\n insetMapView.graphics.add(buildingGraphic);\n\n const insetScreenshotUrl = await getInsetMapUrl(insetMapView);\n\n return insetScreenshotUrl;\n };\n\n const getScreenshotsData = async (prevScreenshotsData: { [key: string]: any } = {}) => {\n const result = prevScreenshotsData;\n\n let date: Date;\n let currentScreenshoot;\n let prevData;\n const currentYear = new Date().getFullYear();\n\n const monthDayTuples = [\n [2, 20], // March 20,\n [5, 21], // June 21,\n [8, 23], // September 23,\n [11, 21], // December 21\n ];\n\n for (const monthDay of monthDayTuples) {\n for (let i = 0; i < 4; i++) {\n const hour = 9 + i * 3; //9, 12, 15, 18 hours\n date = new Date(currentYear, monthDay[0], monthDay[1], hour, 0, 0);\n mapController.view3D.environment.lighting.date = date;\n currentScreenshoot = await takeScreenshot(mapController.view3D);\n prevData = result[date.toString()] ?? [];\n result[date.toString()] = [...prevData, currentScreenshoot];\n }\n }\n\n return result;\n };\n\n const restoreRemovedBuilding = async () => {\n dispatch(popRemovedBuilding());\n\n const definition = api.generateBuildingDefinitionExpression(removedBuildings);\n const layer = mapController.findLayerByTitle(LAYER_TITLES.BUILDINGS);\n if (layer) {\n layer.definitionExpression = definition;\n }\n\n dispatch(setCurrentBuilding(undefined));\n };\n\n const removeCurrentBuilding = async () => {\n if (selectedFile) {\n const point = selectedFile.geometry?.centroid;\n\n if (point) {\n const screenPoint = mapController.view3D.toScreen(point);\n const data = await mapController.view3D.hitTest(screenPoint);\n const [feature] = data.results;\n\n if (feature?.graphic) {\n dispatch(setCurrentBuilding(feature.graphic));\n\n const attributes = feature.graphic.attributes;\n if (attributes.OBJECTID) {\n dispatch(addRemovedBuilding(attributes.OBJECTID));\n } else if (attributes.OID) {\n dispatch(addRemovedBuilding(attributes.OID));\n }\n\n const definition = api.generateBuildingDefinitionExpression(removedBuildings);\n const layer = mapController.findLayerByTitle(LAYER_TITLES.BUILDINGS);\n if (layer) {\n layer.definitionExpression = definition;\n }\n\n dispatch(setCurrentBuilding(undefined));\n } else {\n console.log('getAndSetCurrentBuilding() - Unable to generate feature.graphic');\n }\n }\n }\n };\n\n const getGeometryLotData = async (geometry: __esri.Geometry) => {\n const [lotData, zoneData] = await Promise.all([\n getLotData(geometry, urls.queryLayer),\n getLotData(geometry, urls.zoneQuery),\n ]);\n\n let allSSLs;\n if (Array.isArray(lotData)) {\n allSSLs = lotData?.map((lot) => `${lot.Square} ${lot.LotNum}`);\n }\n\n let zoneLabels;\n if (Array.isArray(zoneData)) {\n zoneLabels = zoneData?.map((result) => result.Zoning_Label);\n }\n\n return { sslResults: allSSLs, zoneResults: zoneLabels };\n };\n\n const getZR16Url = async (longitute: number, latitude: number) => {\n const [webMercatorUtils] = await loadModules(['esri/geometry/support/webMercatorUtils']);\n\n const [x, y] = webMercatorUtils.lngLatToXY(longitute, latitude);\n const zoom = Math.round(mapController.view3D.zoom) > 18 ? 18 : Math.round(mapController.view3D.zoom);\n\n const url = urls.zr16(x, y, zoom);\n\n return url;\n };\n\n const handleSelectedFileShadowReport = async () => {\n if (selectedFile) {\n await mapController.waitViewUpdate();\n\n const { geometry, location, itemId, sceneLayerId } = selectedFile;\n\n const insetScreenshotUrl = await getInsetScreenshot(location.LATITUDE, location.LONGITUDE);\n\n await mapController.view3D.goTo({ target: geometry, zoom: mapController.view3D.zoom, tilt: 0 });\n await removeCurrentBuilding();\n const firstRoundScreenshots = await getScreenshotsData();\n\n await restoreRemovedBuilding();\n const uploadBuildingLayer = mapController.view3D.map.findLayerById(sceneLayerId);\n uploadBuildingLayer.visible = false;\n sketchController.setSketchLayerVisibility(false);\n\n mapController.removeLocationPinGraphic();\n const screenshotsData = await getScreenshotsData(firstRoundScreenshots);\n\n uploadBuildingLayer.visible = true;\n\n const { sslResults, zoneResults } = await getGeometryLotData(geometry);\n\n const zr16Url = await getZR16Url(location.LONGITUDE, location.LATITUDE);\n\n mapController.view3D.environment.lighting.date = new Date();\n setReportData({\n screenshotsData,\n insetScreenshotUrl,\n sslResults,\n zoneResults,\n zr16Url,\n });\n }\n };\n\n const handleSelectedSketchReport = async () => {\n const view3D = mapController.view3D;\n const { geometry } = selectedSketch;\n\n await mapController.waitViewUpdate();\n\n const centerPoint = geometry.extent.center;\n const insetScreenshotUrl = await getInsetScreenshot(centerPoint.latitude, centerPoint.longitude);\n\n sketchController.cancelSketch();\n sketchController.setSketchLayerVisibility(true);\n await view3D.goTo({\n target: geometry,\n tilt: 0,\n scale: view3D.scale,\n zoom: view3D.zoom,\n });\n\n mapController.removeLocationPinGraphic();\n const firstRoundScreenshots = await getScreenshotsData();\n sketchController.setSketchLayerVisibility(false);\n const screenshotsData = await getScreenshotsData(firstRoundScreenshots);\n\n sketchController.setSketchLayerVisibility(true);\n\n const { sslResults, zoneResults } = await getGeometryLotData(geometry);\n const zr16Url = await getZR16Url(centerPoint.longitude, centerPoint.latitude);\n\n mapController.view3D.environment.lighting.date = new Date();\n\n setReportData({\n screenshotsData,\n insetScreenshotUrl,\n sslResults,\n zoneResults,\n zr16Url,\n });\n };\n\n const generateReport = () => {\n setGeneratingReport(true);\n if (selectedFile) {\n handleSelectedFileShadowReport();\n ReactGA.event({\n action: 'shadow_report',\n category: 'uploaded_file',\n });\n } else if (selectedSketch) {\n handleSelectedSketchReport();\n ReactGA.event({\n action: 'shadow_report',\n category: 'sketch',\n });\n }\n };\n\n return (\n \n );\n};\n\nexport default ShadowReport;\n","import { useEffect, useRef } from 'react';\nimport { useSelector } from 'react-redux';\nimport { activeToolState, view3DState } from '../../../../../store/slices/appSlice';\nimport mapController from '../../../../controllers/MapController';\nimport './LineOfSight.scss';\n\nconst LineOfSight = () => {\n const view3D = useSelector(view3DState);\n const activeTool = useSelector(activeToolState);\n const lineOfSightRef = useRef(null);\n\n const hidden = activeTool !== 'line-of-sight';\n\n useEffect(() => {\n if (view3D?.ready && lineOfSightRef.current) {\n mapController.initializeLineOfSightWidget(lineOfSightRef);\n }\n\n return () => {\n mapController.removeLineOfSight();\n };\n }, [lineOfSightRef, view3D?.ready]);\n\n useEffect(() => {\n if (hidden) {\n mapController.stopLineOfSight();\n }\n }, [hidden]);\n\n return (\n \n );\n};\n\nexport default LineOfSight;\n","import { useEffect, useState } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport {\n Select,\n InputLabel,\n Button,\n FormControl,\n MenuItem,\n FormGroup,\n FormControlLabel,\n Switch,\n IconButton,\n} from '@material-ui/core';\nimport EditIcon from '@material-ui/icons/Edit';\nimport CheckIcon from '@material-ui/icons/Check';\nimport VisibleIcon from '@material-ui/icons/Visibility';\nimport NotVisibleIcon from '@material-ui/icons/VisibilityOff';\nimport {\n ISketch,\n isMagnifierOnState,\n selectedSketchState,\n setIsMagnifierOn,\n setSketchStatus,\n sketchesState,\n updateSketch,\n} from '../../../../../../store/slices/sketchSlice';\nimport StyledTooltip from '../../../../shared/StyledTooltip';\nimport sketchController from '../../../../../controllers/SketchController';\nimport mapController from '../../../../../controllers/MapController';\nimport NumberCounter from '../../../../shared/NumberCounter';\nimport apiUtils from '../../../../../utils/api';\nimport { SketchHeightUnit } from '../../../../../configs/config.sketch';\nimport './SketchEdit.scss';\n\nconst SketchEdit = () => {\n const units: SketchHeightUnit[] = ['Feet', 'Meters'];\n const dispatch = useDispatch();\n const selectedSketchGraphic = useSelector(selectedSketchState);\n const sketches = useSelector(sketchesState);\n const selectedSketch = sketches.find((sketch) => sketch.uid === selectedSketchGraphic?.uid);\n const isMagnifierOn = useSelector(isMagnifierOnState);\n const [editingTitle, setEditingTitle] = useState(false);\n const [editingTitleValue, setEditingTitleValue] = useState(() => selectedSketch?.title ?? '');\n\n useEffect(() => {\n setEditingTitleValue(selectedSketch?.title ?? '');\n }, [selectedSketch?.title]);\n\n // TODO: same method is in Sketch component\n const setMagnifier = (isOn: boolean) => {\n mapController.setMagnifierVisibility(isOn);\n dispatch(setIsMagnifierOn(isOn));\n };\n\n if (!selectedSketch) {\n return null;\n }\n\n const unit = selectedSketch.unit;\n const height = selectedSketch.height;\n const title = selectedSketch.title;\n\n const handleDelete = () => {\n sketchController.deleteSelectedSketch();\n };\n\n const handleCancelSketch = () => {\n dispatch(setSketchStatus('idle'));\n setMagnifier(false);\n sketchController.cancelSketch();\n };\n\n const handleUnitChange = (\n event: React.ChangeEvent<{\n name?: string | undefined;\n value: unknown;\n }>\n ) => {\n if (event && height) {\n const newUnit = event.target.value as SketchHeightUnit;\n\n if (newUnit === 'Meters') {\n mapController.setMapMeasurementUnit('metric');\n sketchController.updateSketchHeight(height);\n } else if (newUnit === 'Feet') {\n mapController.setMapMeasurementUnit('english');\n const meters = apiUtils.convertFeetToMeters(height);\n sketchController.updateSketchHeight(meters);\n }\n\n if (selectedSketchGraphic) {\n sketchController.updateSketch(selectedSketchGraphic);\n }\n\n const { uid } = selectedSketchGraphic;\n\n if (newUnit) {\n dispatch(\n updateSketch({\n ...selectedSketch,\n uid,\n height,\n unit: newUnit,\n })\n );\n }\n }\n };\n\n const onHeightIncrement = (targetInputElement: HTMLInputElement) => {\n const { value } = targetInputElement;\n const newValue = Number(value) + 1;\n\n if (unit === 'Meters') {\n sketchController.updateSketchHeight(newValue);\n } else if (unit === 'Feet') {\n const meters = apiUtils.convertFeetToMeters(newValue);\n sketchController.updateSketchHeight(meters);\n }\n\n const { uid } = selectedSketchGraphic;\n\n if (unit) {\n dispatch(\n updateSketch({\n ...selectedSketch,\n uid,\n height: newValue,\n unit,\n })\n );\n }\n };\n\n const onHeightDecrement = (targetInputElement: HTMLInputElement) => {\n if (selectedSketchGraphic && unit) {\n const { value } = targetInputElement;\n const newValue = Number(value) - 1;\n\n if (newValue > 0) {\n if (unit === 'Meters') {\n mapController.updateMeasurementUnit('metric');\n sketchController.updateSketchHeight(newValue);\n } else if (unit === 'Feet') {\n mapController.updateMeasurementUnit('imperial');\n const meters = apiUtils.convertFeetToMeters(newValue);\n sketchController.updateSketchHeight(meters);\n }\n\n const { uid } = selectedSketchGraphic;\n dispatch(\n updateSketch({\n ...selectedSketch,\n uid,\n height: newValue,\n unit,\n })\n );\n }\n }\n };\n\n const handleHeightChange = (event: React.ChangeEvent) => {\n if (selectedSketchGraphic && unit) {\n const value = Number(event.target.value);\n\n if (value > 0) {\n let valueInMeters;\n\n if (unit === 'Meters') {\n valueInMeters = value;\n } else if (unit === 'Feet') {\n valueInMeters = apiUtils.convertFeetToMeters(value);\n }\n\n if (valueInMeters) {\n sketchController.updateSketchHeight(valueInMeters);\n }\n\n const { uid } = selectedSketchGraphic;\n dispatch(\n updateSketch({\n ...selectedSketch,\n uid,\n height: value,\n unit,\n })\n );\n }\n }\n };\n\n const handleMagnifierSwitch = (event: React.ChangeEvent, checked: boolean) => {\n setMagnifier(checked);\n };\n\n const handleEditTitleClick = () => {\n // EditingTitle ? save : edit\n if (editingTitle) {\n dispatch(\n updateSketch({\n ...selectedSketch,\n title: editingTitleValue.length ? editingTitleValue : selectedSketch?.title,\n })\n );\n }\n\n setEditingTitle(!editingTitle);\n };\n\n const handleVisibilityChange = (event: React.MouseEvent, sketch: ISketch) => {\n event.stopPropagation();\n sketchController.setSketchVisibility(sketch, !sketch.visible);\n };\n\n return (\n \n
\n
\n {editingTitle ? (\n
setEditingTitleValue(event.target.value)}\n type='text'\n id='fname'\n name='fname'\n />\n ) : (\n
{title}
\n )}\n
\n {editingTitle ? (\n \n ) : (\n \n )}\n \n
\n
\n handleVisibilityChange(e, selectedSketch!)}>\n {selectedSketch!.visible ? : }\n \n \n
\n\n
\n
\n\n
\n Unit: \n \n \n {units.map((unit) => (\n \n {unit}\n \n ))}\n \n \n
\n\n
\n \n \n }\n label='Magnifier'\n />\n \n
\n
\n\n
\n \n Done\n \n \n Delete Sketch\n \n
\n
\n );\n};\n\nexport default SketchEdit;\n","import { useDispatch, useSelector } from 'react-redux';\nimport IconButton from '@material-ui/core/IconButton';\nimport { ISketch, setSelectedSketch, setSketchStatus, sketchesState } from '../../../../../../store/slices/sketchSlice';\nimport mapController from '../../../../../controllers/MapController';\nimport sketchController from '../../../../../controllers/SketchController';\nimport VisibleIcon from '@material-ui/icons/Visibility';\nimport NotVisibleIcon from '@material-ui/icons/VisibilityOff';\nimport './SketchList.scss';\n\nconst SketchList = () => {\n const sketches = useSelector(sketchesState);\n const dispatch = useDispatch();\n const titlesMap = new Map();\n\n const handleSketchSelection = (sketch: ISketch) => {\n const { uid } = sketch;\n\n if (!sketch.visible) {\n sketchController.setSketchVisibility(sketch, true);\n }\n\n const sketchGraphic = sketchController.getSketchGraphicByUid(uid);\n\n dispatch(setSketchStatus('editing'));\n dispatch(setSelectedSketch(sketchGraphic));\n if (sketchGraphic) {\n sketchController.updateSketch(sketchGraphic);\n mapController.view3D.goTo(sketchGraphic);\n }\n };\n\n const handleVisibilityChange = (event: React.MouseEvent, sketch: ISketch) => {\n event.stopPropagation();\n sketchController.setSketchVisibility(sketch, !sketch.visible);\n };\n\n return sketches?.length ? (\n \n
Current sketches:
\n
\n {sketches.map((sketch) => {\n let titleCount = 0;\n\n if (titlesMap.has(sketch.title)) {\n titleCount = titlesMap.get(sketch.title) + 1;\n } else {\n titleCount = 1;\n }\n\n titlesMap.set(sketch.title, titleCount);\n\n return (\n \n handleSketchSelection(sketch)} className='SketchList__list-item-button'>\n {`${sketch.title}${titleCount > 1 ? ` (${titleCount - 1})` : ''}`}\n handleVisibilityChange(e, sketch)}>\n {sketch.visible ? : }\n \n \n \n );\n })}\n \n
\n ) : null;\n};\n\nexport default SketchList;\n","import { useEffect } from 'react';\nimport { useSelector, useDispatch } from 'react-redux';\nimport { Button } from '@material-ui/core';\nimport sketchController from '../../../../controllers/SketchController';\nimport { setIsMagnifierOn, setSketchStatus, sketchStatusState } from '../../../../../store/slices/sketchSlice';\nimport mapController from '../../../../controllers/MapController';\nimport SketchEdit from './SketchEdit/SketchEdit';\nimport SketchList from './SketchList/SketchList';\nimport './Sketch.scss';\n\nconst Sketch = () => {\n const dispatch = useDispatch();\n const status = useSelector(sketchStatusState);\n\n useEffect(() => {\n return () => {\n dispatch(setSketchStatus('idle'));\n mapController.setMagnifierVisibility(false);\n dispatch(setIsMagnifierOn(false));\n };\n }, []);\n\n const setMagnifier = (isOn: boolean) => {\n mapController.setMagnifierVisibility(isOn);\n dispatch(setIsMagnifierOn(isOn));\n };\n\n const handleStartSketch = () => {\n dispatch(setSketchStatus('active'));\n sketchController.startSketch();\n };\n\n const handleCancelSketch = () => {\n dispatch(setSketchStatus('idle'));\n setMagnifier(false);\n sketchController.cancelSketch();\n };\n\n return (\n \n {status === 'editing' ? (\n
\n ) : (\n <>\n
\n\n
\n {status === 'idle' && (\n \n New Sketch\n \n )}\n\n {(status === 'active' || status === 'sketching') && (\n \n Cancel\n \n )}\n
\n >\n )}\n
\n );\n};\n\nexport default Sketch;\n","import { useEffect } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { activeToolState } from '../../../../../../store/slices/appSlice';\nimport {\n dimensionAnalysisState,\n MEASUREMENT_UNITS,\n selectedMeasurementTypeState,\n selectedMeasurementUnitState,\n setSelectedMeasurementUnit,\n UnitType,\n} from '../../../../../../store/slices/measurementSlice';\nimport dimensionAnalysisController from '../../../../../controllers/DimensionAnalysisController';\nimport mapController from '../../../../../controllers/MapController';\nimport './MeasurementDimensionAnalysis.scss';\n\nconst MeasurementDimensionAnalysis = () => {\n const dispatch = useDispatch();\n const activeTool = useSelector(activeToolState);\n const selectedMeasurementUnit = useSelector(selectedMeasurementUnitState);\n const selectedMeasurementType = useSelector(selectedMeasurementTypeState);\n const hidden = activeTool !== 'measurement' || selectedMeasurementType !== 'dimension-analysis';\n const dimensionAnalysis = useSelector(dimensionAnalysisState);\n const isDimensionAnalysisEmpty = dimensionAnalysis.isEmpty;\n const { status } = dimensionAnalysis;\n\n useEffect(() => {\n mapController.removeAreaMeasurementWidget();\n mapController.removeDirectLineMeasurementWidget();\n\n return () => {\n dimensionAnalysisController.cancel();\n };\n }, []);\n\n useEffect(() => {\n if (hidden) {\n dimensionAnalysisController.cancel();\n }\n }, [hidden]);\n\n const handleNewMeasurementClick = () => {\n dimensionAnalysisController.start();\n };\n\n const handleClearMeasurementClick = () => {\n dimensionAnalysisController.clear();\n };\n\n const handleCancelMeasurementClick = () => {\n dimensionAnalysisController.cancel();\n };\n\n const handleUnitChange = (\n event: React.ChangeEvent<{\n name?: string | undefined;\n value: unknown;\n }>\n ) => {\n if (event) {\n const newUnit = event.target.value as UnitType;\n\n dispatch(setSelectedMeasurementUnit(newUnit));\n }\n };\n\n return (\n \n
\n {(status === 'measuring' || status === 'active') && (\n
\n \n Unit\n \n \n {MEASUREMENT_UNITS.map((unit) => (\n \n {unit}\n \n ))}\n \n
\n )}\n\n {status === 'active' && (\n
\n Start to measure by clicking in the scene to place your first point\n
\n )}\n
\n {(status === 'measuring' || status === 'active') && (\n \n Cancel\n \n )}\n {status === 'idle' && (\n <>\n \n New measurement\n \n >\n )}\n {!isDimensionAnalysisEmpty && (\n \n Clear\n \n )}\n
\n
\n
\n );\n};\n\nexport default MeasurementDimensionAnalysis;\n","import { useEffect, useRef } from 'react';\nimport { useSelector } from 'react-redux';\nimport { activeToolState, view3DState } from '../../../../../../store/slices/appSlice';\nimport {\n directLineMeasurementStatusState,\n selectedMeasurementTypeState,\n} from '../../../../../../store/slices/measurementSlice';\nimport mapController from '../../../../../controllers/MapController';\nimport '../MeasurementEsriWidgets.scss';\n\nconst MeasurementDirectLineWidget = () => {\n const activeTool = useSelector(activeToolState);\n const selectedMeasurementType = useSelector(selectedMeasurementTypeState);\n const hidden = activeTool !== 'measurement' || selectedMeasurementType !== 'direct-line';\n const view3D = useSelector(view3DState);\n const directLineMeasurementStatus = useSelector(directLineMeasurementStatusState);\n const MeasurementDirectLineWidgetRef = useRef(null);\n const showClearButton = directLineMeasurementStatus === 'measured';\n\n useEffect(() => {\n if (view3D?.ready && MeasurementDirectLineWidgetRef.current) {\n mapController.initializeDirectLineMeasurement(MeasurementDirectLineWidgetRef);\n } else {\n mapController.removeDirectLineMeasurementWidget();\n }\n }, [view3D, MeasurementDirectLineWidgetRef]);\n\n useEffect(() => {\n if (hidden) {\n if (directLineMeasurementStatus === 'ready') {\n mapController?.clearDirectLineMeasurement();\n }\n }\n }, [hidden]);\n\n const clearMeasurement = () => {\n mapController?.clearDirectLineMeasurement();\n };\n\n return (\n \n );\n};\n\nexport default MeasurementDirectLineWidget;\n","import { useEffect, useRef } from 'react';\nimport { useSelector } from 'react-redux';\nimport { activeToolState, view3DState } from '../../../../../../store/slices/appSlice';\nimport {\n areaMeasurementStatusState,\n selectedMeasurementTypeState,\n} from '../../../../../../store/slices/measurementSlice';\nimport mapController from '../../../../../controllers/MapController';\nimport '../MeasurementEsriWidgets.scss';\n\nconst MeasurementAreaWidget = () => {\n const activeTool = useSelector(activeToolState);\n const selectedMeasurementType = useSelector(selectedMeasurementTypeState);\n const areaMeasurementStatus = useSelector(areaMeasurementStatusState);\n const hidden = activeTool !== 'measurement' || selectedMeasurementType !== 'area';\n const view3D = useSelector(view3DState);\n const MeasurementAreaWidgetRef = useRef(null);\n const showClearButton = areaMeasurementStatus === 'measured';\n\n useEffect(() => {\n if (view3D?.ready && MeasurementAreaWidgetRef.current) {\n mapController.initializeAreaMeasurement(MeasurementAreaWidgetRef);\n } else {\n mapController.removeAreaMeasurementWidget();\n }\n }, [view3D, MeasurementAreaWidgetRef]);\n\n useEffect(() => {\n if (hidden) {\n if (areaMeasurementStatus === 'ready') {\n mapController?.clearAreaMeasurement();\n }\n }\n }, [hidden]);\n\n const clearMeasurement = () => {\n mapController?.clearAreaMeasurement();\n };\n\n return (\n \n );\n};\n\nexport default MeasurementAreaWidget;\n","import StyledTooltip from '../../../../shared/StyledTooltip';\nimport './MeasurementTypeButton.scss';\n\ninterface IProps {\n description: string;\n title: string;\n selected: boolean;\n type: 'initial' | 'measurement-active';\n iconClass: string;\n handleMeasurementTypeSelection: () => void;\n}\n\nconst MeasurementTypeButton = ({\n description,\n title,\n selected,\n handleMeasurementTypeSelection,\n type,\n iconClass,\n}: IProps) => {\n return (\n \n \n \n \n {title}\n
\n {description}
\n \n \n );\n};\n\nexport default MeasurementTypeButton;\n","import { useEffect } from 'react';\nimport MeasurementDimensionAnalysis from './MeasurementDimensionAnalysis/MeasurementDimensionAnalysis';\nimport MeasurementDirectLineWidget from './MeasurementDirectLineWidget/MeasurementDirectLineWidget';\nimport MeasurementAreaWidget from './MeasurementAreaWidget/MeasurementAreaWidget';\nimport MeasurementTypeButton from './MeasurementTypeButton/MeasurementTypeButton';\nimport {\n MeasurementType,\n selectedMeasurementTypeState,\n setSelectedMeasurementType,\n} from '../../../../../store/slices/measurementSlice';\nimport { useDispatch, useSelector } from 'react-redux';\nimport mapController from '../../../../controllers/MapController';\nimport './Measurement.scss';\nimport { activeToolState } from '../../../../../store/slices/appSlice';\n\nconst DIRECT_LINE_DESCRIPTION = 'Click on the map to measure 3D distance between points.';\nconst AREA_DESCRIPTION = 'Draw a shape on the map and measure the 3D area of that shape.';\nconst DIMENSION_DESCRIPTION =\n 'Click on the map to measure building dimensions with assistance of snapping functionality.';\n\nconst MEASUREMENT_TYPES: {\n id: MeasurementType;\n title: string;\n description: string;\n iconClass: string;\n}[] = [\n {\n id: 'direct-line',\n title: 'Direct Line',\n description: DIRECT_LINE_DESCRIPTION,\n iconClass: 'esri-icon-measure-line',\n },\n {\n id: 'area',\n title: 'Area',\n description: AREA_DESCRIPTION,\n iconClass: 'esri-icon-measure-area',\n },\n {\n id: 'dimension-analysis',\n title: 'Dimension',\n description: DIMENSION_DESCRIPTION,\n iconClass: 'esri-icon-measure',\n },\n];\n\nconst Measurement = () => {\n const dispatch = useDispatch();\n const activeTool = useSelector(activeToolState);\n const selectedMeasurementType = useSelector(selectedMeasurementTypeState);\n\n const hidden = activeTool !== 'measurement';\n\n useEffect(() => {\n return () => {\n mapController.removeAreaMeasurementWidget();\n mapController.removeDirectLineMeasurementWidget();\n dispatch(setSelectedMeasurementType(undefined));\n };\n }, []);\n\n const renderMeasurementWidget = () => {\n switch (selectedMeasurementType) {\n case 'direct-line':\n return ;\n case 'area':\n return ;\n case 'dimension-analysis':\n return ;\n }\n };\n\n return (\n \n
\n {MEASUREMENT_TYPES.map(({ id, title, description, iconClass }) => {\n return (\n <>\n
\n dispatch(setSelectedMeasurementType(id))}\n type={selectedMeasurementType ? 'measurement-active' : 'initial'}\n iconClass={iconClass}\n />\n >\n );\n })}\n\n \n \n\n <>\n
\n
\n
\n >\n
\n );\n};\n\nexport default Measurement;\n","import { useSelector, useDispatch } from 'react-redux';\nimport IconButton from '@material-ui/core/IconButton';\nimport MoreHorizIcon from '@material-ui/icons/MoreHoriz';\nimport MoreVertIcon from '@material-ui/icons/MoreVert';\nimport SiteControls from './SiteControls/SiteControls';\nimport SiteInformation from './site/SiteInformation';\nimport HelpControl from './site/controls/HelpControl';\nimport Submit from './submit/Submit';\nimport LightStudy from './site/controls/LigthStudy/LightStudy';\nimport ShadowReport from './site/controls/shadowReportPDF/ShadowReport';\nimport LineOfSight from './site/controls/LineOfSight/LineOfSight';\nimport { materialStyle } from '../../style/material';\nimport Sketch from './site/controls/Sketch/Sketch';\nimport { activeToolState, setSiteExpanded, siteExpandedState, siteState } from '../../store/slices/appSlice';\nimport Measurement from './site/controls/Measurement/Measurement';\nimport '../../css/components/_controls.scss';\nimport '../../css/components/_sitepanel.scss';\nimport '../../css/components/_buttons.scss';\nimport './SitePanel.scss';\n\nconst SitePanel = () => {\n const dispatch = useDispatch();\n const siteExpanded = useSelector(siteExpandedState);\n const activeTool = useSelector(activeToolState);\n const site = useSelector(siteState);\n\n const displayLeftPanelOnMobile = (activeTool === 'uploaded-files') | (activeTool === 'light');\n\n const expandSitePanel = () => dispatch(setSiteExpanded(!siteExpanded));\n\n const getActiveTool = () => {\n switch (activeTool) {\n case 'submit':\n case 'uploaded-files':\n return ;\n case 'light':\n return ;\n case 'help':\n return ;\n case 'sketch':\n return ;\n case 'default':\n return ;\n case 'delete':\n return ;\n case 'shadow-report':\n return ;\n default:\n break;\n }\n };\n\n const displayMoreInformationClass = siteExpanded ? '' : 'hidden';\n\n return (\n <>\n {((activeTool === 'default' && site?.latlon) || activeTool !== 'default') && (\n \n \n \n \n
\n )}\n \n {((activeTool === 'default' && site?.latlon) || activeTool !== 'default') && (\n
\n \n \n \n
\n )}\n\n
\n
\n {getActiveTool()}\n { }\n { }\n
\n
\n >\n );\n};\n\nexport default SitePanel;\n","import { useEffect, useState } from 'react';\nimport shortid from 'shortid';\nimport api from '../utils/api';\nimport Toggle from 'material-ui/Toggle';\nimport mapController from '../controllers/MapController';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { setDistrictVisibility, zonesState } from '../../store/slices/appSlice';\n\nconst LayerListItem = (props) => {\n const dispatch = useDispatch();\n const zones = useSelector(zonesState);\n const { currentDistrict, text, zoneDistrictAllOnSwitch } = props;\n const zone = zones.find((zone) => zone.type === currentDistrict);\n const [requireMapUpdate, setRequireMapUpdate] = useState(false);\n\n useEffect(() => {\n if (requireMapUpdate) {\n let allDistrictsAreVisible = zone.districts.every((district) => district.visible);\n zoneDistrictAllOnSwitch(allDistrictsAreVisible);\n\n mapController.updateZoneLayerExpression(zones);\n setRequireMapUpdate(false);\n }\n }, [zone, requireMapUpdate]);\n\n const handleToggle = (event, display) => {\n dispatch(setDistrictVisibility({ zoneType: currentDistrict, districtId: text, visible: display }));\n setRequireMapUpdate(true);\n };\n\n return (\n \n \n
\n );\n};\n\nexport default LayerListItem;\n","/* @flow */\nimport { useEffect, useState } from 'react';\nimport shortid from 'shortid';\nimport { materialStyle } from '../../style/material';\nimport LayerListItem from './LayerListItem';\n\nimport Paper from '@material-ui/core/Paper';\nimport Switch from '@material-ui/core/Switch';\nimport IconButton from '@material-ui/core/IconButton';\nimport { List } from 'material-ui/List';\nimport { Toolbar, ToolbarGroup } from 'material-ui/Toolbar';\nimport MoreVertIcon from '@material-ui/icons/MoreVert';\nimport MoreHorizIcon from '@material-ui/icons/MoreHoriz';\nimport * as colors from 'material-ui/styles/colors';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { setZoneDistrictsVisibility, zonesState } from '../../store/slices/appSlice';\nimport mapController from '../controllers/MapController';\n\nconst LayersPanel = () => {\n const dispatch = useDispatch();\n const zones = useSelector(zonesState);\n const [expandLayerPanel, setExpandLayerPanel] = useState(true);\n const [displayPopover, setDisplayPopover] = useState(false);\n const [zoneDistrict, setZoneDistrict] = useState('');\n const [zoneDistrictAllOn, setZoneDistrictAllOn] = useState(true);\n const [paperSelected, setPaperSelected] = useState();\n const [mobileTopNavPanelExpand, setMobileTopNavPanelExpand] = useState(false);\n const [pageWidth, setPageWidth] = useState(window.innerWidth);\n const mobileBreakPoint = 700;\n const [requireMapUpdate, setRequireMapUpdate] = useState(false);\n\n useEffect(() => {\n updateWindowDimensions();\n window.addEventListener('resize', updateWindowDimensions);\n\n return () => window.removeEventListener('resize', updateWindowDimensions);\n }, []);\n\n useEffect(() => {\n if (requireMapUpdate) {\n mapController.updateZoneLayerExpression(zones);\n setRequireMapUpdate(false);\n }\n }, [requireMapUpdate, zones]);\n\n const updateWindowDimensions = () => {\n setPageWidth(window.innerWidth);\n };\n\n const showSublayers = (label) => {\n if (zoneDistrict === label) {\n setDisplayPopover(!displayPopover);\n } else {\n setDisplayPopover(true);\n setZoneDistrict(label);\n }\n };\n\n const hideSublayers = () => {\n setDisplayPopover(false);\n };\n\n const handleLayerPanel = () => {\n setExpandLayerPanel(!expandLayerPanel);\n hideSublayers();\n };\n\n const toggleLayerCategory = (zoneType, visible) => {\n dispatch(setZoneDistrictsVisibility({ zoneType, visible }));\n setZoneDistrictAllOn(visible);\n setRequireMapUpdate(true);\n };\n\n const selectPaper = (index) => {\n if (pageWidth < mobileBreakPoint) {\n setMobileTopNavPanelExpand(true);\n }\n\n setPaperSelected(index);\n };\n\n const buttonMobileTopNavPanelControl = () => {\n setMobileTopNavPanelExpand(!mobileTopNavPanelExpand);\n };\n\n const labelMobileTopNavPanelExpand = (label) => {\n zones.map((zone) => {\n if (zone.type === label) {\n const districtsVisable = zone.districts.map((district) => {\n return district.visible;\n });\n const districtsVisabillityAllClosed = districtsVisable.every((visible) => visible === false);\n setZoneDistrictAllOn(!districtsVisabillityAllClosed);\n }\n });\n\n setMobileTopNavPanelExpand(true);\n };\n\n const zoneDistrictAllOnSwitch = (state) => {\n setZoneDistrictAllOn(state);\n };\n\n const render = () => {\n let layers = [];\n let sublayers = [];\n // Class determininations\n let layerPanelWidth;\n let zonesMobileControlLabel;\n let zonesMobileControlSwitch;\n\n if (pageWidth >= mobileBreakPoint) {\n layerPanelWidth = expandLayerPanel ? mobileBreakPoint : '30px';\n } else {\n layerPanelWidth = pageWidth;\n }\n const displayLayerButton = expandLayerPanel ? '' : 'hidden';\n // Return react components for each layer category and sublayers\n zones.forEach((zone, index) => {\n // For the color bars below layer button\n const colorBar = zone.colors.map((color) => {\n const width = 100 / zone.colors.length;\n return
;\n });\n\n // Layer button\n const typeLabel = zone.type;\n const layer = (\n \n \n {pageWidth >= mobileBreakPoint ? (\n // Desktop\n {\n showSublayers(typeLabel);\n selectPaper(index);\n }}\n className='navigation__label-button'\n >\n {typeLabel}\n \n ) : (\n // Mobile\n {\n labelMobileTopNavPanelExpand(typeLabel);\n showSublayers(typeLabel);\n }}\n className='navigation__label-button mobile'\n >\n {typeLabel.replace(/ zones| purpose/i, '')}\n \n )}\n
\n {colorBar}
\n \n );\n layers.push(layer);\n\n // Populates popover if there is a currently selected district\n if (zone.type === zoneDistrict) {\n zone.districts.forEach((district) => {\n zonesMobileControlLabel = (\n All {zone.type.replace(' Zones', '')} \n );\n zonesMobileControlSwitch = (\n \n OFF \n {\n toggleLayerCategory(zone.type, !zoneDistrictAllOn);\n }}\n />\n ON \n
\n );\n\n const sublayer = (\n \n );\n sublayers.push(sublayer);\n });\n }\n });\n\n return (\n \n \n \n \n \n \n \n
\n\n \n \n
\n\n \n {layers}\n \n\n {displayPopover && (\n = mobileBreakPoint - 1 ? 40 + paperSelected * 220 : null,\n }}\n className={`popup-container__wraper ${mobileTopNavPanelExpand ? 'mobile__open' : 'mobile__closed'}`}\n >\n {pageWidth < mobileBreakPoint && (\n // Only on mobile\n
\n {zonesMobileControlLabel}\n {zonesMobileControlSwitch}\n
\n )}\n \n
\n )}\n \n );\n };\n\n return render();\n};\n\nexport default LayersPanel;\n","import React from 'react';\n\nexport default function Logo() {\n return (\n \n
\n
\n \n );\n}\n","export default __webpack_public_path__ + \"static/media/welcome.f3a92f96.jpeg\";","import { useState } from 'react';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { Dialog } from '@material-ui/core';\nimport { mobileState, setEnableIntro } from '../../../../store/slices/appSlice';\nimport { staticText } from '../../../config';\nimport Logo from '../../../../css/images/welcome.jpeg';\nimport './WelcomeModal.scss';\n\nconst WelcomeModal = () => {\n const dispatch = useDispatch();\n const mobile = useSelector(mobileState);\n const [displayModal, setDisplayModal] = useState(true);\n\n const startInteractiveHelp = () => {\n closeModal();\n dispatch(setEnableIntro(true));\n };\n\n const closeModal = () => {\n setDisplayModal(false);\n };\n\n return (\n \n \n
\n
\n
{staticText.welcome.title} \n
\n
{staticText.welcome.desciption}
\n
{staticText.welcome.closing}
\n
\n
\n \n Explore Map\n \n {mobile ? null : Start Tour }\n
\n
\n
\n \n );\n};\n\nexport default WelcomeModal;\n","import Dialog from '@material-ui/core/Dialog';\nimport DialogContent from '@material-ui/core/DialogContent';\nimport DialogTitle from '@material-ui/core/DialogTitle';\nimport DialogActions from '@material-ui/core/DialogActions';\nimport Button from '@material-ui/core/Button';\nimport List from '@material-ui/core/List';\nimport ListItem from '@material-ui/core/ListItem';\nimport ListItemIcon from '@material-ui/core/ListItemIcon';\nimport ListItemText from '@material-ui/core/ListItemText';\nimport shortid from 'shortid';\nimport { materialStyle } from '../../../style/material';\n// Icons\nimport ExploreIcon from '@material-ui/icons/Explore';\nimport ZoomInIcon from '@material-ui/icons/ZoomIn';\nimport ZoomOutIcon from '@material-ui/icons/ZoomOut';\nimport TouchAppIcon from '@material-ui/icons/TouchApp';\nimport RotateRightIcon from '@material-ui/icons/RotateRight';\nimport MouseIcon from '@material-ui/icons/Mouse';\nimport KeyboardIcon from '@material-ui/icons/Keyboard';\nimport './NavigationModal.scss';\nimport { useDispatch, useSelector } from 'react-redux';\nimport {\n displayNavigation,\n displayNavigationState,\n mobileState,\n setDisplayNavigation,\n} from '../../../store/slices/appSlice';\n\nconst instructions = [\n {\n icon: ,\n text: 'Click + to zoom in.',\n viewInMobile: true,\n },\n {\n icon: ,\n text: 'Click - to zoom out.',\n viewInMobile: true,\n },\n {\n icon: ,\n text:\n 'Click Pan to pan. Click and hold the left mouse button and drag the scene in the direction you want to move it. You can also pan by using the arrow keys on the keyboard.',\n viewInMobile: false,\n },\n {\n icon: ,\n text: 'Click and hold the left mouse button and drag the scene in the direction you want to rotate and tilt it.',\n viewInMobile: false,\n },\n {\n icon: ,\n text:\n 'You can also use your mouse and scroll wheel to zoom in and zoom out, or press and hold the middle mouse button and move down or up to zoom in or out.',\n viewInMobile: false,\n },\n {\n icon: ,\n text:\n 'If you have a two-button mouse, you can use the left mouse button for the primary navigation and the right mouse button for the secondary navigation. For example, if you click Rotate, you can use the left mouse button to rotate and the right mouse button to pan.',\n viewInMobile: false,\n },\n {\n icon: ,\n text:\n 'If you have a touch screen laptop or monitor, pinch zoom with two fingers and pan with one finger. You can also double-tap the scene to zoom in a step towards the tapped location. To rotate the scene, move two fingers in a clockwise or counterclockwise direction. To tilt the scene, drag two fingers up or down the screen.',\n viewInMobile: true,\n },\n {\n icon: ,\n text: 'The compass gives you the orientation of the scene. Click Compass to set your scene to North orientation.',\n viewInMobile: true,\n },\n];\n\nconst NavigationModal = () => {\n const dispatch = useDispatch();\n const mobile = useSelector(mobileState);\n const displayNavigation = useSelector(displayNavigationState);\n\n const closeModal = () => {\n dispatch(setDisplayNavigation(false));\n };\n\n const filterInstructions = (instruction) => {\n return mobile || instruction.viewInMobile;\n };\n\n return (\n \n 3D Scene Navigation \n \n \n {instructions.filter(filterInstructions).map((instruction) => (\n \n {instruction.icon} \n \n \n ))}\n
\n \n \n \n Got it\n \n \n \n );\n};\n\nexport default NavigationModal;\n","import Dialog from 'material-ui/Dialog';\nimport Button from '@material-ui/core/Button';\nimport { materialStyle } from '../../../style/material';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { displayDisclaimerState, setDisplayDisclaimer } from '../../../store/slices/appSlice';\n\nconst DisclaimerModal = () => {\n const dispatch = useDispatch();\n const displayDisclaimer = useSelector(displayDisclaimerState);\n\n const closeModal = () => {\n dispatch(setDisplayDisclaimer(false));\n };\n\n return (\n \n Got it\n ,\n ]}\n open={displayDisclaimer}\n title='Disclaimer'\n titleStyle={{\n ...materialStyle.dialog,\n ...materialStyle.dialogTitleNavigation,\n color: 'black',\n }}\n bodyStyle={materialStyle.dialog}\n actionsContainerStyle={materialStyle.dialog}\n autoScrollBodyContent={true}\n >\n While DCOZ is committed to providing accurate and timely zoning information via the 3D Zoning App, DCOZ cannot\n guarantee the quality, content, accuracy, or completeness of the information, text, graphics, links, and other\n items contained therein. All data visualizations on the 3D Zoning App should be considered approximate.\n Information provided in the zoning map should not be used as a substitute for legal, accounting, real estate,\n business, tax, or other professional advice. DCOZ assumes no liability for any errors, omissions, or inaccuracies\n in the information provided regardless of the cause of such or for any upon any decision made, action taken, or\n action not taken by the user in reliance upon any maps or information provided herein. DCOZ retains the right to\n change any content on its zoning map without prior notice.\n \n );\n};\n\nexport default DisclaimerModal;\n","import { materialStyle } from '../../../style/material';\nimport Dialog from 'material-ui/Dialog';\nimport { List, ListItem } from 'material-ui/List';\nimport Button from '@material-ui/core/Button';\nimport Circle from 'material-ui/svg-icons/image/lens';\nimport { displayUploadHelpState, setDisplayUploadHelp } from '../../../store/slices/appSlice';\nimport { useDispatch, useSelector } from 'react-redux';\n\nconst UploadHelpModal = () => {\n const dispatch = useDispatch();\n const displayUploadHelp = useSelector(displayUploadHelpState);\n\n const closeModal = () => {\n dispatch(setDisplayUploadHelp(false));\n };\n\n return (\n \n Got it\n ,\n ]}\n title='3D Upload Help'\n titleStyle={{\n ...materialStyle.dialog,\n ...materialStyle.dialogTitleNavigation,\n }}\n bodyStyle={materialStyle.dialog}\n actionsContainerStyle={materialStyle.dialog}\n autoScrollBodyContent={true}\n >\n Want to see how your building design looks next to its neighbors, including height and shadow analysis?
\n Upload your KMZ file to this tool, but please note the following:
\n \n }>\n The file must be properly referenced for a location in Washington, DC\n \n }>\n The file must be under 15MB\n \n }>\n File sizes over 5MB may take longer to process\n \n
\n \n Please allow up to 5 minutes for larger file sizes to process. We are working on improvement to file upload\n speeds and expanded file upload types. Please check back for updates.\n
\n \n );\n};\n\nexport default UploadHelpModal;\n","import { materialStyle } from '../../../style/material';\nimport React from 'react';\nimport Dialog from 'material-ui/Dialog';\nimport RaisedButton from 'material-ui/RaisedButton';\nimport { useDispatch, useSelector } from 'react-redux';\nimport { displayZoningStandardState, toggleZoningStandard } from '../../../store/slices/appSlice';\n\nconst ZoningStandardsModal = () => {\n const dispatch = useDispatch();\n const displayZoningStandard = useSelector(displayZoningStandardState);\n\n const closeModal = () => {\n dispatch(toggleZoningStandard(false));\n };\n\n return (\n ,\n ]}\n title='Zoning Development Standards Disclaimer'\n titleStyle={{\n ...materialStyle.dialog,\n ...materialStyle.dialogTitleNavigation,\n }}\n bodyStyle={materialStyle.dialog}\n actionsContainerStyle={materialStyle.dialog}\n autoScrollBodyContent={true}\n >\n \n Zoning Development Standards represent the most common development standards for a particular zone. For more\n detailed height, yard, and set back requirements, please consult the{' '}\n \n Zoning Handbook\n \n .\n
\n \n );\n};\n\nexport default ZoningStandardsModal;\n","import { Hints } from 'intro.js-react';\nimport 'intro.js/introjs.css';\nimport { helpSteps } from '../config';\nimport { useSelector } from 'react-redux';\nimport { enableHelpState } from '../../store/slices/appSlice';\n\nconst InteractiveHelp = () => {\n const enableHelp = useSelector(enableHelpState);\n\n return (\n \n );\n};\n\nexport default InteractiveHelp;\n","import { Steps } from 'intro.js-react';\nimport 'intro.js/introjs.css';\nimport { helpSteps, mobileHelpSteps } from '../config';\nimport { useSelector, useDispatch } from 'react-redux';\nimport { enableIntroState, mobileState, setEnableIntro } from '../../store/slices/appSlice';\n\nconst InteractiveHelp = () => {\n const dispatch = useDispatch();\n const mobile = useSelector(mobileState);\n const enableIntro = useSelector(enableIntroState);\n const steps = mobile ? mobileHelpSteps : helpSteps;\n\n const removeClasses = () => {\n const elems = document.getElementsByClassName('introjs-donebutton');\n if (elems[0]) {\n elems[0].classList = 'introjs-button';\n }\n };\n\n return (\n dispatch(setEnableIntro(false))}\n />\n );\n};\n\nexport default InteractiveHelp;\n","import { useEffect } from 'react';\nimport { useDispatch } from 'react-redux';\nimport ReactGA from 'react-ga4';\nimport MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';\nimport getMuiTheme from 'material-ui/styles/getMuiTheme';\nimport { defaultTheme } from '../../style/theme';\nimport { materialStyle } from '../../style/material';\n\nimport MapView from './MapView';\nimport SitePanel from './SitePanel';\nimport LayersPanel from './LayersPanel';\n\nimport Logo from './Logo';\n\nimport WelcomeModal from './modals/WelcomeModal/WelcomeModal';\nimport NavigationModal from './modals/NavigationModal';\nimport DisclaimerModal from './modals/DisclaimerModal';\nimport UploadHelpModal from './modals/UploadHelpModal';\nimport ZoningStandardsModal from './modals/ZoningStandardsModal';\nimport InteractiveHelp from './InteractiveHelp';\nimport InteractiveIntro from './InteractiveIntro';\nimport '../../css/_esri.scss';\nimport '../../css/_layout.scss';\nimport '../../css/introjs.scss';\nimport { GOOGLE_ANALYTICS_ID } from '../config';\n\nReactGA.initialize(GOOGLE_ANALYTICS_ID, { testMode: process.env.NODE_ENV !== 'production' });\n\nconst App = () => {\n const dispatch = useDispatch();\n\n useEffect(() => {\n ReactGA.send('pageview');\n }, []);\n\n return (\n \n \n \n \n \n \n \n\n \n \n \n \n\n \n \n
\n \n );\n};\n\nexport default App;\n","const reportWebVitals = onPerfEntry => {\n if (onPerfEntry && onPerfEntry instanceof Function) {\n import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {\n getCLS(onPerfEntry);\n getFID(onPerfEntry);\n getFCP(onPerfEntry);\n getLCP(onPerfEntry);\n getTTFB(onPerfEntry);\n });\n }\n};\n\nexport default reportWebVitals;\n","import React from 'react';\nimport ReactDOM from 'react-dom';\nimport './index.scss';\nimport App from './js/components/App';\nimport reportWebVitals from './reportWebVitals';\nimport { Provider } from 'react-redux';\nimport store from './store/index';\nimport { StylesProvider } from '@material-ui/core/styles';\n\nReactDOM.render(\n \n \n \n \n ,\n document.getElementById('root')\n);\n\n// If you want to start measuring performance in your app, pass a function\n// to log results (for example: reportWebVitals(console.log))\n// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals\nreportWebVitals();\n","module.exports = [\n {\n id: 'dark-gray-vector',\n title: 'Dark Gray',\n thumbnail:\n 'https://www.arcgis.com/sharing/rest/content/items/25869b8718c0419db87dad07de5b02d8/info/thumbnail/DGCanvasBase.png',\n },\n {\n id: 'topo-vector',\n title: 'Topographic',\n thumbnail:\n 'https://www.arcgis.com/sharing/rest/content/items/6e03e8c26aad4b9c92a87c1063ddb0e3/info/thumbnail/topo_map_2.jpg',\n },\n {\n id: 'osm',\n title: 'Open Street Map',\n thumbnail:\n 'https://www.arcgis.com/sharing/rest/content/items/5d2bfa736f8448b3a1708e1f6be23eed/info/thumbnail/temposm.jpg',\n },\n {\n id: 'dcBasemap',\n title: 'DC Basemap',\n thumbnail:\n 'https://maps2.dcgis.dc.gov/dcgis/rest/services/DCGIS_DATA/DC_Basemap_WebMercator/MapServer/info/Thumbnail',\n layerOptions: {\n type: 'dynamic',\n options: {\n id: 'dcBasemap',\n title: 'DC Basemap',\n visible: true,\n imageFormat: 'png32',\n url: 'https://maps2.dcgis.dc.gov/dcgis/rest/services/DCGIS_DATA/DC_Basemap_WebMercator/MapServer',\n sublayers: [\n {\n id: 0,\n visible: true,\n },\n ],\n },\n },\n },\n {\n id: 'dcOrtho',\n title: 'DC Ortho',\n thumbnail:\n 'https://maps2.dcgis.dc.gov/dcgis/rest/services/DCGIS_DATA/Ortho2015_WebMercator/MapServer/info/Thumbnail',\n layerOptions: {\n type: 'dynamic',\n options: {\n id: 'dcOrtho',\n title: 'DC Ortho',\n visible: true,\n imageFormat: 'png32',\n url: 'https://maps2.dcgis.dc.gov/dcgis/rest/services/DCGIS_DATA/Ortho2015_WebMercator/MapServer',\n sublayers: [\n {\n id: 0,\n visible: true,\n },\n ],\n },\n },\n },\n {\n id: 'dcBasemapLightGray',\n title: 'DC Basemap (Light Gray)',\n thumbnail:\n 'https://maps2.dcgis.dc.gov/dcgis/rest/services/DCGIS_DATA/DC_Basemap_LightGray_WebMercator/MapServer/info/Thumbnail',\n layerOptions: {\n type: 'dynamic',\n options: {\n id: 'dcBasemapLightGray',\n title: 'DC Basemap (Light Gray)',\n visible: true,\n imageFormat: 'png32',\n url: 'https://maps2.dcgis.dc.gov/dcgis/rest/services/DCGIS_DATA/DC_Basemap_LightGray_WebMercator/MapServer',\n sublayers: [\n {\n id: 0,\n visible: true,\n },\n ],\n },\n },\n },\n];\n","module.exports = {\n mapView: {\n ui: {\n components: [\n 'logo',\n 'attribution'\n ]\n },\n center: [-77.0380427, 38.8993277],\n zoom: 14,\n constraints: { snapToZoom: false }\n },\n sceneView: {\n ui: {\n components: [\n 'attribution'\n ]\n },\n center: [-77.0380427, 38.8993277],\n zoom: 14,\n environment: {\n lighting: {\n date: new Date(),\n directShadowsEnabled: true,\n cameraTrackingEnabled: false\n },\n atmosphere: {\n quality: 'high'\n }\n },\n highlightOptions: {\n color: [244, 66, 241],\n fillOpacity: 0.6,\n haloOpacity: 1\n }\n }\n}","module.exports = [\n {\n element: '#ToolbarGroupID',\n intro: 'Zoning Categories Turn on or off different zoning catagories for any property.',\n hint: 'Zoning Categories Turn on or off different zoning catagories for any property.',\n tooltipClass: 'intro-tooltip-layers',\n position: 'bottom-middle-aligned'\n },\n {\n element: '#search-test',\n intro: 'Search Search for location specific zoning information. You can choose what you want to see on the map to tailor your results.',\n hint: 'Search Search for location specific zoning information. You can choose what you want to see on the map to tailor your results.',\n position: 'bottom',\n tooltipClass: 'intro-tooltip-search'\n },\n {\n element: '#submit',\n intro: 'Add a building Upload your KMZ file and visualize your model building within any given space.',\n hint: 'Add a building Upload your KMZ file and visualize your model building within any given space.',\n position: 'bottom',\n tooltipClass: 'intro-tooltip-submit'\n },\n {\n element: '#light',\n intro: 'Light Study Day or night, pick the best time to view the map. You can even simulate light and shadow movements throughout the day.',\n hint: 'Light Study Day or night, pick the best time to view the map. You can even simulate light and shadow movements throughout the day.',\n position: 'top',\n tooltipClass: 'intro-tooltip-light'\n },\n {\n element: '#compare',\n intro: 'View Before & After Compare new additions side-by-side with an existing layout when you upload a 3D file.',\n hint: 'View Before & After Compare new additions side-by-side with an existing layout when you upload a 3D file.',\n position: 'top',\n tooltipClass: 'intro-tooltip-compare'\n },\n {\n element: '#help',\n intro: 'Help Ever have a question or a problem? Click Help to find your answers.',\n hint: 'Help Ever have a question or a problem? Click Help to find your answers.',\n position: 'top',\n tooltipClass: 'intro-tooltip-help'\n }\n];\n","module.exports = [\n {\n element: '#expand-site-mobile',\n intro: 'Zoning Categories Turn on or off different zoning catagories for any property.',\n hint: 'Zoning Categories Turn on or off different zoning catagories for any property.',\n tooltipClass: 'intro-tooltip-mobile-layers',\n position: 'bottom-middle-aligned'\n },\n {\n element: '#search',\n intro: 'Search Search for location specific zoning information. You can choose what you want to see on the map to tailor your results.',\n hint: 'Search Search for location specific zoning information. You can choose what you want to see on the map to tailor your results.',\n position: 'bottom',\n tooltipClass: 'intro-tooltip-search'\n }\n];\n"],"sourceRoot":""}