diff --git a/.vscode/settings.json b/.vscode/settings.json
index 816e900..822949b 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,4 +1,4 @@
{
"editor.formatOnSave": true,
- "cSpell.words": ["fontawesome", "fortawesome"]
+ "cSpell.words": ["fontawesome", "fortawesome", "TSQL"]
}
diff --git a/package-lock.json b/package-lock.json
index 0e2565c..f23cd28 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -15,6 +15,10 @@
"@testing-library/react": "^13.0.1",
"@testing-library/user-event": "^13.5.0",
"bootstrap": "^5.1.3",
+ "downshift": "^6.1.7",
+ "lodash.debounce": "^4.0.8",
+ "lodash.escaperegexp": "^4.1.2",
+ "lodash.uniqwith": "^4.5.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-scripts": "5.0.1",
@@ -5472,6 +5476,11 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
+ "node_modules/compute-scroll-into-view": {
+ "version": "1.0.17",
+ "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz",
+ "integrity": "sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg=="
+ },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -6392,6 +6401,21 @@
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz",
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA=="
},
+ "node_modules/downshift": {
+ "version": "6.1.7",
+ "resolved": "https://registry.npmjs.org/downshift/-/downshift-6.1.7.tgz",
+ "integrity": "sha512-cVprZg/9Lvj/uhYRxELzlu1aezRcgPWBjTvspiGTVEU64gF5pRdSRKFVLcxqsZC637cLAGMbL40JavEfWnqgNg==",
+ "dependencies": {
+ "@babel/runtime": "^7.14.8",
+ "compute-scroll-into-view": "^1.0.17",
+ "prop-types": "^15.7.2",
+ "react-is": "^17.0.2",
+ "tslib": "^2.3.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.12.0"
+ }
+ },
"node_modules/duplexer": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
@@ -10985,6 +11009,11 @@
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168="
},
+ "node_modules/lodash.escaperegexp": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
+ "integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c="
+ },
"node_modules/lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
@@ -11010,6 +11039,11 @@
"resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
"integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M="
},
+ "node_modules/lodash.uniqwith": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.uniqwith/-/lodash.uniqwith-4.5.0.tgz",
+ "integrity": "sha1-egy/ZfQ7WShiWp1NDcVLGMrcfvM="
+ },
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@@ -20246,6 +20280,11 @@
}
}
},
+ "compute-scroll-into-view": {
+ "version": "1.0.17",
+ "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-1.0.17.tgz",
+ "integrity": "sha512-j4dx+Fb0URmzbwwMUrhqWM2BEWHdFGx+qZ9qqASHRPqvTYdqvWnHg0H1hIbcyLnvgnoNAVMlwkepyqM3DaIFUg=="
+ },
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -20903,6 +20942,18 @@
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz",
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA=="
},
+ "downshift": {
+ "version": "6.1.7",
+ "resolved": "https://registry.npmjs.org/downshift/-/downshift-6.1.7.tgz",
+ "integrity": "sha512-cVprZg/9Lvj/uhYRxELzlu1aezRcgPWBjTvspiGTVEU64gF5pRdSRKFVLcxqsZC637cLAGMbL40JavEfWnqgNg==",
+ "requires": {
+ "@babel/runtime": "^7.14.8",
+ "compute-scroll-into-view": "^1.0.17",
+ "prop-types": "^15.7.2",
+ "react-is": "^17.0.2",
+ "tslib": "^2.3.0"
+ }
+ },
"duplexer": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz",
@@ -24202,6 +24253,11 @@
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168="
},
+ "lodash.escaperegexp": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz",
+ "integrity": "sha1-ZHYsSGGAglGKw99Mz11YhtriA0c="
+ },
"lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
@@ -24227,6 +24283,11 @@
"resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
"integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M="
},
+ "lodash.uniqwith": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.uniqwith/-/lodash.uniqwith-4.5.0.tgz",
+ "integrity": "sha1-egy/ZfQ7WShiWp1NDcVLGMrcfvM="
+ },
"loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
diff --git a/package.json b/package.json
index a7907ff..2779f55 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,10 @@
"@testing-library/react": "^13.0.1",
"@testing-library/user-event": "^13.5.0",
"bootstrap": "^5.1.3",
+ "downshift": "^6.1.7",
+ "lodash.debounce": "^4.0.8",
+ "lodash.escaperegexp": "^4.1.2",
+ "lodash.uniqwith": "^4.5.0",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-scripts": "5.0.1",
diff --git a/src/App.js b/src/App.js
index 26e93e7..7057fd8 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,3 +1,4 @@
+import * as watchUtils from '@arcgis/core/core/watchUtils';
import Map from '@arcgis/core/Map';
import MapView from '@arcgis/core/views/MapView';
import Home from '@arcgis/core/widgets/Home';
@@ -5,10 +6,12 @@ import React from 'react';
import 'typeface-montserrat';
import './App.scss';
import Filter from './components/Filter';
+import { MapServiceProvider, Sherlock } from './components/Sherlock';
import config from './services/config';
function App() {
const [mapView, setMapView] = React.useState(null);
+ const [zoomToGraphic, setZoomToGraphic] = React.useState(null);
React.useEffect(() => {
const map = new Map({
@@ -20,6 +23,71 @@ function App() {
setMapView(view);
}, []);
+ const [displayedZoomGraphic, setDisplayedZoomGraphic] = React.useState(null);
+ const zoomTo = React.useCallback(
+ async (zoomObj) => {
+ if (!Array.isArray(zoomObj.target)) {
+ zoomObj.target = [zoomObj.target];
+ }
+
+ if (!zoomObj.zoom) {
+ if (zoomObj.target.every((graphic) => graphic.geometry.type === 'point')) {
+ zoomObj = {
+ target: zoomObj.target,
+ zoom: 10,
+ };
+ } else {
+ zoomObj = {
+ target: zoomObj.target,
+ };
+ }
+ }
+
+ await mapView.goTo(zoomObj);
+
+ if (displayedZoomGraphic) {
+ mapView.graphics.removeMany(displayedZoomGraphic);
+ }
+
+ setDisplayedZoomGraphic(zoomObj.target);
+
+ mapView.graphics.addMany(zoomObj.target);
+
+ if (!zoomObj.preserve) {
+ watchUtils.once(mapView, 'extent', () => {
+ mapView.graphics.removeAll();
+ });
+ }
+ },
+ [displayedZoomGraphic, mapView]
+ );
+
+ React.useEffect(() => {
+ if (zoomToGraphic) {
+ const { graphic, level, preserve } = zoomToGraphic;
+
+ graphic &&
+ zoomTo({
+ target: graphic,
+ zoom: level,
+ preserve: preserve,
+ });
+ }
+ }, [zoomToGraphic, mapView, zoomTo]);
+
+ const onSherlockMatch = (graphics) => {
+ setZoomToGraphic({
+ graphic: graphics,
+ preserve: false,
+ });
+ };
+
+ const sherlockConfig = {
+ provider: new MapServiceProvider(config.SHERLOCK.serviceUrl, config.SHERLOCK.searchField),
+ placeHolder: 'Search...',
+ onSherlockMatch,
+ };
+
return (
@@ -27,6 +95,7 @@ function App() {
+
);
diff --git a/src/components/Sherlock.js b/src/components/Sherlock.js
new file mode 100644
index 0000000..b68c2e1
--- /dev/null
+++ b/src/components/Sherlock.js
@@ -0,0 +1,494 @@
+import Graphic from '@arcgis/core/Graphic';
+import * as query from '@arcgis/core/rest/query';
+import Query from '@arcgis/core/rest/support/Query';
+import { faSearch } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import Downshift from 'downshift';
+import escapeRegExp from 'lodash.escaperegexp';
+import sortBy from 'lodash.sortby';
+import uniqWith from 'lodash.uniqwith';
+import React from 'react';
+import { Button, Input, InputGroup } from 'reactstrap';
+import './Sherlock.scss';
+
+const defaultSymbols = {
+ polygon: {
+ type: 'simple-fill',
+ color: [240, 240, 240, 0.5],
+ outline: {
+ style: 'solid',
+ color: [255, 255, 0, 0.5],
+ width: 2.5,
+ },
+ },
+ line: {
+ type: 'simple-line',
+ style: 'solid',
+ color: [255, 255, 0],
+ width: 5,
+ },
+ point: {
+ type: 'simple-marker',
+ style: 'circle',
+ color: [255, 255, 0, 0.5],
+ size: 10,
+ },
+};
+
+export function Sherlock({
+ symbols = defaultSymbols,
+ provider,
+ onSherlockMatch,
+ label,
+ placeHolder,
+ maxResultsToDisplay,
+}) {
+ const handleStateChange = async (feature) => {
+ const searchValue = feature.attributes[provider.searchField];
+
+ let contextValue;
+ if (provider.contextField) {
+ contextValue = feature.attributes[provider.contextField];
+ }
+
+ const response = await provider.getFeature(searchValue, contextValue);
+
+ const results = response.data;
+
+ const graphics = results.map(
+ (feature) =>
+ new Graphic({
+ geometry: feature.geometry,
+ attributes: feature.attributes,
+ symbol: symbols[feature.geometry.type],
+ })
+ );
+
+ onSherlockMatch(graphics);
+ };
+
+ const itemToString = (item) => {
+ console.log('Clue:itemToString', arguments);
+
+ return item ? item.attributes[provider.searchField] : '';
+ };
+
+ return (
+
+ {({ getInputProps, getItemProps, highlightedIndex, isOpen, inputValue, getMenuProps }) => (
+
+
{label}
+
+
+
+
+
+
+
+ {!isOpen ? null : (
+
+ {({ short, hasMore, error, data = [] }) => {
+ if (short) {
+ return (
+ -
+ Type more than 2 letters.
+
+ );
+ }
+
+ if (error) {
+ return (
+ -
+ Error! ${error}
+
+ );
+ }
+
+ if (!data.length) {
+ return (
+ -
+ No items found.
+
+ );
+ }
+
+ let items = data.map((item, index) => (
+ -
+
+
{item.attributes[provider.contextField] || ''}
+
+ ));
+
+ if (hasMore) {
+ items.push(
+ -
+ More than {maxResultsToDisplay} items found.
+
+ );
+ }
+
+ return items;
+ }}
+
+ )}
+
+
+
+
+ )}
+
+ );
+}
+
+function Clue({ clue, provider, maxResults, children }) {
+ const [state, setState] = React.useState({
+ data: undefined,
+ loading: false,
+ error: false,
+ short: true,
+ hasMore: false,
+ });
+
+ const updateState = (newProps) => {
+ setState((oldState) => {
+ return {
+ ...oldState,
+ ...newProps,
+ };
+ });
+ };
+
+ const makeNetworkRequest = React.useCallback(async () => {
+ console.log('makeNetworkRequest');
+ const { searchField, contextField } = provider;
+
+ const response = await provider.search(clue).catch((e) => {
+ updateState({
+ data: undefined,
+ error: e.message,
+ loading: false,
+ short: clue.length <= 2,
+ hasMore: false,
+ });
+
+ console.error(e);
+ });
+
+ const iteratee = [`attributes.${searchField}`];
+ let hasContext = false;
+ if (contextField) {
+ iteratee.push(`attributes.${contextField}`);
+ hasContext = true;
+ }
+
+ let features = uniqWith(response.data, (a, b) => {
+ if (hasContext) {
+ return (
+ a.attributes[searchField] === b.attributes[searchField] &&
+ a.attributes[contextField] === b.attributes[contextField]
+ );
+ } else {
+ return a.attributes[searchField] === b.attributes[searchField];
+ }
+ });
+
+ features = sortBy(features, iteratee);
+ let hasMore = false;
+ if (features.length > maxResults) {
+ features = features.slice(0, maxResults);
+ hasMore = true;
+ }
+
+ updateState({
+ data: features,
+ loading: false,
+ error: false,
+ short: clue.length <= 2,
+ hasMore: hasMore,
+ });
+ }, [clue, maxResults, provider]);
+
+ React.useEffect(() => {
+ console.log('clue or makeNetworkRequest changed');
+ updateState({
+ error: false,
+ loading: true,
+ short: clue.length <= 2,
+ hasMore: false,
+ });
+
+ if (clue.length > 2) {
+ makeNetworkRequest();
+ }
+ }, [clue, makeNetworkRequest]);
+
+ const { short, data, loading, error, hasMore } = state;
+
+ return children({
+ short,
+ data,
+ loading,
+ error,
+ hasMore,
+ // refetch: fetchData,
+ });
+}
+
+class ProviderBase {
+ controller = new AbortController();
+ signal = this.controller.signal;
+
+ getOutFields(outFields, searchField, contextField) {
+ outFields = outFields || [];
+
+ // don't mess with '*'
+ if (outFields[0] === '*') {
+ return outFields;
+ }
+
+ const addField = (fld) => {
+ if (fld && outFields.indexOf(fld) === -1) {
+ outFields.push(fld);
+ }
+ };
+
+ addField(searchField);
+ addField(contextField);
+
+ return outFields;
+ }
+
+ getSearchClause(text) {
+ return `UPPER(${this.searchField}) LIKE UPPER('%${text}%')`;
+ }
+
+ getFeatureClause(searchValue, contextValue) {
+ let statement = `${this.searchField}='${searchValue}'`;
+
+ if (this.contextField) {
+ if (contextValue && contextValue.length > 0) {
+ statement += ` AND ${this.contextField}='${contextValue}'`;
+ } else {
+ statement += ` AND ${this.contextField} IS NULL`;
+ }
+ }
+
+ return statement;
+ }
+
+ cancelPendingRequests() {
+ this.controller.abort();
+ }
+}
+
+export class MapServiceProvider extends ProviderBase {
+ constructor(serviceUrl, searchField, options = {}) {
+ console.log('sherlock.MapServiceProvider:constructor', arguments);
+ super();
+
+ this.searchField = searchField;
+ this.contextField = options.contextField;
+ this.serviceUrl = serviceUrl;
+
+ this.setUpQueryTask(options);
+ }
+
+ async setUpQueryTask(options) {
+ const defaultWkid = 3857;
+ this.query = new Query();
+ this.query.outFields = this.getOutFields(options.outFields, this.searchField, options.contextField);
+ this.query.returnGeometry = false;
+ this.query.outSpatialReference = { wkid: options.wkid || defaultWkid };
+ }
+
+ async search(searchString) {
+ console.log('sherlock.MapServiceProvider:search', arguments);
+
+ this.query.where = this.getSearchClause(searchString);
+ const featureSet = await query.executeQueryJSON(this.serviceUrl, this.query);
+
+ return { data: featureSet.features };
+ }
+
+ async getFeature(searchValue, contextValue) {
+ console.log('sherlock.MapServiceProvider', arguments);
+
+ this.query.where = this.getFeatureClause(searchValue, contextValue);
+ this.query.returnGeometry = true;
+ const featureSet = await query.executeQueryJSON(this.serviceUrl, this.query);
+
+ return { data: featureSet.features };
+ }
+}
+
+export class WebApiProvider extends ProviderBase {
+ constructor(apiKey, searchLayer, searchField, options) {
+ super();
+ console.log('sherlock.providers.WebAPI:constructor', arguments);
+
+ const defaultWkid = 3857;
+ this.geometryClasses = {
+ point: console.log,
+ polygon: console.log,
+ polyline: console.log,
+ };
+
+ this.searchLayer = searchLayer;
+ this.searchField = searchField;
+
+ if (options) {
+ this.wkid = options.wkid || defaultWkid;
+ this.contextField = options.contextField;
+ this.outFields = this.getOutFields(options.outFields, this.searchField, this.contextField);
+ } else {
+ this.wkid = defaultWkid;
+ }
+
+ this.outFields = this.getOutFields(null, this.searchField, this.contextField);
+ this.webApi = new WebApi(apiKey, this.signal);
+ }
+
+ async search(searchString) {
+ console.log('sherlock.providers.WebAPI:search', arguments);
+
+ return await this.webApi.search(this.searchLayer, this.outFields, {
+ predicate: this.getSearchClause(searchString),
+ spatialReference: this.wkid,
+ });
+ }
+
+ async getFeature(searchValue, contextValue) {
+ console.log('sherlock.providers.WebAPI:getFeature', arguments);
+
+ return await this.webApi.search(this.searchLayer, this.outFields.concat('shape@'), {
+ predicate: this.getFeatureClause(searchValue, contextValue),
+ spatialReference: this.wkid,
+ });
+ }
+}
+
+const Highlighted = ({ text = '', highlight = '' }) => {
+ if (!highlight.trim()) {
+ return {text}
;
+ }
+
+ const regex = new RegExp(`(${escapeRegExp(highlight)})`, 'gi');
+ const parts = text.split(regex);
+
+ return (
+
+ {parts
+ .filter((part) => part)
+ .map((part, i) => (regex.test(part) ? {part} : {part}))}
+
+ );
+};
+
+class WebApi {
+ constructor(apiKey, signal) {
+ this.baseUrl = 'https://api.mapserv.utah.gov/api/v1/';
+
+ // defaultAttributeStyle: String
+ this.defaultAttributeStyle = 'identical';
+
+ // xhrProvider: dojo/request/* provider
+ // The current provider as determined by the search function
+ this.xhrProvider = null;
+
+ // Properties to be sent into constructor
+
+ // apiKey: String
+ // web api key (http://developer.mapserv.utah.gov/AccountAccess)
+ this.apiKey = apiKey;
+
+ this.signal = signal;
+ }
+
+ async search(featureClass, returnValues, options) {
+ // summary:
+ // search service wrapper (http://api.mapserv.utah.gov/#search)
+ // featureClass: String
+ // Fully qualified feature class name eg: SGID10.Boundaries.Counties
+ // returnValues: String[]
+ // A list of attributes to return eg: ['NAME', 'FIPS'].
+ // To include the geometry use the shape@ token or if you want the
+ // envelope use the shape@envelope token.
+ // options.predicate: String
+ // Search criteria for finding specific features in featureClass.
+ // Any valid ArcObjects where clause will work. If omitted, a TSQL *
+ // will be used instead. eg: NAME LIKE 'K%'
+ // options.geometry: String (not fully implemented)
+ // The point geometry used for spatial queries. Points are denoted as
+ // 'point:[x,y]'.
+ // options.spatialReference: Number
+ // The spatial reference of the input geographic coordinate pair.
+ // Choose any of the wkid's from the Geographic Coordinate System wkid reference
+ // or Projected Coordinate System wkid reference. 26912 is the default.
+ // options.tolerance: Number (not implemented)
+ // options.spatialRelation: String (default: 'intersect')
+ // options.buffer: Number
+ // A distance in meters to buffer the input geometry.
+ // 2000 meters is the maximum buffer.
+ // options.pageSize: Number (not implemented)
+ // options.skip: Number (not implemented)
+ // options.attributeStyle: String (defaults to 'identical')
+ // Controls the casing of the attributes that are returned.
+ // Options:
+ //
+ // 'identical': as is in data.
+ // 'upper': upper cases all attribute names.
+ // 'lower': lower cases all attribute names.
+ // 'camel': camel cases all attribute names
+ //
+ // returns: Promise
+ console.log('WebApi:search', arguments);
+
+ var url = `${this.baseUrl}search/${featureClass}/${encodeURIComponent(returnValues.join(','))}?`;
+
+ if (!options) {
+ options = {};
+ }
+
+ options.apiKey = this.apiKey;
+ if (!options.attributeStyle) {
+ options.attributeStyle = this.defaultAttributeStyle;
+ }
+
+ const response = await fetch(url + URLSearchParams(options), { signal: this.signal });
+
+ if (!response.ok) {
+ return {
+ ok: false,
+ message: response.message || response.statusText,
+ };
+ }
+
+ const result = await response.json();
+
+ if (result.status !== 200) {
+ return {
+ ok: false,
+ message: result.message,
+ };
+ }
+
+ return {
+ ok: true,
+ data: result.result,
+ };
+ }
+}
diff --git a/src/components/Sherlock.scss b/src/components/Sherlock.scss
new file mode 100644
index 0000000..76035d6
--- /dev/null
+++ b/src/components/Sherlock.scss
@@ -0,0 +1,57 @@
+@import '../variables.scss';
+
+.sherlock {
+ position: absolute;
+ z-index: 3;
+ width: 260px;
+ left: 62px;
+ top: 7px;
+ input[type='text'],
+ button.disabled {
+ border-radius: 0;
+ }
+ button.disabled {
+ opacity: 1;
+ background-color: $highlightColor;
+ }
+}
+.sherlock__match-dropdown {
+ width: 280px;
+ background-color: #fff;
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
+ font-size: 12px;
+ position: absolute;
+ z-index: 9999;
+}
+.sherlock__matches {
+ display: block;
+ width: 100%;
+ padding-inline-start: 0;
+ margin-bottom: 0;
+}
+.sherlock__match-item {
+ padding: 2px 12px;
+ display: flex;
+ justify-content: space-between;
+ mark {
+ padding: 0;
+ }
+}
+.sherlock__match-item:hover,
+.sherlock__match-item--selected {
+ background-color: #428bca;
+ color: #fff;
+ cursor: pointer;
+}
+.sherlock__match-item[disabled],
+.sherlock__match-item[disabled]:hover {
+ pointer-events: none;
+ justify-content: center;
+}
+.sherlock__message {
+ display: block;
+ padding: 0.5rem 1rem;
+}
+.dropdown-menu form .sherlock {
+ position: relative;
+}
diff --git a/src/services/config.js b/src/services/config.js
index ee2ea84..e2d8935 100644
--- a/src/services/config.js
+++ b/src/services/config.js
@@ -3,6 +3,10 @@ const config = {
center: [-111.9, 40.75],
zoom: 11,
},
+ SHERLOCK: {
+ serviceUrl: 'https://gis.wfrc.org/arcgis/rest/services/General/ZoomToPlaceNames/FeatureServer/1',
+ searchField: 'NAME',
+ },
};
export default config;