diff --git a/addon/components/coordinates-input.hbs b/addon/components/coordinates-input.hbs index bf4e2f8..14557fa 100644 --- a/addon/components/coordinates-input.hbs +++ b/addon/components/coordinates-input.hbs @@ -3,24 +3,38 @@
- + Select from map -
- - +
+ + -
+
+
+
+ +
+
+ +
+
+
+
Draggable map marker
-
diff --git a/addon/components/coordinates-input.js b/addon/components/coordinates-input.js index 29797e4..4240e03 100644 --- a/addon/components/coordinates-input.js +++ b/addon/components/coordinates-input.js @@ -4,59 +4,183 @@ import { tracked } from '@glimmer/tracking'; import { action } from '@ember/object'; import { isBlank } from '@ember/utils'; import { isArray } from '@ember/array'; +import { later } from '@ember/runloop'; +import getWithDefault from '@fleetbase/ember-core/utils/get-with-default'; const DEFAULT_LATITUDE = 1.3521; const DEFAULT_LONGITUDE = 103.8198; export default class CoordinatesInputComponent extends Component { + /** + * Service for fetching data. + * @type {Service} + * @memberof CoordinatesInputComponent + */ @service fetch; + + /** + * Service for accessing current user information. + * @type {Service} + * @memberof CoordinatesInputComponent + */ @service currentUser; + + /** + * Current zoom level of the map. + * @type {number} + * @memberof CoordinatesInputComponent + */ + @tracked zoom; + + /** + * Controls whether zoom controls are shown. + * @type {boolean} + * @memberof CoordinatesInputComponent + */ + @tracked zoomControl; + + /** + * Reference to the Leaflet map instance. + * @type {Object} + * @memberof CoordinatesInputComponent + */ + @tracked leafletMap; + + /** + * Current latitude of the map center. + * @type {number} + * @memberof CoordinatesInputComponent + */ @tracked latitude; + + /** + * Current longitude of the map center. + * @type {number} + * @memberof CoordinatesInputComponent + */ @tracked longitude; + + /** + * Latitude for map positioning. + * @type {number} + * @memberof CoordinatesInputComponent + */ @tracked mapLat; + + /** + * Longitude for map positioning. + * @type {number} + * @memberof CoordinatesInputComponent + */ @tracked mapLng; + + /** + * Query used for location lookup. + * @type {string} + * @memberof CoordinatesInputComponent + */ @tracked lookupQuery; + + /** + * Indicates if the component is loading data. + * @type {boolean} + * @memberof CoordinatesInputComponent + */ @tracked isLoading = false; + /** + * Indicates if the map is ready. + * @type {boolean} + * @memberof CoordinatesInputComponent + */ + @tracked isReady = false; + + /** + * Flag to track if the initial map movement has ended. + * @type {boolean} + * @memberof CoordinatesInputComponent + */ + @tracked isInitialMoveEnded = false; + + /** + * The URL for the map's tile source. + * @type {string} + * @memberof CoordinatesInputComponent + */ + @tracked tileSourceUrl = 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}.png'; + + /** + * Constructor for CoordinatesInputComponent. Sets initial map coordinates and values. + * @memberof CoordinatesInputComponent + */ constructor() { super(...arguments); this.setInitialMapCoordinates(); this.setInitialValueFromPoint(this.args.value); + this.zoom = getWithDefault(this.args, 'zoom', 9); + this.zoomControl = getWithDefault(this.args, 'zoomControl', false); if (typeof this.args.onInit === 'function') { this.args.onInit(this); } } + /** + * Checks if the provided object is a geographical point. + * @param {Object} point - Object to check. + * @returns {boolean} True if the object is a geographical point, false otherwise. + * @memberof CoordinatesInputComponent + */ isPoint(point) { return typeof point === 'object' && !isBlank(point.type) && point.type === 'Point' && isArray(point.coordinates); } - @action setInitialValueFromPoint(point) { + /** + * Sets the initial value of the map's coordinates from a geographical point. + * @param {Object} point - Geographical point to set the initial value from. + * @memberof CoordinatesInputComponent + */ + setInitialValueFromPoint(point) { if (this.isPoint(point)) { const [longitude, latitude] = point.coordinates; + if (longitude === 0 && latitude === 0) { + return; + } + this.updateCoordinates(latitude, longitude, { fireCallback: false }); } } - @action setInitialMapCoordinates() { + /** + * Sets the initial map coordinates based on the current user's location. + * @memberof CoordinatesInputComponent + */ + setInitialMapCoordinates() { const whois = this.currentUser.getOption('whois'); - this.mapLat = whois.latitude ?? DEFAULT_LATITUDE; - this.mapLng = whois.longitude ?? DEFAULT_LONGITUDE; + this.mapLat = getWithDefault(whois, 'latitude', DEFAULT_LATITUDE); + this.mapLng = getWithDefault(whois, 'longitude', DEFAULT_LONGITUDE); } - @action updateCoordinates(lat, lng, options = {}) { + /** + * Updates the coordinates of the map. + * @param {number|Object} lat - Latitude or object with coordinates. + * @param {number} [lng] - Longitude. + * @param {Object} [options={}] - Additional options. + * @memberof CoordinatesInputComponent + */ + updateCoordinates(lat, lng, options = {}) { if (this.isPoint(lat)) { const [longitude, latitude] = lat.coordinates; return this.updateCoordinates(latitude, longitude); } - const fireCallback = options.fireCallback ?? true; - const updateMap = options.updateMap ?? true; + const { onChange } = this.args; + const fireCallback = getWithDefault(options, 'fireCallback', true); + const updateMap = getWithDefault(options, 'updateMap', true); this.latitude = lat; this.longitude = lng; @@ -66,12 +190,64 @@ export default class CoordinatesInputComponent extends Component { this.mapLng = lng; } - if (fireCallback === true && typeof this.args.onChange === 'function') { - this.args.onChange({ latitude: lat, longitude: lng }); + if (fireCallback === true && typeof onChange === 'function') { + onChange({ latitude: lat, longitude: lng }); + } + } + + /** + * Leaflet event triggered when the map has loaded. Sets the leafletMap property. + * @param {Object} event - The event object containing the map target. + * @memberof CoordinatesInputComponent + */ + @action onMapLoaded({ target }) { + this.leafletMap = target; + + later( + this, + () => { + this.isReady = true; + }, + 300 + ); + } + + /** + * Ember action to zoom in on the map. + * @memberof CoordinatesInputComponent + */ + @action onZoomIn() { + if (this.leafletMap) { + this.leafletMap.zoomIn(); } } - @action setCoordinatesFromMap({ target }) { + /** + * Ember action to zoom out on the map. + * @memberof CoordinatesInputComponent + */ + @action onZoomOut() { + if (this.leafletMap) { + this.leafletMap.zoomOut(); + } + } + + /** + * Ember action to handle closing the map or the component. Resets the map coordinates to the current latitude and longitude. + * @memberof CoordinatesInputComponent + */ + @action onClose() { + this.mapLat = this.latitude; + this.mapLng = this.longitude; + } + + /** + * Ember action to set coordinates based on the map's current position. + * @param {Object} event - The event object containing map details. + * @memberof CoordinatesInputComponent + */ + @action setCoordinatesFromMap(event) { + const { target } = event; const { onUpdatedFromMap } = this.args; const { lat, lng } = target.getCenter(); @@ -82,8 +258,12 @@ export default class CoordinatesInputComponent extends Component { } } - @action async reverseLookup() { - const { onGeocode } = this.args; + /** + * Ember action for performing a reverse geolocation lookup. Updates the coordinates based on the lookup query result. + * @memberof CoordinatesInputComponent + */ + @action reverseLookup() { + const { onGeocode, onGeocodeError } = this.args; const query = this.lookupQuery; if (isBlank(query)) { @@ -108,7 +288,9 @@ export default class CoordinatesInputComponent extends Component { } }) .catch((error) => { - console.log(error); + if (typeof onGeocodeError === 'function') { + onGeocodeError(error); + } }) .finally(() => { this.isLoading = false; diff --git a/addon/components/country-select.hbs b/addon/components/country-select.hbs index 56fec9b..d1ded7c 100644 --- a/addon/components/country-select.hbs +++ b/addon/components/country-select.hbs @@ -1,5 +1,5 @@
- + {{country.emoji}} {{country.name}} diff --git a/addon/components/drawer.js b/addon/components/drawer.js index fadebff..484ef79 100644 --- a/addon/components/drawer.js +++ b/addon/components/drawer.js @@ -57,18 +57,21 @@ export default class DrawerComponent extends Component { minimize: this.minimize, maximize: this.maximize, isOpen: this.isOpen, + isMinimized: this.isMinimized, }; - /** Context object providing drawer control functions and state. */ - context = { - toggle: this.toggle, - open: this.open, - close: this.close, - toggleMinimize: this.toggleMinimize, - minimize: this.minimize, - maximize: this.maximize, - isOpen: this.isOpen, - }; + getContext() { + return { + toggle: this.toggle, + open: this.open, + close: this.close, + toggleMinimize: this.toggleMinimize, + minimize: this.minimize, + maximize: this.maximize, + isOpen: this.isOpen, + isMinimized: this.isMinimized, + }; + } /** * Sets up the component, establishes default properties, and calls the onLoad callback if provided. @@ -87,7 +90,7 @@ export default class DrawerComponent extends Component { this.noBackdrop = getWithDefault(this.args, 'noBackdrop', this.noBackdrop); if (typeof this.args.onLoad === 'function') { - this.args.onLoad(this.context); + this.args.onLoad(this.getContext()); } this._rendered = true; @@ -113,26 +116,50 @@ export default class DrawerComponent extends Component { /** Opens the drawer. */ @action open() { this.isOpen = true; + + if (typeof this.args.onOpen === 'function') { + this.args.onOpen(this.getContext()); + } } /** Closes the drawer. */ @action close() { this.isOpen = false; + + if (typeof this.args.onClose === 'function') { + this.args.onClose(this.getContext()); + } } /** Toggles the minimized state of the drawer. */ - @action toggleMinimize() { - this.isMinimized = !this.isMinimized; + @action toggleMinimize(options = {}) { + if (this.isMinimized) { + this.maximize(); + } else { + this.minimize(); + } + + if (typeof options.onToggle === 'function') { + options.onToggle(this.getContext()); + } } /** Minimizes the drawer. */ - @action minimize() { + @action minimize(options = {}) { this.isMinimized = true; + + if (typeof options.onMinimize === 'function') { + options.onMinimize(this.getContext()); + } } /** Maximizes the drawer. */ - @action maximize() { + @action maximize(options = {}) { this.isMinimized = false; + + if (typeof options.onMaximize === 'function') { + options.onMaximize(this.getContext()); + } } /** @@ -142,7 +169,7 @@ export default class DrawerComponent extends Component { @action startResize(event) { const disableResize = getWithDefault(this.args, 'disableResize', false); const onResizeStart = getWithDefault(this.args, 'onResizeStart', null); - const { drawerPanelNode, isResizable } = this; + const { drawerPanelNode, drawerNode, isResizable } = this; if (disableResize === true || !isResizable || !drawerPanelNode) { return; @@ -172,7 +199,7 @@ export default class DrawerComponent extends Component { // Send up event if (typeof onResizeStart === 'function') { - onResizeStart({ event, drawerPanelNode }); + onResizeStart({ event, drawerNode, drawerPanelNode, context: this.getContext() }); } } @@ -183,7 +210,7 @@ export default class DrawerComponent extends Component { @action resize(event) { const disableResize = getWithDefault(this.args, 'disableResize', false); const onResize = getWithDefault(this.args, 'onResize', null); - const { drawerPanelNode, isResizable } = this; + const { drawerPanelNode, drawerNode, isResizable } = this; if (disableResize === true || !isResizable || !drawerPanelNode) { return; @@ -214,7 +241,7 @@ export default class DrawerComponent extends Component { // Send callback if (typeof onResize === 'function') { - onResize({ event, drawerPanelNode }); + onResize({ event, drawerNode, drawerPanelNode, context: this.getContext() }); } } @@ -224,7 +251,7 @@ export default class DrawerComponent extends Component { */ @action stopResize(event) { const onResizeEnd = getWithDefault(this.args, 'onResizeEnd', null); - const { drawerPanelNode } = this; + const { drawerPanelNode, drawerNode } = this; // End resizing this.isResizing = false; @@ -238,7 +265,7 @@ export default class DrawerComponent extends Component { document.removeEventListener('mouseup', this.stopResize); if (typeof onResizeEnd === 'function') { - onResizeEnd({ event, drawerPanelNode }); + onResizeEnd({ event, drawerNode, drawerPanelNode, context: this.getContext() }); } } } diff --git a/addon/components/filter/country.hbs b/addon/components/filter/country.hbs index 8a1c31a..effb1ec 100644 --- a/addon/components/filter/country.hbs +++ b/addon/components/filter/country.hbs @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/addon/components/pagination.hbs b/addon/components/pagination.hbs index 56fee29..cdd04e7 100644 --- a/addon/components/pagination.hbs +++ b/addon/components/pagination.hbs @@ -1,15 +1,15 @@ -
-
- +
+ -