See #906 - stores settings in localStorage.
authorJakub Pawlowicz <contact@jakubpawlowicz.com>
Sat, 6 May 2017 06:38:29 +0000 (08:38 +0200)
committerJakub Pawlowicz <contact@jakubpawlowicz.com>
Sat, 6 May 2017 09:27:36 +0000 (11:27 +0200)
Why:

* It lets user customize settings once and reuse them at later time;
* user can always reset settings to default if needed.

docs/index.html
docs/js/settings.js

index ec26797..7fe77f2 100644 (file)
         </fieldset>
         <fieldset class="settings__group settings__group--advanced">
           <div class="settings__group__wrapper">
-            <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_0" name="level_0" checked disabled />
+            <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_0" name="level[0]" checked disabled />
             <label class="settings__label" for="level_0">level 0 optimizations</label>
           </div>
           <div class="settings__group__wrapper">
-            <input class="settings__option settings__option--checkbox js-settings-level-1 js-settings-option" type="checkbox" id="level_1" name="level_1" checked />
+            <input class="settings__option settings__option--checkbox js-settings-level-1 js-settings-option" type="checkbox" id="level_1" name="level[1]" checked />
             <label class="settings__label" for="level_1">level 1 optimizations</label>
           </div>
           <ul class="fine-grained-options js-settings-level-1-options" data-visibility-class="fine-grained-options--hidden">
             <li class="fine-grained-options__option">
-              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_1_cleanup_charsets" name="level_1[cleanupCharsets]" checked />
+              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_1_cleanup_charsets" name="level[1][cleanupCharsets]" checked />
               <label class="settings__label" for="level_1_cleanup_charsets">cleanup @charset at-rules</label>
             </li>
             <li class="fine-grained-options__option">
-              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_1_normalize_urls" name="level_1[normalizeUrls]" checked />
+              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_1_normalize_urls" name="level[1][normalizeUrls]" checked />
               <label class="settings__label" for="level_1_normalize_urls">normalize URLs</label>
             </li>
             <li class="fine-grained-options__option">
-              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_1_optimize_background" name="level_1[optimizeBackground]" checked />
+              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_1_optimize_background" name="level[1][optimizeBackground]" checked />
               <label class="settings__label" for="level_1_optimize_background">optimize <em>background</em> properties</label>
             </li>
             <li class="fine-grained-options__option">
-              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_1_optimize_border_radius" name="level_1[optimizeBorderRadius]" checked />
+              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_1_optimize_border_radius" name="level[1][optimizeBorderRadius]" checked />
               <label class="settings__label" for="level_1_optimize_border_radius">optimize <em>border-radius</em> properties</label>
             </li>
             <li class="fine-grained-options__option">
-              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_1_optimize_filter" name="level_1[optimizeFilter]" checked />
+              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_1_optimize_filter" name="level[1][optimizeFilter]" checked />
               <label class="settings__label" for="level_1_optimize_filter">optimize <em>filter</em> / <em>-ms-filter</em> properties</label>
             </li>
             <li class="fine-grained-options__option">
-              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_1_optimize_font" name="level_1[optimizeFont]" checked />
+              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_1_optimize_font" name="level[1][optimizeFont]" checked />
               <label class="settings__label" for="level_1_optimize_font">optimize <em>font</em> properties</label>
             </li>
             <li class="fine-grained-options__option">
-              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_1_optimize_font_weight" name="level_1[optimizeFontWeight]" checked />
+              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_1_optimize_font_weight" name="level[1][optimizeFontWeight]" checked />
               <label class="settings__label" for="level_1_optimize_font_weight">optimize <em>font-weight</em> properties</label>
             </li>
             <li class="fine-grained-options__option">
-              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_1_optimize_outline" name="level_1[optimizeOutline]" checked />
+              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_1_optimize_outline" name="level[1][optimizeOutline]" checked />
               <label class="settings__label" for="level_1_optimize_outline">optimize <em>outline</em> properties</label>
             </li>
             <li class="fine-grained-options__option">
-              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_1_remove_negative_paddings" name="level_1[removeNegativePaddings]" checked />
+              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_1_remove_negative_paddings" name="level[1][removeNegativePaddings]" checked />
               <label class="settings__label" for="level_1_remove_negative_paddings">remove negative <em>padding</em>s</label>
             </li>
             <li class="fine-grained-options__option">
