incyclist-services 1.3.15 → 1.3.17

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.
@@ -54,6 +54,7 @@ export type ActivitySummary = {
54
54
  title: string;
55
55
  name: string;
56
56
  routeId: string;
57
+ routeHash: string;
57
58
  previewImage?: string;
58
59
  startTime: number;
59
60
  rideTime: number;
@@ -153,7 +154,7 @@ export type ActivityLogRecord = {
153
154
  timeDelta: number;
154
155
  speed: number;
155
156
  slope?: number;
156
- cadence: number;
157
+ cadence?: number;
157
158
  heartrate?: number;
158
159
  distance?: number;
159
160
  power: number;
@@ -31,6 +31,7 @@ export declare class ActivitiesRepository {
31
31
  protected loadSummaries(): Promise<void>;
32
32
  protected buildFromLegacy(): Promise<void>;
33
33
  protected listActivities(): Promise<Array<string>>;
34
+ protected fixMissingHash(): Promise<void>;
34
35
  protected loadDetailsByName(name: string): Promise<ActivityDetails>;
35
36
  protected loadDetails(name: string, id?: string): Promise<void>;
36
37
  protected bulkAddActivities(names: Array<string>): Promise<void>;
@@ -148,6 +148,9 @@ let ActivitiesRepository = (() => {
148
148
  if ((result === null || result === void 0 ? void 0 : result.length) > 0 && (criteria === null || criteria === void 0 ? void 0 : criteria.routeId)) {
149
149
  result = result.filter(ai => ai.summary.routeId === criteria.routeId);
150
150
  }
151
+ if ((result === null || result === void 0 ? void 0 : result.length) > 0 && (criteria === null || criteria === void 0 ? void 0 : criteria.routeHash)) {
152
+ result = result.filter(ai => ai.summary.routeHash === criteria.routeHash);
153
+ }
151
154
  if ((result === null || result === void 0 ? void 0 : result.length) > 0 && (criteria === null || criteria === void 0 ? void 0 : criteria.startPos) !== undefined) {
152
155
  result = result.filter(ai => ai.summary.startPos === criteria.startPos);
153
156
  }
@@ -245,6 +248,7 @@ let ActivitiesRepository = (() => {
245
248
  if (cnt !== this.activities.length) {
246
249
  yield this.write(true);
247
250
  }
251
+ yield this.fixMissingHash();
248
252
  return;
249
253
  }
250
254
  yield this.buildFromLegacy();
@@ -266,6 +270,26 @@ let ActivitiesRepository = (() => {
266
270
  return this.getRepo().list(['db']);
267
271
  });
268
272
  }
273
+ fixMissingHash() {
274
+ return __awaiter(this, void 0, void 0, function* () {
275
+ try {
276
+ const target = this.activities.filter(ai => ai.summary && ai.summary.routeHash === undefined);
277
+ if (!target || target.length === 0) {
278
+ return;
279
+ }
280
+ this.logger.logEvent({ message: 'fixing missing hashes in activities', cnt: target.length });
281
+ const promises = [];
282
+ target.forEach(ai => {
283
+ promises.push(this.loadDetails(ai.summary.name).then(() => { var _a, _b; return ai.summary.routeHash = (_b = (_a = ai.details) === null || _a === void 0 ? void 0 : _a.route) === null || _b === void 0 ? void 0 : _b.hash; }));
284
+ });
285
+ yield Promise.allSettled(promises);
286
+ yield this.write(true);
287
+ }
288
+ catch (err) {
289
+ this.logError(err, 'fixMissingHash');
290
+ }
291
+ });
292
+ }
269
293
  loadDetailsByName(name) {
270
294
  return __awaiter(this, void 0, void 0, function* () {
271
295
  let details;
@@ -1,6 +1,7 @@
1
1
  import { UploadInfo } from "../model";
2
2
  export interface ActivitySearchCriteria {
3
3
  routeId?: string;
4
+ routeHash?: string;
4
5
  startPos?: number;
5
6
  realityFactor?: number;
6
7
  uploadStatus?: UploadInfo | Array<UploadInfo>;
@@ -6,6 +6,7 @@ const buildSummary = (activity, proposedName) => {
6
6
  const { id, title, route, screenshots, startTime: startTimeUTC, time: rideTime, distance, startPos, realityFactor = 100, links, laps } = activity;
7
7
  const name = proposedName !== null && proposedName !== void 0 ? proposedName : activity.name;
8
8
  const routeId = route === null || route === void 0 ? void 0 : route.id;
9
+ const routeHash = route === null || route === void 0 ? void 0 : route.hash;
9
10
  const shots = screenshots !== null && screenshots !== void 0 ? screenshots : [];
10
11
  const preview = (_a = shots.find(s => s.isHighlight)) !== null && _a !== void 0 ? _a : shots[0];
11
12
  const previewImage = preview === null || preview === void 0 ? void 0 : preview.fileName;
@@ -17,7 +18,7 @@ const buildSummary = (activity, proposedName) => {
17
18
  uploadStatus.push({ service: 'strava', status: 'success' });
18
19
  }
19
20
  return {
20
- id, title, name, routeId, previewImage, startTime, rideTime, distance, startPos, realityFactor, uploadStatus, laps
21
+ id, title, name, routeId, routeHash, previewImage, startTime, rideTime, distance, startPos, realityFactor, uploadStatus, laps
21
22
  };
22
23
  };
23
24
  exports.buildSummary = buildSummary;
@@ -1 +1,2 @@
1
1
  export * from './service';
2
+ export * from './types';
@@ -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("./service"), exports);
18
+ __exportStar(require("./types"), exports);
@@ -1,6 +1,6 @@
1
1
  import { IncyclistService } from "../../base/service";
2
2
  import { Observer, PromiseObserver } from "../../base/types/observer";
3
- import { ActivitiesRepository } from "../base";
3
+ import { ActivitiesRepository, ActivitySearchCriteria } from "../base";
4
4
  import { ActivityInfo } from "../base/model";
5
5
  export declare class ActivityListService extends IncyclistService {
6
6
  protected preloadObserver: PromiseObserver<void>;
@@ -9,6 +9,9 @@ export declare class ActivityListService extends IncyclistService {
9
9
  protected observer: Observer;
10
10
  constructor();
11
11
  preload(): PromiseObserver<void>;
12
+ getPastActivities(filter: ActivitySearchCriteria, props?: {
13
+ details: boolean;
14
+ }): Array<ActivityInfo>;
12
15
  protected loadActivities(): Promise<void>;
13
16
  protected getRepo(): ActivitiesRepository;
14
17
  protected get activities(): Array<ActivityInfo>;
@@ -92,6 +92,13 @@ let ActivityListService = (() => {
92
92
  }
93
93
  return this.preloadObserver;
94
94
  }
95
+ getPastActivities(filter, props) {
96
+ const activities = this.getRepo().search(filter);
97
+ if (props === null || props === void 0 ? void 0 : props.details) {
98
+ activities.forEach(ai => this.getRepo().getWithDetails(ai.summary.id));
99
+ }
100
+ return activities;
101
+ }
95
102
  loadActivities() {
96
103
  return __awaiter(this, void 0, void 0, function* () {
97
104
  return new Promise(done => {
@@ -0,0 +1,6 @@
1
+ export type PastActivityFilter = {
2
+ routeHash: string;
3
+ startPos: number;
4
+ realityFactor: number;
5
+ minDistance?: number;
6
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,23 @@
1
+ import { Avatar } from "../../avatars";
2
+ export type PastActivityLogEntry = {
3
+ routeHash?: string;
4
+ routeId?: string;
5
+ tsStart: number;
6
+ title: string;
7
+ time?: number;
8
+ power?: number;
9
+ heartrate?: number;
10
+ speed?: number;
11
+ distance?: number;
12
+ routeDistance?: number;
13
+ lap?: number;
14
+ timeGap: string;
15
+ distanceGap: string;
16
+ lat?: number;
17
+ lng?: number;
18
+ };
19
+ export type PastActivityInfo = Array<PastActivityLogEntry | null>;
20
+ export interface PrevRidesListDisplayProps extends PastActivityLogEntry {
21
+ position?: number;
22
+ avatar?: Avatar;
23
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -3,14 +3,16 @@ import { IncyclistService } from "../../base/service";
3
3
  import { Observer, PromiseObserver } from "../../base/types/observer";
4
4
  import { DeviceData } from "incyclist-devices";
5
5
  import { ExtendedIncyclistCapability, HealthStatus } from "../../devices";
6
- import { ActivitiesRepository, ActivityConverterFactory, ActivityDetails, ActivityLogRecord, ActivityRouteType, ScreenShotInfo } from "../base";
6
+ import { ActivitiesRepository, ActivityConverterFactory, ActivityDetails, ActivityInfo, ActivityLogRecord, ActivityRouteType, ScreenShotInfo } from "../base";
7
7
  import { FreeRideStartSettings } from "../../routes/list/types";
8
+ import { RouteSettings } from "../../routes/list/cards/RouteCard";
8
9
  import { RoutePoint } from "../../routes/base/types";
9
10
  import { ActivityState } from "./types";
10
11
  import { Route } from "../../routes/base/model/route";
11
12
  import { ActivityStatsCalculator } from "./stats";
12
13
  import { ActivityDuration } from "./duration";
13
14
  import { ActivityUploadFactory } from "../upload";
15
+ import { PastActivityInfo, PastActivityLogEntry, PrevRidesListDisplayProps } from "../list";
14
16
  export declare class ActivityRideService extends IncyclistService {
15
17
  protected observer: Observer;
16
18
  protected activity: ActivityDetails;
@@ -35,7 +37,7 @@ export declare class ActivityRideService extends IncyclistService {
35
37
  protected current: {
36
38
  route?: Route;
37
39
  position?: RoutePoint;
38
- deviceData: DeviceData;
40
+ deviceData?: DeviceData;
39
41
  endPos?: number;
40
42
  tsDeviceData?: number;
41
43
  tsUpdate?: number;
@@ -45,7 +47,10 @@ export declare class ActivityRideService extends IncyclistService {
45
47
  elevationGainDisplay?: number;
46
48
  elevation?: number;
47
49
  isAutoResume?: boolean;
50
+ showPrev?: boolean;
48
51
  dataState: Record<string, HealthStatus>;
52
+ prevRides?: Array<ActivityInfo>;
53
+ prevRidesLogs?: PastActivityInfo;
49
54
  };
50
55
  constructor();
51
56
  init(id?: string): Observer;
@@ -63,8 +68,13 @@ export declare class ActivityRideService extends IncyclistService {
63
68
  getObserver(): Observer;
64
69
  addSceenshot(screenshot: ScreenShotInfo): void;
65
70
  onRouteUpdate(points: Array<RoutePoint>): void;
71
+ getPrevRideStats(activities: Array<ActivityInfo>, current: ActivityLogRecord): PastActivityInfo;
72
+ getPrevRidesListDisplay(maxEntries?: number): Array<PrevRidesListDisplayProps>;
73
+ protected getPrevActivityLog(ai: ActivityInfo, current: ActivityLogRecord): PastActivityLogEntry | null;
74
+ protected getCurrentActivityLog(ai: ActivityInfo, current: ActivityLogRecord): PastActivityLogEntry | null;
66
75
  protected onPositionUpdate(position: RoutePoint): void;
67
76
  protected onDeviceData(data: DeviceData): void;
77
+ protected initPrevActivities(settings: RouteSettings): void;
68
78
  protected onDeviceHealthUpdate(udid: string, status: HealthStatus, capabilities: Array<ExtendedIncyclistCapability>): void;
69
79
  emit(eventName: string | symbol, ...args: any[]): boolean;
70
80
  protected _save(): Promise<void>;
@@ -46,6 +46,9 @@ var __setFunctionName = (this && this.__setFunctionName) || function (f, name, p
46
46
  if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
47
47
  return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
48
48
  };
49
+ var __importDefault = (this && this.__importDefault) || function (mod) {
50
+ return (mod && mod.__esModule) ? mod : { "default": mod };
51
+ };
49
52
  Object.defineProperty(exports, "__esModule", { value: true });
50
53
  exports.useActivityRide = exports.ActivityRideService = void 0;
51
54
  const service_1 = require("../../base/service");
@@ -65,6 +68,9 @@ const stats_1 = require("./stats");
65
68
  const duration_1 = require("./duration");
66
69
  const upload_1 = require("../upload");
67
70
  const api_1 = require("../../api");
71
+ const list_1 = require("../list");
72
+ const avatars_1 = require("../../avatars");
73
+ const clone_1 = __importDefault(require("../../utils/clone"));
68
74
  const SAVE_INTERVAL = 5000;
69
75
  let ActivityRideService = (() => {
70
76
  let _classDecorators = [types_1.Singleton];
@@ -77,6 +83,7 @@ let ActivityRideService = (() => {
77
83
  super('ActivityRide');
78
84
  this.state = 'idle';
79
85
  this.deviceDataHandler = this.onDeviceData.bind(this);
86
+ this.current = { dataState: {} };
80
87
  }
81
88
  init(id) {
82
89
  let isClean = true;
@@ -101,6 +108,13 @@ let ActivityRideService = (() => {
101
108
  }));
102
109
  }
103
110
  this.activity = this.createActivity(id);
111
+ const settings = (0, routes_1.useRouteList)().getStartSettings();
112
+ if ((settings === null || settings === void 0 ? void 0 : settings.type) === 'Route') {
113
+ const rs = settings;
114
+ if (rs.showPrev) {
115
+ this.initPrevActivities(rs);
116
+ }
117
+ }
104
118
  this.durationCalculator = new duration_1.ActivityDuration(this.activity);
105
119
  this.getDeviceRide().on('data', this.deviceDataHandler);
106
120
  return observer;
@@ -337,6 +351,150 @@ let ActivityRideService = (() => {
337
351
  this.current.route.details.distance = points[points.length - 1].routeDistance;
338
352
  (0, route_1.addDetails)(this.current.route, this.current.route.details);
339
353
  }
354
+ getPrevRideStats(activities, current) {
355
+ var _a;
356
+ if (!(activities === null || activities === void 0 ? void 0 : activities.length))
357
+ return [];
358
+ const logs = (_a = activities.map(ai => { return this.getPrevActivityLog(ai, current); })) === null || _a === void 0 ? void 0 : _a.filter(a => a !== null);
359
+ if (logs.length === 0)
360
+ return logs;
361
+ logs.push(this.getCurrentActivityLog(activities[0], current));
362
+ const sorted = logs.sort((a, b) => b.distance - a.distance);
363
+ this.current.prevRidesLogs = sorted;
364
+ console.log('~~~ PREV RIDES', activities.map(a => `${a.summary.name}:${a.summary.startTime}`), sorted);
365
+ return sorted;
366
+ }
367
+ getPrevRidesListDisplay(maxEntries = 12) {
368
+ const prevRides = this.current.prevRidesLogs;
369
+ if (!(prevRides === null || prevRides === void 0 ? void 0 : prevRides.length))
370
+ return [];
371
+ const sorted = prevRides.filter(a => a !== null).sort((a, b) => b.routeDistance - a.routeDistance);
372
+ const props = sorted.map((a, idx) => {
373
+ const position = idx + 1;
374
+ const key = a.title === 'current' ? 'current' : `${a.tsStart}`;
375
+ const avatar = (0, avatars_1.useAvatars)().get(key);
376
+ return Object.assign({ position, avatar }, a);
377
+ });
378
+ if (props.length > maxEntries) {
379
+ const len = props.length;
380
+ const currentIdx = props.findIndex(a => a.title === 'current');
381
+ if (currentIdx < maxEntries - 1) {
382
+ let deleted = props.splice(maxEntries - 1);
383
+ props.push({ title: `+${deleted.length}`, tsStart: null, distanceGap: '', timeGap: '' });
384
+ }
385
+ else if (currentIdx > maxEntries - 1) {
386
+ if (currentIdx < props.length - 2) {
387
+ const deleted = props.splice(currentIdx + 1);
388
+ props.splice(maxEntries - 2, currentIdx - (maxEntries - 2));
389
+ props.push({ title: `+${deleted.length}`, tsStart: null, distanceGap: '', timeGap: '' });
390
+ }
391
+ else if (currentIdx === props.length - 2) {
392
+ props.splice(maxEntries - 2, props.length - maxEntries);
393
+ }
394
+ else {
395
+ const keep = maxEntries - 1;
396
+ const cut = props.length - maxEntries;
397
+ props.splice(keep, cut);
398
+ }
399
+ }
400
+ else if (currentIdx === props.length - 2) {
401
+ props.splice(maxEntries - 2, props.length - maxEntries);
402
+ }
403
+ else if (currentIdx === maxEntries - 1) {
404
+ let deleted = props.splice(currentIdx + 1, props.length - currentIdx);
405
+ props.push({ title: `+${deleted.length}`, tsStart: null, distanceGap: '', timeGap: '' });
406
+ props.splice(currentIdx - 1, 1);
407
+ }
408
+ }
409
+ return props;
410
+ }
411
+ getPrevActivityLog(ai, current) {
412
+ var _a, _b, _c;
413
+ try {
414
+ if (!ai.details) {
415
+ return null;
416
+ }
417
+ const logs = ai.details.logs;
418
+ const totalDistance = ai.summary.distance;
419
+ if (current.distance > totalDistance)
420
+ return null;
421
+ let prev = undefined;
422
+ let res = (0, clone_1.default)(logs.find((log, idx) => {
423
+ if (log.time >= current.time) {
424
+ if (idx > 0)
425
+ prev = logs[idx - 1];
426
+ return true;
427
+ }
428
+ return false;
429
+ }));
430
+ if (!res) {
431
+ res = (0, clone_1.default)(logs[logs.length - 1]);
432
+ }
433
+ else {
434
+ if (Math.abs(res.time - current.time) > 0.1) {
435
+ const t = res.time - current.time;
436
+ const v = res.speed / 3.6;
437
+ res.distance -= (v * t);
438
+ }
439
+ }
440
+ const distanceDelta = current.distance - res.distance;
441
+ const { power, heartrate, distance, speed } = res;
442
+ const routeDistance = res.distance + ai.summary.startPos;
443
+ let lat, lng;
444
+ if ((_c = (_b = (_a = this.current) === null || _a === void 0 ? void 0 : _a.route) === null || _b === void 0 ? void 0 : _b.description) === null || _c === void 0 ? void 0 : _c.hasGpx) {
445
+ const point = (0, route_1.getPosition)(this.current.route, { distance: routeDistance, nearest: true });
446
+ lat = point === null || point === void 0 ? void 0 : point.lat;
447
+ lng = point === null || point === void 0 ? void 0 : point.lng;
448
+ }
449
+ let prefix = Math.sign(distanceDelta) > 0 ? '+' : '';
450
+ const distanceGap = prefix + (Math.abs(distanceDelta) > 1000 ? `${(distanceDelta / 1000).toFixed(1)}km` : `${distanceDelta.toFixed(0)}m`);
451
+ prev = undefined;
452
+ res = (0, clone_1.default)(logs.find((log, idx) => {
453
+ if (log.distance >= current.distance) {
454
+ if (idx > 0)
455
+ prev = logs[idx - 1];
456
+ return true;
457
+ }
458
+ return false;
459
+ }));
460
+ const s = res.distance - current.distance;
461
+ const v = res.speed / 3.6;
462
+ const t = v === 0 ? Infinity : s / v;
463
+ res.time -= t;
464
+ const timeDelta = res.time - current.time;
465
+ prefix = Math.sign(timeDelta) > 0 ? '+' : '-';
466
+ const timeGap = prefix + (Math.abs(timeDelta) < 60 ? `${Math.abs(timeDelta).toFixed(1)}s` : (0, utils_1.formatTime)(Math.abs(timeDelta), true));
467
+ const routeHash = ai.summary.routeHash;
468
+ const routeId = ai.summary.routeId;
469
+ const tsStart = ai.summary.startTime;
470
+ const title = new Date(ai.summary.startTime).toLocaleDateString();
471
+ const calculate = { time: current.time, routeHash, routeId, tsStart, title, power, heartrate, speed, distance, timeGap, distanceGap, routeDistance, lat, lng };
472
+ return calculate;
473
+ }
474
+ catch (err) {
475
+ this.logError(err, 'getActivityLog');
476
+ return null;
477
+ }
478
+ }
479
+ getCurrentActivityLog(ai, current) {
480
+ try {
481
+ const timeGap = '';
482
+ const distanceGap = '';
483
+ const routeHash = ai.summary.routeHash;
484
+ const routeId = ai.summary.routeId;
485
+ const tsStart = ai.summary.startTime;
486
+ const title = 'current';
487
+ const routeDistance = current.distance;
488
+ const { power, heartrate, distance, speed, lat, lng } = current;
489
+ const calculate = { routeHash, routeId, tsStart, title, power, heartrate, speed, distance, timeGap, distanceGap, lat, lng };
490
+ calculate.routeDistance = calculate.distance + ai.summary.startPos;
491
+ return calculate;
492
+ }
493
+ catch (err) {
494
+ this.logError(err, 'getCurrentActivityLog');
495
+ return null;
496
+ }
497
+ }
340
498
  onPositionUpdate(position) {
341
499
  this.current.position = position;
342
500
  }
@@ -370,6 +528,24 @@ let ActivityRideService = (() => {
370
528
  this.logError(err, 'onDeviceData');
371
529
  }
372
530
  }
531
+ initPrevActivities(settings) {
532
+ var _a;
533
+ try {
534
+ if (!this.current || !((_a = this.activity) === null || _a === void 0 ? void 0 : _a.route))
535
+ return;
536
+ this.current.showPrev = settings.showPrev;
537
+ const { startPos, realityFactor } = settings;
538
+ const routeId = this.activity.route.id;
539
+ const routeHash = this.activity.route.hash;
540
+ const filter = { routeId, routeHash, startPos, realityFactor };
541
+ const prevRides = (0, list_1.useActivityList)().getPastActivities(filter, { details: true })
542
+ .filter(a => a.summary.rideTime > 60);
543
+ this.current.prevRides = prevRides;
544
+ }
545
+ catch (err) {
546
+ this.logError(err, 'initPrevActivities');
547
+ }
548
+ }
373
549
  onDeviceHealthUpdate(udid, status, capabilities) {
374
550
  if (!this.current)
375
551
  return;
@@ -659,8 +835,6 @@ let ActivityRideService = (() => {
659
835
  routeDistance: this.current.routeDistance,
660
836
  distance
661
837
  };
662
- this.emit('data', data);
663
- this.prevEmit = data;
664
838
  const logRecord = this.createLogRecord();
665
839
  if (logRecord) {
666
840
  this.activity.logs.push(logRecord);
@@ -668,6 +842,13 @@ let ActivityRideService = (() => {
668
842
  }
669
843
  this.activity.distance = this.current.routeDistance - this.activity.startPos;
670
844
  this.activity.totalElevation = this.current.elevationGain;
845
+ let list;
846
+ if (this.current.showPrev) {
847
+ list = this.getPrevRideStats(this.current.prevRides, logRecord);
848
+ this.emit('prevRides', list);
849
+ }
850
+ this.emit('data', data, list);
851
+ this.prevEmit = data;
671
852
  this.logActivityUpdateMessage();
672
853
  this.updateRepo();
673
854
  }
@@ -0,0 +1,2 @@
1
+ export * from './service';
2
+ export * from './types';
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./service"), exports);
18
+ __exportStar(require("./types"), exports);
@@ -0,0 +1,17 @@
1
+ import { IncyclistService } from "../base/service";
2
+ import { Avatar, AvatarOption } from "./types";
3
+ export declare class AvatarService extends IncyclistService {
4
+ protected options: Array<AvatarOption>;
5
+ protected registered: Record<string, Avatar>;
6
+ protected currentBucket: number;
7
+ constructor();
8
+ setCurrentRiderAvatar(avatar: Avatar): void;
9
+ get(id: string): Avatar;
10
+ delete(id: string): void;
11
+ deleteAll(): void;
12
+ protected buildOptions(): void;
13
+ protected getNextAvailable(): Avatar;
14
+ protected cleanup(id: string, isCurrent?: boolean): void;
15
+ protected find(avatar: any): AvatarOption;
16
+ }
17
+ export declare const useAvatars: () => AvatarService;
@@ -0,0 +1,139 @@
1
+ "use strict";
2
+ var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
3
+ function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
4
+ var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
5
+ var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
6
+ var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
7
+ var _, done = false;
8
+ for (var i = decorators.length - 1; i >= 0; i--) {
9
+ var context = {};
10
+ for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
11
+ for (var p in contextIn.access) context.access[p] = contextIn.access[p];
12
+ context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
13
+ var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
14
+ if (kind === "accessor") {
15
+ if (result === void 0) continue;
16
+ if (result === null || typeof result !== "object") throw new TypeError("Object expected");
17
+ if (_ = accept(result.get)) descriptor.get = _;
18
+ if (_ = accept(result.set)) descriptor.set = _;
19
+ if (_ = accept(result.init)) initializers.unshift(_);
20
+ }
21
+ else if (_ = accept(result)) {
22
+ if (kind === "field") initializers.unshift(_);
23
+ else descriptor[key] = _;
24
+ }
25
+ }
26
+ if (target) Object.defineProperty(target, contextIn.name, descriptor);
27
+ done = true;
28
+ };
29
+ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
30
+ var useValue = arguments.length > 2;
31
+ for (var i = 0; i < initializers.length; i++) {
32
+ value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
33
+ }
34
+ return useValue ? value : void 0;
35
+ };
36
+ var __setFunctionName = (this && this.__setFunctionName) || function (f, name, prefix) {
37
+ if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
38
+ return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
39
+ };
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.useAvatars = exports.AvatarService = void 0;
42
+ const service_1 = require("../base/service");
43
+ const types_1 = require("../base/types");
44
+ let AvatarService = (() => {
45
+ let _classDecorators = [types_1.Singleton];
46
+ let _classDescriptor;
47
+ let _classExtraInitializers = [];
48
+ let _classThis;
49
+ let _classSuper = service_1.IncyclistService;
50
+ var AvatarService = _classThis = class extends _classSuper {
51
+ constructor() {
52
+ super('Avatars');
53
+ this.options = [];
54
+ this.registered = {};
55
+ this.currentBucket = 0;
56
+ this.buildOptions();
57
+ }
58
+ setCurrentRiderAvatar(avatar) {
59
+ this.cleanup('current', true);
60
+ let currentOption = this.find(avatar);
61
+ if (!currentOption) {
62
+ currentOption = { avatar, usageCnt: 0 };
63
+ this.options.push(currentOption);
64
+ }
65
+ currentOption.usageCnt++;
66
+ return;
67
+ }
68
+ get(id) {
69
+ if (!this.registered[id])
70
+ this.registered[id] = this.getNextAvailable();
71
+ return this.registered[id];
72
+ }
73
+ delete(id) {
74
+ this.cleanup(id);
75
+ }
76
+ deleteAll() {
77
+ const currentAvatar = this.registered.current;
78
+ this.options.forEach(o => { o.usageCnt = 0; });
79
+ this.currentBucket = 0;
80
+ const currentOption = this.find(currentAvatar);
81
+ currentOption.usageCnt = 1;
82
+ this.registered = { current: currentAvatar };
83
+ }
84
+ buildOptions() {
85
+ var _a;
86
+ if (((_a = this.options) === null || _a === void 0 ? void 0 : _a.length) > 0)
87
+ return;
88
+ this.options = [];
89
+ const colors = ['yellow', 'red', 'blue', 'green', 'purple', 'orange', 'hotpink', 'brown', 'grey', 'black', 'white', 'violet', 'lime', 'olive', 'cyan', 'skyblue'];
90
+ colors.forEach(helmet => {
91
+ colors.forEach(shirt => {
92
+ this.options.push({ avatar: { helmet, shirt }, usageCnt: 0 });
93
+ });
94
+ });
95
+ }
96
+ getNextAvailable() {
97
+ let available = this.options.find(a => a.usageCnt === this.currentBucket);
98
+ if (!available) {
99
+ this.currentBucket++;
100
+ const currentOption = this.find('current');
101
+ currentOption.usageCnt++;
102
+ available = this.options.find(a => a.usageCnt === this.currentBucket);
103
+ }
104
+ available.usageCnt++;
105
+ return available === null || available === void 0 ? void 0 : available.avatar;
106
+ }
107
+ cleanup(id, isCurrent = false) {
108
+ if (this.registered[id]) {
109
+ const prev = this.find(this.registered[id]);
110
+ if (prev) {
111
+ if (isCurrent)
112
+ prev.usageCnt = 0;
113
+ else
114
+ prev.usageCnt--;
115
+ }
116
+ delete this.registered[id];
117
+ }
118
+ const currentAvatar = this.registered.current;
119
+ const inUse = this.options.filter(a => !currentAvatar || (a.avatar.helmet !== currentAvatar.helmet && a.avatar.shirt !== currentAvatar.shirt)).sort((a, b) => b.usageCnt - a.usageCnt);
120
+ this.currentBucket = inUse[0].usageCnt;
121
+ }
122
+ find(avatar) {
123
+ return this.options.find(a => a.avatar.helmet === avatar.helmet && a.avatar.shirt === avatar.shirt);
124
+ }
125
+ };
126
+ __setFunctionName(_classThis, "AvatarService");
127
+ (() => {
128
+ var _a;
129
+ const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create((_a = _classSuper[Symbol.metadata]) !== null && _a !== void 0 ? _a : null) : void 0;
130
+ __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
131
+ AvatarService = _classThis = _classDescriptor.value;
132
+ if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
133
+ __runInitializers(_classThis, _classExtraInitializers);
134
+ })();
135
+ return AvatarService = _classThis;
136
+ })();
137
+ exports.AvatarService = AvatarService;
138
+ const useAvatars = () => new AvatarService();
139
+ exports.useAvatars = useAvatars;
@@ -0,0 +1,9 @@
1
+ export type Color = 'yellow' | 'red' | 'blue' | 'green' | 'purple' | 'orange' | 'hotpink' | 'brown' | 'grey' | 'black' | 'white' | 'violet' | 'lime' | 'olive' | 'cyan' | 'skyblue';
2
+ export type Avatar = {
3
+ helmet: Color;
4
+ shirt: Color;
5
+ };
6
+ export type AvatarOption = {
7
+ avatar: Avatar;
8
+ usageCnt: number;
9
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/lib/index.d.ts CHANGED
@@ -4,5 +4,6 @@ export * from './api';
4
4
  export * from './routes';
5
5
  export * from './workouts';
6
6
  export * from './coaches';
7
+ export * from './avatars';
7
8
  export * from './activities';
8
9
  export { useUserSettings, initUserSettings, UserSettingsService, UserSettingsBinding, IUserSettingsBinding } from './settings';
package/lib/index.js CHANGED
@@ -21,6 +21,7 @@ __exportStar(require("./api"), exports);
21
21
  __exportStar(require("./routes"), exports);
22
22
  __exportStar(require("./workouts"), exports);
23
23
  __exportStar(require("./coaches"), exports);
24
+ __exportStar(require("./avatars"), exports);
24
25
  __exportStar(require("./activities"), exports);
25
26
  var settings_1 = require("./settings");
26
27
  Object.defineProperty(exports, "useUserSettings", { enumerable: true, get: function () { return settings_1.useUserSettings; } });
@@ -31,6 +31,7 @@ export interface StartSettings {
31
31
  convertProgress?: number;
32
32
  loopOverwrite?: boolean;
33
33
  nextOverwrite?: boolean;
34
+ showPrev?: boolean;
34
35
  }
35
36
  export type RouteSettings = StartSettings & RouteStartSettings;
36
37
  export type RouteCardProps = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "incyclist-services",
3
- "version": "1.3.15",
3
+ "version": "1.3.17",
4
4
  "peerDependencies": {
5
5
  "gd-eventlog": "^0.1.26"
6
6
  },