migrations.ts (7031B)
1 /* 2 * Contains a function for migrating localStorage items after app updates 3 */ 4 5 import { defaultBatchOptions, defaultGlobalOptions, defaultPaceOptions, defaultRaceOptions, 6 defaultSplitOptions, defaultWorkoutOptions } from '@/core/calculators'; 7 import { deepCopy, getLocalStorage, setLocalStorage, unsetLocalStorage } from '@/core/utils'; 8 9 /* 10 * The type for string-indexable objects 11 */ 12 type dict = { 13 [key: string]: json, 14 }; 15 16 /* 17 * The type for JSON-compatable values 18 */ 19 type json = dict | string | number | boolean; 20 21 /** 22 * Get the value of an arbitrary property on an object 23 * @param {dict} obj The object 24 * @param {string} key The property path 25 * @returns {json | undefined} The value of the property 26 */ 27 function getObjProperty(obj: dict, key: string): json | undefined { 28 const keys = key.split("."); 29 while (true) { 30 if (keys.length === 0) { 31 return obj; 32 } else if (obj[keys[0]] === undefined) { 33 return undefined; 34 } else { 35 obj = obj[keys[0]] as dict; 36 keys.shift(); 37 } 38 } 39 } 40 41 /** 42 * Set the value of an arbitrary property on an object 43 * @param {dict} obj The object 44 * @param {string} key The property path 45 * @param {json} value The new value of the property 46 */ 47 function setObjProperty(obj: dict, key: string, value: json) { 48 const keys = key.split("."); 49 while (true) { 50 if (keys.length === 1) { 51 obj[keys[0]] = value; 52 return; 53 } else if (obj[keys[0]] === undefined) { 54 obj[keys[0]] = {}; 55 obj = obj[keys[0]] as dict; 56 keys.shift(); 57 } else { 58 obj = obj[keys[0]] as dict; 59 keys.shift(); 60 } 61 } 62 } 63 64 /** 65 * Remove an arbitrary property on an object 66 * @param {dict} obj The object 67 * @param {string} key The property path 68 */ 69 function removeObjProperty(obj: dict, key: string) { 70 const keys = key.split("."); 71 while (true) { 72 if (keys.length === 1) { 73 delete obj[keys[0]]; 74 return; 75 } else if (obj[keys[0]] === undefined) { 76 return; 77 } else { 78 obj = obj[keys[0]] as dict; 79 keys.shift(); 80 } 81 } 82 } 83 84 /** 85 * Add a property to an existing localStorage item 86 * @param {string} dest The localStorage item 87 * @param {string} key The localStorage item property path 88 * @param {object | string | number | boolean} value The default property value 89 */ 90 function addProperty(dest: string, key: string, value: object | string | number | boolean) { 91 const dest_value = getLocalStorage<dict>(dest); 92 if (dest_value !== null && getObjProperty(dest_value, key) === undefined) { 93 setObjProperty(dest_value, key, deepCopy(value as json)); 94 setLocalStorage(dest, dest_value); 95 } 96 } 97 98 /** 99 * Move an existing localStorage property to a new location 100 * @param {string} src The original localStorage item 101 * @param {string} src_key The original localStorage item property path 102 * @param {string} dest The new parent localStorage item 103 * @param {string} dest_key The new localStorage item property path 104 * @param {object} dest_default The default value of the new parent localStorage item 105 */ 106 function moveProperty(src: string, src_key: string, dest: string, dest_key: string, 107 dest_default: object) { 108 const src_value = getLocalStorage<dict>(src); 109 const dest_value = getLocalStorage<dict>(dest) || deepCopy(dest_default as dict); 110 if (src_value !== null && getObjProperty(src_value, src_key) !== undefined) { 111 setObjProperty(dest_value, dest_key, getObjProperty(src_value, src_key) as json); 112 setLocalStorage(dest, dest_value); 113 removeObjProperty(src_value, src_key); 114 setLocalStorage(src, src_value); 115 } 116 addProperty(dest, dest_key, getObjProperty(dest_default as dict, dest_key) as json); 117 } 118 119 /** 120 * Move an existing localStorage item to a property of another localStorage item 121 * @param {string} src The original localStorage item 122 * @param {string} dest The new parent localStorage item 123 * @param {string} dest_key The new localStorage item property path 124 * @param {object} dest_default The default value of the new parent localStorage item 125 */ 126 function moveItemToProperty(src: string, dest: string, dest_key: string, dest_default: object) { 127 const src_value = getLocalStorage<dict>(src); 128 const dest_value = getLocalStorage<dict>(dest) || deepCopy(dest_default as dict); 129 if (src_value !== null) { 130 setObjProperty(dest_value, dest_key, src_value); 131 setLocalStorage(dest, dest_value); 132 unsetLocalStorage(src); 133 } 134 addProperty(dest, dest_key, (dest_default as dict)[dest_key]); 135 } 136 137 /** 138 * Migrate outdated localStorage options 139 */ 140 export function migrateLocalStorage() { 141 // Move default-unit-system to global-options.defaultUnitSystem (>1.4.1) 142 moveItemToProperty('default-unit-system', 'global-options', 'defaultUnitSystem', 143 defaultGlobalOptions); 144 145 // Move {race,workout}-calculator-options.{model,riegelExponent} into 146 // global-options.racePredictionOptions (>1.4.1) 147 moveProperty('workout-calculator-options', 'model', 'global-options', 148 'racePredictionOptions.model', defaultGlobalOptions); 149 moveProperty('workout-calculator-options', 'riegelExponent', 'global-options', 150 'racePredictionOptions.riegelExponent', defaultGlobalOptions); 151 moveProperty('race-calculator-options', 'model', 'global-options', 152 'racePredictionOptions.model', defaultGlobalOptions); 153 moveProperty('race-calculator-options', 'riegelExponent', 'global-options', 154 'racePredictionOptions.riegelExponent', defaultGlobalOptions); 155 156 // Add label property to batch-calculator-options (>1.4.1) 157 addProperty('batch-calculator-options', 'label', defaultBatchOptions.label); 158 159 // Add customTargetNames property to workout-calculator-options (>1.4.1) 160 addProperty('workout-calculator-options', 'customTargetNames', 161 defaultWorkoutOptions.customTargetNames); 162 163 // Move *-calculator-input into *-calculator-options (>1.4.1) 164 moveItemToProperty('batch-calculator-input', 'batch-calculator-options', 165 'input', defaultBatchOptions); 166 moveItemToProperty('pace-calculator-input', 'pace-calculator-options', 167 'input', defaultPaceOptions); 168 moveItemToProperty('race-calculator-input', 'race-calculator-options', 169 'input', defaultRaceOptions); 170 moveItemToProperty('workout-calculator-input', 'workout-calculator-options', 171 'input', defaultWorkoutOptions); 172 173 // Move *-calculator-target-set into *-calculator-options (>1.4.1) 174 moveItemToProperty('pace-calculator-target-set', 'pace-calculator-options', 175 'selectedTargetSet', defaultPaceOptions); 176 moveItemToProperty('race-calculator-target-set', 'race-calculator-options', 177 'selectedTargetSet', defaultRaceOptions); 178 moveItemToProperty('split-calculator-target-set', 'split-calculator-options', 179 'selectedTargetSet', defaultSplitOptions); 180 moveItemToProperty('workout-calculator-target-set', 'workout-calculator-options', 181 'selectedTargetSet', defaultWorkoutOptions); 182 }