Add state.js to easier toggle/track app states.

BUG=None
TEST=Tested by toggling options/switching-modes/taking-pictures.

Change-Id: Id29865c74469e58dc416596693cfdef725294de8
Reviewed-on: https://chromium-review.googlesource.com/c/1470282
Reviewed-by: Sheng-hao Tsao <shenghao@google.com>
Commit-Queue: yuli <yuli@chromium.org>
Cr-Commit-Position: refs/heads/master@{#632165}
diff --git a/chrome/browser/resources/chromeos/camera/Makefile b/chrome/browser/resources/chromeos/camera/Makefile
index 8e511a7..9e357b1 100644
--- a/chrome/browser/resources/chromeos/camera/Makefile
+++ b/chrome/browser/resources/chromeos/camera/Makefile
@@ -117,6 +117,7 @@
 	src/js/nav.js \
 	src/js/scrollbar.js \
 	src/js/sound.js \
+	src/js/state.js \
 	src/js/toast.js \
 	src/js/tooltip.js \
 	src/js/util.js \
diff --git a/chrome/browser/resources/chromeos/camera/src/js/main.js b/chrome/browser/resources/chromeos/camera/src/js/main.js
index 36457946..043b6b4 100644
--- a/chrome/browser/resources/chromeos/camera/src/js/main.js
+++ b/chrome/browser/resources/chromeos/camera/src/js/main.js
@@ -58,8 +58,7 @@
  * @return {boolean} Whether applicable or not.
  */
 cca.App.useGalleryApp = function() {
-  return chrome.fileManagerPrivate &&
-      document.body.classList.contains('ext-fs');
+  return chrome.fileManagerPrivate && cca.state.get('ext-fs');
 };
 
 /**
@@ -90,7 +89,7 @@
     element.addEventListener('keypress', (event) =>
         cca.util.getShortcutIdentifier(event) == 'Enter' && element.click());
 
-    var css = element.getAttribute('data-css');
+    var css = element.getAttribute('data-state');
     var key = element.getAttribute('data-key');
     var payload = () => {
       var keys = {};
@@ -99,7 +98,7 @@
     };
     element.addEventListener('change', (event) => {
       if (css) {
-        document.body.classList.toggle(css, element.checked);
+        cca.state.set(css, element.checked);
       }
       if (event.isTrusted) {
         element.save();
@@ -139,7 +138,7 @@
       }
     });
   }).then((external) => {
-    document.body.classList.toggle('ext-fs', external);
+    cca.state.set('ext-fs', external);
     this.model_.addObserver(this.galleryButton_);
     if (!cca.App.useGalleryApp()) {
       this.model_.addObserver(this.browserView_);
diff --git a/chrome/browser/resources/chromeos/camera/src/js/nav.js b/chrome/browser/resources/chromeos/camera/src/js/nav.js
index 1984c536..bbe928c9 100644
--- a/chrome/browser/resources/chromeos/camera/src/js/nav.js
+++ b/chrome/browser/resources/chromeos/camera/src/js/nav.js
@@ -69,7 +69,7 @@
  * @private
  */
 cca.nav.isShown_ = function(index) {
-  return document.body.classList.contains(cca.nav.views_[index].root.id);
+  return cca.state.get(cca.nav.views_[index].root.id);
 };
 
 /**
@@ -82,7 +82,7 @@
 cca.nav.show_ = function(index) {
   var view = cca.nav.views_[index];
   if (!cca.nav.isShown_(index)) {
-    document.body.classList.add(view.root.id);
+    cca.state.set(view.root.id, true);
     view.layout();
     if (index > cca.nav.topmostIndex_) {
       if (cca.nav.topmostIndex_ >= 0) {
@@ -110,7 +110,7 @@
     }
     cca.nav.topmostIndex_ = next;
   }
-  document.body.classList.remove(cca.nav.views_[index].root.id);
+  cca.state.set(cca.nav.views_[index].root.id, false);
 };
 
 /**
diff --git a/chrome/browser/resources/chromeos/camera/src/js/state.js b/chrome/browser/resources/chromeos/camera/src/js/state.js
new file mode 100644
index 0000000..a9c67d7
--- /dev/null
+++ b/chrome/browser/resources/chromeos/camera/src/js/state.js
@@ -0,0 +1,33 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+'use strict';
+
+/**
+ * Namespace for the Camera app.
+ */
+var cca = cca || {};
+
+/**
+ * Namespace for the app state.
+ */
+cca.state = cca.state || {};
+
+/**
+ * Checks if the specified state exists.
+ * @param {string} state State to be checked.
+ * @return {boolean} Whether the state exists.
+ */
+cca.state.get = function(state) {
+  return document.body.classList.contains(state);
+};
+
+/**
+ * Sets the specified state on or off.
+ * @param {string} state State to be set.
+ * @param {boolean} val True to set the state on, false otherwise.
+ */
+cca.state.set = function(state, val) {
+  document.body.classList.toggle(state, val);
+};
diff --git a/chrome/browser/resources/chromeos/camera/src/js/views/camera.js b/chrome/browser/resources/chromeos/camera/src/js/views/camera.js
index 9b4a3cd..78eff316 100644
--- a/chrome/browser/resources/chromeos/camera/src/js/views/camera.js
+++ b/chrome/browser/resources/chromeos/camera/src/js/views/camera.js
@@ -145,14 +145,8 @@
 
 cca.views.Camera.prototype = {
   __proto__: cca.views.View.prototype,
-  get streaming() {
-    return document.body.classList.contains('streaming');
-  },
-  get taking() {
-    return document.body.classList.contains('taking');
-  },
   get recordMode() {
-    return document.body.classList.contains('record-mode');
+    return cca.state.get('record-mode');
   },
 };
 
@@ -169,10 +163,10 @@
  * @private
  */
 cca.views.Camera.prototype.onShutterButtonClicked_ = function(event) {
-  if (!this.streaming) {
+  if (!cca.state.get('streaming')) {
     return;
   }
-  if (this.taking) {
+  if (cca.state.get('taking')) {
     // End the prior ongoing take if any; a new take shouldn't be started
     // until the prior one is ended.
     this.endTake_();
@@ -199,9 +193,10 @@
 cca.views.Camera.prototype.updateShutterLabel_ = function() {
   var label;
   if (this.recordMode) {
-    label = this.taking ? 'record_video_stop_button' : 'record_video_start_button';
+    label = cca.state.get('taking') ?
+        'record_video_stop_button' : 'record_video_start_button';
   } else {
-    label = (this.taking && document.body.classList.contains('timer')) ?
+    label = (cca.state.get('taking') && cca.state.get('timer')) ?
         'take_photo_cancel_button' : 'take_photo_button';
   }
   this.shutterButton_.setAttribute('aria-label', chrome.i18n.getMessage(label));
@@ -230,7 +225,7 @@
  * @private
  */
 cca.views.Camera.prototype.beginTake_ = function() {
-  document.body.classList.add('taking');
+  cca.state.set('taking', true);
   this.updateShutterLabel_();
 
   cca.views.camera.timertick.start().then(() => {
@@ -285,7 +280,7 @@
   }).catch(console.error).finally(() => {
     // Re-enable UI controls after finishing the take.
     this.take_ = null;
-    document.body.classList.remove('taking');
+    cca.state.set('taking', false);
     this.updateShutterLabel_();
   });
 };
@@ -331,7 +326,7 @@
     };
     enableAudio(true);
     this.mediaRecorder_.start();
-    enableAudio(document.body.classList.contains('mic'));
+    enableAudio(cca.state.get('mic'));
     this.recordTime_.start();
   });
 };
@@ -441,7 +436,7 @@
   // Wait for ongoing 'start' and 'take' done before restarting camera.
   return Promise.all([
     this.started_,
-    Promise.resolve(!this.taking || this.endTake_()),
+    Promise.resolve(!cca.state.get('taking') || this.endTake_()),
   ]).finally(() => {
     this.preview_.stop();
     this.mediaRecorder_ = null;
diff --git a/chrome/browser/resources/chromeos/camera/src/js/views/camera/layout.js b/chrome/browser/resources/chromeos/camera/src/js/views/camera/layout.js
index cafc67b..843b263 100644
--- a/chrome/browser/resources/chromeos/camera/src/js/views/camera/layout.js
+++ b/chrome/browser/resources/chromeos/camera/src/js/views/camera/layout.js
@@ -106,10 +106,10 @@
  * Updates the layout for video-size or window-size changes.
  */
 cca.views.camera.Layout.prototype.update = function() {
-  // TODO(yuli): Check if the app runs on a tablet display.
+  // TODO(yuli): Replace tablet-landscape with full-wnd/vert-orien.
   var fullWindow = cca.util.isWindowFullSize();
   var tabletLandscape = fullWindow && (window.innerWidth > window.innerHeight);
-  document.body.classList.toggle('tablet-landscape', tabletLandscape);
+  cca.state.set('tablet-landscape', tabletLandscape);
 
   var [letterboxW, letterboxH] = this.updatePreviewSize_();
   var [halfW, halfH] = [letterboxW / 2, letterboxH / 2];
@@ -128,11 +128,11 @@
     var [, leastShutter] = dimens(true);
     return (measure > leastShutter) && (measure < leastShutter * 2);
   };
-  if (document.body.classList.toggle('shift-preview-left',
+  if (cca.state.set('shift-preview-left',
       fullWindow && tabletLandscape && accommodate(letterboxW))) {
     [rightBox, leftBox] = [letterboxW, 0];
   }
-  if (document.body.classList.toggle('shift-preview-top',
+  if (cca.state.set('shift-preview-top',
       fullWindow && !tabletLandscape && accommodate(letterboxH))) {
     [bottomBox, topBox] = [letterboxH, 0];
   }
@@ -146,7 +146,7 @@
   };
   var shift = (stripe, name, measure, shutter) => {
     var [preset, least, gap, baseline] = dimens(shutter);
-    if (document.body.classList.toggle('shift-' + name + '-stripe',
+    if (cca.state.set('shift-' + name + '-stripe',
         measure > gap && measure < preset)) {
       baseline = calc(measure, least);
       stripe.setProperty(name, baseline + 'px');
@@ -156,7 +156,7 @@
   };
   var symm = (stripe, name, measure, shutterBaseline) => {
     if (measure && shutterBaseline) {
-      document.body.classList.add('shift-' + name + '-stripe');
+      cca.state.set('shift-' + name + '-stripe', true);
       stripe.setProperty(name, shutterBaseline + 'px');
       return true;
     }
diff --git a/chrome/browser/resources/chromeos/camera/src/js/views/camera/options.js b/chrome/browser/resources/chromeos/camera/src/js/views/camera/options.js
index fc6c8bd2..edfd383 100644
--- a/chrome/browser/resources/chromeos/camera/src/js/views/camera/options.js
+++ b/chrome/browser/resources/chromeos/camera/src/js/views/camera/options.js
@@ -106,8 +106,7 @@
 
 cca.views.camera.Options.prototype = {
   get newStreamRequestDisabled() {
-    return !document.body.classList.contains('streaming') ||
-        document.body.classList.contains('taking');
+    return !cca.state.get('streaming') || cca.state.get('taking');
   },
 };
 
@@ -120,10 +119,9 @@
   if (this.newStreamRequestDisabled) {
     return;
   }
-  document.body.classList.toggle('record-mode', record);
-  document.body.classList.add('mode-switching');
-  this.onNewStreamNeeded_().then(
-      () => document.body.classList.remove('mode-switching'));
+  cca.state.set('record-mode', record);
+  cca.state.set('mode-switching', true);
+  this.onNewStreamNeeded_().then(() => cca.state.set('mode-switching', false));
 };
 
 /**
@@ -258,7 +256,7 @@
   this.videoDevices_.then((devices) => {
     multi = devices.length >= 2;
   }).catch(console.error).finally(() => {
-    document.body.classList.toggle('multi-camera', multi);
+    cca.state.set('multi-camera', multi);
     this.refreshingVideoDeviceIds_ = false;
   });
 };
diff --git a/chrome/browser/resources/chromeos/camera/src/js/views/camera/preview.js b/chrome/browser/resources/chromeos/camera/src/js/views/camera/preview.js
index 60f4a38..e64a103 100644
--- a/chrome/browser/resources/chromeos/camera/src/js/views/camera/preview.js
+++ b/chrome/browser/resources/chromeos/camera/src/js/views/camera/preview.js
@@ -146,7 +146,7 @@
       }
     }, 100);
     this.stream_ = stream;
-    document.body.classList.add('streaming');
+    cca.state.set('streaming', true);
   });
 };
 
@@ -164,7 +164,7 @@
     this.stream_.getVideoTracks()[0].stop();
     this.stream_ = null;
   }
-  document.body.classList.remove('streaming');
+  cca.state.set('streaming', false);
 };
 
 /**
diff --git a/chrome/browser/resources/chromeos/camera/src/js/views/camera/timertick.js b/chrome/browser/resources/chromeos/camera/src/js/views/camera/timertick.js
index 250d0e9..74db9a7 100644
--- a/chrome/browser/resources/chromeos/camera/src/js/views/camera/timertick.js
+++ b/chrome/browser/resources/chromeos/camera/src/js/views/camera/timertick.js
@@ -36,7 +36,7 @@
  */
 cca.views.camera.timertick.start = function() {
   cca.views.camera.timertick.cancel_ = null;
-  if (!document.body.classList.contains('timer')) {
+  if (!cca.state.get('timer')) {
     return Promise.resolve();
   }
   return new Promise((resolve, reject) => {
@@ -51,7 +51,7 @@
       reject();
     };
 
-    var tickCounter = document.body.classList.contains('_10sec') ? 10 : 3;
+    var tickCounter = cca.state.get('_10sec') ? 10 : 3;
     var onTimerTick = () => {
       if (tickCounter == 0) {
         resolve();
diff --git a/chrome/browser/resources/chromeos/camera/src/views/main.html b/chrome/browser/resources/chromeos/camera/src/views/main.html
index c32e9dee..f902321a 100644
--- a/chrome/browser/resources/chromeos/camera/src/views/main.html
+++ b/chrome/browser/resources/chromeos/camera/src/views/main.html
@@ -12,6 +12,7 @@
     <script src="../js/util.js"></script>
     <script src="../js/toast.js"></script>
     <script src="../js/tooltip.js"></script>
+    <script src="../js/state.js"></script>
     <script src="../js/sound.js"></script>
     <script src="../js/scrollbar.js"></script>
     <script src="../js/gallerybutton.js"></script>
@@ -56,8 +57,8 @@
       </div>
       <div class="top-stripe right-stripe buttons">
         <input type="checkbox" id="toggle-mic" tabindex="0"
-               i18n-label="toggle_mic_button" data-css="mic" data-key="toggleMic"
-               checked>
+               i18n-label="toggle_mic_button" data-state="mic"
+               data-key="toggleMic" checked>
       </div>
       <div class="top-stripe left-stripe buttons">
         <button id="open-settings" tabindex="0"
@@ -65,12 +66,12 @@
       </div>
       <div class="left-stripe options-group buttons">
         <input type="checkbox" id="toggle-mirror" tabindex="0"
-               i18n-label="toggle_mirror_button" data-css="mirror" checked>
+               i18n-label="toggle_mirror_button" data-state="mirror" checked>
         <input type="checkbox" id="toggle-grid" tabindex="0"
-               i18n-label="toggle_grid_button" data-css="grid"
+               i18n-label="toggle_grid_button" data-state="grid"
                data-key="toggleGrid">
         <input type="checkbox" id="toggle-timer" tabindex="0"
-               i18n-label="toggle_timer_button" data-css="timer"
+               i18n-label="toggle_timer_button" data-state="timer"
                data-key="toggleTimer">
       </div>
       <div class="bottom-stripe left-stripe buttons circle">
@@ -139,17 +140,17 @@
         </div>
         <label class="menu-item circle" for="grid-3x3">
           <input class="icon" id="grid-3x3" type="radio" tabindex="0"
-                 name="gridtype" data-css="_3x3" data-key="toggle3x3" checked>
+                 name="gridtype" data-state="_3x3" data-key="toggle3x3" checked>
           <span i18n-content="label_grid_3x3" i18n-aria="aria_grid_3x3"></span>
         </label>
         <label class="menu-item circle" for="grid-4x4">
           <input class="icon" id="grid-4x4" type="radio" tabindex="0"
-                 name="gridtype" data-css="_4x4" data-key="toggle4x4">
+                 name="gridtype" data-state="_4x4" data-key="toggle4x4">
           <span i18n-content="label_grid_4x4" i18n-aria="aria_grid_4x4"></span>
         </label>
         <label class="menu-item circle" for="grid-golden">
           <input class="icon" id="grid-golden" type="radio" tabindex="0"
-                 name="gridtype" data-css="golden" data-key="toggleGolden">
+                 name="gridtype" data-state="golden" data-key="toggleGolden">
           <span i18n-content="label_grid_golden"></span>
         </label>
       </div>
@@ -162,12 +163,13 @@
         </div>
         <label class="menu-item circle" for="timer-3s">
           <input class="icon" id="timer-3s" type="radio" tabindex="0"
-                 name="timerdur" data-css="_3sec" data-key="toggle3sec" checked>
+                 name="timerdur" data-state="_3sec" data-key="toggle3sec"
+                 checked>
           <span i18n-content="label_timer_3s"></span>
         </label>
         <label class="menu-item circle" for="timer-10s">
           <input class="icon" id="timer-10s" type="radio" tabindex="0"
-                 name="timerdur" data-css="_10sec" data-key="toggle10sec">
+                 name="timerdur" data-state="_10sec" data-key="toggle10sec">
           <span i18n-content="label_timer_10s"></span>
         </label>
       </div>