-              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_1_remove_quotes" name="level_1[removeQuotes]" checked />
+              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_1_remove_quotes" name="level[1][removeQuotes]" checked />
               <label class="settings__label" for="level_1_remove_quotes">remove quotes</label>
             </li>
             <li class="fine-grained-options__option">
-              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_1_remove_whitespace" name="level_1[removeWhitespace]" checked />
+              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_1_remove_whitespace" name="level[1][removeWhitespace]" checked />
               <label class="settings__label" for="level_1_remove_whitespace">remove whitespace</label>
             </li>
             <li class="fine-grained-options__option">
-              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_1_replace_multiple_zeros" name="level_1[replaceMultipleZeros]" checked />
+              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_1_replace_multiple_zeros" name="level[1][replaceMultipleZeros]" checked />
               <label class="settings__label" for="level_1_replace_multiple_zeros">replace multiple zeros</label>
             </li>
             <li class="fine-grained-options__option">
-              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_1_replace_time_units" name="level_1[replaceTimeUnits]" checked />
+              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_1_replace_time_units" name="level[1][replaceTimeUnits]" checked />
               <label class="settings__label" for="level_1_replace_time_units">replace time units</label>
             </li>
             <li class="fine-grained-options__option">
-              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_1_replace_zero_units" name="level_1[replaceZeroUnits]" checked />
+              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_1_replace_zero_units" name="level[1][replaceZeroUnits]" checked />
               <label class="settings__label" for="level_1_replace_zero_units">replace <em>0</em> units</label>
             </li>
             <li class="fine-grained-options__option">
               <label class="settings__label" for="level_1_rounding_precision">unit rounding precision</label>
-              <input class="settings__option js-settings-option" type="text" id="level_1_rounding_precision" name="level_1[roundingPrecision]" value="" placeholder="e.g 5 or *=off,px=4" />
+              <input class="settings__option js-settings-option" type="text" id="level_1_rounding_precision" name="level[1][roundingPrecision]" value="" placeholder="e.g 5 or *=off,px=4" />
             </li>
             <li class="fine-grained-options__option">
-              <select class="settings__option settings__option--select js-settings-option" id="level_1_selectors_sorting_method" name="level_1[selectorsSortingMethod]">
+              <select class="settings__option settings__option--select js-settings-option" id="level_1_selectors_sorting_method" name="level[1][selectorsSortingMethod]">
                 <option value="natural">Selectors sorting method: natural</option>
                 <option value="standard" selected>Selectors sorting method: standard</option>
               </select>
             </li>
             <li class="fine-grained-options__option">
-              <select class="settings__option settings__option--select js-settings-option" id="level_1_keep_special_comments" name="level_1[keepSpecialComments]">
+              <select class="settings__option settings__option--select js-settings-option" id="level_1_keep_special_comments" name="level[1][keepSpecialComments]">
                 <option value="*" selected>Special comments: keep all</option>
                 <option value="1">Special comments: remove all but the first one</option>
                 <option value="0">Special comments: remove all</option>
               </select>
             </li>
             <li class="fine-grained-options__option">
-              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_1_tidy_at_rules" name="level_1[tidyAtRules]" checked />
+              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_1_tidy_at_rules" name="level[1][tidyAtRules]" checked />
               <label class="settings__label" for="level_1_tidy_at_rules">tidy at-rules</label>
             </li>
             <li class="fine-grained-options__option">
-              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_1_tidy_block_scopes" name="level_1[tidyBlockScopes]" checked />
+              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_1_tidy_block_scopes" name="level[1][tidyBlockScopes]" checked />
               <label class="settings__label" for="level_1_tidy_block_scopes">tidy block scopes</label>
             </li>
             <li class="fine-grained-options__option">
-              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_1_tidy_selectors" name="level_1[tidySelectors]" checked />
+              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_1_tidy_selectors" name="level[1][tidySelectors]" checked />
               <label class="settings__label" for="level_1_tidy_selectors">tidy selectors</label>
             </li>
           </ul>
           <div class="settings__group__wrapper">
-            <input class="settings__option settings__option--checkbox js-settings-option js-settings-level-2" type="checkbox" id="level_2" name="level_2" />
+            <input class="settings__option settings__option--checkbox js-settings-option js-settings-level-2" type="checkbox" id="level_2" name="level[2]" />
             <label class="settings__label" for="level_2">level 2 optimizations</label>
           </div>
           <ul class="fine-grained-options fine-grained-options--hidden js-settings-level-2-options" data-visibility-class="fine-grained-options--hidden">
             <li class="fine-grained-options__option">
