hevy-shared 1.0.960 → 1.0.962
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/.eslintignore +2 -0
- package/.eslintrc +21 -0
- package/.github/workflows/ci.yml +15 -0
- package/.github/workflows/npm-publish.yml +59 -0
- package/.github/workflows/pr-auto-assign.yml +15 -0
- package/.prettierrc.js +5 -0
- package/README.md +2 -17
- package/built/chat.d.ts +23 -25
- package/built/coachPlans.d.ts +1 -2
- package/built/coachPlans.js +2 -2
- package/built/filterExercises.d.ts +3 -19
- package/built/filterExercises.js +60 -72
- package/built/index.d.ts +304 -1140
- package/built/index.js +75 -269
- package/built/setIndicatorUtils.d.ts +3 -4
- package/built/setIndicatorUtils.js +1 -15
- package/built/tests/utils.test.js +0 -748
- package/built/tests/workoutVolume.test.js +49 -165
- package/built/units.d.ts +7 -14
- package/built/units.js +14 -24
- package/built/utils.d.ts +5 -192
- package/built/utils.js +85 -598
- package/built/websocket.d.ts +2 -14
- package/built/workoutVolume.d.ts +5 -24
- package/built/workoutVolume.js +34 -25
- package/jest.config.js +4 -0
- package/package.json +10 -32
- package/src/chat.ts +130 -0
- package/src/coachPlans.ts +57 -0
- package/src/constants.ts +14 -0
- package/src/filterExercises.ts +222 -0
- package/src/index.ts +1576 -0
- package/src/setIndicatorUtils.ts +137 -0
- package/src/tests/utils.test.ts +156 -0
- package/src/tests/workoutVolume.test.ts +93 -0
- package/src/units.ts +41 -0
- package/src/utils.ts +516 -0
- package/src/websocket.ts +36 -0
- package/src/workoutVolume.ts +175 -0
- package/tsconfig.json +70 -0
- package/built/API/APIClient.d.ts +0 -157
- package/built/API/APIClient.js +0 -381
- package/built/API/index.d.ts +0 -2
- package/built/API/index.js +0 -18
- package/built/API/types.d.ts +0 -38
- package/built/API/types.js +0 -18
- package/built/adjustEventTokens.d.ts +0 -16
- package/built/adjustEventTokens.js +0 -18
- package/built/adminPermissions.d.ts +0 -4
- package/built/adminPermissions.js +0 -22
- package/built/async.d.ts +0 -50
- package/built/async.js +0 -170
- package/built/cue.d.ts +0 -12
- package/built/cue.js +0 -22
- package/built/exerciseLocaleUtils.d.ts +0 -17
- package/built/exerciseLocaleUtils.js +0 -62
- package/built/hevyTrainer.d.ts +0 -250
- package/built/hevyTrainer.js +0 -676
- package/built/muscleHeatmaps.d.ts +0 -31
- package/built/muscleHeatmaps.js +0 -68
- package/built/muscleSplits.d.ts +0 -36
- package/built/muscleSplits.js +0 -100
- package/built/normalizedWorkoutUtils.d.ts +0 -88
- package/built/normalizedWorkoutUtils.js +0 -112
- package/built/notifications.d.ts +0 -215
- package/built/notifications.js +0 -9
- package/built/routineUtils.d.ts +0 -14
- package/built/routineUtils.js +0 -186
- package/built/schemas.d.ts +0 -6
- package/built/schemas.js +0 -12
- package/built/tests/async.test.d.ts +0 -1
- package/built/tests/async.test.js +0 -49
- package/built/tests/hevyTrainer.test.d.ts +0 -1
- package/built/tests/hevyTrainer.test.js +0 -1199
- package/built/tests/muscleSplit.test.d.ts +0 -1
- package/built/tests/muscleSplit.test.js +0 -153
- package/built/tests/routineUtils.test.d.ts +0 -1
- package/built/tests/routineUtils.test.js +0 -745
- package/built/tests/testUtils.d.ts +0 -85
- package/built/tests/testUtils.js +0 -319
- package/built/translations/index.d.ts +0 -2
- package/built/translations/index.js +0 -18
- package/built/translations/translationUtils.d.ts +0 -2
- package/built/translations/translationUtils.js +0 -61
- package/built/translations/types.d.ts +0 -8
- package/built/translations/types.js +0 -20
- package/built/typeUtils.d.ts +0 -70
- package/built/typeUtils.js +0 -55
package/built/utils.js
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
7
|
-
exports.indexByNearestValue = exports.roundToKnownValue = exports.rawInstructionsToIndexedSteps = exports.formatSetValue = exports.exerciseWeight = exports.distance = exports.weekdayNumberMap = exports.startOfWeek = void 0;
|
|
8
|
-
const dayjs_1 = __importDefault(require("dayjs"));
|
|
9
|
-
const _1 = require(".");
|
|
3
|
+
exports.isExerciseTemplate = exports.isCompareExerciseSupported = exports.comparableExerciseTemplateIds = exports.numberWithCommas = exports.setVolume = exports.oneRepMax = exports.oneRepMaxPercentageMap = exports.workoutSetCount = exports.workoutDistanceMeters = exports.workoutReps = exports.workoutDurationSeconds = exports.removeAccents = exports.getClosestDataPointBeforeTargetDate = exports.stringToNumber = exports.forceStringToNumber = exports.formatDurationInput = exports.isValidFormattedTime = exports.customExerciseTemplateToExerciseTemplate = exports.isValidPhoneNumber = exports.isValidWebUrl = exports.isValidEmail = exports.secondsToWordFormatMinutes = exports.secondsToWordFormat = exports.secondsToClockFormat = exports.isValidUsername = exports.divide = exports.num = void 0;
|
|
10
4
|
/**
|
|
11
5
|
* Doesn't matter what you throw in the function it'll
|
|
12
6
|
* always return a number. Non number values will return
|
|
@@ -18,48 +12,26 @@ const num = (value) => {
|
|
|
18
12
|
return value !== null && value !== void 0 ? value : 0;
|
|
19
13
|
};
|
|
20
14
|
exports.num = num;
|
|
21
|
-
const clampNumber = (value, limits) => {
|
|
22
|
-
var _a, _b;
|
|
23
|
-
const min = (_a = limits.min) !== null && _a !== void 0 ? _a : -Infinity;
|
|
24
|
-
const max = (_b = limits.max) !== null && _b !== void 0 ? _b : Infinity;
|
|
25
|
-
if (min > max) {
|
|
26
|
-
return NaN;
|
|
27
|
-
}
|
|
28
|
-
return Math.max(min, Math.min(max, value));
|
|
29
|
-
};
|
|
30
|
-
exports.clampNumber = clampNumber;
|
|
31
15
|
const divide = (numerator, denominator) => {
|
|
32
16
|
if (denominator === 0)
|
|
33
17
|
return 0;
|
|
34
18
|
return numerator / denominator;
|
|
35
19
|
};
|
|
36
20
|
exports.divide = divide;
|
|
37
|
-
const roundToTwoDecimal = (value) => Math.round(value * 100) / 100;
|
|
38
|
-
exports.roundToTwoDecimal = roundToTwoDecimal;
|
|
39
|
-
const roundToOneDecimal = (value) => Math.round(value * 10) / 10;
|
|
40
|
-
exports.roundToOneDecimal = roundToOneDecimal;
|
|
41
|
-
const roundToWholeNumber = (value) => Math.round(value);
|
|
42
|
-
exports.roundToWholeNumber = roundToWholeNumber;
|
|
43
21
|
const isValidUsername = (username) => {
|
|
44
22
|
if (username.length > 20) {
|
|
45
23
|
return false;
|
|
46
24
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
25
|
+
const lowercase = username.toLowerCase();
|
|
26
|
+
if (lowercase.length < 3 ||
|
|
27
|
+
lowercase.startsWith('_') ||
|
|
28
|
+
lowercase.endsWith('_')) {
|
|
50
29
|
return false;
|
|
51
30
|
}
|
|
52
31
|
const re = /^[a-z0-9_]+$/;
|
|
53
|
-
return re.test(
|
|
32
|
+
return re.test(lowercase);
|
|
54
33
|
};
|
|
55
34
|
exports.isValidUsername = isValidUsername;
|
|
56
|
-
const secondsToClockParts = (totalSeconds) => {
|
|
57
|
-
const hours = Math.floor(totalSeconds / 3600);
|
|
58
|
-
const minutes = Math.floor((totalSeconds - hours * 3600) / 60);
|
|
59
|
-
const seconds = totalSeconds - hours * 3600 - minutes * 60;
|
|
60
|
-
return { hours, minutes, seconds };
|
|
61
|
-
};
|
|
62
|
-
exports.secondsToClockParts = secondsToClockParts;
|
|
63
35
|
/**
|
|
64
36
|
* 01:25
|
|
65
37
|
* 02:25:36
|
|
@@ -107,12 +79,10 @@ exports.secondsToWordFormat = secondsToWordFormat;
|
|
|
107
79
|
/**
|
|
108
80
|
* 14min
|
|
109
81
|
* 2h 4min
|
|
110
|
-
* 2h 0min
|
|
111
82
|
*/
|
|
112
83
|
const secondsToWordFormatMinutes = (seconds) => {
|
|
113
|
-
const
|
|
114
|
-
const minutes =
|
|
115
|
-
const hours = Math.floor(totalMinutes / 60);
|
|
84
|
+
const hours = Math.floor(seconds / 3600);
|
|
85
|
+
const minutes = Math.floor((seconds - hours * 3600) / 60);
|
|
116
86
|
if (hours) {
|
|
117
87
|
return `${hours}h ${minutes}min`;
|
|
118
88
|
}
|
|
@@ -124,32 +94,9 @@ const isValidEmail = (email) => {
|
|
|
124
94
|
return re.test(String(email).toLowerCase());
|
|
125
95
|
};
|
|
126
96
|
exports.isValidEmail = isValidEmail;
|
|
127
|
-
/**
|
|
128
|
-
* Matches strings with a base format of `domain.tld`.
|
|
129
|
-
* - `domain` may contain letters, digits and dashes, but it can't begin or
|
|
130
|
-
* end with a dash
|
|
131
|
-
* - `domain` may be prefixed once or multiple times with another `domain.`
|
|
132
|
-
* - `tld` may be followed by `/path-to-page`
|
|
133
|
-
* - the entire string may be prefixed by `https://` or `http://`
|
|
134
|
-
*/
|
|
135
|
-
exports.URL_REGEX = RegExp([
|
|
136
|
-
// optionally begin with http(s)://
|
|
137
|
-
/(https?:\/\/)?/,
|
|
138
|
-
// followed by the domain:
|
|
139
|
-
// - any combination of letters, numbers and dashes
|
|
140
|
-
// - but without a dash at the start or end
|
|
141
|
-
// - maximum 255 characters per domain
|
|
142
|
-
// - each domain ends with a `.`
|
|
143
|
-
/((?!-)[-a-z0-9]{1,255}(?<!-)\.)+/,
|
|
144
|
-
// followed by the top-level domain
|
|
145
|
-
/[a-z]{2,10}/,
|
|
146
|
-
// optionally followed by a path
|
|
147
|
-
/(\/[-a-z0-9@:%_+.~#?!&/=]*)?/,
|
|
148
|
-
]
|
|
149
|
-
.map((r) => r.source)
|
|
150
|
-
.join(''));
|
|
151
97
|
const isValidWebUrl = (url) => {
|
|
152
|
-
|
|
98
|
+
const re = /^(https?:\/\/)?(www\.)?[-a-z0-9.]{2,256}\.[a-z]{2,10}\b([-a-z0-9@:%_+.~#?&//=]*)$/;
|
|
99
|
+
return re.test(String(url).toLowerCase());
|
|
153
100
|
};
|
|
154
101
|
exports.isValidWebUrl = isValidWebUrl;
|
|
155
102
|
/**
|
|
@@ -171,23 +118,21 @@ const isValidPhoneNumber = (phoneNumber) => {
|
|
|
171
118
|
return typeof phoneNumber === 'string' && /^\+\d{9,15}$/.test(phoneNumber);
|
|
172
119
|
};
|
|
173
120
|
exports.isValidPhoneNumber = isValidPhoneNumber;
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
};
|
|
190
|
-
exports.isWholeNumber = isWholeNumber;
|
|
121
|
+
const customExerciseTemplateToExerciseTemplate = (cet) => ({
|
|
122
|
+
id: cet.id,
|
|
123
|
+
title: cet.title,
|
|
124
|
+
priority: 10,
|
|
125
|
+
muscle_group: cet.muscle_group,
|
|
126
|
+
other_muscles: cet.other_muscles || [],
|
|
127
|
+
exercise_type: cet.exercise_type,
|
|
128
|
+
equipment_category: cet.equipment_category,
|
|
129
|
+
is_custom: true,
|
|
130
|
+
is_archived: cet.is_archived,
|
|
131
|
+
custom_exercise_image_url: cet.custom_exercise_image_url,
|
|
132
|
+
thumbnail_url: cet.thumbnail_url,
|
|
133
|
+
parent_exercise_template_id: cet.parent_exercise_template_id,
|
|
134
|
+
});
|
|
135
|
+
exports.customExerciseTemplateToExerciseTemplate = customExerciseTemplateToExerciseTemplate;
|
|
191
136
|
/**
|
|
192
137
|
* Return true is value is of format: NN:NN or NN:NN:NN
|
|
193
138
|
*/
|
|
@@ -284,77 +229,6 @@ const stringToNumber = (value) => {
|
|
|
284
229
|
return isNaN(numOrNaN) ? undefined : numOrNaN;
|
|
285
230
|
};
|
|
286
231
|
exports.stringToNumber = stringToNumber;
|
|
287
|
-
/**
|
|
288
|
-
* Returns the first non-undefined value produced by transform function being
|
|
289
|
-
* applied to elements of the array in iteration order, or `undefined` if no
|
|
290
|
-
* such value was produced. Equivalent to `firstNotNullOfOrNull` in Kotlin.
|
|
291
|
-
*/
|
|
292
|
-
const findMapped = (array, transform) => {
|
|
293
|
-
for (const element of array) {
|
|
294
|
-
const result = transform(element);
|
|
295
|
-
if (result !== undefined) {
|
|
296
|
-
return result;
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
};
|
|
300
|
-
exports.findMapped = findMapped;
|
|
301
|
-
/**
|
|
302
|
-
* converts any array into an array of JSON chunks with a maximum given length
|
|
303
|
-
* @param data an array of objects or primitives to be split into chunks
|
|
304
|
-
* @param maxLength maximum length of any single returned JSON string
|
|
305
|
-
* @returns an array of JSON strings, each one no longer than `maxLength`
|
|
306
|
-
* @throws an error if fragmentation is impossible due to input data structure
|
|
307
|
-
*/
|
|
308
|
-
const toFragmentedJSON = (data, maxLength, options = { lengthIn: 'utf8bytes' }) => {
|
|
309
|
-
const outputArray = [];
|
|
310
|
-
const recurse = (left, right) => {
|
|
311
|
-
if (left === right)
|
|
312
|
-
return;
|
|
313
|
-
// slice the array and convert it to JSON
|
|
314
|
-
const sliceJson = JSON.stringify(data.slice(left, right));
|
|
315
|
-
const length = options.lengthIn === 'characters'
|
|
316
|
-
? sliceJson.length
|
|
317
|
-
: Buffer.byteLength(sliceJson, 'utf8');
|
|
318
|
-
if (length <= maxLength) {
|
|
319
|
-
// the size is good, push the chunk to the output array
|
|
320
|
-
outputArray.push(sliceJson);
|
|
321
|
-
}
|
|
322
|
-
else if (right > left + 1) {
|
|
323
|
-
// there are at least 2 elements in the current slice
|
|
324
|
-
const middle = Math.ceil((left + right) / 2);
|
|
325
|
-
recurse(left, middle);
|
|
326
|
-
recurse(middle, right);
|
|
327
|
-
}
|
|
328
|
-
else {
|
|
329
|
-
// we can't subdivide any further but the string is still too long
|
|
330
|
-
throw new Error(`couldn't subdivide element`);
|
|
331
|
-
}
|
|
332
|
-
};
|
|
333
|
-
outputArray.length = 0;
|
|
334
|
-
recurse(0, data.length);
|
|
335
|
-
return outputArray;
|
|
336
|
-
};
|
|
337
|
-
exports.toFragmentedJSON = toFragmentedJSON;
|
|
338
|
-
/**
|
|
339
|
-
* Finds the closest data point that occurs before or at the target date using binary search.
|
|
340
|
-
* Assumes input array is sorted by date in ascending order.
|
|
341
|
-
*
|
|
342
|
-
* @param data - Array of items containing dates in ascending order
|
|
343
|
-
* @param dateExtractor - Function to extract Date from each item
|
|
344
|
-
* @param targetDate - Target date to search for
|
|
345
|
-
* @returns The closest item before or at the target date, or undefined if not found
|
|
346
|
-
*
|
|
347
|
-
* @example
|
|
348
|
-
* const data = [
|
|
349
|
-
* { id: 1, date: dayjs('2023-01-01').toDate() },
|
|
350
|
-
* { id: 2, date: dayjs('2023-04-01').toDate() }
|
|
351
|
-
* ];
|
|
352
|
-
* const result = getClosestDataPointBeforeTargetDate(
|
|
353
|
-
* data,
|
|
354
|
-
* item => item.date,
|
|
355
|
-
* dayjs('2023-03-15').toDate(),
|
|
356
|
-
* ); // Returns { id: 1, date: Date('2023-01-01') }
|
|
357
|
-
*/
|
|
358
232
|
const getClosestDataPointBeforeTargetDate = (data, dateExtractor, targetDate) => {
|
|
359
233
|
var _a;
|
|
360
234
|
if (!data.length)
|
|
@@ -388,37 +262,6 @@ const getClosestDataPointBeforeTargetDate = (data, dateExtractor, targetDate) =>
|
|
|
388
262
|
}
|
|
389
263
|
};
|
|
390
264
|
exports.getClosestDataPointBeforeTargetDate = getClosestDataPointBeforeTargetDate;
|
|
391
|
-
/**
|
|
392
|
-
* Finds the closest data point to the target date.
|
|
393
|
-
*
|
|
394
|
-
* @param data - Array of items containing dates
|
|
395
|
-
* @param dateExtractor - Function to extract Date from each item
|
|
396
|
-
* @param targetDate - Target date to search for
|
|
397
|
-
* @returns The closest item to the target date, or undefined if array is empty
|
|
398
|
-
*
|
|
399
|
-
* @example
|
|
400
|
-
* const data = [
|
|
401
|
-
* { id: 1, date: dayjs('2023-01-01').toDate() },
|
|
402
|
-
* { id: 2, date: dayjs('2023-04-01').toDate() }
|
|
403
|
-
* ];
|
|
404
|
-
* const result = getClosestDataPoint(
|
|
405
|
-
* data,
|
|
406
|
-
* item => item.date,
|
|
407
|
-
* dayjs('2023-03-15').toDate(),
|
|
408
|
-
* ); // Returns { id: 2, date: Date('2023-04-01') }
|
|
409
|
-
*/
|
|
410
|
-
const getClosestDataPointAroundTargetDate = (data, dateExtractor, targetDate) => {
|
|
411
|
-
if (!data.length)
|
|
412
|
-
return undefined;
|
|
413
|
-
return data.reduce((closest, current) => {
|
|
414
|
-
if (!closest)
|
|
415
|
-
return current;
|
|
416
|
-
const closestDiff = Math.abs(dateExtractor(closest).getTime() - targetDate.getTime());
|
|
417
|
-
const currentDiff = Math.abs(dateExtractor(current).getTime() - targetDate.getTime());
|
|
418
|
-
return currentDiff < closestDiff ? current : closest;
|
|
419
|
-
}, undefined);
|
|
420
|
-
};
|
|
421
|
-
exports.getClosestDataPointAroundTargetDate = getClosestDataPointAroundTargetDate;
|
|
422
265
|
const removeAccents = (str) => {
|
|
423
266
|
return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '');
|
|
424
267
|
};
|
|
@@ -452,84 +295,12 @@ const workoutDistanceMeters = (workout) => {
|
|
|
452
295
|
}, 0);
|
|
453
296
|
};
|
|
454
297
|
exports.workoutDistanceMeters = workoutDistanceMeters;
|
|
455
|
-
/**
|
|
456
|
-
* Calculate the set weight for a given user exercise set
|
|
457
|
-
* to be used in the exercise stats calculations on the web and coach app
|
|
458
|
-
*/
|
|
459
|
-
const userExerciseSetWeight = (set, exerciseStore, hundredPercentBodyweightExercise) => {
|
|
460
|
-
if (!set)
|
|
461
|
-
return 0;
|
|
462
|
-
const exercise = exerciseStore.find((e) => e.id === set.exercise_template_id);
|
|
463
|
-
if (!exercise) {
|
|
464
|
-
return (0, exports.num)(set.weight_kg);
|
|
465
|
-
}
|
|
466
|
-
if (exercise.exercise_type === 'bodyweight_reps') {
|
|
467
|
-
return hundredPercentBodyweightExercise
|
|
468
|
-
? (0, exports.num)(set.user_bodyweight_kg) + (0, exports.num)(set.weight_kg)
|
|
469
|
-
: (0, exports.num)(set.weight_kg);
|
|
470
|
-
}
|
|
471
|
-
else if (exercise.exercise_type === 'bodyweight_assisted_reps') {
|
|
472
|
-
return hundredPercentBodyweightExercise
|
|
473
|
-
? Math.max((0, exports.num)(set.user_bodyweight_kg) - (0, exports.num)(set.weight_kg), 0)
|
|
474
|
-
: 0;
|
|
475
|
-
}
|
|
476
|
-
else if (exercise.exercise_type === 'reps_only') {
|
|
477
|
-
return hundredPercentBodyweightExercise ? (0, exports.num)(set.user_bodyweight_kg) : 0;
|
|
478
|
-
}
|
|
479
|
-
else {
|
|
480
|
-
return (0, exports.num)(set.weight_kg);
|
|
481
|
-
}
|
|
482
|
-
};
|
|
483
|
-
exports.userExerciseSetWeight = userExerciseSetWeight;
|
|
484
298
|
const workoutSetCount = (w) => {
|
|
485
299
|
return w.exercises.reduce((accu, exercise) => {
|
|
486
300
|
return accu + exercise.sets.length;
|
|
487
301
|
}, 0);
|
|
488
302
|
};
|
|
489
303
|
exports.workoutSetCount = workoutSetCount;
|
|
490
|
-
const UserFacingIndicatorToSetIndicator = (indicator) => {
|
|
491
|
-
switch (indicator) {
|
|
492
|
-
case 'dropset':
|
|
493
|
-
return 'dropset';
|
|
494
|
-
case 'warmup':
|
|
495
|
-
return 'warmup';
|
|
496
|
-
case 'failure':
|
|
497
|
-
return 'failure';
|
|
498
|
-
default:
|
|
499
|
-
return 'normal';
|
|
500
|
-
}
|
|
501
|
-
};
|
|
502
|
-
exports.UserFacingIndicatorToSetIndicator = UserFacingIndicatorToSetIndicator;
|
|
503
|
-
exports.ESTIMATED_SET_DURATION = 45;
|
|
504
|
-
exports.ESTIMATED_REST_TIMER_DURATION = 90;
|
|
505
|
-
const isDurationExercise = (type) => {
|
|
506
|
-
return (type === 'duration' ||
|
|
507
|
-
type === 'weight_duration' ||
|
|
508
|
-
type === 'distance_duration' ||
|
|
509
|
-
type === 'floors_duration' ||
|
|
510
|
-
type === 'steps_duration');
|
|
511
|
-
};
|
|
512
|
-
const getEstimatedExercisesDurationSeconds = ({ exercises, }) => {
|
|
513
|
-
const totalSeconds = exercises.reduce((exercisesTotal, exercise) => {
|
|
514
|
-
var _a;
|
|
515
|
-
const restPerSet = (_a = exercise.rest_seconds) !== null && _a !== void 0 ? _a : exports.ESTIMATED_REST_TIMER_DURATION;
|
|
516
|
-
const setsTotal = exercise.sets.reduce((setTotal, set) => {
|
|
517
|
-
var _a;
|
|
518
|
-
// Sometimes we get 0 values for duration on exercises that aren't duration.
|
|
519
|
-
// Added this check to prevent having a estimated duration of 0 for these.
|
|
520
|
-
const duration = isDurationExercise(exercise.exercise_type)
|
|
521
|
-
? (_a = set.duration_seconds) !== null && _a !== void 0 ? _a : exports.ESTIMATED_SET_DURATION
|
|
522
|
-
: exports.ESTIMATED_SET_DURATION;
|
|
523
|
-
// if it is a dropset, we don't add the rest time
|
|
524
|
-
if (set.indicator === 'dropset')
|
|
525
|
-
return setTotal + duration;
|
|
526
|
-
return setTotal + duration + restPerSet;
|
|
527
|
-
}, 0);
|
|
528
|
-
return exercisesTotal + setsTotal;
|
|
529
|
-
}, 0);
|
|
530
|
-
return totalSeconds;
|
|
531
|
-
};
|
|
532
|
-
exports.getEstimatedExercisesDurationSeconds = getEstimatedExercisesDurationSeconds;
|
|
533
304
|
exports.oneRepMaxPercentageMap = {
|
|
534
305
|
1: 1.0,
|
|
535
306
|
2: 0.97,
|
|
@@ -574,357 +345,73 @@ const setVolume = (weight, reps) => {
|
|
|
574
345
|
return weight * reps;
|
|
575
346
|
};
|
|
576
347
|
exports.setVolume = setVolume;
|
|
577
|
-
/** @deprecated use `numberToLocaleString` */
|
|
578
348
|
const numberWithCommas = (x) => {
|
|
579
349
|
return x.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
580
350
|
};
|
|
581
351
|
exports.numberWithCommas = numberWithCommas;
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
352
|
+
exports.comparableExerciseTemplateIds = new Set([
|
|
353
|
+
'79D0BB3A' /* Bench Press (Barbell) **/,
|
|
354
|
+
'D04AC939' /* Squat (Barbell) **/,
|
|
355
|
+
'A5AC6449' /* Bicep Curl (Barbell) **/,
|
|
356
|
+
'5F4E6DD3' /* Deadlift (Barbell) **/,
|
|
357
|
+
'D20D7BBE' /* Sumo Deadlift **/,
|
|
358
|
+
'50DFDFAB' /* Incline Bench (Barbell) **/,
|
|
359
|
+
'55E6546F' /* Penlay Row **/,
|
|
360
|
+
'C6272009' /* Deadlift (Barbell) **/,
|
|
361
|
+
'7B8D84E8' /* Overhead Press (Barbell) **/,
|
|
362
|
+
'C7973E0E' /* Leg Press **/,
|
|
363
|
+
'DA0F0470' /* Decline Bench (Barbell) **/,
|
|
364
|
+
'5046D0A9' /* Front Squat **/,
|
|
365
|
+
'1283BBA6' /* Full Squat **/,
|
|
366
|
+
'6622E5A0' /* Sumo Squat (Barbell) **/,
|
|
367
|
+
'FE389074' /* Rack Pull **/,
|
|
368
|
+
'92B8C7E1' /* Hip Thrust **/,
|
|
369
|
+
'1B2B1E7C' /* Pull Up **/,
|
|
370
|
+
'392887AA' /* Push Up **/,
|
|
371
|
+
'29083183' /* Chin Up **/,
|
|
372
|
+
'7AB9A362' /* Upright Row (Barbell) **/,
|
|
373
|
+
'90E506D5' /* Thruster (Barbell) **/,
|
|
374
|
+
'2A48E443' /* Straight Leg Deadlift **/,
|
|
375
|
+
'073032BB' /* Standing Military Press (Barbell) **/,
|
|
376
|
+
'9694DA61' /* Squat (Bodyweight) **/,
|
|
377
|
+
'FB09C938' /* Snatch **/,
|
|
378
|
+
'875F585F' /* Skullcrusher (Barbell) **/,
|
|
379
|
+
'022DF610' /* Sit Up **/,
|
|
380
|
+
'0B841777' /* Shrug (Barbell) **/,
|
|
381
|
+
'91AF29E0' /* Seated Overhead Press (Barbell) **/,
|
|
382
|
+
'2B4B7310' /* Romanian Deadlift (Barbell) **/,
|
|
383
|
+
'818BA121' /* Reverse Lunge (Barbell) **/,
|
|
384
|
+
'542F3CD5' /* Push Press **/,
|
|
385
|
+
'E22F9358' /* Power Snatch **/,
|
|
386
|
+
'C628D768' /* Power Clean **/,
|
|
387
|
+
'3FF6A22E' /* Pistol Squat **/,
|
|
388
|
+
'0EFE8162' /* Pike Pushup **/,
|
|
389
|
+
'018ADC12' /* Pendlay Row (Barbell) **/,
|
|
390
|
+
'6E6EE645' /* Lunge (Barbell) **/,
|
|
391
|
+
'5E1A7777' /* Lunge **/,
|
|
392
|
+
'B74A95BB' /* Kneeling Push Up **/,
|
|
393
|
+
'70D4EBBF' /* Jump Squat **/,
|
|
394
|
+
'D57C2EC7' /* Hip Thrust (Barbell) **/,
|
|
395
|
+
'4180C405' /* Good Morning (Barbell) **/,
|
|
396
|
+
'6575F52D' /* Diamond Push Up **/,
|
|
397
|
+
'DCF3B31B' /* Crunch **/,
|
|
398
|
+
'652FEA39' /* Clean Pull **/,
|
|
399
|
+
'D3095577' /* Clean and Press **/,
|
|
400
|
+
'9E09CEC3' /* Clean and Jerk **/,
|
|
401
|
+
'ABB00838' /* Clean **/,
|
|
402
|
+
'BB792A36' /* Burpee **/,
|
|
403
|
+
'E644F828' /* Bench Press - Wide Grip (Barbell) **/,
|
|
404
|
+
'35B51B87' /* Bench Press - Close Grip (Barbell) **/,
|
|
405
|
+
]);
|
|
406
|
+
const isCompareExerciseSupported = (temlpateId) => {
|
|
407
|
+
return exports.comparableExerciseTemplateIds.has(temlpateId);
|
|
408
|
+
};
|
|
409
|
+
exports.isCompareExerciseSupported = isCompareExerciseSupported;
|
|
410
|
+
const isExerciseTemplate = (x) => {
|
|
608
411
|
return (x.id !== undefined &&
|
|
609
412
|
x.title !== undefined &&
|
|
610
413
|
x.muscle_group !== undefined &&
|
|
611
414
|
x.exercise_type !== undefined &&
|
|
612
415
|
x.equipment_category !== undefined);
|
|
613
416
|
};
|
|
614
|
-
exports.
|
|
615
|
-
const _v4uuidCheckRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-([0-9a-f])[0-9a-f]{3}-([0-9a-f])[0-9a-f]{3}-([0-9a-f]{12})/i;
|
|
616
|
-
/**
|
|
617
|
-
* Generates a subsample or test group given a user id. Technically, it just
|
|
618
|
-
* calculates a trivial checksum of the last (random) part of a v4 UUID.
|
|
619
|
-
* @param userId a v4 UUID; _must_ be v4 to ensure random distribution
|
|
620
|
-
* @param numGroups number of possible groups, from 2 to 2^32
|
|
621
|
-
* @returns a number in the `[0, numGroups)` range, always equal for a given
|
|
622
|
-
* `(userId, numGroups)` pair, or an error, inside a Result<T> object
|
|
623
|
-
*/
|
|
624
|
-
const generateUserGroup = (userId, numGroups) => {
|
|
625
|
-
if (numGroups < 2 || numGroups > 2 ** 32) {
|
|
626
|
-
return { isSuccess: false, error: 'invalid-number-of-groups' };
|
|
627
|
-
}
|
|
628
|
-
const match = _v4uuidCheckRegex.exec(userId);
|
|
629
|
-
if (!match) {
|
|
630
|
-
return { isSuccess: false, error: 'invalid-uuid' };
|
|
631
|
-
}
|
|
632
|
-
const [, version, variant, tail] = match;
|
|
633
|
-
if (version !== '4') {
|
|
634
|
-
return { isSuccess: false, error: 'uuid-not-v4' };
|
|
635
|
-
}
|
|
636
|
-
if (!['8', '9', 'a', 'b', 'c', 'd'].includes(variant.toLowerCase())) {
|
|
637
|
-
return { isSuccess: false, error: 'invalid-variant' };
|
|
638
|
-
}
|
|
639
|
-
let value;
|
|
640
|
-
try {
|
|
641
|
-
const idHexString = `0x${tail}`;
|
|
642
|
-
const idBigInt = BigInt(idHexString);
|
|
643
|
-
const numGroupsBigInt = BigInt(numGroups);
|
|
644
|
-
const remainderBigInt = idBigInt % numGroupsBigInt;
|
|
645
|
-
value = Number(remainderBigInt);
|
|
646
|
-
}
|
|
647
|
-
catch (e) {
|
|
648
|
-
throw new Error(`generateUserGroup: tail="${tail}": ${e === null || e === void 0 ? void 0 : e.message}`);
|
|
649
|
-
}
|
|
650
|
-
return { isSuccess: true, value };
|
|
651
|
-
};
|
|
652
|
-
exports.generateUserGroup = generateUserGroup;
|
|
653
|
-
/**
|
|
654
|
-
* Get the user group value for a given user id and number of groups.
|
|
655
|
-
* @param userId a v4 UUID; _must_ be v4 to ensure random distribution
|
|
656
|
-
* @param numGroups number of possible groups, from 2 to 2^32
|
|
657
|
-
* @returns User group value (A, B, C, etc.), or undefined if the user id is not a v4 UUID
|
|
658
|
-
* or if an error occurs.
|
|
659
|
-
*/
|
|
660
|
-
const generateUserGroupValue = (userId, numGroups) => {
|
|
661
|
-
const group = (0, exports.generateUserGroup)(userId, numGroups);
|
|
662
|
-
if (group.isSuccess) {
|
|
663
|
-
switch (group.value) {
|
|
664
|
-
case 0:
|
|
665
|
-
return 'A';
|
|
666
|
-
case 1:
|
|
667
|
-
return 'B';
|
|
668
|
-
case 2:
|
|
669
|
-
return 'C';
|
|
670
|
-
case 3:
|
|
671
|
-
return 'D';
|
|
672
|
-
case 4:
|
|
673
|
-
return 'E';
|
|
674
|
-
case 5:
|
|
675
|
-
return 'F';
|
|
676
|
-
case 6:
|
|
677
|
-
return 'G';
|
|
678
|
-
case 7:
|
|
679
|
-
return 'H';
|
|
680
|
-
case 8:
|
|
681
|
-
return 'I';
|
|
682
|
-
case 9:
|
|
683
|
-
return 'J';
|
|
684
|
-
default:
|
|
685
|
-
return undefined;
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
else {
|
|
689
|
-
return undefined;
|
|
690
|
-
}
|
|
691
|
-
};
|
|
692
|
-
exports.generateUserGroupValue = generateUserGroupValue;
|
|
693
|
-
/**
|
|
694
|
-
* @example
|
|
695
|
-
* isVersionAGreaterOrEqualToVersionB('1.2.3', '1.2') // true
|
|
696
|
-
* isVersionAGreaterOrEqualToVersionB('1.2.3', '1.2.2') // true
|
|
697
|
-
* isVersionAGreaterOrEqualToVersionB('1.2.3', '1.2.3') // true
|
|
698
|
-
*
|
|
699
|
-
* isVersionAGreaterOrEqualToVersionB('1.2.3', '1.2.4') // false
|
|
700
|
-
* isVersionAGreaterOrEqualToVersionB('1.2.3', '1.3') // false
|
|
701
|
-
*/
|
|
702
|
-
const isVersionAGreaterOrEqualToVersionB = (versionA, versionB) => {
|
|
703
|
-
var _a, _b;
|
|
704
|
-
const versionAparts = `${versionA}`.split('.');
|
|
705
|
-
const versionBparts = `${versionB}`.split('.');
|
|
706
|
-
const longestVersionLength = Math.max(versionAparts.length, versionBparts.length);
|
|
707
|
-
for (let i = 0; i < longestVersionLength; i++) {
|
|
708
|
-
const versionApart = (_a = versionAparts[i]) !== null && _a !== void 0 ? _a : 0;
|
|
709
|
-
const versionBpart = (_b = versionBparts[i]) !== null && _b !== void 0 ? _b : 0;
|
|
710
|
-
if (parseInt(versionApart, 10) > parseInt(versionBpart, 10)) {
|
|
711
|
-
return true;
|
|
712
|
-
}
|
|
713
|
-
if (parseInt(versionApart, 10) < parseInt(versionBpart, 10)) {
|
|
714
|
-
return false;
|
|
715
|
-
}
|
|
716
|
-
// if equal, continue to next part
|
|
717
|
-
}
|
|
718
|
-
// the versions are entirely equal
|
|
719
|
-
return true;
|
|
720
|
-
};
|
|
721
|
-
exports.isVersionAGreaterOrEqualToVersionB = isVersionAGreaterOrEqualToVersionB;
|
|
722
|
-
const splitAtUsernamesAndLinks = (text) => {
|
|
723
|
-
const mentionsRegex = /\B@[a-z0-9_-]+/gi;
|
|
724
|
-
const linksRegex = exports.URL_REGEX;
|
|
725
|
-
const regex = new RegExp(`(${mentionsRegex.source}|${linksRegex.source})`, 'gi');
|
|
726
|
-
const matches = text.match(regex);
|
|
727
|
-
const format = [];
|
|
728
|
-
if (!matches) {
|
|
729
|
-
return [{ format: 'none', text }];
|
|
730
|
-
}
|
|
731
|
-
let lastIndex = 0;
|
|
732
|
-
matches.forEach((match) => {
|
|
733
|
-
const index = text.indexOf(match, lastIndex);
|
|
734
|
-
if (index > lastIndex) {
|
|
735
|
-
format.push({ format: 'none', text: text.substring(lastIndex, index) });
|
|
736
|
-
}
|
|
737
|
-
if (match.match(linksRegex)) {
|
|
738
|
-
format.push({ format: 'link', text: match });
|
|
739
|
-
}
|
|
740
|
-
else if (match.match(mentionsRegex)) {
|
|
741
|
-
format.push({ format: '@', text: match });
|
|
742
|
-
}
|
|
743
|
-
lastIndex = index + match.length;
|
|
744
|
-
});
|
|
745
|
-
if (lastIndex < text.length) {
|
|
746
|
-
format.push({ format: 'none', text: text.substring(lastIndex) });
|
|
747
|
-
}
|
|
748
|
-
return format;
|
|
749
|
-
};
|
|
750
|
-
exports.splitAtUsernamesAndLinks = splitAtUsernamesAndLinks;
|
|
751
|
-
// Single comprehensive YouTube URL regex for both validation and ID extraction
|
|
752
|
-
const YOUTUBE_URL_REGEX = /^(?:https?:\/\/)?(?:(?:www\.)?youtube\.com\/(?:watch\?v=|shorts\/|embed\/|v\/|user\/[^/]+#p\/|attribution_link\?a=|channel\/|c\/|playlist\?list=)|youtu\.be\/)([a-zA-Z0-9_-]{11})(?:[/?#&].*)?$/;
|
|
753
|
-
const validateYoutubeUrl = (url) => {
|
|
754
|
-
return YOUTUBE_URL_REGEX.test(url);
|
|
755
|
-
};
|
|
756
|
-
exports.validateYoutubeUrl = validateYoutubeUrl;
|
|
757
|
-
const getYoutubeVideoId = (url) => {
|
|
758
|
-
const match = url.match(YOUTUBE_URL_REGEX);
|
|
759
|
-
return match && match[1] && match[1].length === 11 ? match[1] : undefined;
|
|
760
|
-
};
|
|
761
|
-
exports.getYoutubeVideoId = getYoutubeVideoId;
|
|
762
|
-
/**@param workouts must be sorted descending by start_time */
|
|
763
|
-
const calculateCurrentWeekStreak = (workouts, firstWeekday, untilUnix = (0, dayjs_1.default)().unix()) => {
|
|
764
|
-
let streakCount = 0;
|
|
765
|
-
let weekStart = (0, exports.startOfWeek)(dayjs_1.default.unix(untilUnix), firstWeekday).subtract(1, 'week');
|
|
766
|
-
let weekEnd = weekStart.add(1, 'week');
|
|
767
|
-
let i = 0;
|
|
768
|
-
/** Iterate through the workouts from most recent to least and keep track of the week-long
|
|
769
|
-
* sliding window in which there needs to be a workout present in order for the streak to count.
|
|
770
|
-
* If there is a workout present during this window, move the window into the past by a week
|
|
771
|
-
* and keep iterating through the workouts. If there are no workouts during this window,
|
|
772
|
-
* the streak count is not incremented.
|
|
773
|
-
*/
|
|
774
|
-
while (i < workouts.length) {
|
|
775
|
-
const workoutStart = workouts[i].start_time;
|
|
776
|
-
if (workoutStart >= weekStart.unix() && workoutStart <= weekEnd.unix()) {
|
|
777
|
-
streakCount += 1;
|
|
778
|
-
weekStart = weekStart.subtract(1, 'week');
|
|
779
|
-
weekEnd = weekEnd.subtract(1, 'week');
|
|
780
|
-
}
|
|
781
|
-
else if (workoutStart < weekStart.unix()) {
|
|
782
|
-
/** This workout is before the sliding window. We don't get to this point unless
|
|
783
|
-
* the user didn't workout during the window. So that means the streak is over
|
|
784
|
-
*/
|
|
785
|
-
break;
|
|
786
|
-
}
|
|
787
|
-
i += 1;
|
|
788
|
-
}
|
|
789
|
-
// check if the current week has any workouts
|
|
790
|
-
weekStart = (0, exports.startOfWeek)(dayjs_1.default.unix(untilUnix), firstWeekday);
|
|
791
|
-
weekEnd = weekStart.add(1, 'week');
|
|
792
|
-
if (workouts.find(({ start_time }) => start_time >= weekStart.unix() && start_time <= weekEnd.unix())) {
|
|
793
|
-
streakCount += 1;
|
|
794
|
-
}
|
|
795
|
-
return streakCount;
|
|
796
|
-
};
|
|
797
|
-
exports.calculateCurrentWeekStreak = calculateCurrentWeekStreak;
|
|
798
|
-
const startOfWeek = (d, firstDayOfWeek) => {
|
|
799
|
-
const firstDayOfWeekIndex = exports.weekdayNumberMap[firstDayOfWeek];
|
|
800
|
-
let testDay = d;
|
|
801
|
-
while (testDay.day() !== firstDayOfWeekIndex) {
|
|
802
|
-
testDay = testDay.subtract(1, 'day');
|
|
803
|
-
}
|
|
804
|
-
return testDay.startOf('day');
|
|
805
|
-
};
|
|
806
|
-
exports.startOfWeek = startOfWeek;
|
|
807
|
-
exports.weekdayNumberMap = {
|
|
808
|
-
sunday: 0,
|
|
809
|
-
monday: 1,
|
|
810
|
-
tuesday: 2,
|
|
811
|
-
wednesday: 3,
|
|
812
|
-
thursday: 4,
|
|
813
|
-
friday: 5,
|
|
814
|
-
saturday: 6,
|
|
815
|
-
};
|
|
816
|
-
const distance = (value, distanceUnit) => {
|
|
817
|
-
if (distanceUnit === 'miles') {
|
|
818
|
-
const unrounded = (0, _1.exactMetersToMiles)(value);
|
|
819
|
-
return (0, exports.roundToTwoDecimal)(unrounded);
|
|
820
|
-
}
|
|
821
|
-
return (0, exports.roundToTwoDecimal)(value / 1000);
|
|
822
|
-
};
|
|
823
|
-
exports.distance = distance;
|
|
824
|
-
const exerciseWeight = (value, weightUnit) => {
|
|
825
|
-
if (weightUnit === 'lbs') {
|
|
826
|
-
return (0, exports.roundToTwoDecimal)((0, _1.exactKgtoLbs)(value));
|
|
827
|
-
}
|
|
828
|
-
return (0, exports.roundToTwoDecimal)(value);
|
|
829
|
-
};
|
|
830
|
-
exports.exerciseWeight = exerciseWeight;
|
|
831
|
-
const formatSetValue = ({ exerciseType, set, units, lokalizedLabels, }) => {
|
|
832
|
-
var _a, _b, _c;
|
|
833
|
-
const unitToLabelMap = {
|
|
834
|
-
kg: lokalizedLabels.kg,
|
|
835
|
-
lbs: lokalizedLabels.lbs,
|
|
836
|
-
km: lokalizedLabels.km,
|
|
837
|
-
mi: lokalizedLabels.mi,
|
|
838
|
-
m: lokalizedLabels.m,
|
|
839
|
-
yd: lokalizedLabels.yd,
|
|
840
|
-
steps: lokalizedLabels.steps,
|
|
841
|
-
floors: lokalizedLabels.floors,
|
|
842
|
-
};
|
|
843
|
-
switch (exerciseType) {
|
|
844
|
-
case 'weight_reps':
|
|
845
|
-
case 'bodyweight_reps': {
|
|
846
|
-
const weight = `${(0, exports.exerciseWeight)(set.weight_kg || 0, units.weight)} ${unitToLabelMap[units.weight]}`;
|
|
847
|
-
const reps = `${(_a = set.reps) !== null && _a !== void 0 ? _a : 0}`;
|
|
848
|
-
return `${weight} x ${reps}`;
|
|
849
|
-
}
|
|
850
|
-
case 'bodyweight_assisted_reps': {
|
|
851
|
-
const weight = `-${(0, exports.exerciseWeight)(set.weight_kg || 0, units.weight)} ${unitToLabelMap[units.weight]}`;
|
|
852
|
-
const reps = `${(_b = set.reps) !== null && _b !== void 0 ? _b : 0}`;
|
|
853
|
-
return `${weight} x ${reps}`;
|
|
854
|
-
}
|
|
855
|
-
case 'reps_only':
|
|
856
|
-
return `${(_c = set.reps) !== null && _c !== void 0 ? _c : 0}`;
|
|
857
|
-
case 'distance_duration': {
|
|
858
|
-
const distanceUnitShort = units.distance === 'kilometers' ? 'km' : 'mi';
|
|
859
|
-
const distanceValue = `${(0, exports.distance)(set.distance_meters || 0, units.distance)} ${unitToLabelMap[distanceUnitShort]}`;
|
|
860
|
-
const duration = `${(0, exports.secondsToWordFormat)(Number(set.duration_seconds))}`;
|
|
861
|
-
return `${distanceValue} - ${duration}`;
|
|
862
|
-
}
|
|
863
|
-
case 'duration':
|
|
864
|
-
return (0, exports.secondsToWordFormat)(Number(set.duration_seconds));
|
|
865
|
-
case 'short_distance_weight': {
|
|
866
|
-
const weight = `${(0, exports.exerciseWeight)(set.weight_kg || 0, units.weight)} ${unitToLabelMap[units.weight]}`;
|
|
867
|
-
const shortDistance = (0, exports.roundToTwoDecimal)(set.distance_meters || 0);
|
|
868
|
-
const shortDistanceUnitShort = units.distance === 'kilometers' ? 'm' : 'yd';
|
|
869
|
-
const shortDistanceUnitShortLabel = unitToLabelMap[shortDistanceUnitShort];
|
|
870
|
-
return `${weight} - ${shortDistance} ${shortDistanceUnitShortLabel}`;
|
|
871
|
-
}
|
|
872
|
-
case 'weight_duration': {
|
|
873
|
-
const weight = `${(0, exports.exerciseWeight)(set.weight_kg || 0, units.weight)} ${unitToLabelMap[units.weight]}`;
|
|
874
|
-
const duration = `${(0, exports.secondsToWordFormat)(Number(set.duration_seconds))}`;
|
|
875
|
-
return `${weight} - ${duration}`;
|
|
876
|
-
}
|
|
877
|
-
case 'floors_duration':
|
|
878
|
-
return `${set.custom_metric ? (0, exports.roundToWholeNumber)(set.custom_metric) : 0} ${unitToLabelMap.floors} - ${(0, exports.secondsToWordFormat)(Number(set.duration_seconds))}`;
|
|
879
|
-
case 'steps_duration':
|
|
880
|
-
return `${set.custom_metric ? (0, exports.roundToWholeNumber)(set.custom_metric) : 0} ${unitToLabelMap.steps} - ${(0, exports.secondsToWordFormat)(Number(set.duration_seconds))}`;
|
|
881
|
-
default:
|
|
882
|
-
(0, _1.exhaustiveTypeCheck)(exerciseType);
|
|
883
|
-
return '';
|
|
884
|
-
}
|
|
885
|
-
};
|
|
886
|
-
exports.formatSetValue = formatSetValue;
|
|
887
|
-
const rawInstructionsToIndexedSteps = (rawInstructions) => {
|
|
888
|
-
const instructionsByLine = rawInstructions
|
|
889
|
-
.split('\n')
|
|
890
|
-
.map((l) => l.trim())
|
|
891
|
-
.filter((l) => l.length > 0);
|
|
892
|
-
const instructionsByStep = instructionsByLine.map((s) => {
|
|
893
|
-
var _a, _b;
|
|
894
|
-
const parsedStep = s.match(/^([0-9]*)\\?\.(.*)$/);
|
|
895
|
-
if (!parsedStep) {
|
|
896
|
-
// this line doesn't contain a number, so just return it unchanged
|
|
897
|
-
return { description: s };
|
|
898
|
-
}
|
|
899
|
-
const index = Number(parsedStep[1]) || undefined; // if it's NaN or 0, convert it to undefined
|
|
900
|
-
const description = (_b = (_a = parsedStep[2]) === null || _a === void 0 ? void 0 : _a.trim()) !== null && _b !== void 0 ? _b : s;
|
|
901
|
-
return { index, description };
|
|
902
|
-
});
|
|
903
|
-
return instructionsByStep;
|
|
904
|
-
};
|
|
905
|
-
exports.rawInstructionsToIndexedSteps = rawInstructionsToIndexedSteps;
|
|
906
|
-
const roundToKnownValue = (value, knownValues) => {
|
|
907
|
-
if (knownValues.length === 0)
|
|
908
|
-
return undefined;
|
|
909
|
-
const boundingValues = knownValues.reduce(([lower, upper], v) => {
|
|
910
|
-
return [
|
|
911
|
-
v > lower && v <= value ? v : lower,
|
|
912
|
-
v < upper && v >= value ? v : upper,
|
|
913
|
-
];
|
|
914
|
-
}, [-Infinity, Infinity]);
|
|
915
|
-
if (boundingValues[0] === -Infinity && boundingValues[1] === Infinity)
|
|
916
|
-
return undefined;
|
|
917
|
-
if (boundingValues[0] === -Infinity)
|
|
918
|
-
return boundingValues[1];
|
|
919
|
-
if (boundingValues[1] === Infinity)
|
|
920
|
-
return boundingValues[0];
|
|
921
|
-
return value - boundingValues[0] < boundingValues[1] - value
|
|
922
|
-
? boundingValues[0]
|
|
923
|
-
: boundingValues[1];
|
|
924
|
-
};
|
|
925
|
-
exports.roundToKnownValue = roundToKnownValue;
|
|
926
|
-
const indexByNearestValue = (value, map) => {
|
|
927
|
-
const index = (0, exports.roundToKnownValue)(value, Object.keys(map).map((e) => Number(e)));
|
|
928
|
-
return index ? map[index] : undefined;
|
|
929
|
-
};
|
|
930
|
-
exports.indexByNearestValue = indexByNearestValue;
|
|
417
|
+
exports.isExerciseTemplate = isExerciseTemplate;
|