hevy-shared 1.0.699 → 1.0.701

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.
@@ -1,3 +1,4 @@
1
+ import Fuse from 'fuse.js';
1
2
  import { EquipmentFilter, Language, LibraryExercise, MuscleGroupFilter, CustomExercise } from './index';
2
3
  export interface SearchableLibraryExercise extends LibraryExercise {
3
4
  localized_muscle_group_name: string;
@@ -20,6 +21,11 @@ interface FilterExercisesProps {
20
21
  searchText: string;
21
22
  language: Language;
22
23
  }
24
+ export declare const createFuseInstance: () => Fuse<string>;
25
+ export declare const newFilterExercises: ({ fuseInstance, exercises, muscleGroupFilter, equipmentType, searchText, language, muscleGroupMatchPenalty, }: FilterExercisesProps & {
26
+ muscleGroupMatchPenalty: number;
27
+ fuseInstance: Fuse<string>;
28
+ }) => SearchableExerciseTemplateWithStringScore[];
23
29
  export declare const filterExercises: ({ exercises, muscleGroupFilter, equipmentType, searchText, language, }: FilterExercisesProps) => (CustomExercise | LibraryExercise)[];
24
30
  /**
25
31
  * Exercise templates that are shown at the top of the Exercise Library to
@@ -1,8 +1,31 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.popularExerciseTemplateIds = exports.filterExercises = void 0;
6
+ exports.popularExerciseTemplateIds = exports.filterExercises = exports.newFilterExercises = exports.createFuseInstance = void 0;
7
+ const fuse_js_1 = __importDefault(require("fuse.js"));
4
8
  const utils_1 = require("./utils");
5
9
  const lodash_1 = require("lodash");
10
+ const createFuseInstance = () => {
11
+ const stringSimilarityFuseInstance = new fuse_js_1.default([''], {
12
+ includeScore: true,
13
+ threshold: 0.4,
14
+ location: 0, // Prefer matches at the beginning of the string
15
+ });
16
+ return stringSimilarityFuseInstance;
17
+ };
18
+ exports.createFuseInstance = createFuseInstance;
19
+ const newFilterExercises = ({ fuseInstance, exercises, muscleGroupFilter, equipmentType, searchText, language, muscleGroupMatchPenalty, }) => {
20
+ let result = exercises;
21
+ result = filterExercisesWithMuscleGroup(muscleGroupFilter, result);
22
+ result = filterExercisesWithEquipment(equipmentType, result);
23
+ const normalizedSearchText = normalizeSearchText(searchText, language);
24
+ result = newFilterExercisesWithSearchText(fuseInstance, normalizedSearchText, result, language, muscleGroupMatchPenalty);
25
+ result = orderExercisesWithSearchText(normalizedSearchText, result);
26
+ return result;
27
+ };
28
+ exports.newFilterExercises = newFilterExercises;
6
29
  const filterExercises = ({ exercises, muscleGroupFilter, equipmentType, searchText, language, }) => {
7
30
  let result = exercises;
8
31
  result = filterExercisesWithMuscleGroup(muscleGroupFilter, result);
@@ -25,6 +48,25 @@ const filterExercisesWithEquipment = (equipmentType, exercises) => {
25
48
  }
26
49
  return exercises.filter((rd) => rd.equipment_category === equipmentType);
27
50
  };
51
+ const fuseScoreToSimilarity = (score) => {
52
+ if (score === 0)
53
+ return 'exact';
54
+ if (score < 0.05)
55
+ return 'extremely-similar';
56
+ if (score < 0.3)
57
+ return 'very-similar';
58
+ if (score < 0.6)
59
+ return 'somewhat-similar';
60
+ return 'not-similar';
61
+ };
62
+ const getStringSimilarityScore = (searchString, targetString, fuseInstance) => {
63
+ var _a, _b;
64
+ fuseInstance.setCollection([targetString]);
65
+ const result = fuseInstance.search(searchString);
66
+ const score = (_b = (_a = result[0]) === null || _a === void 0 ? void 0 : _a.score) !== null && _b !== void 0 ? _b : 1.0;
67
+ return score;
68
+ };
69
+ // Old filter exercise functionality, will be removed when new feature goes live
28
70
  const filterExercisesWithSearchText = (searchText, exercises, language) => {
29
71
  if (searchText.length === 0) {
30
72
  return exercises;
@@ -84,6 +126,89 @@ const filterExercisesWithSearchText = (searchText, exercises, language) => {
84
126
  return false;
85
127
  });
86
128
  };
129
+ const newFilterExercisesWithSearchText = (fuseInstance, searchText, exercises, language,
130
+ // 0 means no penalty, 1 means don't match muscle groups (because a score > 1 means no match)
131
+ muscleGroupMatchPenalty) => {
132
+ if (searchText.length === 0) {
133
+ return exercises;
134
+ }
135
+ return exercises.reduce((accu, e) => {
136
+ const searchableSearchText = searchable(searchText);
137
+ const searchableEnglishTitle = searchable(e.title);
138
+ const searchableLocalizedTitleMap = !e.is_custom
139
+ ? {
140
+ en: searchable(e.title),
141
+ es: e.es_title ? searchable(e.es_title) : undefined,
142
+ de: e.de_title ? searchable(e.de_title) : undefined,
143
+ fr: e.fr_title ? searchable(e.fr_title) : undefined,
144
+ it: e.it_title ? searchable(e.it_title) : undefined,
145
+ pt: e.pt_title ? searchable(e.pt_title) : undefined,
146
+ tr: e.tr_title ? searchable(e.tr_title) : undefined,
147
+ zh_CN: e.zh_cn_title ? searchable(e.zh_cn_title) : undefined,
148
+ zh_TW: e.zh_tw_title ? searchable(e.zh_tw_title) : undefined,
149
+ ru: e.ru_title ? searchable(e.ru_title) : undefined,
150
+ ja: e.ja_title ? searchable(e.ja_title) : undefined,
151
+ ko: e.ko_title ? searchable(e.ko_title) : undefined,
152
+ }
153
+ : {
154
+ en: searchable(e.title),
155
+ };
156
+ const searchableLocalizedTitle = searchableLocalizedTitleMap[language] || searchableLocalizedTitleMap.en;
157
+ const searchableLocalizedMuscleGroupName = searchable(e.localized_muscle_group_name);
158
+ const searchableMuscleGroup = searchable(e.muscle_group);
159
+ const searchableMisspelledDumbbellEquipment = e.equipment_category === 'dumbbell' ? 'dumbell' : '';
160
+ const searchableTags = !e.is_custom && e.manual_tag ? searchable(e.manual_tag) : '';
161
+ const searchableAka = !e.is_custom && e.aka ? searchable(e.aka) : '';
162
+ const englishSimilarityScore = getStringSimilarityScore(searchableSearchText, searchableEnglishTitle, fuseInstance);
163
+ const localizedSimilarityScore = getStringSimilarityScore(searchableSearchText, searchableLocalizedTitle, fuseInstance);
164
+ let englishMuscleGroupSimilarityScore = 1;
165
+ let muscleGroupSimilarityScore = 1;
166
+ if (muscleGroupMatchPenalty < 1) {
167
+ englishMuscleGroupSimilarityScore = getStringSimilarityScore(searchableSearchText, searchableMuscleGroup, fuseInstance);
168
+ muscleGroupSimilarityScore = getStringSimilarityScore(searchableSearchText, searchableLocalizedMuscleGroupName, fuseInstance);
169
+ }
170
+ // We always return the best score, regardless of where the match happens
171
+ const bestScore = Math.min(englishSimilarityScore, localizedSimilarityScore, englishMuscleGroupSimilarityScore + muscleGroupMatchPenalty, muscleGroupSimilarityScore + muscleGroupMatchPenalty);
172
+ if (fuseScoreToSimilarity(englishSimilarityScore) !== 'not-similar') {
173
+ accu.push(Object.assign(Object.assign({}, e), { searchStringScore: bestScore }));
174
+ return accu;
175
+ }
176
+ if (fuseScoreToSimilarity(localizedSimilarityScore) !== 'not-similar') {
177
+ accu.push(Object.assign(Object.assign({}, e), { searchStringScore: bestScore }));
178
+ return accu;
179
+ }
180
+ if (['exact', 'extremely-similar'].includes(fuseScoreToSimilarity(englishMuscleGroupSimilarityScore))) {
181
+ accu.push(Object.assign(Object.assign({}, e), { searchStringScore: bestScore }));
182
+ return accu;
183
+ }
184
+ if (['exact', 'extremely-similar'].includes(fuseScoreToSimilarity(muscleGroupSimilarityScore))) {
185
+ accu.push(Object.assign(Object.assign({}, e), { searchStringScore: bestScore }));
186
+ return accu;
187
+ }
188
+ if (e.is_custom && 'custom'.includes(searchableSearchText)) {
189
+ accu.push(e);
190
+ return accu;
191
+ }
192
+ const splitSearch = searchableSearchText.split(' ');
193
+ const splitTitle = [
194
+ searchableLocalizedTitle,
195
+ searchableEnglishTitle,
196
+ searchableTags,
197
+ searchableAka,
198
+ searchableMisspelledDumbbellEquipment,
199
+ ]
200
+ .join(' ')
201
+ .split(' ');
202
+ // Find:
203
+ // title ['arnold', 'press', '(barbell)']
204
+ // search ['press', 'bar']
205
+ if ((0, lodash_1.intersectionWith)(splitSearch, splitTitle, (search, title) => title.includes(search)).length === splitSearch.length) {
206
+ accu.push(e);
207
+ return accu;
208
+ }
209
+ return accu;
210
+ }, []);
211
+ };
87
212
  const orderExercisesWithSearchText = (searchText, exercises) => {
88
213
  if (searchText.length === 0) {
89
214
  return exercises;
package/built/index.d.ts CHANGED
@@ -72,6 +72,7 @@ export interface AccountResponse {
72
72
  is_coached: boolean;
73
73
  birthday?: string;
74
74
  sex?: Sex;
75
+ height_cm?: number;
75
76
  }
76
77
  export interface UserAccountResponse {
77
78
  id: string;
@@ -106,6 +107,7 @@ export interface UserAccountResponse {
106
107
  sex?: Sex;
107
108
  email_consent: boolean;
108
109
  email_verified: boolean;
110
+ height_cm?: number;
109
111
  }
110
112
  export interface CoachAppPushTarget {
111
113
  type: 'android' | 'ios';
@@ -160,6 +162,7 @@ export interface AccountUpdate {
160
162
  accepted_terms_and_conditions?: boolean;
161
163
  sex?: Sex;
162
164
  birthday?: string;
165
+ height_cm?: number;
163
166
  }
164
167
  export interface AppleSignUpRequest {
165
168
  email?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hevy-shared",
3
- "version": "1.0.699",
3
+ "version": "1.0.701",
4
4
  "description": "",
5
5
  "main": "built/index.js",
6
6
  "types": "built/index.d.ts",
@@ -35,6 +35,7 @@
35
35
  "typescript": "5.8.2"
36
36
  },
37
37
  "dependencies": {
38
+ "fuse.js": "^7.0.0",
38
39
  "lodash": "^4.17.21"
39
40
  }
40
41
  }