-              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_2_merge_adjacent_rules" name="level_2[mergeAdjacentRules]" checked />
+              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_2_merge_adjacent_rules" name="level[2][mergeAdjacentRules]" checked />
               <label class="settings__label" for="level_2_merge_adjacent_rules">merge adjacent rules</label>
             </li>
             <li class="fine-grained-options__option">
-              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_2_merge_into_shorthands" name="level_2[mergeIntoShorthands]" checked />
+              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_2_merge_into_shorthands" name="level[2][mergeIntoShorthands]" checked />
               <label class="settings__label" for="level_2_merge_into_shorthands">merge components into shorthand properties</label>
             </li>
             <li class="fine-grained-options__option">
-              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_2_merge_media" name="level_2[mergeMedia]" checked />
+              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_2_merge_media" name="level[2][mergeMedia]" checked />
               <label class="settings__label" for="level_2_merge_media">merge <em>@media</em> nested blocks</label>
             </li>
             <li class="fine-grained-options__option">
-              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_2_merge_non_adjacent_rules" name="level_2[mergeNonAdjacentRules]" checked />
+              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_2_merge_non_adjacent_rules" name="level[2][mergeNonAdjacentRules]" checked />
               <label class="settings__label" for="level_2_merge_non_adjacent_rules">merge non-adjacent rules</label>
             </li>
             <li class="fine-grained-options__option">
-              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_2_merge_semantically" name="level_2[mergeSemantically]" />
+              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_2_merge_semantically" name="level[2][mergeSemantically]" />
               <label class="settings__label" for="level_2_merge_semantically">merge semantically</label>
             </li>
             <li class="fine-grained-options__option">
-              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_2_override_properties" name="level_2[overrideProperties]" checked />
+              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_2_override_properties" name="level[2][overrideProperties]" checked />
               <label class="settings__label" for="level_2_override_properties">override properties based on understandability</label>
             </li>
             <li class="fine-grained-options__option">
-              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_2_reduce_non_adjacent_rules" name="level_2[reduceNonAdjacentRules]" checked />
+              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_2_reduce_non_adjacent_rules" name="level[2][reduceNonAdjacentRules]" checked />
               <label class="settings__label" for="level_2_reduce_non_adjacent_rules">reduce non-adjacent rules</label>
             </li>
             <li class="fine-grained-options__option">
-              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_2_remove_duplicate_font_rules" name="level_2[removeDuplicateFontRules]" checked />
+              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_2_remove_duplicate_font_rules" name="level[2][removeDuplicateFontRules]" checked />
               <label class="settings__label" for="level_2_remove_duplicate_font_rules">remove duplicate <em>@font-face</em> rules</label>
             </li>
             <li class="fine-grained-options__option">
-              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_2_remove_duplicate_media_blocks" name="level_2[removeDuplicateMediaBlocks]" checked />
+              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_2_remove_duplicate_media_blocks" name="level[2][removeDuplicateMediaBlocks]" checked />
               <label class="settings__label" for="level_2_remove_duplicate_media_blocks">remove duplicate <em>@media</em> nested blocks</label>
             </li>
             <li class="fine-grained-options__option">
-              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_2_remove_duplicate_rules" name="level_2[removeDuplicateRules]" checked />
+              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_2_remove_duplicate_rules" name="level[2][removeDuplicateRules]" checked />
               <label class="settings__label" for="level_2_remove_duplicate_rules">remove duplicate rules</label>
             </li>
             <li class="fine-grained-options__option">
-              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_2_restructure_rules" name="level_2[restructureRules]" />
+              <input class="settings__option settings__option--checkbox js-settings-option" type="checkbox" id="level_2_restructure_rules" name="level[2][restructureRules]" />
               <label class="settings__label" for="level_2_restructure_rules">restructure rules</label>
             </li>
           </ul>
index deb204f..910ace8 100644 (file)
@@ -2,6 +2,13 @@
   var OPTION_NAME_PATTERN = /^\S+\[(\w+)\]$/
   var DELAY_RESET_SETTINGS_BY = 250
 
