incyclist-services 1.7.49-beta → 1.7.49

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.
@@ -392,7 +392,7 @@ let ActiveRidesService = (() => {
392
392
  return 'Anonymous';
393
393
  const names = ['Alex', 'Bart', 'Cosmas', 'Dirk', 'Ernesto', 'Frank', 'Guido', 'Hans', 'Irene', 'John', 'Kai', 'Lorenzo', 'Martin', 'Naijb', 'Oswaldo', 'Pete', 'Quentin', 'Rachel', 'Sophia', 'Trevor', 'Ute', 'Vivian', 'Wil', 'Xaver', 'Younes', 'Zoe'];
394
394
  const fnKey = id.charAt(0).toLowerCase();
395
- const idx = !Number.isNaN(Number.parseInt(fnKey)) ? Number.parseInt(fnKey) : fnKey.charCodeAt(0) - 96;
395
+ const idx = !Number.isNaN(Number.parseInt(fnKey)) ? Number.parseInt(fnKey) : (fnKey.codePointAt(0) ?? 0) - 96;
396
396
  const lnKey = id.charAt(0).toUpperCase();
397
397
  const ln = !Number.isNaN(Number.parseInt(lnKey)) ? '' : lnKey;
398
398
  return `${names[idx]}${ln}`;
@@ -9,7 +9,7 @@ class ActivityConverter {
9
9
  static async convert(activity, format) {
10
10
  if (!ActivityConverter.factory) {
11
11
  ActivityConverter.factory = new factory_1.ActivityConverterFactory();
12
- ActivityConverter.factory.add('fit', new fit_1.RemoteFitConverter());
12
+ ActivityConverter.factory.add('fit', new fit_1.LocalFitConverter());
13
13
  ActivityConverter.factory.add('tcx', new tcx_1.TcxConverter());
14
14
  }
15
15
  return await ActivityConverter.factory.convert(activity, format);
@@ -15,3 +15,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./remote-fit"), exports);
18
+ __exportStar(require("./local-fit"), exports);
@@ -0,0 +1,208 @@
1
+ "use strict";
2
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
3
+ var useValue = arguments.length > 2;
4
+ for (var i = 0; i < initializers.length; i++) {
5
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
6
+ }
7
+ return useValue ? value : void 0;
8
+ };
9
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
10
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
11
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
12
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
13
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
14
+ var _, done = false;
15
+ for (var i = decorators.length - 1; i >= 0; i--) {
16
+ var context = {};
17
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
18
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
19
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
20
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
21
+ if (kind === "accessor") {
22
+ if (result === void 0) continue;
23
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
24
+ if (_ = accept(result.get)) descriptor.get = _;
25
+ if (_ = accept(result.set)) descriptor.set = _;
26
+ if (_ = accept(result.init)) initializers.unshift(_);
27
+ }
28
+ else if (_ = accept(result)) {
29
+ if (kind === "field") initializers.unshift(_);
30
+ else descriptor[key] = _;
31
+ }
32
+ }
33
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
34
+ done = true;
35
+ };
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.LocalFitConverter = void 0;
38
+ const fitsdk_1 = require("@garmin/fitsdk");
39
+ const gd_eventlog_1 = require("gd-eventlog");
40
+ const settings_1 = require("../../../../settings");
41
+ const decorators_1 = require("../../../../base/decorators");
42
+ const DEG_TO_SEMICIRCLES = (2 ** 31) / 180;
43
+ let LocalFitConverter = (() => {
44
+ let _instanceExtraInitializers = [];
45
+ let _getUserSettings_decorators;
46
+ return class LocalFitConverter {
47
+ static {
48
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
49
+ _getUserSettings_decorators = [decorators_1.Injectable];
50
+ __esDecorate(this, null, _getUserSettings_decorators, { kind: "method", name: "getUserSettings", static: false, private: false, access: { has: obj => "getUserSettings" in obj, get: obj => obj.getUserSettings }, metadata: _metadata }, null, _instanceExtraInitializers);
51
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
52
+ }
53
+ logger = __runInitializers(this, _instanceExtraInitializers);
54
+ constructor() {
55
+ this.logger = new gd_eventlog_1.EventLogger('LocalFitExporter');
56
+ }
57
+ async convert(activity) {
58
+ try {
59
+ this.logger.logEvent({ message: 'convert start', format: 'FIT' });
60
+ const fitActivity = this.getFitActivity(activity);
61
+ const data = this.encode(fitActivity);
62
+ this.logger.logEvent({ message: 'convert success', format: 'FIT' });
63
+ return data;
64
+ }
65
+ catch (err) {
66
+ this.logger.logEvent({ message: 'convert result', format: 'FIT', result: 'error', reason: err.message });
67
+ throw err;
68
+ }
69
+ }
70
+ getFitActivity(activity) {
71
+ const { id, title, time, timeTotal, timePause, distance } = activity;
72
+ const status = 'created';
73
+ const startTime = new Date(activity.startTime).toISOString();
74
+ const logs = activity.logs.map(this.mapLogToFit.bind(this));
75
+ const screenshots = [];
76
+ const laps = this.mapLapsToFit(activity.laps ?? [], activity.startTime);
77
+ const user = {
78
+ id: this.getUserSettings().get('uuid', undefined),
79
+ weight: activity.user.weight
80
+ };
81
+ return { id, title, status, logs, laps, startTime, time, timeTotal, timePause, distance, user, screenshots };
82
+ }
83
+ mapLapsToFit(laps, activityStartTime) {
84
+ const activityStartMs = new Date(activityStartTime).getTime();
85
+ return laps.map((lap, i) => {
86
+ const prevDistance = i > 0 ? laps[i - 1].distance : 0;
87
+ return {
88
+ lapNo: lap.num,
89
+ startTime: new Date(lap.startTime).toISOString(),
90
+ stopTime: new Date(lap.startTime + lap.rideTime * 1000).toISOString(),
91
+ lapDistance: lap.distance - prevDistance,
92
+ totalDistance: lap.distance,
93
+ lapTime: lap.rideTime,
94
+ totalTime: (lap.startTime - activityStartMs) / 1000 + lap.rideTime,
95
+ };
96
+ });
97
+ }
98
+ mapLogToFit(log) {
99
+ const { time, speed, slope, cadence: cadenceOrg, heartrate: heartrateOrg, distance, power: powerOrg, lat, lng, elevation } = log;
100
+ const cadence = Math.round(cadenceOrg);
101
+ const heartrate = Math.round(heartrateOrg);
102
+ const power = Math.round(powerOrg);
103
+ return { time, speed, slope, cadence, heartrate, distance, power, lat, lon: lng, elevation };
104
+ }
105
+ encode(activity) {
106
+ const encoder = new fitsdk_1.Encoder();
107
+ const startTime = new Date(activity.startTime);
108
+ encoder.onMesg(fitsdk_1.Profile.MesgNum.FILE_ID, {
109
+ type: 'activity',
110
+ manufacturer: 'development',
111
+ product: 0,
112
+ timeCreated: startTime,
113
+ });
114
+ encoder.onMesg(fitsdk_1.Profile.MesgNum.EVENT, {
115
+ timestamp: startTime,
116
+ event: 'timer',
117
+ eventType: 'start',
118
+ data: 0,
119
+ });
120
+ let lastTimestampMs = startTime.getTime();
121
+ activity.logs.forEach((log) => {
122
+ if (log.time != null) {
123
+ lastTimestampMs = startTime.getTime() + log.time * 1000;
124
+ }
125
+ const record = {
126
+ timestamp: new Date(lastTimestampMs),
127
+ };
128
+ if (log.lat != null && log.lon != null) {
129
+ record.positionLat = Math.round(log.lat * DEG_TO_SEMICIRCLES);
130
+ record.positionLong = Math.round(log.lon * DEG_TO_SEMICIRCLES);
131
+ }
132
+ if (log.elevation != null)
133
+ record.altitude = log.elevation;
134
+ if (log.heartrate != null)
135
+ record.heartRate = log.heartrate;
136
+ if (log.cadence != null)
137
+ record.cadence = log.cadence;
138
+ if (log.distance != null)
139
+ record.distance = log.distance;
140
+ if (log.speed != null)
141
+ record.speed = log.speed / 3.6;
142
+ if (log.power != null)
143
+ record.power = log.power;
144
+ if (log.slope != null)
145
+ record.grade = log.slope;
146
+ encoder.onMesg(fitsdk_1.Profile.MesgNum.RECORD, record);
147
+ });
148
+ const endTime = new Date(lastTimestampMs);
149
+ encoder.onMesg(fitsdk_1.Profile.MesgNum.EVENT, {
150
+ timestamp: endTime,
151
+ event: 'timer',
152
+ eventType: 'stopAll',
153
+ data: 0,
154
+ });
155
+ if (activity.laps.length > 0) {
156
+ activity.laps.forEach(lap => {
157
+ const lapStart = new Date(lap.startTime);
158
+ const lapEnd = new Date(lap.stopTime);
159
+ encoder.onMesg(fitsdk_1.Profile.MesgNum.LAP, {
160
+ timestamp: lapEnd,
161
+ startTime: lapStart,
162
+ totalElapsedTime: lap.lapTime,
163
+ totalTimerTime: lap.lapTime,
164
+ totalDistance: lap.lapDistance,
165
+ event: 'lap',
166
+ eventType: 'stop',
167
+ });
168
+ });
169
+ }
170
+ else {
171
+ encoder.onMesg(fitsdk_1.Profile.MesgNum.LAP, {
172
+ timestamp: endTime,
173
+ startTime,
174
+ totalElapsedTime: activity.timeTotal,
175
+ totalTimerTime: activity.time,
176
+ totalDistance: activity.distance,
177
+ event: 'lap',
178
+ eventType: 'stop',
179
+ });
180
+ }
181
+ encoder.onMesg(fitsdk_1.Profile.MesgNum.SESSION, {
182
+ timestamp: endTime,
183
+ startTime,
184
+ totalElapsedTime: activity.timeTotal,
185
+ totalTimerTime: activity.time,
186
+ totalDistance: activity.distance,
187
+ sport: 'cycling',
188
+ subSport: 'virtualActivity',
189
+ event: 'session',
190
+ eventType: 'stopDisableAll',
191
+ });
192
+ encoder.onMesg(fitsdk_1.Profile.MesgNum.ACTIVITY, {
193
+ timestamp: endTime,
194
+ totalTimerTime: activity.time,
195
+ numSessions: 1,
196
+ type: 'manual',
197
+ event: 'activity',
198
+ eventType: 'stop',
199
+ });
200
+ const uint8Array = encoder.close();
201
+ return uint8Array.buffer;
202
+ }
203
+ getUserSettings() {
204
+ return (0, settings_1.useUserSettings)();
205
+ }
206
+ };
207
+ })();
208
+ exports.LocalFitConverter = LocalFitConverter;
@@ -278,7 +278,7 @@ let RLVDisplayService = (() => {
278
278
  getUIText(input) {
279
279
  let text = input;
280
280
  if (text?.length) {
281
- text = text.replace(/\\n/g, '<br>');
281
+ text = text.replaceAll('\\n', '<br>');
282
282
  }
283
283
  return text;
284
284
  }
@@ -61,7 +61,7 @@ class BinaryReader {
61
61
  const c = part.readUInt16LE(i * 2);
62
62
  if (c === 0)
63
63
  return str;
64
- str += String.fromCharCode(c);
64
+ str += String.fromCodePoint(c);
65
65
  i++;
66
66
  }
67
67
  return str;
@@ -6,7 +6,7 @@ const formatDateTime = (date, fstr = '%Y%m%d%H%M%S', utc = false) => {
6
6
  if (!(0, valid_1.valid)(date) || !(date instanceof Date))
7
7
  return;
8
8
  const prefix = utc ? 'getUTC' : 'get';
9
- return fstr.replace(/%[YmdHMS]/g, (m) => {
9
+ return fstr.replaceAll(/%[YmdHMS]/g, (m) => {
10
10
  switch (m) {
11
11
  case '%Y': return date[prefix + 'FullYear']();
12
12
  case '%m':
@@ -17,7 +17,8 @@ const calculateDistance = (lat1, lon1, lat2, lon2, radius = rEarth * 1000) => {
17
17
  return distance;
18
18
  };
19
19
  exports.calculateDistance = calculateDistance;
20
- const distanceBetween = (p1, p2, props = { abs: true, latLng: true }) => {
20
+ const DEFAULT_DISTANCE_PROPS = { abs: true, latLng: true };
21
+ const distanceBetween = (p1, p2, props = DEFAULT_DISTANCE_PROPS) => {
21
22
  if (p1 === undefined)
22
23
  return 0;
23
24
  let dist;
@@ -17,7 +17,7 @@ const removeUTFBom = (str) => {
17
17
  if (!str || typeof (str) !== 'string') {
18
18
  return str;
19
19
  }
20
- while (str.charCodeAt(0) === 0 || str.charCodeAt(0) === 0xFEFF || str.charCodeAt(0) === 0xFFFD)
20
+ while (str.codePointAt(0) === 0 || str.codePointAt(0) === 0xFEFF || str.codePointAt(0) === 0xFFFD)
21
21
  str = str.substring(1);
22
22
  return str;
23
23
  };
@@ -216,7 +216,7 @@ class ZwoParser {
216
216
  let tag;
217
217
  parser.parseString(data, (err, result) => {
218
218
  if (err) {
219
- err.message = 'File contains error(s): ' + err.message.replace(/\n/g, ' ');
219
+ err.message = 'File contains error(s): ' + err.message.replaceAll('\n', ' ');
220
220
  this.logger.logEvent({ message: 'error', fn: 'parse()', error: err.message, stack: err.stack });
221
221
  return reject(err);
222
222
  }
@@ -389,7 +389,7 @@ let ActiveRidesService = (() => {
389
389
  return 'Anonymous';
390
390
  const names = ['Alex', 'Bart', 'Cosmas', 'Dirk', 'Ernesto', 'Frank', 'Guido', 'Hans', 'Irene', 'John', 'Kai', 'Lorenzo', 'Martin', 'Naijb', 'Oswaldo', 'Pete', 'Quentin', 'Rachel', 'Sophia', 'Trevor', 'Ute', 'Vivian', 'Wil', 'Xaver', 'Younes', 'Zoe'];
391
391
  const fnKey = id.charAt(0).toLowerCase();
392
- const idx = !Number.isNaN(Number.parseInt(fnKey)) ? Number.parseInt(fnKey) : fnKey.charCodeAt(0) - 96;
392
+ const idx = !Number.isNaN(Number.parseInt(fnKey)) ? Number.parseInt(fnKey) : (fnKey.codePointAt(0) ?? 0) - 96;
393
393
  const lnKey = id.charAt(0).toUpperCase();
394
394
  const ln = !Number.isNaN(Number.parseInt(lnKey)) ? '' : lnKey;
395
395
  return `${names[idx]}${ln}`;
@@ -1,12 +1,12 @@
1
1
  import { ActivityConverterFactory } from "./factory";
2
- import { RemoteFitConverter } from "./fit";
2
+ import { LocalFitConverter } from "./fit";
3
3
  import { TcxConverter } from "./tcx";
4
4
  export class ActivityConverter {
5
5
  static factory;
6
6
  static async convert(activity, format) {
7
7
  if (!ActivityConverter.factory) {
8
8
  ActivityConverter.factory = new ActivityConverterFactory();
9
- ActivityConverter.factory.add('fit', new RemoteFitConverter());
9
+ ActivityConverter.factory.add('fit', new LocalFitConverter());
10
10
  ActivityConverter.factory.add('tcx', new TcxConverter());
11
11
  }
12
12
  return await ActivityConverter.factory.convert(activity, format);
@@ -1 +1,2 @@
1
1
  export * from './remote-fit';
2
+ export * from './local-fit';
@@ -0,0 +1,205 @@
1
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
2
+ var useValue = arguments.length > 2;
3
+ for (var i = 0; i < initializers.length; i++) {
4
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
5
+ }
6
+ return useValue ? value : void 0;
7
+ };
8
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
9
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
10
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
11
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
12
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
13
+ var _, done = false;
14
+ for (var i = decorators.length - 1; i >= 0; i--) {
15
+ var context = {};
16
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
17
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
18
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
19
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
20
+ if (kind === "accessor") {
21
+ if (result === void 0) continue;
22
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
23
+ if (_ = accept(result.get)) descriptor.get = _;
24
+ if (_ = accept(result.set)) descriptor.set = _;
25
+ if (_ = accept(result.init)) initializers.unshift(_);
26
+ }
27
+ else if (_ = accept(result)) {
28
+ if (kind === "field") initializers.unshift(_);
29
+ else descriptor[key] = _;
30
+ }
31
+ }
32
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
33
+ done = true;
34
+ };
35
+ import { Encoder, Profile } from '@garmin/fitsdk';
36
+ import { EventLogger } from 'gd-eventlog';
37
+ import { useUserSettings } from '../../../../settings';
38
+ import { Injectable } from '../../../../base/decorators';
39
+ const DEG_TO_SEMICIRCLES = (2 ** 31) / 180;
40
+ let LocalFitConverter = (() => {
41
+ let _instanceExtraInitializers = [];
42
+ let _getUserSettings_decorators;
43
+ return class LocalFitConverter {
44
+ static {
45
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0;
46
+ _getUserSettings_decorators = [Injectable];
47
+ __esDecorate(this, null, _getUserSettings_decorators, { kind: "method", name: "getUserSettings", static: false, private: false, access: { has: obj => "getUserSettings" in obj, get: obj => obj.getUserSettings }, metadata: _metadata }, null, _instanceExtraInitializers);
48
+ if (_metadata) Object.defineProperty(this, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
49
+ }
50
+ logger = __runInitializers(this, _instanceExtraInitializers);
51
+ constructor() {
52
+ this.logger = new EventLogger('LocalFitExporter');
53
+ }
54
+ async convert(activity) {
55
+ try {
56
+ this.logger.logEvent({ message: 'convert start', format: 'FIT' });
57
+ const fitActivity = this.getFitActivity(activity);
58
+ const data = this.encode(fitActivity);
59
+ this.logger.logEvent({ message: 'convert success', format: 'FIT' });
60
+ return data;
61
+ }
62
+ catch (err) {
63
+ this.logger.logEvent({ message: 'convert result', format: 'FIT', result: 'error', reason: err.message });
64
+ throw err;
65
+ }
66
+ }
67
+ getFitActivity(activity) {
68
+ const { id, title, time, timeTotal, timePause, distance } = activity;
69
+ const status = 'created';
70
+ const startTime = new Date(activity.startTime).toISOString();
71
+ const logs = activity.logs.map(this.mapLogToFit.bind(this));
72
+ const screenshots = [];
73
+ const laps = this.mapLapsToFit(activity.laps ?? [], activity.startTime);
74
+ const user = {
75
+ id: this.getUserSettings().get('uuid', undefined),
76
+ weight: activity.user.weight
77
+ };
78
+ return { id, title, status, logs, laps, startTime, time, timeTotal, timePause, distance, user, screenshots };
79
+ }
80
+ mapLapsToFit(laps, activityStartTime) {
81
+ const activityStartMs = new Date(activityStartTime).getTime();
82
+ return laps.map((lap, i) => {
83
+ const prevDistance = i > 0 ? laps[i - 1].distance : 0;
84
+ return {
85
+ lapNo: lap.num,
86
+ startTime: new Date(lap.startTime).toISOString(),
87
+ stopTime: new Date(lap.startTime + lap.rideTime * 1000).toISOString(),
88
+ lapDistance: lap.distance - prevDistance,
89
+ totalDistance: lap.distance,
90
+ lapTime: lap.rideTime,
91
+ totalTime: (lap.startTime - activityStartMs) / 1000 + lap.rideTime,
92
+ };
93
+ });
94
+ }
95
+ mapLogToFit(log) {
96
+ const { time, speed, slope, cadence: cadenceOrg, heartrate: heartrateOrg, distance, power: powerOrg, lat, lng, elevation } = log;
97
+ const cadence = Math.round(cadenceOrg);
98
+ const heartrate = Math.round(heartrateOrg);
99
+ const power = Math.round(powerOrg);
100
+ return { time, speed, slope, cadence, heartrate, distance, power, lat, lon: lng, elevation };
101
+ }
102
+ encode(activity) {
103
+ const encoder = new Encoder();
104
+ const startTime = new Date(activity.startTime);
105
+ encoder.onMesg(Profile.MesgNum.FILE_ID, {
106
+ type: 'activity',
107
+ manufacturer: 'development',
108
+ product: 0,
109
+ timeCreated: startTime,
110
+ });
111
+ encoder.onMesg(Profile.MesgNum.EVENT, {
112
+ timestamp: startTime,
113
+ event: 'timer',
114
+ eventType: 'start',
115
+ data: 0,
116
+ });
117
+ let lastTimestampMs = startTime.getTime();
118
+ activity.logs.forEach((log) => {
119
+ if (log.time != null) {
120
+ lastTimestampMs = startTime.getTime() + log.time * 1000;
121
+ }
122
+ const record = {
123
+ timestamp: new Date(lastTimestampMs),
124
+ };
125
+ if (log.lat != null && log.lon != null) {
126
+ record.positionLat = Math.round(log.lat * DEG_TO_SEMICIRCLES);
127
+ record.positionLong = Math.round(log.lon * DEG_TO_SEMICIRCLES);
128
+ }
129
+ if (log.elevation != null)
130
+ record.altitude = log.elevation;
131
+ if (log.heartrate != null)
132
+ record.heartRate = log.heartrate;
133
+ if (log.cadence != null)
134
+ record.cadence = log.cadence;
135
+ if (log.distance != null)
136
+ record.distance = log.distance;
137
+ if (log.speed != null)
138
+ record.speed = log.speed / 3.6;
139
+ if (log.power != null)
140
+ record.power = log.power;
141
+ if (log.slope != null)
142
+ record.grade = log.slope;
143
+ encoder.onMesg(Profile.MesgNum.RECORD, record);
144
+ });
145
+ const endTime = new Date(lastTimestampMs);
146
+ encoder.onMesg(Profile.MesgNum.EVENT, {
147
+ timestamp: endTime,
148
+ event: 'timer',
149
+ eventType: 'stopAll',
150
+ data: 0,
151
+ });
152
+ if (activity.laps.length > 0) {
153
+ activity.laps.forEach(lap => {
154
+ const lapStart = new Date(lap.startTime);
155
+ const lapEnd = new Date(lap.stopTime);
156
+ encoder.onMesg(Profile.MesgNum.LAP, {
157
+ timestamp: lapEnd,
158
+ startTime: lapStart,
159
+ totalElapsedTime: lap.lapTime,
160
+ totalTimerTime: lap.lapTime,
161
+ totalDistance: lap.lapDistance,
162
+ event: 'lap',
163
+ eventType: 'stop',
164
+ });
165
+ });
166
+ }
167
+ else {
168
+ encoder.onMesg(Profile.MesgNum.LAP, {
169
+ timestamp: endTime,
170
+ startTime,
171
+ totalElapsedTime: activity.timeTotal,
172
+ totalTimerTime: activity.time,
173
+ totalDistance: activity.distance,
174
+ event: 'lap',
175
+ eventType: 'stop',
176
+ });
177
+ }
178
+ encoder.onMesg(Profile.MesgNum.SESSION, {
179
+ timestamp: endTime,
180
+ startTime,
181
+ totalElapsedTime: activity.timeTotal,
182
+ totalTimerTime: activity.time,
183
+ totalDistance: activity.distance,
184
+ sport: 'cycling',
185
+ subSport: 'virtualActivity',
186
+ event: 'session',
187
+ eventType: 'stopDisableAll',
188
+ });
189
+ encoder.onMesg(Profile.MesgNum.ACTIVITY, {
190
+ timestamp: endTime,
191
+ totalTimerTime: activity.time,
192
+ numSessions: 1,
193
+ type: 'manual',
194
+ event: 'activity',
195
+ eventType: 'stop',
196
+ });
197
+ const uint8Array = encoder.close();
198
+ return uint8Array.buffer;
199
+ }
200
+ getUserSettings() {
201
+ return useUserSettings();
202
+ }
203
+ };
204
+ })();
205
+ export { LocalFitConverter };
@@ -275,7 +275,7 @@ let RLVDisplayService = (() => {
275
275
  getUIText(input) {
276
276
  let text = input;
277
277
  if (text?.length) {
278
- text = text.replace(/\\n/g, '<br>');
278
+ text = text.replaceAll('\\n', '<br>');
279
279
  }
280
280
  return text;
281
281
  }
@@ -58,7 +58,7 @@ export class BinaryReader {
58
58
  const c = part.readUInt16LE(i * 2);
59
59
  if (c === 0)
60
60
  return str;
61
- str += String.fromCharCode(c);
61
+ str += String.fromCodePoint(c);
62
62
  i++;
63
63
  }
64
64
  return str;
@@ -3,7 +3,7 @@ export const formatDateTime = (date, fstr = '%Y%m%d%H%M%S', utc = false) => {
3
3
  if (!valid(date) || !(date instanceof Date))
4
4
  return;
5
5
  const prefix = utc ? 'getUTC' : 'get';
6
- return fstr.replace(/%[YmdHMS]/g, (m) => {
6
+ return fstr.replaceAll(/%[YmdHMS]/g, (m) => {
7
7
  switch (m) {
8
8
  case '%Y': return date[prefix + 'FullYear']();
9
9
  case '%m':
@@ -12,7 +12,8 @@ export const calculateDistance = (lat1, lon1, lat2, lon2, radius = rEarth * 1000
12
12
  const distance = abs(R * c);
13
13
  return distance;
14
14
  };
15
- export const distanceBetween = (p1, p2, props = { abs: true, latLng: true }) => {
15
+ const DEFAULT_DISTANCE_PROPS = { abs: true, latLng: true };
16
+ export const distanceBetween = (p1, p2, props = DEFAULT_DISTANCE_PROPS) => {
16
17
  if (p1 === undefined)
17
18
  return 0;
18
19
  let dist;
@@ -13,7 +13,7 @@ export const removeUTFBom = (str) => {
13
13
  if (!str || typeof (str) !== 'string') {
14
14
  return str;
15
15
  }
16
- while (str.charCodeAt(0) === 0 || str.charCodeAt(0) === 0xFEFF || str.charCodeAt(0) === 0xFFFD)
16
+ while (str.codePointAt(0) === 0 || str.codePointAt(0) === 0xFEFF || str.codePointAt(0) === 0xFFFD)
17
17
  str = str.substring(1);
18
18
  return str;
19
19
  };
@@ -210,7 +210,7 @@ export class ZwoParser {
210
210
  let tag;
211
211
  parser.parseString(data, (err, result) => {
212
212
  if (err) {
213
- err.message = 'File contains error(s): ' + err.message.replace(/\n/g, ' ');
213
+ err.message = 'File contains error(s): ' + err.message.replaceAll('\n', ' ');
214
214
  this.logger.logEvent({ message: 'error', fn: 'parse()', error: err.message, stack: err.stack });
215
215
  return reject(err);
216
216
  }
@@ -1 +1,2 @@
1
1
  export * from './remote-fit';
2
+ export * from './local-fit';
@@ -0,0 +1,12 @@
1
+ import { EventLogger } from 'gd-eventlog';
2
+ import { ActivityDetails, ActivityLogRecord, FitExportActivity, FitLapEntry, FitLogEntry, LapSummary } from '../../model';
3
+ export declare class LocalFitConverter {
4
+ protected logger: EventLogger;
5
+ constructor();
6
+ convert(activity: ActivityDetails): Promise<ArrayBuffer>;
7
+ protected getFitActivity(activity: ActivityDetails): FitExportActivity;
8
+ protected mapLapsToFit(laps: LapSummary[], activityStartTime: string): FitLapEntry[];
9
+ protected mapLogToFit(log: ActivityLogRecord): FitLogEntry;
10
+ protected encode(activity: FitExportActivity): ArrayBuffer;
11
+ protected getUserSettings(): import("../../../../settings").UserSettingsService;
12
+ }
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "incyclist-services",
3
- "version": "1.7.49-beta",
3
+ "version": "1.7.49",
4
4
  "peerDependencies": {
5
5
  "gd-eventlog": "^0.1.27"
6
6
  },
7
7
  "dependencies": {
8
+ "@garmin/fitsdk": "^21.200.0",
8
9
  "axios": "^1.15.0",
9
10
  "incyclist-devices": "^3.0.12",
10
11
  "promise.any": "^2.0.6",