diff --git a/addon/components/google-maps-markup/component.js b/addon/components/google-maps-markup/component.js index 938522f..da47cfc 100644 --- a/addon/components/google-maps-markup/component.js +++ b/addon/components/google-maps-markup/component.js @@ -8,8 +8,10 @@ import overlayToFeature from '../../utils/overlay-to-feature'; import featureCenter from '../../utils/feature-center'; import initMeasureLabel from '../../utils/init-measure-label'; import MapLabel from '../../utils/map-label'; +import Marker from '../../utils/marker'; import DynamicLabel from '../../utils/dynamic-label'; import labelPlotter from '../../utils/label-plotter'; +import hoverColor from '../../utils/hover-color'; if (!window.google) { throw new Error('Sorry, but `google` defined globally is required for this addon'); @@ -191,7 +193,7 @@ export default Ember.Component.extend(ParentMixin, { poly.getPath().push(e.latLng); }); - google.maps.event.addListenerOnce(map, 'mouseup', (e) => { + google.maps.event.addListenerOnce(map, 'mouseup', () => { google.maps.event.removeListener(move); poly.setMap(null); @@ -214,6 +216,7 @@ export default Ember.Component.extend(ParentMixin, { }, actions: { + updateOptionValue(tool, prop, value) { set(tool, prop, value); }, @@ -233,6 +236,9 @@ export default Ember.Component.extend(ParentMixin, { }, changeTool(toolId) { + this.resetAllLayers(); + this.clearListeners(); + let markupDataService = this.get('markupData'); let activeLayer = this.get('activeLayer'); let map = this.get('map'); @@ -244,9 +250,6 @@ export default Ember.Component.extend(ParentMixin, { this.set('drawFinished', false); markupDataService.set('activeTool', tool.id); - this.resetAllLayers(); - this.clearListeners(); - if (activeLayer) { if (tool.id === 'pan') { activeLayer.data.setDrawingMode(null); @@ -255,7 +258,6 @@ export default Ember.Component.extend(ParentMixin, { let clickListener = activeLayer.data.addListener('click', event => { let childComponents = this.get('childComponents'); - let results = this.get('results'); let found = childComponents.find(function (comp) { return comp.get('data').feature.getId() === event.feature.getId(); }); @@ -282,6 +284,8 @@ export default Ember.Component.extend(ParentMixin, { event.stop(); }); listeners.pushObjects([ mapListener, dataListener ]); + } else if (tool.dataId === 'Point') { + activeLayer.data.setDrawingMode(tool.dataId); } else if (tool.dataId) { let style = Ember.copy(tool.style); @@ -332,6 +336,8 @@ export default Ember.Component.extend(ParentMixin, { if (result.type === 'text') { textGeoJson.removeObject(result.geojson); } + } else if (result.markerData) { + result.markerData.setMap(null); } }); @@ -352,6 +358,9 @@ export default Ember.Component.extend(ParentMixin, { if (result.type === 'text') { result.feature.setMap(null); textGeoJson.removeObject(result.geojson); + } else if (result.type === 'marker') { + result.markerData.setMap(null); + layer.data.remove(result.feature); } else { layer.data.remove(result.feature); } @@ -462,6 +471,11 @@ export default Ember.Component.extend(ParentMixin, { scaledSize: new google.maps.Size(22, 40), } }; + + if(data.hoverStyle){ + style = data.hoverStyle; + } + } else if (data.type === 'text') { data.feature.highlight(); } else { @@ -707,16 +721,85 @@ export default Ember.Component.extend(ParentMixin, { let plotter; let onClick = run.bind(this, (event) => { + let activeLayer = this.get('activeLayer'); let toolId = this.get('toolId'); + let tool = this.getTool(toolId); let mode = this.get('mode'); let mapDiv = map.getDiv(); let target = event.target; let withinMap = mapDiv.contains(target); + let results = this.get('results'); if (mode === 'draw') { if (withinMap && toolId === 'freeFormPolygon') { this.enableFreeFormPolygon(); + } else if (withinMap && toolId === 'marker') { + let length = results.get('length'); + let arrayIndexOffSet = 1; + let lastObjectIndex = length - arrayIndexOffSet; + let data = results.get('lastObject'); + + let iconObj = tool.icons.find(function(iconObj){ + return iconObj.id === tool.icon.id; + }); + + let markerObj = tool.markers.find(function(markerObj){ + return markerObj.id === tool.marker.id; + }); + + if (markerObj.id !== 'default') { + + let style = { + icon: { + path: markerObj.path, + fillColor: tool.style.color, + fillOpacity: 1, + strokeColor: '', + strokeWeight: 0, + scaledSize: new google.maps.Size(22, 40) + }, + map_icon_label: '' + markerObj.id + '' + }; + + let hoverStyle = { + icon: { + path: markerObj.path, + fillColor: hoverColor(tool.style.color), + fillOpacity: 1, + strokeColor: '', + strokeWeight: 0, + scaledSize: new google.maps.Size(22, 40) + } + }; + + if (tool.icon.id !== 'default') { + var marker = new Marker({ + position: calculateLatLng(map, event), + map: map, + icon: { + path: markerObj.path, + fillColor: tool.style.color, + fillOpacity: 1, + strokeColor: '', + strokeWeight: 0, + scaledSize: new google.maps.Size(22, 40) + }, + map_icon_label: '' + iconObj.id + '' + }) + + data.markerData = marker; + + // To add the marker to the map, call setMap(); + marker.setMap(map); + } + + data.hoverStyle = hoverStyle; + data.style = style; + results[lastObjectIndex] = data; + activeLayer.data.overrideStyle(data.feature, style); + } } + return; } diff --git a/addon/components/google-maps-markup/template.hbs b/addon/components/google-maps-markup/template.hbs index 1813f4f..2a6f60d 100644 --- a/addon/components/google-maps-markup/template.hbs +++ b/addon/components/google-maps-markup/template.hbs @@ -34,6 +34,7 @@ {{#each activeTool.options as |opt|}} {{#if (is-equal opt.type 'color')}}
+ {{#unless (is-equal activeTool.icon 'default')}} {{color-palette selected=(get activeTool opt.id) onselect=(action 'updateOptionValue' activeTool opt.id)}} + {{/unless}} +
+ {{/if}} + {{#if (is-equal opt.type 'Marker')}} + + {{/if}} + {{#unless (is-equal activeTool.marker.id 'default')}} + {{#if (is-equal opt.type 'icon')}} + {{/if}} + {{/unless}} + {{/each}} {{/if}} diff --git a/addon/components/markup-result-item/component.js b/addon/components/markup-result-item/component.js index 94ba6e8..8f886ac 100644 --- a/addon/components/markup-result-item/component.js +++ b/addon/components/markup-result-item/component.js @@ -8,7 +8,6 @@ import featureCenter from '../../utils/feature-center'; const { on, run, - set, computed } = Ember; diff --git a/addon/utils/hover-color.js b/addon/utils/hover-color.js new file mode 100644 index 0000000..36898d2 --- /dev/null +++ b/addon/utils/hover-color.js @@ -0,0 +1,87 @@ + +/* hexToComplimentary : Converts hex value to HSL, shifts + * hue by 180 degrees and then converts hex, giving complimentary color + * as a hex value + * @param [String] hex : hex value + * @return [String] : complimentary color as hex value + */ +export default function hoverColor(hex) { + // Convert hex to rgb + var rgb = 'rgb(' + (hex = hex.replace('#', '')).match(new RegExp('(.{' + hex.length/3 + '})', 'g')).map(function(l) { return parseInt(hex.length%2 ? l+l : l, 16); }).join(',') + ')'; + + // Get array of RGB values + rgb = rgb.replace(/[^\d,]/g, '').split(','); + + var r = rgb[0], g = rgb[1], b = rgb[2]; + + // Convert RGB to HSL + r /= 255.0; + g /= 255.0; + b /= 255.0; + var max = Math.max(r, g, b); + var min = Math.min(r, g, b); + var h, s, l = (max + min) / 2.0; + + if(max === min) { + h = s = 0; //achromatic + } else { + var d = max - min; + s = (l > 0.5 ? d / (2.0 - max - min) : d / (max + min)); + + if(max === r && g >= b) { + h = 1.0472 * (g - b) / d ; + } else if(max === r && g < b) { + h = 1.0472 * (g - b) / d + 6.2832; + } else if(max === g) { + h = 1.0472 * (b - r) / d + 2.0944; + } else if(max === b) { + h = 1.0472 * (r - g) / d + 4.1888; + } + } + + h = h / 6.2832 * 360.0 + 0; + + // Shift hue to opposite side of wheel and convert to [0-1] value + h+= 180; + if (h > 360) { h -= 360; } + h /= 360; + + // Convert h s and l values into r g and b values + if(s === 0){ + r = g = b = l; // achromatic + } else { + var hue2rgb = function hue2rgb(p, q, t){ + if(t < 0) { + t += 1; + } + if(t > 1){ + t -= 1; + } + if(t < 1/6) { + return p + (q - p) * 6 * t; + } + if(t < 1/2) { + return q; + } + if(t < 2/3) { + return p + (q - p) * (2/3 - t) * 6; + } + return p; + }; + + var q = l < 0.5 ? l * (1 + s) : l + s - l * s; + var p = 2 * l - q; + + r = hue2rgb(p, q, h + 1/3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1/3); + } + + r = Math.round(r * 255); + g = Math.round(g * 255); + b = Math.round(b * 255); + + // Convert r b and g values to hex + rgb = b | (g << 8) | (r << 16); + return "#" + (0x1000000 | rgb).toString(16).substring(1); +} \ No newline at end of file diff --git a/addon/utils/marker-label.js b/addon/utils/marker-label.js new file mode 100644 index 0000000..978cf49 --- /dev/null +++ b/addon/utils/marker-label.js @@ -0,0 +1,65 @@ +class MarkerLabel extends google.maps.OverlayView { + constructor(options) { + super(...arguments); + + var self = this; + this.setValues(options); + + this.position = options.position; + + // Create the label container + this.div = document.createElement('div'); + this.div.className = ''; + + // Trigger the marker click handler if clicking on the label + google.maps.event.addDomListener(this.div, 'click', function(e){ + (e.stopPropagation) && e.stopPropagation(); + google.maps.event.trigger(self.marker, 'click'); + }); + } + + onAdd() { + var pane = this.getPanes().overlayImage.appendChild(this.div); + var self = this; + + this.listeners = [ + google.maps.event.addListener(this, 'position_changed', function() { self.draw(); }), + google.maps.event.addListener(this, 'text_changed', function() { self.draw(); }), + google.maps.event.addListener(this, 'zindex_changed', function() { self.draw(); }) + ]; + } + + // Marker Label onRemove + onRemove() { + this.div.parentNode.removeChild(this.div); + + for (var i = 0, I = this.listeners.length; i < I; ++i) { + google.maps.event.removeListener(this.listeners[i]); + } + } + + // Implement draw + draw() { + var projection = this.getProjection(); + var position = projection.fromLatLngToDivPixel(this.get('position')); + var div = this.div; + + this.div.innerHTML = this.get('text').toString(); + + div.style.zIndex = this.get('zIndex'); // Allow label to overlay marker + div.style.position = 'absolute'; + div.style.display = 'block'; + div.style.left = (position.x - (div.offsetWidth / 2) + 12) + 'px'; + div.style.top = (position.y - (div.offsetHeight/2) - 24) + 'px'; + } + + set position(value) { + this.latlng = value; + } + + get position() { + return this.latlng; + } +} + +export default MarkerLabel; diff --git a/addon/utils/marker.js b/addon/utils/marker.js new file mode 100644 index 0000000..bc60941 --- /dev/null +++ b/addon/utils/marker.js @@ -0,0 +1,25 @@ +import MarkerLabel from './marker-label'; + +class Marker extends google.maps.OverlayView { + constructor(options) { + super(...arguments); + + this.icon = options.icon; + + if (options.map_icon_label) { + this.MarkerLabel = new MarkerLabel({ + position: options.position, + map: this.map, + marker: this, + text: options.map_icon_label + }); + } + } + + // Custom Marker SetMap + setMap() { + (this.MarkerLabel) && this.MarkerLabel.setMap.apply(this.MarkerLabel, arguments); + } +} + +export default Marker; diff --git a/addon/utils/tools.js b/addon/utils/tools.js index 955d91e..5da8ce9 100644 --- a/addon/utils/tools.js +++ b/addon/utils/tools.js @@ -19,7 +19,66 @@ export default { id: 'marker', dataId: 'Point', name: 'Marker', - title: 'Marker Tool' + title: 'Marker Tool', + options: [ + { name: 'Marker', type: 'Marker', id: 'marker', display: 'marker.display' }, + { name: 'Icon', type: 'icon', id: 'icon', display: 'icon.display' }, + { name: 'Color', type: 'color', id: 'style.color' }, + ], + style: { + color: '#374046', + }, + icons: [{ + id: 'default', + display: 'Default' + }, { + id: 'grade', + display: 'Grade' + }, { + id: 'help', + display: 'Help' + }, { + id: 'favorite', + display: 'Favorite' + }, { + id: 'check_circle', + display: 'Check Circle' + }, { + id: 'lens', + display: 'Lens' + }, { + id: 'filter_vintage', + display: 'Filter Vintage' + }, { + id: 'photo_camera', + display: 'Photo Camera' + }, { + id: 'place', + display: 'Place' + }], + icon: { + id: 'default', + display: 'Default' + }, + + markers: [{ + id: 'default', + display: 'Default', + }, { + id: 'pin', + display: 'Pin', + path: 'M0-48c-9.8 0-17.7 7.8-17.7 17.4 0 15.5 17.7 30.6 17.7 30.6s17.7-15.4 17.7-30.6c0-9.6-7.9-17.4-17.7-17.4z', + pathDropdown: 'M24 0c-9.8 0-17.7 7.8-17.7 17.4 0 15.5 17.7 30.6 17.7 30.6s17.7-15.4 17.7-30.6c0-9.6-7.9-17.4-17.7-17.4z' + }, { + id: 'squarePin', + display: 'Square Pin', + path: 'M22-48h-44v43h16l6 5 6-5h16z', + pathDropdown: 'M45.5 0h-43v43h16.2l5.9 5 5.8-5h15.1z' + }], + marker: { + id: 'default', + display: 'Default', + } }, polyline: { id: 'polyline', diff --git a/app/styles/app.less b/app/styles/app.less new file mode 100644 index 0000000..a5379e8 --- /dev/null +++ b/app/styles/app.less @@ -0,0 +1,4 @@ + +@import "ember-bootstrap/bootstrap"; + +@import "ember-power-select"; diff --git a/package.json b/package.json index c1e0f59..5827e26 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "ember-uuid": "1.0.0" }, "devDependencies": { + "bootstrap": "3.3.7", "broccoli-asset-rev": "^2.4.5", "broccoli-merge-trees": "^1.2.1", "broccoli-static-compiler": "^0.2.1", @@ -51,6 +52,7 @@ "ember-disable-prototype-extensions": "^1.1.0", "ember-export-application-global": "^1.0.5", "ember-load-initializers": "^0.6.3", + "ember-power-select": "1.8.2", "ember-resolver": "^2.0.3", "loader.js": "^4.0.10" }, diff --git a/public/images/ic_person_pin_black_24px.svg b/public/images/ic_person_pin_black_24px.svg new file mode 100644 index 0000000..0cf5662 --- /dev/null +++ b/public/images/ic_person_pin_black_24px.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/public/images/ic_person_pin_circle_black_24px.svg b/public/images/ic_person_pin_circle_black_24px.svg new file mode 100644 index 0000000..f1ab0e0 --- /dev/null +++ b/public/images/ic_person_pin_circle_black_24px.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/public/images/ic_place_black_24px.svg b/public/images/ic_place_black_24px.svg new file mode 100644 index 0000000..d7844b2 --- /dev/null +++ b/public/images/ic_place_black_24px.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/vendor/google-maps-markup/styles.css b/vendor/google-maps-markup/styles.css index 7a9e6bf..3fd8500 100644 --- a/vendor/google-maps-markup/styles.css +++ b/vendor/google-maps-markup/styles.css @@ -55,3 +55,63 @@ .google-maps-markup-map-label:placeholder-shown { /* Standard (https://drafts.csswg.org/selectors-4/#placeholder) */ color: #444; } + +.drop_down .ember-power-select-trigger{ + width: 200px; +} + +@font-face { + font-family: 'Material Icons'; + font-style: normal; + font-weight: 400; + src: local('Material Icons'), local('MaterialIcons-Regular'), url(https://fonts.gstatic.com/s/materialicons/v22/2fcrYFNaTjcS6g4U3t-Y5ZjZjT5FdEJ140U2DJYC3mY.woff2) format('woff2'); +} + +.material-icons { + font-family: 'Material Icons'; + font-weight: normal; + font-style: normal; + font-size: 24px; + width: 48px; + color: #FFFFFF; + letter-spacing: normal; + text-transform: none; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + -moz-font-feature-settings: 'liga'; + -moz-osx-font-smoothing: grayscale; +} + +.ember-power-select-option i.material-icons, +.ember-power-select-selected-item i.material-icons { + color: #444444; + font-size: 15px; +} + +.ember-power-select-selected-item, .ember-power-select-option{ + display: flex; + align-items: center; +} +.ember-power-select-selected-item .material-icons, .ember-power-select-option .material-icons,.ember-power-select-option svg{ + margin-left: 8px; +} + +.ember-power-select-option[aria-current="true"]:hover i.material-icons { + color: #FFFFFF; +} + +.ember-power-select-selected-item svg { + margin-left: 8px; +} + +.ember-power-select-selected-item svg path,.ember-power-select-option svg path { + fill: #444444; +} + +.ember-power-select-option:hover svg path { + fill: #FFFFFF; +} + +