+  var saveOptions = 'localStorage' in window && 'setItem' in window.localStorage ?
+    saveOptionsIntoLocalStorage :
+    Function.prototype /* noop */
+  var loadOptions = 'localStorage' in window ?
+    loadOptionsFromLocalStorage :
+    Function.prototype /* noop */
+
   function show(settingsForm) {
     return function (event) {
       if (event.target.classList.contains('js-settings')) {
         },
         sourceMap: false
       }
+
+      if (event) {
+        // not a manual trigger so let's persist options
+        saveOptions(Optimizer.options)
+      }
     }
   }
 
     return Array.prototype.slice.call(allOptionNodes, 0)
       .reduce(function (accumulator, optionNode) {
         var name = optionNode.name
-        var value = extractValue(optionNode)
+        var value = getValue(optionNode)
         var optionName = OPTION_NAME_PATTERN.exec(name)[1]
 
         accumulator[optionName] = value
       }, {})
   }
 
-  function extractValue(node) {
+  function getValue(node) {
     if (node.type == 'checkbox') {
       return node.checked
     } else {
     }
   }
 
-  function resetSettings(settingsForm) {
-    var formatOptionsContainer = settingsForm.querySelector('.js-settings-format-options')
+  function setValue(node, value) {
+    if (node.type == 'checkbox') {
+      node.checked = value
+    } else {
+      node.value = value
+    }
+  }
+
+  function saveOptionsIntoLocalStorage(options) {
+    window.localStorage.setItem('settings',
+      JSON.stringify({
+        version: 4,
+        options: options
+      })
+    )
+  }
+
+  function loadOptionsFromLocalStorage(settingsForm) {
+    var options
+
+    if (!('settings' in window.localStorage)) {
+      return
+    }
+
+    options = JSON.parse(window.localStorage.settings).options
+    loadOptionsRecursively(settingsForm, options, '')
+    setGroupOptionsVisibility(settingsForm)
+  }
+
+  function loadOptionsRecursively(settingsForm, options, scope) {
+    var option
+    var value
+    var castedValue
+    var name
+    var node
+
+    for (option in options) {
+      value = options[option]
+      castedValue = typeof value == 'object' ?
+        true :
+        value
+      name = scope.length > 0 ?
+        scope + '[' + option + ']' :
+        option
+      node = settingsForm.querySelector('[name=\'' + name + '\']')
+
+      if (node) {
+        setValue(node, castedValue)
+      }
+
+      if (typeof value == 'object') {
+        loadOptionsRecursively(settingsForm, value, name)
+      }
+    }
+  }
+
+  function setGroupOptionsVisibility(settingsForm) {
+    var level1Option = settingsForm.querySelector('.js-settings-level-1')
     var level1OptionsContainer = settingsForm.querySelector('.js-settings-level-1-options')
+    var level2Option = settingsForm.querySelector('.js-settings-level-2')
     var level2OptionsContainer = settingsForm.querySelector('.js-settings-level-2-options')
-    var setOptions = setOptionsFrom(settingsForm)
+    var formatOption = settingsForm.querySelector('.js-settings-format')
+    var formatOptionsContainer = settingsForm.querySelector('.js-settings-format-options')
 
-    return function () {
-      formatOptionsContainer.classList.add(formatOptionsContainer.dataset.visibilityClass)
-      level1OptionsContainer.classList.remove(level1OptionsContainer.dataset.visibilityClass)
-      level2OptionsContainer.classList.add(level2OptionsContainer.dataset.visibilityClass)
+    toggleClass(level1OptionsContainer, level1OptionsContainer.dataset.visibilityClass, !level1Option.checked)
+    toggleClass(level2OptionsContainer, level2OptionsContainer.dataset.visibilityClass, !level2Option.checked)
+    toggleClass(formatOptionsContainer, formatOptionsContainer.dataset.visibilityClass, !formatOption.checked)
+  }
 
-      setTimeout(setOptions, DELAY_RESET_SETTINGS_BY)
+  function toggleClass(node, className, force) {
+    if (force) {
+      node.classList.add(className)
+    } else {
+      node.classList.remove(className)
+    }
+  }
+
+  function resetSettings(settingsForm) {
+    function doResetSettings() {
+      setOptionsFrom(settingsForm)()
+      saveOptions(Optimizer.options)
+      setGroupOptionsVisibility(settingsForm)
+    }
+
+    return function () {
+      setTimeout(doResetSettings, DELAY_RESET_SETTINGS_BY)
     }
   }
 
     level2Checkbox.addEventListener('click', toggleOptions(level2Checkbox), false)
     formattingCheckbox.addEventListener('click', toggleOptions(formattingCheckbox), false)
 
+    loadOptions(settingsForm)
     setOptionsFrom(settingsForm)()
   })
 })()