@community-release/nx-ui 0.0.32 → 0.0.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/module.d.mts CHANGED
@@ -101,12 +101,15 @@ var defaultComponentsStyle = {
101
101
  },
102
102
  button: {
103
103
  "border-radius": "var(--ui-input-height-large)"
104
+ },
105
+ map: {
106
+ "user-position-color": "var(--ui-color-primary)"
104
107
  }
105
108
  };
106
109
 
107
110
  var generateComponentsDefaults = (options) => {
108
111
  let result = "";
109
- const components = options?.components ? options.components : {};
112
+ const components = options?.componentsStyle ? options.componentsStyle : {};
110
113
  for (let name in defaultComponentsStyle) {
111
114
  const style = Object.assign(defaultComponentsStyle[name], components[name] ? components[name] : {});
112
115
  for (let prop in style) {
package/dist/module.d.ts CHANGED
@@ -101,12 +101,15 @@ var defaultComponentsStyle = {
101
101
  },
102
102
  button: {
103
103
  "border-radius": "var(--ui-input-height-large)"
104
+ },
105
+ map: {
106
+ "user-position-color": "var(--ui-color-primary)"
104
107
  }
105
108
  };
106
109
 
107
110
  var generateComponentsDefaults = (options) => {
108
111
  let result = "";
109
- const components = options?.components ? options.components : {};
112
+ const components = options?.componentsStyle ? options.componentsStyle : {};
110
113
  for (let name in defaultComponentsStyle) {
111
114
  const style = Object.assign(defaultComponentsStyle[name], components[name] ? components[name] : {});
112
115
  for (let prop in style) {
package/dist/module.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "ui",
3
3
  "configKey": "ui",
4
- "version": "0.0.32"
4
+ "version": "0.0.34"
5
5
  }
package/dist/module.mjs CHANGED
@@ -101,12 +101,15 @@ const defaultComponentsStyle = {
101
101
  },
102
102
  button: {
103
103
  "border-radius": "var(--ui-input-height-large)"
104
+ },
105
+ map: {
106
+ "user-position-color": "var(--ui-color-primary)"
104
107
  }
105
108
  };
106
109
 
107
110
  const generateComponentsDefaults = (options) => {
108
111
  let result = "";
109
- const components = options?.components ? options.components : {};
112
+ const components = options?.componentsStyle ? options.componentsStyle : {};
110
113
  for (let name in defaultComponentsStyle) {
111
114
  const style = Object.assign(defaultComponentsStyle[name], components[name] ? components[name] : {});
112
115
  for (let prop in style) {
@@ -0,0 +1,2 @@
1
+ declare function _default(ms?: number): any;
2
+ export default _default;
@@ -0,0 +1 @@
1
+ export default (ms = 0) => new Promise(r => {setTimeout(r, ms)})
@@ -1,5 +1,5 @@
1
1
  <template>
2
- <section class="component-ui-loading2" :class="{'tag-active': active}">
2
+ <section class="component-ui-loading" :class="{'tag-active': active}">
3
3
  <svg width="32" height="32">
4
4
  <circle cx="16" cy="16" r="10" ref="progress" />
5
5
  </svg>
@@ -51,7 +51,7 @@ const props = defineProps({
51
51
  }
52
52
  }
53
53
 
54
- .component-ui-loading2 {
54
+ .component-ui-loading {
55
55
  transition: visibility @com-ani-time @com-ani-ease, opacity @com-ani-time @com-ani-ease;
56
56
 
57
57
  opacity: 0;
@@ -15,7 +15,7 @@ const props = defineProps({
15
15
  },
16
16
  label: {
17
17
  default: '',
18
- }
18
+ },
19
19
  });
20
20
  const store = useMapStore();
21
21
 
@@ -1,37 +1,114 @@
1
1
  <template>
2
2
  <section class="component-ui-map-location-nearest" @click="handleClick">
3
- <i v-if="icon" class="icon" :class="icon"></i>
3
+ <i v-if="icon" class="icon" :class="[icon, loading ? 'tag-loading' : '']">
4
+ <ui-loading :active="loading" />
5
+ </i>
4
6
  <span class="label"><span>{{ label }}</span></span>
5
7
  </section>
6
8
  </template>
7
9
 
8
10
  <script setup>
9
- const props = defineProps({
10
- label: {
11
- required: true,
12
- type: String
13
- },
14
- icon: {
15
- required: true,
16
- type: String
17
- },
18
- });
19
-
20
- function getPosition() {
21
- return new Promise((res, rej) => {
22
- navigator.geolocation.getCurrentPosition(res, rej);
23
- });
24
- }
11
+ // Import
12
+ import { ref } from 'vue';
13
+ import UiLoading from '../../loading.vue';
14
+ import { useMapStore } from '../store';
15
+ import wait from '../../helpers/wait';
16
+
17
+ // Data
18
+ const props = defineProps({
19
+ label: {
20
+ required: true,
21
+ type: String
22
+ },
23
+ icon: {
24
+ required: true,
25
+ type: String
26
+ },
27
+ locations: {
28
+ required: true,
29
+ type: Array
30
+ },
31
+ });
32
+ const emit = defineEmits(['error']);
33
+ const store = useMapStore();
34
+ const loading = ref(false);
35
+ let cachedPosition = null;
36
+ let cachedTime = 0;
25
37
 
26
- async function handleClick() {
27
- let pos = null;
38
+ // Methods
39
+ // Get distance between points
40
+ function getDistance(p1, p2) {
41
+ const dx = p1.coord[0] - p2.coord[0];
42
+ const dy = p1.coord[1] - p2.coord[1];
28
43
 
29
- try {
30
- pos = await getPosition();
31
- } catch (err) {
32
- console.log('handleClick err', err);
44
+ return Math.sqrt(dx * dx + dy * dy);
45
+ }
46
+
47
+ // Find nearest points
48
+ function findNearestPoint(points, target) {
49
+ if (points.length === 0) return [];
50
+
51
+ // Calculate distances
52
+ const pointsWithDistance = points.map(point => ({
53
+ data: point,
54
+ distance: getDistance(point, target)
55
+ }));
56
+
57
+ // Sort by distance
58
+ pointsWithDistance.sort((a, b) => a.distance - b.distance);
59
+
60
+ // Select the first n points
61
+ return pointsWithDistance[0].data;
62
+ }
63
+
64
+ function getPosition() {
65
+ return new Promise((res, rej) => {
66
+ navigator.geolocation.getCurrentPosition(res, rej);
67
+ });
68
+ }
69
+
70
+ let clickInProcess = false;
71
+ async function handleClick() {
72
+ if (clickInProcess) return;
73
+ clickInProcess = true;
74
+
75
+ try {
76
+ // If cache not exist or it's older than 1 minute
77
+ if (!cachedPosition || Date.now() - cachedTime > 60000) {
78
+ loading.value = true;
79
+
80
+ const pos = await getPosition();
81
+
82
+ cachedPosition = [pos.coords.longitude, pos.coords.latitude];
83
+ cachedTime = Date.now();
84
+
85
+ store.setUserCoord(cachedPosition);
86
+ }
87
+
88
+ // Find nearest point
89
+ const nearest = findNearestPoint(
90
+ props.locations,
91
+ { coord: cachedPosition }
92
+ );
93
+
94
+ // Set coord and zoom
95
+ store.setCoord(cachedPosition, 750);
96
+ store.setZoom(14);
97
+
98
+ await wait(2000);
99
+
100
+ store.setCoord(nearest.coord, 1500);
101
+ store.setZoom(nearest?.zoom ? nearest.zoom : 14);
102
+ } catch (err) {
103
+ console.log('handleClick err', err);
104
+
105
+ emit('error', 'error-geo-not-enabled-on-device');
106
+ }
107
+
108
+ store.setSelectedMarker(null);
109
+ loading.value = false;
110
+ clickInProcess = false;
33
111
  }
34
- }
35
112
  </script>
36
113
 
37
114
  <style lang="less">
@@ -50,19 +127,42 @@ async function handleClick() {
50
127
  @com-color-bg: var(--ui-color-bg);
51
128
  @com-color-primary: var(--ui-color-primary);
52
129
 
130
+ @com-ani-time: var(--ui-ani-time);
131
+ @com-ani-ease: var(--ui-ani-ease);
132
+
53
133
  .component-ui-map-location-nearest {
54
134
  height: @com-height;
55
135
  background: @com-color-bg;
56
136
  border-radius: @com-border-radius-default;
57
137
 
138
+ position: relative;
58
139
  display: flex;
59
140
  cursor: pointer;
60
141
 
61
142
  .icon {
143
+ position: relative;
62
144
  flex: 0 0 38px;
63
145
  line-height: @com-height;
64
146
  font-size: @com-text-medium;
65
147
  color: @com-color-primary;
148
+
149
+ .component-ui-loading {
150
+ transform: translate3d(-50%, -50%, 0);
151
+ position: absolute;
152
+ top: 50%;
153
+ left: 50%;
154
+ }
155
+
156
+ &:before {
157
+ transition: opacity @com-ani-time @com-ani-ease;
158
+ }
159
+
160
+ &.tag-loading {
161
+ &:before {
162
+ opacity: 0;
163
+ }
164
+ }
165
+
66
166
  }
67
167
 
68
168
  .label {
@@ -73,16 +173,8 @@ async function handleClick() {
73
173
  font-size: @com-text-small;
74
174
 
75
175
  span {
76
- .mix-miltiline-text-overflow(2);
176
+ .mix-multiline-text-overflow(2);
77
177
  }
78
178
  }
79
179
  }
80
-
81
- // @media only screen and (max-width: 390px) {
82
- // .component-ui-map-location-nearest {
83
- // .icon {
84
- // flex-basis: 32px;
85
- // }
86
- // }
87
- // }
88
180
  </style>
@@ -1,6 +1,9 @@
1
1
  <template>
2
- <div class="component-ui-map-openlayer component-ui-map-engine">
2
+ <div class="component-ui-map-openlayer component-ui-map-engine" :class="{'tag-user-position': userCoord.length}">
3
3
  <div class="map" ref="refMap"></div>
4
+ <div style="display:none">
5
+ <div id="user-position-marker"></div>
6
+ </div>
4
7
  <slot />
5
8
  </div>
6
9
  </template>
@@ -29,6 +32,7 @@
29
32
  import { Cluster, OSM, Vector as VectorSource } from 'ol/source.js';
30
33
  import { Tile as TileLayer, Vector as VectorLayer } from 'ol/layer.js';
31
34
  import { boundingExtent } from 'ol/extent.js';
35
+ import Overlay from 'ol/Overlay.js';
32
36
 
33
37
  import Map from 'ol/Map.js';
34
38
  import View from 'ol/View.js';
@@ -52,10 +56,12 @@
52
56
  let initialized = false;
53
57
  let features = [];
54
58
  let cluster = null;
59
+ let userPositionMarker = null;
55
60
 
56
61
  // Store
57
62
  const store = useMapStore();
58
63
  const {
64
+ requestCoordChange,
59
65
  coord,
60
66
  zoom,
61
67
  zoomMin,
@@ -66,7 +72,8 @@
66
72
  markerImage,
67
73
  markerActiveImage,
68
74
  markerDisabledImage,
69
- selectedMarker
75
+ selectedMarker,
76
+ userCoord
70
77
  } = storeToRefs(store);
71
78
 
72
79
  watch(() => selectedMarker.value, (v) => {
@@ -78,7 +85,29 @@
78
85
  });
79
86
 
80
87
  // Watch coord
81
- watch(coord, (v) => { view.setCenter(fromLonLat(v)); });
88
+ watch([requestCoordChange, coord], (v) => {
89
+ const req = v[0];
90
+
91
+ if (!req) return;
92
+
93
+ const coord = fromLonLat(v[1]);
94
+
95
+ // If req === 1 then set coord instantly
96
+ if (req === 1) {
97
+ view.setCenter(coord);
98
+ // If req === 2 then set coord in default duration, else treat req as duration time in ms
99
+ } else {
100
+ const duration = req === 2 ? 300 : req;
101
+ view.animate({center: coord, duration});
102
+ }
103
+
104
+ requestCoordChange.value = 0;
105
+ });
106
+
107
+ // Watch user coord
108
+ watch(userCoord, (v) => {
109
+ userPositionMarker.setPosition(fromLonLat(v));
110
+ });
82
111
 
83
112
  // Watch zoom
84
113
  watch(zoom, (v) => {
@@ -290,6 +319,15 @@
290
319
  controls: []
291
320
  });
292
321
 
322
+ // Add user position marker
323
+ userPositionMarker = new Overlay({
324
+ position: fromLonLat(coord.value),
325
+ positioning: 'center-center',
326
+ element: document.getElementById('user-position-marker'),
327
+ stopEvent: false,
328
+ });
329
+ map.addOverlay(userPositionMarker);
330
+
293
331
  // Add mouse wheel interaction
294
332
  mouseWheelInt = new mouseWheelZoomInteraction({
295
333
  condition(e) {
@@ -307,8 +345,6 @@
307
345
  }, 50);
308
346
  });
309
347
 
310
- initialized = true;
311
-
312
348
  // Handle map marker click
313
349
  map.on('click', (e) => {
314
350
  cluster.getFeatures(e.pixel).then((clickedFeatures) => {
@@ -335,7 +371,12 @@
335
371
  });
336
372
  });
337
373
 
338
- map.on('loadend', () => { emit('initialized', payload); });
374
+ map.on('loadend', () => {
375
+ if (initialized) return;
376
+
377
+ initialized = true;
378
+ emit('initialized', payload);
379
+ });
339
380
  }
340
381
 
341
382
  // Hooks
@@ -345,6 +386,86 @@
345
386
  </script>
346
387
 
347
388
  <style lang="less">
389
+ @import (less) '../../styles/components.less';
390
+ @com-user-position-color: @ui-map-user-position-color;
391
+
392
+ @keyframes pulsar {
393
+ from {
394
+ transform: scale(1);
395
+ opacity: 0;
396
+ }
397
+
398
+ 50% { opacity: 0.7; }
399
+
400
+ to {
401
+ transform: scale(2);
402
+ opacity: 0;
403
+ }
404
+ }
405
+
406
+ @keyframes pulsar-base {
407
+ from { transform: scale(1); }
408
+ 50% { transform: scale(0.95); }
409
+ to { transform: scale(1); }
410
+ }
411
+
412
+ .mix-pulsar(@com-pulsar-size: 18px, @com-pulsar-color: @com-user-position-color) {
413
+ @com-pulsar-duration: 2s;
414
+
415
+ pointer-events: none;
416
+ width: @com-pulsar-size;
417
+ height: @com-pulsar-size;
418
+
419
+ &:before {
420
+ content: '';
421
+
422
+ transform-origin: 50% 50%;
423
+
424
+ opacity: 0;
425
+ position: absolute;
426
+ top: 0;
427
+ left: 0;
428
+ width: 100%;
429
+ height: 100%;
430
+
431
+ border-radius: 50%;
432
+
433
+ background: @com-pulsar-color;
434
+
435
+ animation-name: pulsar;
436
+ animation-duration: @com-pulsar-duration;
437
+ animation-iteration-count: infinite;
438
+ animation-timing-function: linear;
439
+ animation-fill-mode: forwards;
440
+ animation-delay: 0.7s;
441
+ }
442
+
443
+ &:after {
444
+ content: '';
445
+
446
+ transform-origin: 50% 50%;
447
+ opacity: 0.9;
448
+
449
+ box-sizing: border-box;
450
+ position: absolute;
451
+ top: 0;
452
+ left: 0;
453
+ width: 100%;
454
+ height: 100%;
455
+
456
+ border-radius: 50%;
457
+ border: 2px solid #fff;
458
+ background: @com-pulsar-color;
459
+
460
+ animation-name: pulsar-base;
461
+ animation-duration: @com-pulsar-duration;
462
+ animation-iteration-count: infinite;
463
+ animation-timing-function: linear;
464
+ animation-fill-mode: forwards;
465
+ animation-delay: 0.7s;
466
+ }
467
+ }
468
+
348
469
  .component-ui-map-openlayer {
349
470
  position: relative;
350
471
 
@@ -353,5 +474,17 @@
353
474
  inset: 0;
354
475
  background: #add19e;
355
476
  }
477
+
478
+ #user-position-marker {
479
+ .mix-pulsar;
480
+
481
+ display: none;
482
+ }
483
+
484
+ &.tag-user-position {
485
+ #user-position-marker {
486
+ display: block;
487
+ }
488
+ }
356
489
  }
357
490
  </style>
@@ -1,5 +1,6 @@
1
1
  {
2
2
  "zoom": 8,
3
3
  "zoomMin": 7,
4
- "zoomMax": 18
4
+ "zoomMax": 18,
5
+ "userMarkerColor": "var(--ui-color-primary)"
5
6
  }
@@ -1,4 +1,5 @@
1
1
  export const useMapStore: import("pinia").StoreDefinition<"map", {
2
+ requestCoordChange: number;
2
3
  coord: never[];
3
4
  zoom: number;
4
5
  zoomMin: number;
@@ -10,6 +11,7 @@ export const useMapStore: import("pinia").StoreDefinition<"map", {
10
11
  markers: never[];
11
12
  selectedMarker: null;
12
13
  disabledMarkers: never[];
14
+ userCoord: never[];
13
15
  }, {
14
16
  /**
15
17
  * Get marker by id
@@ -40,8 +42,14 @@ export const useMapStore: import("pinia").StoreDefinition<"map", {
40
42
  /**
41
43
  * Set map coord
42
44
  * @param {MapCoord} v
45
+ * @param {number} requestCoordChange - 1 default, 2 animated
43
46
  */
44
- setCoord(v: MapCoord): void;
47
+ setCoord(v: MapCoord, requestCoordChange?: number): void;
48
+ /**
49
+ * Set user coord
50
+ * @param {MapCoord} v
51
+ */
52
+ setUserCoord(v: MapCoord): void;
45
53
  /**
46
54
  * Enable/disable device zoom, if call withour params then toggle current state
47
55
  * @param {boolean=} v
@@ -18,6 +18,7 @@ import { defineStore } from 'pinia';
18
18
  export const useMapStore = defineStore('map', {
19
19
  state: () => {
20
20
  return {
21
+ requestCoordChange: 0,
21
22
  coord: [],
22
23
 
23
24
  zoom: 8,
@@ -31,7 +32,9 @@ export const useMapStore = defineStore('map', {
31
32
  markerDisabledImage: '',
32
33
  markers: [],
33
34
  selectedMarker: null,
34
- disabledMarkers: []
35
+ disabledMarkers: [],
36
+
37
+ userCoord: []
35
38
  }
36
39
  },
37
40
 
@@ -85,11 +88,21 @@ export const useMapStore = defineStore('map', {
85
88
  /**
86
89
  * Set map coord
87
90
  * @param {MapCoord} v
91
+ * @param {number} requestCoordChange - 1 default, 2 animated
88
92
  */
89
- setCoord(v) {
93
+ setCoord(v, requestCoordChange = 1) {
94
+ this.requestCoordChange = requestCoordChange;
90
95
  this.coord = v;
91
96
  },
92
97
 
98
+ /**
99
+ * Set user coord
100
+ * @param {MapCoord} v
101
+ */
102
+ setUserCoord(v) {
103
+ this.userCoord = v;
104
+ },
105
+
93
106
  /**
94
107
  * Enable/disable device zoom, if call withour params then toggle current state
95
108
  * @param {boolean=} v
@@ -1,3 +1,4 @@
1
1
  @ui-accordion-title-font-size: var(--ui-text-medium);
2
2
  @ui-accordion-text-font-size: var(--ui-text-default);
3
3
  @ui-button-border-radius: var(--ui-input-height-large);
4
+ @ui-map-user-position-color: var(--color-primary);
@@ -1,4 +1,4 @@
1
- .mix-miltiline-text-overflow(@line-numbers: 2) {
1
+ .mix-multiline-text-overflow(@line-numbers: 2) {
2
2
  overflow: hidden;
3
3
  display: -webkit-box;
4
4
  -webkit-line-clamp: @line-numbers;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@community-release/nx-ui",
3
- "version": "0.0.32",
3
+ "version": "0.0.34",
4
4
  "description": "nx-ui - Nuxt UI library",
5
5
  "repository": {
6
6
  "type": "git",
@@ -52,7 +52,7 @@
52
52
  },
53
53
  "scripts": {
54
54
  "prepack": "nuxt-module-build build",
55
- "dev": "nuxi dev docs --host",
55
+ "dev": "nuxi dev docs --host --port 7012",
56
56
  "build": "nuxi build docs",
57
57
  "prepare": "nuxt-module-build build && nuxt-module-build prepare && nuxi prepare docs",
58
58
  "release": "npm run test && npm run prepack && changelogen --release && npm publish && git push --follow-tags",