lubba-encoder 1.0.0

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/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # Lubba Encoder
2
+
3
+ Encodes Lubba workouts from/to yaml, uint8 array and base64 hash.
4
+
5
+ Can be used to create workouts that the Lubba app can import.
@@ -0,0 +1,279 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var encoding = require('lib0/dist/encoding.cjs');
6
+ var decoding = require('lib0/dist/decoding.cjs');
7
+ var yaml = require('yaml');
8
+
9
+ function _interopNamespace(e) {
10
+ if (e && e.__esModule) return e;
11
+ var n = Object.create(null);
12
+ if (e) {
13
+ Object.keys(e).forEach(function (k) {
14
+ if (k !== 'default') {
15
+ var d = Object.getOwnPropertyDescriptor(e, k);
16
+ Object.defineProperty(n, k, d.get ? d : {
17
+ enumerable: true,
18
+ get: function () { return e[k]; }
19
+ });
20
+ }
21
+ });
22
+ }
23
+ n["default"] = e;
24
+ return Object.freeze(n);
25
+ }
26
+
27
+ var encoding__namespace = /*#__PURE__*/_interopNamespace(encoding);
28
+ var decoding__namespace = /*#__PURE__*/_interopNamespace(decoding);
29
+
30
+ /* global btoa atob */
31
+
32
+ const reverseObject = (object) => (
33
+ Object.keys(object).reduce((res, key) => { res[object[key]] = key; return res }, {})
34
+ );
35
+
36
+ const partTitleMap = {
37
+ Warmup: 1,
38
+ Activity: 2,
39
+ Fast: 3,
40
+ Recover: 4,
41
+ Rest: 5,
42
+ Cooldown: 6,
43
+ Ski: 7,
44
+ Run: 8,
45
+ Skate: 9,
46
+ Walk: 10
47
+ };
48
+ const layoutMap = {
49
+ 1: 1,
50
+ '2+1': 2,
51
+ '2+2': 3,
52
+ '2+3': 4
53
+ };
54
+ const valueGoalTypeMap = {
55
+ duration: 1,
56
+ distance: 2,
57
+ distanceTest: 3,
58
+ paceCurrent: 4,
59
+ paceTest: 5,
60
+ paceTest2: 6,
61
+ paceAverageDuration: 7,
62
+ bpm: 8,
63
+ bpmAverageDuration: 9,
64
+ intervalLeft: 10,
65
+ durationInterval: 11,
66
+ distanceInterval: 12,
67
+ desiredAccuracy: 13,
68
+ pace: 14, // used for target type
69
+ paceSplits: 15
70
+ };
71
+
72
+ const partTitleMapReverse = reverseObject(partTitleMap);
73
+ const layoutMapReverse = reverseObject(layoutMap);
74
+ const valueGoalTypeMapReverse = reverseObject(valueGoalTypeMap);
75
+
76
+ function getBit (number, bitPosition) {
77
+ return (number & (1 << bitPosition)) === 0 ? 0 : 1
78
+ }
79
+
80
+ function updateBit (number, bitPosition, bitValue) {
81
+ const bitValueNormalized = bitValue ? 1 : 0;
82
+ const clearMask = ~(1 << bitPosition);
83
+ return (number & clearMask) | (bitValueNormalized << bitPosition)
84
+ }
85
+
86
+ function encodePart (part, encoder) {
87
+ // Set flags for nil values
88
+ let flagsA = 0;
89
+ let flagsB = 0;
90
+
91
+ flagsA = updateBit(flagsA, 0, part.title != null);
92
+ flagsA = updateBit(flagsA, 1, part.layout != null);
93
+ flagsA = updateBit(flagsA, 2, part.layoutValues != null && part.layoutValues.length > 0);
94
+ flagsA = updateBit(flagsA, 3, part.goalType != null);
95
+ flagsA = updateBit(flagsA, 4, part.goalValue != null);
96
+ flagsA = updateBit(flagsA, 5, part.goalIsVariable === true);
97
+ flagsA = updateBit(flagsA, 6, part.targetType != null);
98
+ flagsA = updateBit(flagsA, 7, part.targetMin != null);
99
+
100
+ flagsB = updateBit(flagsB, 0, part.targetMax != null);
101
+ flagsB = updateBit(flagsB, 1, part.repeats != null && part.repeats > 0);
102
+ flagsB = updateBit(flagsB, 2, part.repeatParts != null && part.repeatParts.length > 0);
103
+
104
+ encoding__namespace.writeUint8(encoder, flagsA);
105
+ encoding__namespace.writeUint8(encoder, flagsB);
106
+
107
+ // Use part title map, or custom title
108
+ if (part.title != null) {
109
+ const titleIndex = partTitleMap[part.title];
110
+ if (titleIndex) {
111
+ encoding__namespace.writeUint8(encoder, titleIndex);
112
+ } else {
113
+ encoding__namespace.writeUint8(encoder, 0); // 0 indicates custom string
114
+ encoding__namespace.writeVarString(encoder, part.title);
115
+ }
116
+ }
117
+
118
+ if (part.layout != null) encoding__namespace.writeUint8(encoder, layoutMap[part.layout]);
119
+ if (part.layoutValues != null && part.layoutValues.length > 0) {
120
+ encoding__namespace.writeUint8(encoder, part.layoutValues.length);
121
+
122
+ for (const value of part.layoutValues) {
123
+ encoding__namespace.writeUint8(encoder, valueGoalTypeMap[value]);
124
+ }
125
+ }
126
+
127
+ if (part.goalType != null) encoding__namespace.writeUint8(encoder, valueGoalTypeMap[part.goalType]);
128
+ if (part.goalValue != null) encoding__namespace.writeFloat64(encoder, part.goalValue);
129
+
130
+ if (part.targetType != null) encoding__namespace.writeUint8(encoder, valueGoalTypeMap[part.targetType]);
131
+ if (part.targetMin != null) encoding__namespace.writeFloat64(encoder, part.targetMin);
132
+ if (part.targetMax != null) encoding__namespace.writeFloat64(encoder, part.targetMax);
133
+
134
+ if (part.repeats != null && part.repeats > 0) encoding__namespace.writeUint8(encoder, part.repeats);
135
+
136
+ if (part.repeatParts != null && part.repeatParts.length > 0) {
137
+ encoding__namespace.writeUint8(encoder, part.repeatParts.length);
138
+
139
+ for (const subPart of part.repeatParts) {
140
+ encodePart(subPart, encoder);
141
+ }
142
+ }
143
+ }
144
+
145
+ function decodePart (decoder) {
146
+ const part = {};
147
+
148
+ // Some flags to indicate what values is non-nil
149
+ const flagsA = decoding__namespace.readUint8(decoder);
150
+ const flagsB = decoding__namespace.readUint8(decoder);
151
+
152
+ const hasTitle = getBit(flagsA, 0);
153
+ const hasLayout = getBit(flagsA, 1);
154
+ const hasLayoutValues = getBit(flagsA, 2);
155
+ const hasGoalType = getBit(flagsA, 3);
156
+ const hasGoalValue = getBit(flagsA, 4);
157
+ const hasGoalIsVariable = getBit(flagsA, 5);
158
+ const hasTargetType = getBit(flagsA, 6);
159
+ const hasTargetMin = getBit(flagsA, 7);
160
+
161
+ const hasTargetMax = getBit(flagsB, 0);
162
+ const hasRepeats = getBit(flagsB, 1);
163
+ const hasRepeatParts = getBit(flagsB, 2);
164
+
165
+ // Use part title map, or custom title
166
+ if (hasTitle) {
167
+ const titleIndex = decoding__namespace.readUint8(decoder);
168
+ const titleIsCustomString = titleIndex === 0;
169
+ part.title = titleIsCustomString ? decoding__namespace.readVarString(decoder) : partTitleMapReverse[titleIndex];
170
+ }
171
+
172
+ if (hasLayout) part.layout = layoutMapReverse[decoding__namespace.readUint8(decoder)];
173
+ if (hasLayoutValues) {
174
+ const amountValues = decoding__namespace.readUint8(decoder);
175
+ part.layoutValues = [];
176
+
177
+ for (let j = 0; j < amountValues; j++) {
178
+ part.layoutValues.push(valueGoalTypeMapReverse[decoding__namespace.readUint8(decoder)]);
179
+ }
180
+ }
181
+
182
+ if (hasGoalType) part.goalType = valueGoalTypeMapReverse[decoding__namespace.readUint8(decoder)];
183
+ if (hasGoalValue) part.goalValue = decoding__namespace.readFloat64(decoder);
184
+ if (hasGoalIsVariable) part.goalIsVariable = true;
185
+
186
+ if (hasTargetType) part.targetType = valueGoalTypeMapReverse[decoding__namespace.readUint8(decoder)];
187
+ if (hasTargetMin) part.targetMin = decoding__namespace.readFloat64(decoder);
188
+ if (hasTargetMax) part.targetMax = decoding__namespace.readFloat64(decoder);
189
+
190
+ if (hasRepeats) part.repeats = decoding__namespace.readUint8(decoder);
191
+ if (hasRepeatParts) {
192
+ const amountParts = decoding__namespace.readUint8(decoder);
193
+ part.repeatParts = [];
194
+
195
+ for (let j = 0; j < amountParts; j++) {
196
+ part.repeatParts.push(decodePart(decoder));
197
+ }
198
+ }
199
+
200
+ return part
201
+ }
202
+
203
+ function encodeYamlToLubbaData (yaml$1, encodingVersion) {
204
+ const object = yaml.parse(yaml$1);
205
+
206
+ return encodeJsonObjectToLubbaData(object, encodingVersion)
207
+ }
208
+
209
+ function encodeJsonObjectToLubbaData (object, encodingVersion) {
210
+ const encoder = encoding__namespace.createEncoder();
211
+
212
+ encoding__namespace.writeUint8(encoder, encodingVersion);
213
+ encoding__namespace.writeUint8(encoder, object.isMetric ? 1 : 0);
214
+ encoding__namespace.writeVarString(encoder, object.title);
215
+ encoding__namespace.writeUint32(encoder, object.activityType);
216
+ encoding__namespace.writeUint8(encoder, object.locationType);
217
+
218
+ // Parts
219
+ const amountParts = (object.parts && object.parts.length) || 0;
220
+ encoding__namespace.writeUint8(encoder, amountParts);
221
+
222
+ for (const part of object.parts) {
223
+ encodePart(part, encoder);
224
+ }
225
+
226
+ return encoding__namespace.toUint8Array(encoder)
227
+ }
228
+
229
+ function decodeLubbaData (data) {
230
+ const decoder = decoding__namespace.createDecoder(data);
231
+ const encodingVersion = decoding__namespace.readUint8(decoder);
232
+
233
+ if (encodingVersion === 1) {
234
+ const decoded = {
235
+ isCustom: true // Assume all are custom
236
+ };
237
+
238
+ decoded.isMetric = decoding__namespace.readUint8(decoder) === 1;
239
+ decoded.title = decoding__namespace.readVarString(decoder);
240
+ decoded.activityType = decoding__namespace.readUint32(decoder);
241
+ decoded.locationType = decoding__namespace.readUint8(decoder);
242
+
243
+ // Parts
244
+ const amountParts = decoding__namespace.readUint8(decoder);
245
+ decoded.parts = [];
246
+ for (let i = 0; i < amountParts; i++) {
247
+ decoded.parts.push(decodePart(decoder));
248
+ }
249
+
250
+ return decoded
251
+ }
252
+
253
+ return null
254
+ }
255
+
256
+ // Some short encoding (base64)
257
+ function encodeLubbaDataToHash (data) {
258
+ let s = '';
259
+ for (const byte of data) {
260
+ s += String.fromCharCode(byte);
261
+ }
262
+ return btoa(s)
263
+ }
264
+
265
+ function decodeLubbaDataFromHash (hash) {
266
+ const a = atob(hash);
267
+ const bytes = new Uint8Array(a.length);
268
+ for (let i = 0; i < a.length; i++) {
269
+ bytes[i] = a.charCodeAt(i);
270
+ }
271
+ return bytes
272
+ }
273
+
274
+ exports.decodeLubbaData = decodeLubbaData;
275
+ exports.decodeLubbaDataFromHash = decodeLubbaDataFromHash;
276
+ exports.encodeJsonObjectToLubbaData = encodeJsonObjectToLubbaData;
277
+ exports.encodeLubbaDataToHash = encodeLubbaDataToHash;
278
+ exports.encodeYamlToLubbaData = encodeYamlToLubbaData;
279
+ //# sourceMappingURL=lubba-encoder.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lubba-encoder.cjs","sources":["../src/index.js"],"sourcesContent":["/* global btoa atob */\nimport * as encoding from 'lib0/dist/encoding.cjs'\nimport * as decoding from 'lib0/dist/decoding.cjs'\nimport { parse as parseYaml } from 'yaml'\n\nconst reverseObject = (object) => (\n Object.keys(object).reduce((res, key) => { res[object[key]] = key; return res }, {})\n)\n\nconst partTitleMap = {\n Warmup: 1,\n Activity: 2,\n Fast: 3,\n Recover: 4,\n Rest: 5,\n Cooldown: 6,\n Ski: 7,\n Run: 8,\n Skate: 9,\n Walk: 10\n}\nconst layoutMap = {\n 1: 1,\n '2+1': 2,\n '2+2': 3,\n '2+3': 4\n}\nconst valueGoalTypeMap = {\n duration: 1,\n distance: 2,\n distanceTest: 3,\n paceCurrent: 4,\n paceTest: 5,\n paceTest2: 6,\n paceAverageDuration: 7,\n bpm: 8,\n bpmAverageDuration: 9,\n intervalLeft: 10,\n durationInterval: 11,\n distanceInterval: 12,\n desiredAccuracy: 13,\n pace: 14, // used for target type\n paceSplits: 15\n}\n\nconst partTitleMapReverse = reverseObject(partTitleMap)\nconst layoutMapReverse = reverseObject(layoutMap)\nconst valueGoalTypeMapReverse = reverseObject(valueGoalTypeMap)\n\nfunction getBit (number, bitPosition) {\n return (number & (1 << bitPosition)) === 0 ? 0 : 1\n}\n\nfunction updateBit (number, bitPosition, bitValue) {\n const bitValueNormalized = bitValue ? 1 : 0\n const clearMask = ~(1 << bitPosition)\n return (number & clearMask) | (bitValueNormalized << bitPosition)\n}\n\nfunction encodePart (part, encoder) {\n // Set flags for nil values\n let flagsA = 0\n let flagsB = 0\n\n flagsA = updateBit(flagsA, 0, part.title != null)\n flagsA = updateBit(flagsA, 1, part.layout != null)\n flagsA = updateBit(flagsA, 2, part.layoutValues != null && part.layoutValues.length > 0)\n flagsA = updateBit(flagsA, 3, part.goalType != null)\n flagsA = updateBit(flagsA, 4, part.goalValue != null)\n flagsA = updateBit(flagsA, 5, part.goalIsVariable === true)\n flagsA = updateBit(flagsA, 6, part.targetType != null)\n flagsA = updateBit(flagsA, 7, part.targetMin != null)\n\n flagsB = updateBit(flagsB, 0, part.targetMax != null)\n flagsB = updateBit(flagsB, 1, part.repeats != null && part.repeats > 0)\n flagsB = updateBit(flagsB, 2, part.repeatParts != null && part.repeatParts.length > 0)\n\n encoding.writeUint8(encoder, flagsA)\n encoding.writeUint8(encoder, flagsB)\n\n // Use part title map, or custom title\n if (part.title != null) {\n const titleIndex = partTitleMap[part.title]\n if (titleIndex) {\n encoding.writeUint8(encoder, titleIndex)\n } else {\n encoding.writeUint8(encoder, 0) // 0 indicates custom string\n encoding.writeVarString(encoder, part.title)\n }\n }\n\n if (part.layout != null) encoding.writeUint8(encoder, layoutMap[part.layout])\n if (part.layoutValues != null && part.layoutValues.length > 0) {\n encoding.writeUint8(encoder, part.layoutValues.length)\n\n for (const value of part.layoutValues) {\n encoding.writeUint8(encoder, valueGoalTypeMap[value])\n }\n }\n\n if (part.goalType != null) encoding.writeUint8(encoder, valueGoalTypeMap[part.goalType])\n if (part.goalValue != null) encoding.writeFloat64(encoder, part.goalValue)\n\n if (part.targetType != null) encoding.writeUint8(encoder, valueGoalTypeMap[part.targetType])\n if (part.targetMin != null) encoding.writeFloat64(encoder, part.targetMin)\n if (part.targetMax != null) encoding.writeFloat64(encoder, part.targetMax)\n\n if (part.repeats != null && part.repeats > 0) encoding.writeUint8(encoder, part.repeats)\n\n if (part.repeatParts != null && part.repeatParts.length > 0) {\n encoding.writeUint8(encoder, part.repeatParts.length)\n\n for (const subPart of part.repeatParts) {\n encodePart(subPart, encoder)\n }\n }\n}\n\nfunction decodePart (decoder) {\n const part = {}\n\n // Some flags to indicate what values is non-nil\n const flagsA = decoding.readUint8(decoder)\n const flagsB = decoding.readUint8(decoder)\n\n const hasTitle = getBit(flagsA, 0)\n const hasLayout = getBit(flagsA, 1)\n const hasLayoutValues = getBit(flagsA, 2)\n const hasGoalType = getBit(flagsA, 3)\n const hasGoalValue = getBit(flagsA, 4)\n const hasGoalIsVariable = getBit(flagsA, 5)\n const hasTargetType = getBit(flagsA, 6)\n const hasTargetMin = getBit(flagsA, 7)\n\n const hasTargetMax = getBit(flagsB, 0)\n const hasRepeats = getBit(flagsB, 1)\n const hasRepeatParts = getBit(flagsB, 2)\n\n // Use part title map, or custom title\n if (hasTitle) {\n const titleIndex = decoding.readUint8(decoder)\n const titleIsCustomString = titleIndex === 0\n part.title = titleIsCustomString ? decoding.readVarString(decoder) : partTitleMapReverse[titleIndex]\n }\n\n if (hasLayout) part.layout = layoutMapReverse[decoding.readUint8(decoder)]\n if (hasLayoutValues) {\n const amountValues = decoding.readUint8(decoder)\n part.layoutValues = []\n\n for (let j = 0; j < amountValues; j++) {\n part.layoutValues.push(valueGoalTypeMapReverse[decoding.readUint8(decoder)])\n }\n }\n\n if (hasGoalType) part.goalType = valueGoalTypeMapReverse[decoding.readUint8(decoder)]\n if (hasGoalValue) part.goalValue = decoding.readFloat64(decoder)\n if (hasGoalIsVariable) part.goalIsVariable = true\n\n if (hasTargetType) part.targetType = valueGoalTypeMapReverse[decoding.readUint8(decoder)]\n if (hasTargetMin) part.targetMin = decoding.readFloat64(decoder)\n if (hasTargetMax) part.targetMax = decoding.readFloat64(decoder)\n\n if (hasRepeats) part.repeats = decoding.readUint8(decoder)\n if (hasRepeatParts) {\n const amountParts = decoding.readUint8(decoder)\n part.repeatParts = []\n\n for (let j = 0; j < amountParts; j++) {\n part.repeatParts.push(decodePart(decoder))\n }\n }\n\n return part\n}\n\nexport function encodeYamlToLubbaData (yaml, encodingVersion) {\n const object = parseYaml(yaml)\n\n return encodeJsonObjectToLubbaData(object, encodingVersion)\n}\n\nexport function encodeJsonObjectToLubbaData (object, encodingVersion) {\n const encoder = encoding.createEncoder()\n\n encoding.writeUint8(encoder, encodingVersion)\n encoding.writeUint8(encoder, object.isMetric ? 1 : 0)\n encoding.writeVarString(encoder, object.title)\n encoding.writeUint32(encoder, object.activityType)\n encoding.writeUint8(encoder, object.locationType)\n\n // Parts\n const amountParts = (object.parts && object.parts.length) || 0\n encoding.writeUint8(encoder, amountParts)\n\n for (const part of object.parts) {\n encodePart(part, encoder)\n }\n\n return encoding.toUint8Array(encoder)\n}\n\nexport function decodeLubbaData (data) {\n const decoder = decoding.createDecoder(data)\n const encodingVersion = decoding.readUint8(decoder)\n\n if (encodingVersion === 1) {\n const decoded = {\n isCustom: true // Assume all are custom\n }\n\n decoded.isMetric = decoding.readUint8(decoder) === 1\n decoded.title = decoding.readVarString(decoder)\n decoded.activityType = decoding.readUint32(decoder)\n decoded.locationType = decoding.readUint8(decoder)\n\n // Parts\n const amountParts = decoding.readUint8(decoder)\n decoded.parts = []\n for (let i = 0; i < amountParts; i++) {\n decoded.parts.push(decodePart(decoder))\n }\n\n return decoded\n }\n\n return null\n}\n\n// Some short encoding (base64)\nexport function encodeLubbaDataToHash (data) {\n let s = ''\n for (const byte of data) {\n s += String.fromCharCode(byte)\n }\n return btoa(s)\n}\n\nexport function decodeLubbaDataFromHash (hash) {\n const a = atob(hash)\n const bytes = new Uint8Array(a.length)\n for (let i = 0; i < a.length; i++) {\n bytes[i] = a.charCodeAt(i)\n }\n return bytes\n}\n"],"names":["encoding","decoding","yaml","parseYaml"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "lubba-encoder",
3
+ "version": "1.0.0",
4
+ "main": "./dist/lubba-encoder.cjs",
5
+ "license": "MIT",
6
+ "exports": {
7
+ "./lubba-encoder": "./dist/lubba-encoder.cjs"
8
+ },
9
+ "scripts": {
10
+ "dist": "rm -rf dist && rollup -c",
11
+ "prepare": "npm run dist",
12
+ "test": "jest",
13
+ "lint": "standard"
14
+ },
15
+ "devDependencies": {
16
+ "jest": "27.5.1",
17
+ "rollup": "^2.58.3",
18
+ "standard": "16.0.4"
19
+ },
20
+ "dependencies": {
21
+ "lib0": "0.2.49",
22
+ "yaml": "2.0.0"
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "https://github.com/hesselbom/lubba-encoder"
27
+ }
28
+ }
@@ -0,0 +1,12 @@
1
+ export default [
2
+ {
3
+ input: 'src/index.js',
4
+ external: id => /^(lib0|yaml)/.test(id),
5
+ output: {
6
+ name: 'LubbaEncoder',
7
+ file: 'dist/lubba-encoder.cjs',
8
+ format: 'cjs',
9
+ sourcemap: true
10
+ }
11
+ }
12
+ ]
package/src/index.js ADDED
@@ -0,0 +1,246 @@
1
+ /* global btoa atob */
2
+ import * as encoding from 'lib0/dist/encoding.cjs'
3
+ import * as decoding from 'lib0/dist/decoding.cjs'
4
+ import { parse as parseYaml } from 'yaml'
5
+
6
+ const reverseObject = (object) => (
7
+ Object.keys(object).reduce((res, key) => { res[object[key]] = key; return res }, {})
8
+ )
9
+
10
+ const partTitleMap = {
11
+ Warmup: 1,
12
+ Activity: 2,
13
+ Fast: 3,
14
+ Recover: 4,
15
+ Rest: 5,
16
+ Cooldown: 6,
17
+ Ski: 7,
18
+ Run: 8,
19
+ Skate: 9,
20
+ Walk: 10
21
+ }
22
+ const layoutMap = {
23
+ 1: 1,
24
+ '2+1': 2,
25
+ '2+2': 3,
26
+ '2+3': 4
27
+ }
28
+ const valueGoalTypeMap = {
29
+ duration: 1,
30
+ distance: 2,
31
+ distanceTest: 3,
32
+ paceCurrent: 4,
33
+ paceTest: 5,
34
+ paceTest2: 6,
35
+ paceAverageDuration: 7,
36
+ bpm: 8,
37
+ bpmAverageDuration: 9,
38
+ intervalLeft: 10,
39
+ durationInterval: 11,
40
+ distanceInterval: 12,
41
+ desiredAccuracy: 13,
42
+ pace: 14, // used for target type
43
+ paceSplits: 15
44
+ }
45
+
46
+ const partTitleMapReverse = reverseObject(partTitleMap)
47
+ const layoutMapReverse = reverseObject(layoutMap)
48
+ const valueGoalTypeMapReverse = reverseObject(valueGoalTypeMap)
49
+
50
+ function getBit (number, bitPosition) {
51
+ return (number & (1 << bitPosition)) === 0 ? 0 : 1
52
+ }
53
+
54
+ function updateBit (number, bitPosition, bitValue) {
55
+ const bitValueNormalized = bitValue ? 1 : 0
56
+ const clearMask = ~(1 << bitPosition)
57
+ return (number & clearMask) | (bitValueNormalized << bitPosition)
58
+ }
59
+
60
+ function encodePart (part, encoder) {
61
+ // Set flags for nil values
62
+ let flagsA = 0
63
+ let flagsB = 0
64
+
65
+ flagsA = updateBit(flagsA, 0, part.title != null)
66
+ flagsA = updateBit(flagsA, 1, part.layout != null)
67
+ flagsA = updateBit(flagsA, 2, part.layoutValues != null && part.layoutValues.length > 0)
68
+ flagsA = updateBit(flagsA, 3, part.goalType != null)
69
+ flagsA = updateBit(flagsA, 4, part.goalValue != null)
70
+ flagsA = updateBit(flagsA, 5, part.goalIsVariable === true)
71
+ flagsA = updateBit(flagsA, 6, part.targetType != null)
72
+ flagsA = updateBit(flagsA, 7, part.targetMin != null)
73
+
74
+ flagsB = updateBit(flagsB, 0, part.targetMax != null)
75
+ flagsB = updateBit(flagsB, 1, part.repeats != null && part.repeats > 0)
76
+ flagsB = updateBit(flagsB, 2, part.repeatParts != null && part.repeatParts.length > 0)
77
+
78
+ encoding.writeUint8(encoder, flagsA)
79
+ encoding.writeUint8(encoder, flagsB)
80
+
81
+ // Use part title map, or custom title
82
+ if (part.title != null) {
83
+ const titleIndex = partTitleMap[part.title]
84
+ if (titleIndex) {
85
+ encoding.writeUint8(encoder, titleIndex)
86
+ } else {
87
+ encoding.writeUint8(encoder, 0) // 0 indicates custom string
88
+ encoding.writeVarString(encoder, part.title)
89
+ }
90
+ }
91
+
92
+ if (part.layout != null) encoding.writeUint8(encoder, layoutMap[part.layout])
93
+ if (part.layoutValues != null && part.layoutValues.length > 0) {
94
+ encoding.writeUint8(encoder, part.layoutValues.length)
95
+
96
+ for (const value of part.layoutValues) {
97
+ encoding.writeUint8(encoder, valueGoalTypeMap[value])
98
+ }
99
+ }
100
+
101
+ if (part.goalType != null) encoding.writeUint8(encoder, valueGoalTypeMap[part.goalType])
102
+ if (part.goalValue != null) encoding.writeFloat64(encoder, part.goalValue)
103
+
104
+ if (part.targetType != null) encoding.writeUint8(encoder, valueGoalTypeMap[part.targetType])
105
+ if (part.targetMin != null) encoding.writeFloat64(encoder, part.targetMin)
106
+ if (part.targetMax != null) encoding.writeFloat64(encoder, part.targetMax)
107
+
108
+ if (part.repeats != null && part.repeats > 0) encoding.writeUint8(encoder, part.repeats)
109
+
110
+ if (part.repeatParts != null && part.repeatParts.length > 0) {
111
+ encoding.writeUint8(encoder, part.repeatParts.length)
112
+
113
+ for (const subPart of part.repeatParts) {
114
+ encodePart(subPart, encoder)
115
+ }
116
+ }
117
+ }
118
+
119
+ function decodePart (decoder) {
120
+ const part = {}
121
+
122
+ // Some flags to indicate what values is non-nil
123
+ const flagsA = decoding.readUint8(decoder)
124
+ const flagsB = decoding.readUint8(decoder)
125
+
126
+ const hasTitle = getBit(flagsA, 0)
127
+ const hasLayout = getBit(flagsA, 1)
128
+ const hasLayoutValues = getBit(flagsA, 2)
129
+ const hasGoalType = getBit(flagsA, 3)
130
+ const hasGoalValue = getBit(flagsA, 4)
131
+ const hasGoalIsVariable = getBit(flagsA, 5)
132
+ const hasTargetType = getBit(flagsA, 6)
133
+ const hasTargetMin = getBit(flagsA, 7)
134
+
135
+ const hasTargetMax = getBit(flagsB, 0)
136
+ const hasRepeats = getBit(flagsB, 1)
137
+ const hasRepeatParts = getBit(flagsB, 2)
138
+
139
+ // Use part title map, or custom title
140
+ if (hasTitle) {
141
+ const titleIndex = decoding.readUint8(decoder)
142
+ const titleIsCustomString = titleIndex === 0
143
+ part.title = titleIsCustomString ? decoding.readVarString(decoder) : partTitleMapReverse[titleIndex]
144
+ }
145
+
146
+ if (hasLayout) part.layout = layoutMapReverse[decoding.readUint8(decoder)]
147
+ if (hasLayoutValues) {
148
+ const amountValues = decoding.readUint8(decoder)
149
+ part.layoutValues = []
150
+
151
+ for (let j = 0; j < amountValues; j++) {
152
+ part.layoutValues.push(valueGoalTypeMapReverse[decoding.readUint8(decoder)])
153
+ }
154
+ }
155
+
156
+ if (hasGoalType) part.goalType = valueGoalTypeMapReverse[decoding.readUint8(decoder)]
157
+ if (hasGoalValue) part.goalValue = decoding.readFloat64(decoder)
158
+ if (hasGoalIsVariable) part.goalIsVariable = true
159
+
160
+ if (hasTargetType) part.targetType = valueGoalTypeMapReverse[decoding.readUint8(decoder)]
161
+ if (hasTargetMin) part.targetMin = decoding.readFloat64(decoder)
162
+ if (hasTargetMax) part.targetMax = decoding.readFloat64(decoder)
163
+
164
+ if (hasRepeats) part.repeats = decoding.readUint8(decoder)
165
+ if (hasRepeatParts) {
166
+ const amountParts = decoding.readUint8(decoder)
167
+ part.repeatParts = []
168
+
169
+ for (let j = 0; j < amountParts; j++) {
170
+ part.repeatParts.push(decodePart(decoder))
171
+ }
172
+ }
173
+
174
+ return part
175
+ }
176
+
177
+ export function encodeYamlToLubbaData (yaml, encodingVersion) {
178
+ const object = parseYaml(yaml)
179
+
180
+ return encodeJsonObjectToLubbaData(object, encodingVersion)
181
+ }
182
+
183
+ export function encodeJsonObjectToLubbaData (object, encodingVersion) {
184
+ const encoder = encoding.createEncoder()
185
+
186
+ encoding.writeUint8(encoder, encodingVersion)
187
+ encoding.writeUint8(encoder, object.isMetric ? 1 : 0)
188
+ encoding.writeVarString(encoder, object.title)
189
+ encoding.writeUint32(encoder, object.activityType)
190
+ encoding.writeUint8(encoder, object.locationType)
191
+
192
+ // Parts
193
+ const amountParts = (object.parts && object.parts.length) || 0
194
+ encoding.writeUint8(encoder, amountParts)
195
+
196
+ for (const part of object.parts) {
197
+ encodePart(part, encoder)
198
+ }
199
+
200
+ return encoding.toUint8Array(encoder)
201
+ }
202
+
203
+ export function decodeLubbaData (data) {
204
+ const decoder = decoding.createDecoder(data)
205
+ const encodingVersion = decoding.readUint8(decoder)
206
+
207
+ if (encodingVersion === 1) {
208
+ const decoded = {
209
+ isCustom: true // Assume all are custom
210
+ }
211
+
212
+ decoded.isMetric = decoding.readUint8(decoder) === 1
213
+ decoded.title = decoding.readVarString(decoder)
214
+ decoded.activityType = decoding.readUint32(decoder)
215
+ decoded.locationType = decoding.readUint8(decoder)
216
+
217
+ // Parts
218
+ const amountParts = decoding.readUint8(decoder)
219
+ decoded.parts = []
220
+ for (let i = 0; i < amountParts; i++) {
221
+ decoded.parts.push(decodePart(decoder))
222
+ }
223
+
224
+ return decoded
225
+ }
226
+
227
+ return null
228
+ }
229
+
230
+ // Some short encoding (base64)
231
+ export function encodeLubbaDataToHash (data) {
232
+ let s = ''
233
+ for (const byte of data) {
234
+ s += String.fromCharCode(byte)
235
+ }
236
+ return btoa(s)
237
+ }
238
+
239
+ export function decodeLubbaDataFromHash (hash) {
240
+ const a = atob(hash)
241
+ const bytes = new Uint8Array(a.length)
242
+ for (let i = 0; i < a.length; i++) {
243
+ bytes[i] = a.charCodeAt(i)
244
+ }
245
+ return bytes
246
+ }
@@ -0,0 +1,220 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
4
+ /* eslint-env jest */
5
+ import {
6
+ encodeYamlToLubbaData,
7
+ encodeJsonObjectToLubbaData,
8
+ decodeLubbaData,
9
+ encodeLubbaDataToHash,
10
+ decodeLubbaDataFromHash
11
+ } from './index'
12
+
13
+ const yaml = `id: 123abc-dsa
14
+ isMetric: true
15
+ title: Runcoach Speed 04/13
16
+ isCustom: true
17
+ activityType: 37
18
+ locationType: 3
19
+ parts:
20
+ - title: Warmup
21
+ goalType: distance
22
+ goalValue: 2e+3
23
+ - title: Drills
24
+ - title: Custom layout
25
+ layout: 2+2
26
+ layoutValues:
27
+ - distance
28
+ - duration
29
+ - bpm
30
+ - paceCurrent
31
+ - repeats: 4
32
+ repeatParts:
33
+ - title: Run
34
+ goalType: duration
35
+ goalValue: 3e+2
36
+ targetType: pace
37
+ targetMin: 2.56e+2
38
+ targetMax: 2.76e+2
39
+ - title: Recover
40
+ goalType: duration
41
+ goalValue: 1.8e+2
42
+ - title: Pace Splits
43
+ goalType: distance
44
+ goalValue: 2e+3
45
+ targetType: paceSplits
46
+ targetMin: 3e+2
47
+ targetMax: -1
48
+ - title: Cooldown
49
+ goalType: distance
50
+ goalIsVariable: true
51
+ targetType: bpm
52
+ targetMin: 0
53
+ targetMax: 3e+2
54
+ `
55
+
56
+ const jsonObject = {
57
+ id: '123abc-dsa',
58
+ isMetric: true,
59
+ title: 'Runcoach Speed 04/13',
60
+ isCustom: true,
61
+ activityType: 37,
62
+ locationType: 3,
63
+ parts: [
64
+ {
65
+ title: 'Warmup',
66
+ goalType: 'distance',
67
+ goalValue: 2000
68
+ },
69
+ {
70
+ title: 'Drills'
71
+ },
72
+ {
73
+ title: 'Custom layout',
74
+ layout: '2+2',
75
+ layoutValues: [
76
+ 'distance',
77
+ 'duration',
78
+ 'bpm',
79
+ 'paceCurrent'
80
+ ]
81
+ },
82
+ {
83
+ repeats: 4,
84
+ repeatParts: [
85
+ {
86
+ title: 'Run',
87
+ goalType: 'duration',
88
+ goalValue: 300,
89
+ targetType: 'pace',
90
+ targetMin: 256,
91
+ targetMax: 276
92
+ },
93
+ {
94
+ title: 'Recover',
95
+ goalType: 'duration',
96
+ goalValue: 180
97
+ },
98
+ {
99
+ title: 'Pace Splits',
100
+ goalType: 'distance',
101
+ goalValue: 2000,
102
+ targetType: 'paceSplits',
103
+ targetMin: 300,
104
+ targetMax: -1
105
+ }
106
+ ]
107
+ },
108
+ {
109
+ title: 'Cooldown',
110
+ goalType: 'distance',
111
+ goalIsVariable: true,
112
+ targetType: 'bpm',
113
+ targetMin: 0,
114
+ targetMax: 300
115
+ }
116
+ ]
117
+ }
118
+
119
+ const result = {
120
+ isMetric: true,
121
+ title: 'Runcoach Speed 04/13',
122
+ isCustom: true,
123
+ activityType: 37,
124
+ locationType: 3,
125
+ parts: [
126
+ { title: 'Warmup', goalType: 'distance', goalValue: 2000 },
127
+ { title: 'Drills' },
128
+ {
129
+ title: 'Custom layout',
130
+ layout: '2+2',
131
+ layoutValues: ['distance', 'duration', 'bpm', 'paceCurrent']
132
+ },
133
+ {
134
+ repeats: 4,
135
+ repeatParts: [
136
+ {
137
+ title: 'Run',
138
+ goalType: 'duration',
139
+ goalValue: 300,
140
+ targetType: 'pace',
141
+ targetMin: 256,
142
+ targetMax: 276
143
+ },
144
+ {
145
+ title: 'Recover',
146
+ goalType: 'duration',
147
+ goalValue: 180
148
+ },
149
+ {
150
+ title: 'Pace Splits',
151
+ goalType: 'distance',
152
+ goalValue: 2000,
153
+ targetType: 'paceSplits',
154
+ targetMin: 300,
155
+ targetMax: -1
156
+ }
157
+ ]
158
+ },
159
+ {
160
+ title: 'Cooldown',
161
+ goalType: 'distance',
162
+ goalIsVariable: true,
163
+ targetType: 'bpm',
164
+ targetMin: 0,
165
+ targetMax: 300
166
+ }
167
+ ]
168
+ }
169
+
170
+ test('works encoding and then decoding with yaml', () => {
171
+ const encodingVersion = 1
172
+ const encoded = encodeYamlToLubbaData(yaml, encodingVersion)
173
+ const decoded = decodeLubbaData(encoded)
174
+
175
+ // Expect first byte to be version
176
+ expect(encoded[0]).toBe(encodingVersion)
177
+
178
+ expect(decoded).toEqual(result)
179
+ })
180
+
181
+ test('works encoding and then decoding with json object', () => {
182
+ const encodingVersion = 1
183
+ const encoded = encodeJsonObjectToLubbaData(jsonObject, encodingVersion)
184
+ const decoded = decodeLubbaData(encoded)
185
+
186
+ // Expect first byte to be version
187
+ expect(encoded[0]).toBe(encodingVersion)
188
+
189
+ expect(decoded).toEqual(result)
190
+ })
191
+
192
+ test('works encoding for url hash', () => {
193
+ /*
194
+ https://stackoverflow.com/a/44532746/511949
195
+ Max hash size:
196
+
197
+ Chrome: 50K+
198
+ Firefox: 50K+
199
+ Safari (iOS): 50K+
200
+ Internet Explorer 11: Fails between 2,025 and 2,050
201
+ Microsoft Edge: Fails between 2,025 and 2,050
202
+ */
203
+ const MAX_HASH_SIZE = 2020
204
+ const encodingVersion = 1
205
+ const encoded = encodeYamlToLubbaData(yaml, encodingVersion)
206
+ const hash = encodeLubbaDataToHash(encoded)
207
+ const decodedFromHash = decodeLubbaDataFromHash(hash)
208
+ const decoded = decodeLubbaData(decodedFromHash)
209
+
210
+ // No need to check exact hash, but just that we get a string
211
+ expect(typeof hash).toBe('string')
212
+ expect(hash.length < MAX_HASH_SIZE).toBe(true)
213
+
214
+ // Log hash to be able to test against other implementations
215
+ console.log('Encoded bytes:', encoded.join(','))
216
+ console.log('Encoded hash:', hash)
217
+
218
+ expect(decodedFromHash).toEqual(encoded)
219
+ expect(decoded).toEqual(result)
220
+ })