Merge "todosapp: refinements for demo"
diff --git a/browser/defaults.js b/browser/defaults.js
index 6a876f3..9818dc6 100644
--- a/browser/defaults.js
+++ b/browser/defaults.js
@@ -50,6 +50,12 @@
 
 function initData(disp, cb) {
   cb = util.logFn('initData', cb);
+  if (util.DEMO) {
+    process.nextTick(function() {
+      return cb();
+    });
+    return;
+  }
   var timestamp = Date.now();
   async.each(data, function(list, cb) {
     disp.addList({name: list.name}, function(err, listId) {
diff --git a/browser/dom_log.js b/browser/dom_log.js
index e7beb8b..51bdc60 100644
--- a/browser/dom_log.js
+++ b/browser/dom_log.js
@@ -36,7 +36,7 @@
     logEl.classList.add('visible');
   };
 
-  Mousetrap.bind(['ctrl+l', 'meta+l'], function() {
+  Mousetrap.bind(['ctrl+shift+l', 'meta+shift+l'], function() {
     logEl.classList.toggle('visible');
   });
 };
diff --git a/browser/index.js b/browser/index.js
index f916a9e..55a8852 100644
--- a/browser/index.js
+++ b/browser/index.js
@@ -84,7 +84,7 @@
   throw err;
 }
 
-// HACKETY HACK
+// HACKETY HACK for demo.
 function emailToBlessing(email) {
   return 'dev.v.io/u/' + email;
 }
@@ -291,10 +291,10 @@
   render: function() {
     var that = this;
     var children = [];
-    // TODO(sadovsky): If there are no lists (and thus no todos), we end up
-    // rendering "Loading..." here, which is wrong.
-    if (!this.props.listId || !this.props.todos) {
-      children.push(h('div.loading', {key: 'loading'}, 'Loading...'));
+    if (!this.props.listId) {
+      children.push(h('div.msg', {key: 'msg'}, 'No list selected.'));
+    } else if (!this.props.todos) {
+      children.push(h('div.msg', {key: 'msg'}, 'Loading...'));
     } else {
       var tagFilter = this.props.tagFilter, items = [];
       _.each(this.props.todos, function(todo) {
@@ -372,7 +372,7 @@
         }
       }, list.name)));
     }
-    return h('div.list' + (list.selected ? '.selected' : ''), {
+    return h('div.list' + (that.props.selected ? '.selected' : ''), {
       onClick: function() {
         that.props.setListId(list._id);
       },
@@ -396,11 +396,16 @@
   },
   render: function() {
     var that = this, list = this.props.list, shared = Boolean(list.sg);
-    var shareUrl;
+    var hShare;
     if (shared) {
-      var encodedSgName = util.strToHex(list.sg.name);
-      var loc = window.location;
-      shareUrl = loc.origin + '/share/' + encodedSgName + loc.search;
+      if (util.DEMO) {
+        hShare = h('span', list.sg.name);
+      } else {
+        var loc = window.location;
+        var encodedSgName = util.strToHex(list.sg.name);
+        var shareUrl = '/share/' + encodedSgName + loc.search;
+        hShare = h('a', {href: shareUrl}, shareUrl);
+      }
     }
     return h('div#status-pane', {
       onClick: function(e) {
@@ -447,8 +452,8 @@
         }).join(', '))
       ]),
       !shared ? null : h('div.url', {key: 'url'}, [
-        h('div.subtitle', {key: 'subtitle'}, 'URL to share with invitees'),
-        h('div.value', {key: 'value'}, h('a', {href: shareUrl}, shareUrl))
+        h('div.subtitle', {key: 'subtitle'}, 'Thing to share with invitees'),
+        h('div.value', {key: 'value'}, hShare)
       ]),
       h('div.close', {
         key: 'close',
@@ -466,21 +471,23 @@
     var that = this;
     var children = [h('div.lists-title', {key: 'title'}, 'Todo Lists')];
     if (!this.props.lists) {
-      children.push(h('div.loading', {key: 'loading'}, 'Loading...'));
+      children.push(h('div.msg', {key: 'msg'}, 'Loading...'));
     } else {
-      var lists = [];
-      _.each(this.props.lists, function(list) {
-        list.selected = that.props.listId === list._id;
-        lists.push(List({
+      children.push(h('div', {
+        key: 'lists'
+      }, _.map(this.props.lists, function(list) {
+        return List({
           key: list._id,
           list: list,
+          selected: that.props.listId === list._id,
           useSyncbase: that.props.useSyncbase,
           setListId: that.props.setListId,
           openStatusDialog: that.props.openStatusDialog
-        }));
-      });
-      children.push(h('div', {key: 'lists'}, lists));
-      children.push(h('div.new-list', {key: 'new-list'}, h('input', _.assign({
+        });
+      })));
+      children.push(h('div.input-row', {
+        key: 'new-list'
+      }, h('input', _.assign({
         type: 'text',
         placeholder: 'New list'
       }, okCancelEvents({
@@ -492,6 +499,22 @@
           e.target.value = '';
         }
       })))));
+      if (util.DEMO && that.props.useSyncbase) {
+        children.push(h('div.input-row', {
+          key: 'join-list'
+        }, h('input', _.assign({
+          type: 'text',
+          placeholder: 'Join list'
+        }, okCancelEvents({
+          ok: function(value, e) {
+            that.props.joinSyncGroup(value, alertOnError);
+            e.target.value = '';
+          }
+        })))));
+      }
+    }
+    if (that.props.useSyncbase) {
+      children.push(h('div.user-id', {'data-text': userEmail}, userEmail));
     }
     return h('div#lists-pane', children);
   }
@@ -590,6 +613,24 @@
       }, cb);
     });
   },
+  // Joins the specified syncgroup and displays the associated list.
+  joinSyncGroup_: function(sgName, cb) {
+    var that = this;
+    console.assert(this.props.dispType === DISP_TYPE_SYNCBASE);
+    disp.joinSyncGroup(sgName, function(err) {
+      // Note, joinSyncGroup is a noop (no error) if the caller is already a
+      // member, which is the desired behavior here.
+      if (err) return cb(err);
+      var listId = disp.sgNameToListId(sgName);
+      // TODO(sadovsky): Wait for all items to get synced before attempting to
+      // read them?
+      that.updateTodos_(listId, function(err) {
+        if (err) return cb(err);
+        // Note, componentDidUpdate() will update the url.
+        that.setState({listId: listId}, cb);
+      });
+    });
+  },
   componentWillMount: function() {
     var that = this, props = this.props;
     if (props.benchmark) {
@@ -602,22 +643,9 @@
     initDispatcher(props.dispType, props.syncbaseName, function(err) {
       alertOnError(err);
       that.setState({dispInitialized: true}, function() {
-        if (!props.joinSgName) return;
-        // TODO(sadovsky): Show "please wait..." modal?
-        console.assert(props.dispType === DISP_TYPE_SYNCBASE);
-        disp.joinSyncGroup(props.joinSgName, function(err) {
-          // Note, joinSyncGroup is a noop (no error) if the caller is already a
-          // member, which is the desired behavior here.
-          alertOnError(err);
-          var listId = disp.sgNameToListId(props.joinSgName);
-          // TODO(sadovsky): Wait for all items to get synced before attempting
-          // to read them?
-          that.updateTodos_(listId, function(err) {
-            alertOnError(err);
-            // Note, componentDidUpdate() will update the url.
-            that.setState({listId: listId});
-          });
-        });
+        if (props.joinSgName) {
+          that.joinSyncGroup_(props.joinSgName, alertOnError);
+        }
       });
     });
   },
@@ -719,6 +747,9 @@
           // will be merged with ours.
           that.setListId_(listId);
           that.setState({showStatusDialog: true});
+        },
+        joinSyncGroup: function(sgName, cb) {
+          that.joinSyncGroup_(sgName, cb);
         }
       }),
       h('div#tags-and-todos-pane', {key: 'tags-and-todos-pane'}, [
@@ -751,13 +782,14 @@
 ////////////////////////////////////////
 // Initialization
 
-// Start our DOM log module. Developers can press Ctrl+L (or Meta+L) to toggle
+// DOM log module. Developers can press Ctrl+Shift+L (or Meta+Shift+L) to toggle
 // visibility of the log.
 domLog.init();
 
 function render(props) {
+  var defaultDispType = util.DEMO ? DISP_TYPE_SYNCBASE : DISP_TYPE_COLLECTION;
   props = _.assign({
-    dispType: u.query.d || DISP_TYPE_COLLECTION,
+    dispType: u.query.d || defaultDispType,
     syncbaseName: syncbaseName,
     benchmark: Boolean(u.query.bm)
   }, props);
diff --git a/browser/util.js b/browser/util.js
index 6fab1e4..7dc9060 100644
--- a/browser/util.js
+++ b/browser/util.js
@@ -6,6 +6,12 @@
 var React = require('react');
 var vtrace = require('vanadium').vtrace;
 
+// If true, run in "demo mode":
+// - Start from a blank slate (no predefined lists)
+// - Default to syncbase dispatcher
+// - Include "join list" input box, share list codes instead of urls
+exports.DEMO = true;
+
 exports.h = function(selector, props, children) {
   if (_.isPlainObject(props)) {
     console.assert(!props.id && !props.className);
diff --git a/stylesheets/constants.less b/stylesheets/constants.less
index 8f36820..8c170fd 100644
--- a/stylesheets/constants.less
+++ b/stylesheets/constants.less
@@ -1,4 +1,6 @@
 /* https://www.google.com/design/spec/style/color.html */
+@color-green-100: #c8e6c9;
 @color-green-700: #388e3c;
+@color-red-100: #ffcdd2;
 @color-red-500: #f44336;
 @color-red-700: #d32f2f;
diff --git a/stylesheets/index.less b/stylesheets/index.less
index 7bf81b5..5d00d6a 100644
--- a/stylesheets/index.less
+++ b/stylesheets/index.less
@@ -86,6 +86,7 @@
 }
 
 #todos-pane, #lists-pane, #tags-pane {
+  position: relative;
   overflow: auto;
 }
 
@@ -101,11 +102,11 @@
   align-items: center;
 }
 
-.loading {
+#page-pane .msg {
   padding: 4px 8px;
 }
 
-.tag {
+#page-pane .tag {
   display: inline-block;
   margin-left: 8px;
   padding: 4px 8px;
@@ -131,14 +132,14 @@
     text-align: center;
   }
 
-  .list, .new-list {
+  .list, .input-row {
     .vcenter;
     padding: 0 12px;
     width: 100%;
     height: 40px;
   }
 
-  .list{
+  .list {
     &.selected {
       background-color: #9be;
       font-weight: bold;
@@ -170,10 +171,27 @@
     }
   }
 
-  .new-list input {
+  .input-row input {
     margin-top: 8px;
     width: 100%;
   }
+
+  .user-id {
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    width: 100%;
+    padding: 8px;
+    word-break: break-word;
+
+    /* HACKETY HACK for demo. */
+    &[data-text*=hpucha] {
+      background-color: @color-green-100;
+    }
+    &[data-text*=sadovsky] {
+      background-color: @color-red-100;
+    }
+  }
 }
 
 /* Status pane (currently, within #lists-pane) ********************************/