running-tools

A collection of tools for runners and their coaches
git clone https://git.ashermorgan.net/running-tools/
Log | Files | Refs | README

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 }