Sitic has submitted this change and it was merged.

Change subject: Redesign, switch from bootstrap to material design
......................................................................


Redesign, switch from bootstrap to material design

Started as an experiment, looks much better.

Refactored frontend code.

Has preformance issues on IE (barely usable).

Bug: T100341
Change-Id: I3fa2dba3aac6da73e2f6e8fc03169d4f1e71feeb
---
M backend/server/__init__.py
M frontend/bower.json
M frontend/gulp/build.js
A frontend/src/app/filters.js
M frontend/src/app/index.css
M frontend/src/app/index.js
M frontend/src/app/main/main.controller.js
M frontend/src/app/main/main.html
A frontend/src/app/runBlock.js
A frontend/src/app/services.js
A frontend/src/app/welcome/welcome.html
M frontend/src/assets/images/flags/readme.txt
A frontend/src/components/footer/footer.html
M frontend/src/components/navbar/navbar.controller.js
M frontend/src/components/navbar/navbar.html
A frontend/src/components/settings/settings.controller.js
A frontend/src/components/settings/settings.html
M frontend/src/components/watchlist/entry.directive.js
M frontend/src/components/watchlist/watchlist.controller.js
M frontend/src/components/watchlist/watchlist.html
M frontend/src/i18n/locale-de.json
M frontend/src/i18n/locale-en.json
M frontend/src/i18n/locale-pt.json
M frontend/src/index.html
24 files changed, 687 insertions(+), 731 deletions(-)

Approvals:
  Sitic: Verified; Looks good to me, approved



diff --git a/backend/server/__init__.py b/backend/server/__init__.py
index c3fe141..00edefc 100644
--- a/backend/server/__init__.py
+++ b/backend/server/__init__.py
@@ -85,7 +85,7 @@
     index_path = os.path.join(pwd_path, 'public/' + toolname + '/index.html')
     static_handlers = [
         (r"/()", StaticFileHandler, {"path": index_path}),
-        (r"/" + toolname + r"/()", StaticFileHandler, {"path": index_path}),
+        (r"/" + toolname + r"/\w*()", StaticFileHandler, {"path": index_path}),
         (r"/" + toolname, RedirectHandler, {"url": "/" + toolname + "/"}),
         (r"/(.*)", StaticFileHandler, {"path": static_path})
         ]
diff --git a/frontend/bower.json b/frontend/bower.json
index fd3f0b0..dd3f642 100644
--- a/frontend/bower.json
+++ b/frontend/bower.json
@@ -7,16 +7,15 @@
     "angular-route": "~1.4.0",
     "angular-sanitize": "~1.4.0",
     "angular-sockjs": "0.0.x",
-    "angular-strap": "~2.2.4",
     "angular-translate": "~2.7.0",
     "angular-translate-loader-static-files": "~2.7.0",
     "angular-translate-storage": "~0.0.3",
-    "fontawesome": "~4.3.0",
     "sockjs-client": "~1.0.0",
     "angular-moment": "0.x.x",
     "angular-local-storage": "0.2.x",
-    "bootstrap": "~3.3.4",
-    "angular-motion": "~0.4.2"
+    "angular-motion": "~0.4.2",
+    "angular-material": "~0.10.0",
+    "material-design-icons": "~2.0.0"
   },
   "devDependencies": {
     "angular-mocks": "~1.4.0"
@@ -27,6 +26,9 @@
   "overrides": {
     "moment": {
       "main": "min/moment-with-locales.min.js"
+    },
+    "material-design-icons": {
+      "main": "./iconfont/*"
     }
   }
 }
diff --git a/frontend/gulp/build.js b/frontend/gulp/build.js
index 1e43438..3013bbe 100644
--- a/frontend/gulp/build.js
+++ b/frontend/gulp/build.js
@@ -84,7 +84,7 @@
 
 gulp.task('fonts', function () {
   return gulp.src($.mainBowerFiles())
-    .pipe($.filter('**/*.{eot,svg,ttf,woff}'))
+    .pipe($.filter('**/*.{eot,svg,ttf,woff,woff2}'))
     .pipe($.flatten())
     .pipe(gulp.dest(paths.dist + '/fonts/'));
 });
diff --git a/frontend/src/app/filters.js b/frontend/src/app/filters.js
new file mode 100644
index 0000000..b057e63
--- /dev/null
+++ b/frontend/src/app/filters.js
@@ -0,0 +1,66 @@
+'use strict';
+angular
+  .module('crosswatch')
+  .filter('urlEncode', urlEncodeFilter)
+  .filter('list', listFilter)
+  .filter('projects', projectsFilter)
+  .filter('editflags', editflagsFilter)
+;
+
+/**
+ * Filter for encoding strings for use in URL query strings
+ */
+function urlEncodeFilter () {
+  return window.encodeURIComponent;
+}
+
+/**
+ * Formats lists:
+ *   ["a", "b", "c"] -> "(a, b, c)"
+ *   [] -> "(–)"
+ */
+function listFilter () {
+  return function (list) {
+    list = list || [];
+    var result = list.join(', ');
+    if (result.length === 0) {
+      result = "–";
+    }
+    result = "(" + result + ")";
+
+    return result;
+  }
+}
+
+/**
+ * Filters watchlist based on selected / blacklisted wikis
+ */
+function projectsFilter () {
+  return function (items, projects) {
+    return items.filter(filter, projects)
+  };
+
+  function filter (item) {
+    return (this.indexOf(item.project) > -1);
+  }
+}
+
+/**
+ * Filters watchlist based on editflags (minor, bot, registered)
+ */
+function editflagsFilter () {
+  return function (items, flags) {
+    return items.filter(filter, flags)
+  };
+
+  function filter (item) {
+    if (item.type === 'log') {
+      return true;
+    }
+    var minor = this.minor || !item.minor;
+    var bot = this.bot || !item.bot;
+    var anon = this.anon || !(item.anon === "");
+    var registered = this.registered || !(item.userid !== 0);
+    return (minor && bot && anon && registered);
+  }
+}
diff --git a/frontend/src/app/index.css b/frontend/src/app/index.css
index 57f3fd6..e39a44b 100644
--- a/frontend/src/app/index.css
+++ b/frontend/src/app/index.css
@@ -1,92 +1,109 @@
-.browsehappy {
-  margin: 0.2em 0;
-  background: #ccc;
-  color: #000;
-  padding: 0.2em 0;
+/*
+html {
+  font-size: 62.5%;
+  line-height: 1.4;
+  background-color: #edecec;
 }
-
+*/
 
 body {
-  padding:10px
+  background-color: #edecec;
+  font-family: 'Roboto', sans-serif;
+  /* font-size: 1.6rem; */
 }
 
-.navbar{
-  margin-bottom:30px
+div[role=main] {
+   background-color: #edecec;
+   min-height: calc(100vh - 165px);
+ }
+
+#welcome div {
+  background-color: #edecec;
+  min-height: calc(100vh - 180px);
+  height: 0; /* IE fix https://stackoverflow.com/questions/19371626 */
 }
 
-
-.jumbotron {
-  padding: 60px;
-  margin-bottom: 30px;
-  background-color: gainsboro;
-  border-radius: 6px;
-  text-align: center;
+div[role=footer] {
+  background-color: #edecec;
 }
 
-.mw-echo-notifications-badge {
-  min-width: 7px;
-  border-radius: 2px 2px 2px 2px;
-  padding: 0.25em 0.45em 0.2em;
-  margin-left: -4px;
-  text-align: center;
-  background-color: rgb(210, 210, 210);
-  font-weight: bold;
+.md-content {
+  background-color: #edecec;
+}
+
+#projects md-select {
+  padding-left: 20px;
+  padding-right: 20px;
+}
+
+md-input-container.md-default-theme .md-input {
+  color: rgba(255,255,255,0.87);
+  border-color: rgba(255,255,255,0.87);
+  margin-top: 24px;
+}
+/*** Browser fixes ***/
+::-webkit-input-placeholder { /* WebKit browsers */
+  color:    rgba(255,255,255,0.87);
+}
+::-moz-placeholder { /* Mozilla Firefox 19+ */
+  color:    rgba(255,255,255,0.87);
+  opacity:  1;
+}
+:-ms-input-placeholder { /* Internet Explorer 10+ */
+  color:    rgba(255,255,255,0.87);
+}
+
+md-divider[role=vertical] {
+  border-top: 0;
+  border-left: 1px solid;
+  border-left-color: rgba(0, 0, 0, 0.12);
+}
+
+/* Should be fixed in ngMaterial 0.10
+md-select.md-default-theme:not([disabled]):focus .md-select-label {
   color: white;
-  cursor: pointer;
+}
+*/
+
+a {
+  /*color: #106CC8;*/
+  color: #0645ad;
   text-decoration: none;
+  font-weight: 400;
+  border-bottom: none;
+  -webkit-transition: border-bottom 0.25s;
+  -moz-transition: border-bottom 0.25s;
+  -ms-transition: border-bottom 0.25s;
+  -o-transition: border-bottom 0.25s;
+  transition: border-bottom 0.25s;
 }
-.mw-echo-overlay {
-  position: absolute;
-  top: 30px;
-  left: 0;
-  border: 1px solid silver;
-  background-color: rgb(255, 255, 255);
-  width: 450px;
-  min-height: 2em;
-  padding: 0;
-  color: rgb(109, 109, 109);
-  z-index: 100;
-  box-shadow: 0 3px 8px rgba(50, 50, 50, 0.35);
+a:hover, a:focus {
+  /*border-bottom: 1px solid #4054B2;*/
+  border-bottom: 1px solid #0645ad;
 }
-.mw-echo-notification{
-  list-style: none none;
+a.md-button {
+  border-bottom: none;
 }
-.mw-echo-notifications{
-  margin:0;
-  padding:0;
-  list-style: none none;
-  overflow:auto;
-  background-color: rgb(238, 238,238);
+
+.text-success{
+  color:#3c763d
 }
-.mw-echo-notification-wrapper {
-  display: block;
-  background-color: rgb(241, 241, 241);
-  border-bottom: 1px solid rgb(221, 221, 221);
-  padding: 15px 40px 10px 10px;
-  white-space: normal;
-  font-size: 13px;
-  line-height: 16px;
-  color: inherit;
-  text-decoration: inherit;
+.text-danger{
+  color:#a94442
 }
-.mw-echo-icon {
-  width: 30px;
-  height: 30px;
-  float: left;
-  margin-right: 10px;
-  margin-left: 10px;
+.text-muted{
+  color:#777
 }
-.mw-echo-content {
-  font-size: 13px;
-  font-weight:normal;
-  line-height: 16px;
-  overflow: hidden;
+
+.whitebox{
+  background-color: white;
+  width: 100%;
+  max-width: 1200px;
 }
 
 .watchlist{
-  list-style: none none;
-  padding-bottom: 8px;
   overflow: hidden;
+  width: 100%;
 }
 
 .watchlist a{
@@ -96,16 +113,19 @@
 .watchlist .left {
   float: left;
   width: 125px;
-}
-
-.watchlist .right {
-  width: auto;
   overflow: hidden;
-  word-break: break-all;
 }
 
 .watchlist a.project {
-  color: #333;
+  color: rgba(0, 0, 0, 0.87);
+}
+
+.watchlist .right {
+  overflow: hidden;
+}
+
+#watchlist:last-child md-divider {
+  border-top: 0;
 }
 
 span.comment{
@@ -127,115 +147,70 @@
 .mw-title
 {
   font-weight: bold;
-  color:#0645ad;
 }
 
-
-/*
-a {
-  text-decoration: none;
+/** material design icons **/
+@font-face {
+  font-family: 'Material Icons';
+  font-style: normal;
+  font-weight: 400;
+  src: url(//tools.wmflabs.org/crosswatch/fonts/MaterialIcons-Regular.eot); /* 
For IE6-8 */
+  src: local('Material Icons'),
+  local('MaterialIcons-Regular'),
+  url(//tools.wmflabs.org/crosswatch/fonts/MaterialIcons-Regular.woff2) 
format('woff2'),
+  url(//tools.wmflabs.org/crosswatch/fonts/MaterialIcons-Regular.woff) 
format('woff'),
+  url(//tools.wmflabs.org/crosswatch/fonts/MaterialIcons-Regular.ttf) 
format('truetype');
 }
 
-.container {
-  width: 1170px;
-  margin-right: auto;
-  margin-left: auto;
-  padding-left: 15px;
-  padding-right: 15px;
-}
-
-.navbar {
-  position: relative;
-  min-height: 50px;
-  margin-bottom: 20px;
-  background-color: #222;
-}
-
-.navbar > a {
-  float: left;
-  color: lightgrey;
-  padding: 15px 15px;
-  font-size: 18px;
-  line-height: 20px;
-  height: 50px;
-}
-
-.navbar ul {
-  float: left;
-  padding: 0;
-  margin: 0;
-}
-
-.navbar ul li {
-  float: left;
-  position: relative;
-  display: block;
-}
-
-.navbar ul li a {
-  color: white;
-  position: relative;
-  display: block;
-  padding: 15px;
-  line-height: 20px;
-}
-
-.navbar ul li.active a {
-  background-color: black;
-}
-
-.jumbotron {
-  padding: 60px;
-  margin-bottom: 30px;
-  background-color: gainsboro;
-  border-radius: 6px;
-  text-align: center;
-}
-
-.btn {
+.material-icons {
+  font-family: 'Material Icons';
+  font-weight: normal;
+  font-style: normal;
+  font-size: 24px;  /* Preferred icon size */
   display: inline-block;
-  text-align: center;
-  vertical-align: middle;
-  cursor: pointer;
-  border: 1px solid transparent;
-  color: white;
-  background-color: #5cb85c;
-  border-color: #4cae4c;
-  padding: 10px 16px;
-  font-size: 18px;
-  line-height: 1.33;
-  border-radius: 6px;
+  width: 1em;
+  height: 1em;
+  line-height: 1;
+  text-transform: none;
+  letter-spacing: normal;
+  word-wrap: normal;
+
+  /* Support for all WebKit browsers. */
+  -webkit-font-smoothing: antialiased;
+  /* Support for Safari and Chrome. */
+  text-rendering: optimizeLegibility;
+
+  /* Support for Firefox. */
+  -moz-osx-font-smoothing: grayscale;
+
+  /* Support for IE. */
+  font-feature-settings: 'liga';
 }
 
-.col {
-  float: left;
-  width: 33.33%;
-  position: relative;
-  padding-left: 15px;
-  padding-right: 15px;
-  box-sizing: border-box;
+.material-icons.md-18 { font-size: 18px; }
+.material-icons.md-24 { font-size: 24px; }
+
+/*** IE fixes  ***/
+.browsehappy {
+  margin: 0.2em 0;
+  background: #ccc;
+  color: #000;
+  padding: 0.2em 0;
 }
 
-.col .thumbnail {
-  height: 200px;
-  border: 1px solid lightgrey;
-  border-radius: 4px;
-  margin-bottom: 20px;
+a img{
+  border: 0;
 }
 
-.col .thumbnail .caption {
-  padding: 9px;
+/* https://github.com/angular/material/issues/2213 */
+@media screen and (min-width:0\0) {
+  md-select .md-select-icon {
+    float: right;
+  }
 }
 
-.col .thumbnail img.pull-right {
-  width: 50px;
+/* https://github.com/angular/material/issues/2769 */
+_:-ms-fullscreen, :root /* IE11 */ md-select-label *:first-child{
+  flex-basis: auto;
+  overflow: visible;
 }
-
-.pull-right {
-  float: right;
-}
-
-hr {
-  clear: both;
-}
-*/
diff --git a/frontend/src/app/index.js b/frontend/src/app/index.js
index 062d496..bc1a3e1 100644
--- a/frontend/src/app/index.js
+++ b/frontend/src/app/index.js
@@ -4,9 +4,9 @@
     'ngAnimate',
     'ngSanitize',
     'ngRoute',
+    'ngMaterial',
     'LocalStorageModule',
     'angular-translate-storage',
-    'mgcrea.ngStrap',
     'pascalprecht.translate',
     'bd.sockjs',
     'angularMoment'
@@ -15,13 +15,6 @@
   .config(locationConfig)
   .config(storageConfig)
   .factory('socket', socketFactory)
-  .filter('urlEncode', urlEncodeFilter)
-  .filter('list', listFilter)
-  .filter('projects', projectsFilter)
-  .filter('editflags', editflagsFilter)
-  .service('authService', authService)
-  .service('dataService', dataService)
-  .run(runBlock)
 ;
 
 function routeConfig ($routeProvider) {
@@ -30,6 +23,9 @@
       templateUrl: 'app/main/main.html',
       controller: 'MainCtrl',
       controllerAs: 'ctrl'
+    })
+    .when('/welcome', {
+      templateUrl: 'app/welcome/welcome.html'
     })
     .otherwise({
       redirectTo: '/'
@@ -56,284 +52,7 @@
   if ($location.host() === 'localhost') { // debug – use tools backend when 
developing
     sockjsUrl = 'https://tools.wmflabs.org/crosswatch/sockjs'
   }
-
   return socketFactory({
     url: sockjsUrl
   });
-}
-
-/**
- * Filter for encoding strings for use in URL query strings
- */
-function urlEncodeFilter () {
-  return window.encodeURIComponent;
-}
-
-/**
- * Formats lists:
- *   ["a", "b", "c"] -> "(a, b, c)"
- *   [] -> "(–)"
- */
-function listFilter () {
-  return function (list) {
-    list = list || [];
-    var result = list.join(', ');
-    if (result.length === 0) {
-      result = "–";
-    }
-    result = "(" + result + ")";
-
-    return result;
-  }
-}
-
-/**
- * Filters watchlist based on selected / blacklisted wikis
- */
-function projectsFilter () {
-  return function (items, projects) {
-    return items.filter(filter, projects)
-  };
-
-  function filter (item) {
-    return (this.indexOf(item.project) > -1);
-  }
-}
-
-/**
- * Filters watchlist based on editflags (minor, bot, registered)
- */
-function editflagsFilter () {
-  return function (items, flags) {
-    return items.filter(filter, flags)
-  };
-
-  function filter (item) {
-    if (item.type === 'log') {
-      return true;
-    }
-    var minor = this.minor || !item.minor;
-    var bot = this.bot || !item.bot;
-    var anon = this.anon || !(item.anon === "");
-    var registered = this.registered || !(item.userid !== 0);
-    return (minor && bot && anon && registered);
-  }
-}
-
-function authService (localStorageService, $rootScope, $log) {
-  $rootScope.$watch(function () { return 
localStorageService.cookie.get('user');}, function (newValue) {
-    if (newValue !== null) {
-      $log.info('Singed in as ' + newValue);
-      $rootScope.$emit('login', newValue);
-    }
-  });
-
-  this.tokens = function () {
-    return 
angular.fromJson(localStorageService.cookie.get('auth').replace(/\\054/g, ','));
-  };
-
-  this.user = function () {
-    return localStorageService.cookie.get('user');
-  };
-
-  this.isloggedin = function () {
-    return (localStorageService.cookie.get('auth') !== null);
-  };
-
-}
-
-function dataService (socket, authService, localStorageService, $log) {
-  var vm = this;
-
-  vm.icons = {};
-  vm.icons['wikibooks']   = 
"//upload.wikimedia.org/wikipedia/commons/f/fa/Wikibooks-logo.svg";
-  vm.icons['wiktionary']  = 
"//upload.wikimedia.org/wikipedia/commons/e/ef/Wikitionary.svg";
-  vm.icons['wikiquote']   = 
"//upload.wikimedia.org/wikipedia/commons/f/fa/Wikiquote-logo.svg";
-  vm.icons['wikipedia']   = 
"//upload.wikimedia.org/wikipedia/commons/8/80/Wikipedia-logo-v2.svg";
-  vm.icons['wikinews']    = 
"//upload.wikimedia.org/wikipedia/commons/2/24/Wikinews-logo.svg";
-  vm.icons['wikivoyage']  = 
"//upload.wikimedia.org/wikipedia/commons/8/8a/Wikivoyage-logo.svg";
-  vm.icons['wikisource']  = 
"//upload.wikimedia.org/wikipedia/commons/4/4c/Wikisource-logo.svg";
-  vm.icons['wikiversity'] = 
"//upload.wikimedia.org/wikipedia/commons/9/91/Wikiversity-logo.svg";
-  vm.icons['foundation']  = 
"//upload.wikimedia.org/wikipedia/commons/c/c4/Wikimedia_Foundation_RGB_logo_with_text.svg";
-  vm.icons['mediawiki']   = 
"//upload.wikimedia.org/wikipedia/commons/3/3d/Mediawiki-logo.png";
-  vm.icons['meta']        = 
"//upload.wikimedia.org/wikipedia/commons/7/75/Wikimedia_Community_Logo.svg";
-  vm.icons['wikidata']    = 
"//upload.wikimedia.org/wikipedia/commons/f/ff/Wikidata-logo.svg";
-  vm.icons['commons']     = 
"//upload.wikimedia.org/wikipedia/commons/4/4a/Commons-logo.svg";
-  vm.icons['species']     = 
"//upload.wikimedia.org/wikipedia/en/b/bf/Wikispecies-logo-35px.png";
-  vm.icons['incubator']   = 
"//upload.wikimedia.org/wikipedia/commons/e/e3/Incubator-logo.svg";
-  vm.icons['test']        = 
"//upload.wikimedia.org/wikipedia/commons/4/4a/Wikipedia_logo_v2_%28black%29.svg";
-
-  vm.flags = ["ad", "ae", "af", "ag", "ai", "al", "am", "an", "ao", "ar", 
"as", "at", "au", "aw", "ax", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", 
"bi", "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by", "bz", "ca", 
"cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl", "cm", "cn", "co", "cr", "cs", 
"cu", "cv", "cx", "cy", "cz", "da", "de", "dj", "dk", "dm", "do", "dz", "ec", 
"ee", "eg", "eh", "en", "er", "es", "et", "fam", "fi", "fj", "fk", "fm", "fo", 
"fr", "ga", "gb", "gd", "ge", "gf", "gh", "gi", "gl", "gm", "gn", "gp", "gq", 
"gr", "gs", "gt", "gu", "gw", "gy", "he", "hk", "hm", "hn", "hr", "ht", "hu", 
"id", "ie", "il", "in", "io", "iq", "ir", "is", "it", "jm", "jo", "jp", "ke", 
"kg", "kh", "ki", "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc", 
"li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc", "md", "me", "mg", 
"mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq", "mr", "ms", "mt", "mu", "mv", 
"mw", "mx", "my", "mz", "na", "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", 
"nr", "nu", "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm", "pn", 
"pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "rs", "ru", "rw", "sa", "sb", 
"scotland", "sc", "sd", "se", "sg", "sh", "si", "sj", "sk", "sl", "sm", "sn", 
"so", "sr", "st", "sv", "sy", "sz", "tc", "td", "tf", "tg", "th", "tj", "tk", 
"tl", "tm", "tn", "to", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "um", "us", 
"uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu", "wales", "wf", "ws", 
"ye", "yt", "za", "zh", "zm", "zw"];
-
-  /**
-   * Array that contains all watchlist entries.
-   * @type {Array}
-   */
-  vm.watchlist = [];
-
-  /**
-   * Initialize user settings
-   */
-  vm.defaultconfig = {
-    /**
-     * true: show only latest change
-     * false: show all changes
-     */
-    lastrevonly: false,
-    /**
-     * Timeperiod for which the watchlist is retrieved in days
-     */
-    watchlistperiod: 1.5,
-    flagsenable: false,
-    /**
-     * Options to show minor, bot, anon and registered user edits
-     */
-    editflags: false,
-    /**
-     * List of all known projects (wikis)
-     */
-    projectsList: false,
-    /**
-     * List of selected projects (wikis)
-     */
-    projectsSelected: false
-  };
-  if (localStorageService.get('config') !== null) {
-    vm.config = localStorageService.get('config');
-    vm.config.__proto__ = vm.defaultconfig;
-  } else {
-    vm.config = Object.create(vm.defaultconfig);
-  }
-  // Sadly no other way to do this right
-  vm.config.projectsList = vm.config.projectsList || [];
-  vm.config.projectsSelected = vm.config.projectsSelected || [];
-  vm.config.editflags = vm.config.editflags || {
-      minor: true,
-      bot: false,
-      anon: true,
-      registered: true
-    };
-
-  /**
-   * Process an array of new watchlist entries.
-   * @param entries
-   */
-  vm.addWatchlistEntries = function (entries) {
-    vm.watchlist.push.apply(vm.watchlist, entries);
-    var project = entries[0].project;
-    if (vm.config.projectsList.indexOf(project) === -1) {
-      vm.config.projectsList.push(project);
-      vm.config.projectsSelected.push(project);
-      vm.saveConfig();
-    }
-  };
-
-  /**
-   * Reset watchlist and query it again
-   */
-  vm.resetWatchlist = function () {
-    vm.watchlist = [];
-    vm.queryWatchlist();
-    vm.saveConfig();
-  };
-
-  /**
-   * Save user config to local storage
-   */
-  vm.saveConfig = function() {
-    localStorageService.set('config', vm.config);
-  };
-
-  /**
-   * If logged in, query watchlist.
-   */
-  vm.queryWatchlist = function () {
-    if (authService.isloggedin()) {
-      var watchlistQuery = {
-        action: 'watchlist',
-        access_token: authService.tokens(),
-        watchlistperiod: vm.config.watchlistperiod,
-        allrev: !vm.config.lastrevonly,
-        projects: vm.config.projectsList
-      };
-      try {
-        socket.send(angular.toJson(watchlistQuery));
-      } catch(err) {
-        // INVALID_STATE_ERR when sockjs is not yet connected
-        if (err.message === 'InvalidStateError: The connection has not been 
established yet') {
-          $log.warn('Wachtlist queried before socketjs connected.');
-        } else {
-          $log.error(err);
-        }
-      }
-    }
-  };
-}
-
-function runBlock (socket, $rootScope, dataService, $log, $alert, $timeout, 
$translate, amMoment, localStorageService) {
-  var connectionError = true;
-
-  /**
-   * Set Moment.js language
-   * Strange bug, $translate.use() is sometimes undefined if loaded by 
angular-translate from local storage
-   */
-  var lang = $translate.use();
-  if (typeof lang === "undefined") {
-    lang = localStorageService.get('NG_TRANSLATE_LANG_KEY');
-    $translate.use(lang);
-  }
-  amMoment.changeLocale(lang);
-
-  /**
-   * Query watchlist on login.
-   * Not part of authService to prevent circular dependency.
-   */
-  $rootScope.$on('login', function () {
-    dataService.queryWatchlist();
-    $timeout(errorHandler, 15000); // show error if no watchlist after 15 secs
-  });
-
-  /**
-   * If logged in before sockjs connected, query watchlist again.
-   */
-  socket.setHandler('open', function () {
-    $log.info('sockjs connected');
-    dataService.queryWatchlist();
-  });
-
-  /**
-   * Handle all sockjs incoming messages here.
-   */
-  socket.setHandler('message', function (msg) {
-    msg = angular.fromJson(msg);
-    var data = angular.fromJson(msg.data);
-    if (data.msgtype === 'watchlist') {
-      dataService.addWatchlistEntries(data.entires);
-    } else if (data.msgtype === 'loginerror') {
-      $rootScope.$emit('error', 'loginerror');
-      $log.error('login failed!');
-    } else {
-      $log.error(data);
-    }
-
-    if (connectionError) {
-      connectionError = false;
-    }
-  });
-
-  /**
-   * Show error message if no watchlist entries are retrieved.
-   */
-  function errorHandler () {
-    if (connectionError) {
-      $log.warn('No websocket message after 15 seconds.');
-      $alert({
-        title: 'Error,',
-        content: 'no watchlist could be retrieved. This might be an internal 
server error.',
-        type: 'danger',
-        container: '#message-container',
-        show: true
-      });
-    }
-  }
 }
diff --git a/frontend/src/app/main/main.controller.js 
b/frontend/src/app/main/main.controller.js
index dedac98..09f4719 100644
--- a/frontend/src/app/main/main.controller.js
+++ b/frontend/src/app/main/main.controller.js
@@ -1,56 +1,11 @@
 'use strict';
 
 angular.module('crosswatch')
-  .controller('MainCtrl', function ($alert, socket, $rootScope, $translate, 
authService, $timeout, $log) {
+  .controller('MainCtrl', function ($rootScope, authService) {
     var vm = this;
     vm.loggedin = authService.isloggedin();
-    showLoggedinAlert();
-
-    $alert({
-      title: 'Caution,',
-      content: 'this tool is an early prototype. It\'s not ready for the prime 
time.',
-      type: 'warning',
-      container: '#message-container',
-      show: true
-    });
 
     $rootScope.$on('login', function (event, msg) {
       vm.loggedin = true;
-      showLoggedinAlert();
     });
-
-    $rootScope.$on('error', function (event, msg) {
-      if (msg === 'loginerror') {
-        $alert({
-          title: '',
-          content: 'You must <a href="login" target="_blank">sign in</a> to 
save changes.',
-          type: 'danger',
-          container: '#message-container',
-          show: true
-        });
-      }
-    });
-
-    $rootScope.$on('$translateChangeSuccess', function () {
-      if (authService.isloggedin() && (typeof vm.loginAlert !== "undefined")) {
-        vm.loginAlert.destroy();
-        showLoggedinAlert();
-      }
-    });
-
-    function showLoggedinAlert() {
-      if (!authService.isloggedin()) {
-        return;
-      }
-      vm.loginAlertDict = {
-        title: '',
-        type: 'success',
-        container: '#message-container',
-        show: true
-      };
-      $translate('SIGNED_IN_MSG', { username: authService.user() 
}).then(function (msg) {
-        vm.loginAlertDict.content = msg;
-        vm.loginAlert = $alert(vm.loginAlertDict);
-      });
-    }
   });
diff --git a/frontend/src/app/main/main.html b/frontend/src/app/main/main.html
index 2c9d437..cac7489 100644
--- a/frontend/src/app/main/main.html
+++ b/frontend/src/app/main/main.html
@@ -1,48 +1,7 @@
-<div class="container">
-  <div ng-include="'components/navbar/navbar.html'"></div>
-
-  <!--
-  <div ng-if="ctrl.loggedin">
-    <div class="bs-header" id="content">
-      <div class="container">
-        <div class="col-md-9">
-          <h1>crosswatch</h1>
-          <p class="lead" translate="DESCRIPTION">
-          </p>
-        </div>
-      </div>
-    </div>
-  </div>
-   -->
-
-  <div class="container-fluid">
-    <div id="message-container"></div>
-  </div>
-
-  <div ng-if="!ctrl.loggedin">
-    <div class="jumbotron">
-      <h1>Welcome to crosswatch</h1>
-      <p class="lead" translate="DESCRIPTION"></p>
-      <p><a href="login" target="_self" class="btn btn-primary btn-lg" 
role="button"><i class="fa fa-sign-in"></i>
-        &nbsp;<span translate="SIGN_IN_BUTTON"></span>
-      </a></p>
-    </div>
-  </div>
-
-
-  <div ng-if="ctrl.loggedin" 
ng-include="'components/watchlist/watchlist.html'"></div>
-
-  <hr>
-
-  <div id="footer">
-    <p class="pull-right"><a href="//tools.wmflabs.org">
-      <img id="footer-icon" 
src="//tools.wmflabs.org/static/res/logos/powered-by-tool-labs.png" width="105" 
height="40"
-           title="Powered by Wikimedia Tool Labs" alt="Powered by Wikimedia 
Tool Labs">
-    </a></p>
-    <p class="text-muted">
-      Maintained by <a href="//de.wikipedia.org/wiki/Benutzer:Sitic" 
title="sitic @ de.wikipedia">sitic</a>.
-      <a 
href="https://git.wikimedia.org/summary/labs%2Ftools%2Fcrosswatch";>Code</a> 
licensed under the
-      <a 
href="https://git.wikimedia.org/summary/labs%2Ftools%2Fcrosswatch.git/master/LICENSE";>ISC
 License</a>.
-    </p>
-  </div>
+<header ng-include="'components/navbar/navbar.html'"></header>
+<div layout="column" flex class="md-padding" role="main">
+  <div ng-include="'components/settings/settings.html'"></div>
+  <div ng-include="'components/watchlist/watchlist.html'"></div>
 </div>
+
+<ng-include src="'components/footer/footer.html'"></ng-include>
diff --git a/frontend/src/app/runBlock.js b/frontend/src/app/runBlock.js
new file mode 100644
index 0000000..0965138
--- /dev/null
+++ b/frontend/src/app/runBlock.js
@@ -0,0 +1,105 @@
+'use strict';
+angular
+  .module('crosswatch')
+  .run(runBlock)
+;
+
+function runBlock (socket, $rootScope, dataService, $log, $timeout, 
$translate, amMoment, localStorageService,
+                   $mdDialog, authService, $location) {
+
+  /**
+   * Redirect if not logged in
+   */
+  $rootScope.$on('$routeChangeStart', function (event) {
+    if (!authService.isloggedin()) {
+      $location.path('/welcome');
+    }
+    else {
+      $location.path('/');
+    }
+  });
+
+  /**
+   * Set Moment.js language
+   * Strange bug, $translate.use() is sometimes undefined if loaded by 
angular-translate from local storage
+   */
+  var lang = $translate.use();
+  if (typeof lang === "undefined") {
+    lang = localStorageService.get('NG_TRANSLATE_LANG_KEY');
+    $translate.use(lang);
+  }
+  amMoment.changeLocale(lang);
+
+  /**
+   * Query watchlist on login.
+   * Not part of authService to prevent circular dependency.
+   */
+  $rootScope.$on('login', function () {
+    dataService.queryWatchlist();
+    $timeout(errorHandler, 20000); // show error if no watchlist after 20 secs
+  });
+
+  /**
+   * If logged in before sockjs connected, query watchlist again.
+   */
+  socket.setHandler('open', function () {
+    $log.info('sockjs connected');
+    dataService.queryWatchlist();
+  });
+
+  /**
+   * Handle all sockjs incoming messages here.
+   */
+  var connectionError = true;
+  socket.setHandler('message', function (msg) {
+    msg = angular.fromJson(msg);
+    var data = angular.fromJson(msg.data);
+    if (data.msgtype === 'watchlist') {
+      dataService.addWatchlistEntries(data.entires);
+    } else if (data.msgtype === 'loginerror') {
+      $log.error('login failed!');
+
+      $translate(['OAUTH_FAILURE_TITLE', 'OAUTH_FAILURE_CONTENT', 
'CLOSE']).then(function (translations) {
+        var alert = $mdDialog.alert({
+          title: translations['OAUTH_FAILURE_TITLE'],
+          content: translations['OAUTH_FAILURE_CONTENT'],
+          ok: translations['CLOSE']
+        });
+
+        $mdDialog
+          .show( alert )
+          .finally(function() {
+            alert = undefined;
+          });
+      });
+    } else {
+      $log.error(data);
+    }
+
+    if (connectionError) {
+      connectionError = false;
+    }
+  });
+
+  /**
+   * Show error message if no watchlist entries are retrieved.
+   */
+  function errorHandler () {
+    if (connectionError) {
+      $log.warn('No websocket message after 20 seconds.');
+      $translate(['SERVER_ERROR_TITLE', 'SERVER_ERROR_CONTENT', 
'CLOSE']).then(function (translations) {
+        var alert = $mdDialog.alert({
+          title: translations['SERVER_ERROR_TITLE'],
+          content: translations['SERVER_ERROR_CONTENT'],
+          ok: translations['CLOSE']
+        });
+
+        $mdDialog
+          .show(alert)
+          .finally(function () {
+            alert = undefined;
+          });
+      });
+    }
+  }
+}
diff --git a/frontend/src/app/services.js b/frontend/src/app/services.js
new file mode 100644
index 0000000..8330af0
--- /dev/null
+++ b/frontend/src/app/services.js
@@ -0,0 +1,136 @@
+'use strict';
+angular
+  .module('crosswatch')
+  .service('authService', authService)
+  .service('dataService', dataService)
+;
+
+function authService (localStorageService, $rootScope, $log) {
+  $rootScope.$watch(function () { return 
localStorageService.cookie.get('user');}, function (newValue) {
+    if (newValue !== null) {
+      $log.info('Singed in as ' + newValue);
+      $rootScope.$emit('login', newValue);
+    }
+  });
+
+  this.tokens = function () {
+    return 
angular.fromJson(localStorageService.cookie.get('auth').replace(/\\054/g, ','));
+  };
+
+  this.user = function () {
+    return localStorageService.cookie.get('user');
+  };
+
+  this.isloggedin = function () {
+    return (localStorageService.cookie.get('auth') !== null);
+  };
+
+}
+
+function dataService (socket, authService, localStorageService, $log) {
+  var vm = this;
+
+  /**
+   * Array that contains all watchlist entries.
+   * @type {Array}
+   */
+  vm.watchlist = [];
+
+  /**
+   * Initialize user settings
+   */
+  vm.defaultconfig = {
+    /**
+     * true: show only latest change
+     * false: show all changes
+     */
+    lastrevonly: false,
+    /**
+     * Timeperiod for which the watchlist is retrieved in days
+     */
+    watchlistperiod: 1.5,
+    flagsenable: false,
+    /**
+     * Options to show minor, bot, anon and registered user edits
+     */
+    editflags: false,
+    /**
+     * List of all known projects (wikis)
+     */
+    projectsList: false,
+    /**
+     * List of selected projects (wikis)
+     */
+    projectsSelected: false
+  };
+  if (localStorageService.get('config') !== null) {
+    vm.config = localStorageService.get('config');
+    vm.config.__proto__ = vm.defaultconfig;
+  } else {
+    vm.config = Object.create(vm.defaultconfig);
+  }
+  // Sadly no other way to do this right
+  vm.config.projectsList = vm.config.projectsList || [];
+  vm.config.projectsSelected = vm.config.projectsSelected || [];
+  vm.config.editflags = vm.config.editflags || {
+      minor: true,
+      bot: false,
+      anon: true,
+      registered: true
+    };
+
+  /**
+   * Process an array of new watchlist entries.
+   * @param entries
+   */
+  vm.addWatchlistEntries = function (entries) {
+    vm.watchlist.push.apply(vm.watchlist, entries);
+    var project = entries[0].project;
+    if (vm.config.projectsList.indexOf(project) === -1) {
+      vm.config.projectsList.push(project);
+      vm.config.projectsSelected.push(project);
+      vm.saveConfig();
+    }
+  };
+
+  /**
+   * Reset watchlist and query it again
+   */
+  vm.resetWatchlist = function () {
+    vm.watchlist.length = 0;
+    vm.queryWatchlist();
+    vm.saveConfig();
+  };
+
+  /**
+   * Save user config to local storage
+   */
+  vm.saveConfig = function() {
+    localStorageService.set('config', vm.config);
+  };
+
+  /**
+   * If logged in, query watchlist.
+   */
+  vm.queryWatchlist = function () {
+    if (authService.isloggedin()) {
+      var watchlistQuery = {
+        action: 'watchlist',
+        access_token: authService.tokens(),
+        watchlistperiod: vm.config.watchlistperiod,
+        allrev: !vm.config.lastrevonly,
+        projects: vm.config.projectsList
+      };
+      try {
+        socket.send(angular.toJson(watchlistQuery));
+      } catch(err) {
+        // INVALID_STATE_ERR when sockjs is not yet connected
+        if (err.message === 'InvalidStateError: The connection has not been 
established yet') {
+          $log.warn('Wachtlist queried before socketjs connected.');
+        } else {
+          $log.error(err);
+        }
+      }
+    }
+  };
+}
diff --git a/frontend/src/app/welcome/welcome.html 
b/frontend/src/app/welcome/welcome.html
new file mode 100644
index 0000000..1a55e41
--- /dev/null
+++ b/frontend/src/app/welcome/welcome.html
@@ -0,0 +1,15 @@
+<header ng-include="'components/navbar/navbar.html'"></header>
+<div layout="column" flex class="md-padding" id="welcome">
+  <div layout="row" layout-align="center center" flex>
+    <md-card flex-sm="90">
+      <md-card-content layout="column" layout-align="center center">
+        <h1>crosswatch</h1>
+        <p class="text-center" translate="DESCRIPTION"></p>
+        <md-button href="login" target="_self" class="md-raised md-accent">
+          {{'SIGN_IN_BUTTON' | translate}}
+        </md-button>
+      </md-card-content>
+    </md-card>
+  </div>
+</div>
+<ng-include src="'components/footer/footer.html'"></ng-include>
diff --git a/frontend/src/assets/images/flags/readme.txt 
b/frontend/src/assets/images/flags/readme.txt
old mode 100755
new mode 100644
diff --git a/frontend/src/components/footer/footer.html 
b/frontend/src/components/footer/footer.html
new file mode 100644
index 0000000..05c3f12
--- /dev/null
+++ b/frontend/src/components/footer/footer.html
@@ -0,0 +1,11 @@
+<div role="footer" layout="row" layout-align="space-around center">
+  <p class="text-muted">
+    Maintained by <a href="//de.wikipedia.org/wiki/Benutzer:Sitic" 
title="sitic @ de.wikipedia">sitic</a>.
+    <a 
href="https://git.wikimedia.org/summary/labs%2Ftools%2Fcrosswatch";>Code</a> 
licensed under the
+    <a href="https://en.wikipedia.org/wiki/ISC_license";>ISC License</a>.
+  </p>
+  <p class="pull-right"><a href="//tools.wmflabs.org">
+    <img id="footer-icon" 
src="//tools.wmflabs.org/static/res/logos/powered-by-tool-labs.png" width="105" 
height="40"
+         title="Powered by Wikimedia Tool Labs" alt="Powered by Wikimedia Tool 
Labs">
+  </a></p>
+</div>
diff --git a/frontend/src/components/navbar/navbar.controller.js 
b/frontend/src/components/navbar/navbar.controller.js
index 4b71cff..c6c4713 100644
--- a/frontend/src/components/navbar/navbar.controller.js
+++ b/frontend/src/components/navbar/navbar.controller.js
@@ -1,7 +1,8 @@
 'use strict';
 
 angular.module('crosswatch')
-  .controller('NavbarCtrl', function (translationList, $translate, $rootScope, 
authService, amMoment, localStorageService) {
+  .controller('NavbarCtrl', function (translationList, $translate, $rootScope, 
authService, amMoment,
+                                      localStorageService) {
     var vm = this;
     vm.langs = translationList;
     vm.selectedLang = $translate.use();
diff --git a/frontend/src/components/navbar/navbar.html 
b/frontend/src/components/navbar/navbar.html
index 59636db..52eeeae 100644
--- a/frontend/src/components/navbar/navbar.html
+++ b/frontend/src/components/navbar/navbar.html
@@ -1,45 +1,22 @@
-<nav class="navbar navbar-default" ng-controller="NavbarCtrl as ctrl">
-  <div class="container-fluid">
-    <form class="navbar-form navbar-left" role="language">
-      <button type="button" class="btn btn-default"
-              ng-model="ctrl.selectedLang"
-              bs-options="lang.key as lang.language for lang in ctrl.langs"
-              ng-change="ctrl.changeLanguage(ctrl.selectedLang)"
-              bs-select></button>
-    </form>
-    <div>
-      <ul class="nav navbar-nav">
-        <li>
-          <a href="https://meta.wikimedia.org/wiki/Crosswatch"; target="_blank">
-            <i class="fa fa-info-circle"></i>&nbsp;<span 
translate="DOCUMENTATION"></span>
-          </a>
-        </li>
-        <li>
-          <a href="https://meta.wikimedia.org/wiki/Talk:Crosswatch"; 
target="_blank">
-            <i class="fa fa-comments"></i>&nbsp;<span translate="TALK"></span>
-          </a>
-        </li>
-        <li>
-          <a href="https://phabricator.wikimedia.org/project/view/1224/"; 
target="_blank">
-            <i class="fa fa-bug"></i>&nbsp;<span 
translate="REPORT_BUGS"></span>
-          </a>
-        </li>
-        <li>
-          <a 
href="https://git.wikimedia.org/summary/labs%2Ftools%2Fcrosswatch"; 
target="_blank">
-            <i class="fa fa-github"></i>&nbsp;Git
-          </a>
-        </li>
-      </ul>
-    </div>
-    <ul class="nav navbar-nav navbar-right">
-      <li ng-hide="ctrl.loggedin">
-        <a href="login" target="_self" class="navbar-link"><i class="fa 
fa-sign-in"></i>
-          &nbsp;<span translate="SIGN_IN"></span></a>
-      </li>
-      <li ng-show="ctrl.loggedin">
-        <a href="logout" target="_self" class="navbar-link">
-          &nbsp;<span translate="SIGN_OUT"></span> <i class="fa 
fa-sign-out"></i></a>
-      </li>
-    </ul>
-  </div>
-</nav>
+<md-toolbar layout="row" layout-align="center center" 
ng-controller="NavbarCtrl as ctrl" class="md-toolbar-tools">
+  <md-select ng-model="ctrl.selectedLang"
+             placeholder="Language"
+             ng-change="ctrl.changeLanguage(ctrl.selectedLang)">
+    <md-option ng-repeat="lang in ctrl.langs" 
value="{{lang.key}}">{{lang.language}}</md-option>
+  </md-select>
+  <md-button href="https://meta.wikimedia.org/wiki/Crosswatch"; target="_blank" 
class="md-flat">
+    {{'DOCUMENTATION' | translate}}
+  </md-button>
+  <md-button href="https://meta.wikimedia.org/wiki/Talk:Crosswatch"; 
target="_blank" class="md-flat">
+    {{'TALK' | translate}}
+  </md-button>
+  <md-button href="https://phabricator.wikimedia.org/project/view/1224/"; 
target="_blank" class="md-flat">
+    {{'REPORT_BUGS' | translate}}
+  </md-button>
+  <md-button href="login" target="_self" class="md-flat" 
ng-hide="ctrl.loggedin">
+    {{'SIGN_IN' | translate}}
+  </md-button>
+  <md-button href="logout" target="_self" class="md-flat" 
ng-show="ctrl.loggedin">
+    {{'SIGN_OUT' | translate}}
+  </md-button>
+</md-toolbar>
diff --git a/frontend/src/components/settings/settings.controller.js 
b/frontend/src/components/settings/settings.controller.js
new file mode 100644
index 0000000..ddaceef
--- /dev/null
+++ b/frontend/src/components/settings/settings.controller.js
@@ -0,0 +1,34 @@
+'use strict';
+
+angular.module('crosswatch')
+  .controller('SettingsCtrl', function ($translate, socket, $log, dataService, 
$rootScope) {
+    var vm = this;
+    vm.config = dataService.config;
+    vm.saveConfig = dataService.saveConfig;
+
+    updatePeriodList();
+    $rootScope.$on('$translateChangeSuccess', updatePeriodList);
+
+    function updatePeriodList () {
+      var hours = [0.5, 1, 1.5];
+      var days = [2, 3, 7, 14, 21, 30];
+
+      vm.periodList = [];
+      var addItem = function (value) {
+        return function (label) {
+          vm.periodList.push({value: value, label: label})
+        }
+      };
+      for (var i=0; i<hours.length; i++) {
+        $translate('SHOW_LAST_HOURS', {number: 
24*hours[i]}).then(addItem(hours[i]));
+      }
+      for (var j=0; j<days.length; j++) {
+        $translate('SHOW_LAST_DAYS', {number: days[j]}).then(addItem(days[j]));
+      }
+    }
+
+    vm.resetWatchlist = function () {
+      $log.info('resetting watchlist');
+      dataService.resetWatchlist();
+    };
+  });
diff --git a/frontend/src/components/settings/settings.html 
b/frontend/src/components/settings/settings.html
new file mode 100644
index 0000000..6526e93
--- /dev/null
+++ b/frontend/src/components/settings/settings.html
@@ -0,0 +1,61 @@
+<div ng-controller="SettingsCtrl as ctrl" layout="row" layout-align="center 
start">
+  <md-whitefram class="md-whiteframe-z5 whitebox">
+    <div layout="row">
+      <div flex="50">
+        <div layout="column" class="md-padding">
+          <md-list>
+            <md-subheader class="md-no-sticky">{{'WIKIS' | 
translate}}</md-subheader>
+            <md-item id="projects">
+              <md-select ng-model="ctrl.config.projectsSelected" 
multiple="true" placeholder="Loading …"
+                         ng-change="ctrl.saveConfig()">
+                <md-option ng-repeat="item in ctrl.config.projectsList" 
value="{{item}}">{{item}}</md-option>
+              </md-select>
+            </md-item>
+            <md-subheader class="md-no-sticky">{{'FILTER' | 
translate}}</md-subheader>
+            <md-item layout="row">
+              <md-list flex="50">
+                <md-list-item>
+                  <p translate="MINOR_EDITS"></p>
+                  <md-checkbox ng-model="ctrl.config.editflags.minor" 
ng-change="ctrl.saveConfig()"></md-checkbox>
+                </md-list-item>
+                <md-list-item>
+                  <p translate="BOT_EDITS"></p>
+                  <md-checkbox ng-model="ctrl.config.editflags.bot" 
ng-change="ctrl.saveConfig()"></md-checkbox>
+                </md-list-item>
+              </md-list>
+              <md-list flex="50">
+                <md-list-item>
+                  <p translate="ANON_EDITS"></p>
+                  <md-checkbox ng-model="ctrl.config.editflags.anon" 
ng-change="ctrl.saveConfig()"></md-checkbox>
+                </md-list-item>
+                <md-list-item>
+                  <p translate="USER_EDITS"></p>
+                  <md-checkbox ng-model="ctrl.config.editflags.registered" 
ng-change="ctrl.saveConfig()"></md-checkbox>
+                </md-list-item>
+              </md-list>
+            </md-item>
+          </md-list>
+        </div>
+      </div>
+      <md-divider role="vertical"></md-divider>
+      <md-list flex="50">
+        <div layout="column" class="md-padding">
+          <md-subheader class="md-no-sticky">{{'PREFERENCES' | 
translate}}</md-subheader>
+          <md-list-item>
+            <md-select ng-model="ctrl.config.watchlistperiod" 
placeholder="timeperiod" ng-change="ctrl.resetWatchlist()">
+              <md-option ng-repeat="item in ctrl.periodList" 
value="{{item.value}}">{{item.label}}</md-option>
+            </md-select>
+          </md-list-item>
+          <md-list-item>
+            <p translate="LASTREVONLY"></p>
+            <md-checkbox ng-model="ctrl.config.lastrevonly" 
ng-change="ctrl.resetWatchlist()"></md-checkbox>
+          </md-list-item>
+          <md-list-item>
+            <p translate="FLAGS"></p>
+            <md-checkbox ng-model="ctrl.config.flagsenable" 
ng-change="ctrl.saveConfig()"></md-checkbox>
+          </md-list-item>
+        </div>
+      </md-list>
+    </div>
+  </md-whitefram>
+</div>
diff --git a/frontend/src/components/watchlist/entry.directive.js 
b/frontend/src/components/watchlist/entry.directive.js
index 097781b..54d4105 100644
--- a/frontend/src/components/watchlist/entry.directive.js
+++ b/frontend/src/components/watchlist/entry.directive.js
@@ -4,13 +4,9 @@
 
 function watchlistEntry() {
   var directive = {
-    link: link,
+    scope: true,
     templateUrl: 'components/watchlist/entry.directive.html',
     restrict: 'EA'
   };
   return directive;
-
-  function link(scope, element, attrs) {
-    /* */
-  }
 }
diff --git a/frontend/src/components/watchlist/watchlist.controller.js 
b/frontend/src/components/watchlist/watchlist.controller.js
index 999dbaf..e8cc97a 100644
--- a/frontend/src/components/watchlist/watchlist.controller.js
+++ b/frontend/src/components/watchlist/watchlist.controller.js
@@ -4,34 +4,30 @@
   .controller('WatchlistCtrl', function ($translate, socket, $log, 
dataService, $rootScope) {
     var vm = this;
     vm.watchlist = dataService.watchlist;
-    vm.echolist = dataService.echolist;
-    vm.icons = dataService.icons;
-    vm.flags = dataService.flags;
     vm.config = dataService.config;
-    vm.saveConfig = dataService.saveConfig;
 
-    updatePeriodList();
-    $rootScope.$on('$translateChangeSuccess', updatePeriodList);
+    vm.icons = {};
+    vm.icons['wikibooks']   = 
"//upload.wikimedia.org/wikipedia/commons/f/fa/Wikibooks-logo.svg";
+    vm.icons['wiktionary']  = 
"//upload.wikimedia.org/wikipedia/commons/e/ef/Wikitionary.svg";
+    vm.icons['wikiquote']   = 
"//upload.wikimedia.org/wikipedia/commons/f/fa/Wikiquote-logo.svg";
+    vm.icons['wikipedia']   = 
"//upload.wikimedia.org/wikipedia/commons/8/80/Wikipedia-logo-v2.svg";
+    vm.icons['wikinews']    = 
"//upload.wikimedia.org/wikipedia/commons/2/24/Wikinews-logo.svg";
+    vm.icons['wikivoyage']  = 
"//upload.wikimedia.org/wikipedia/commons/8/8a/Wikivoyage-logo.svg";
+    vm.icons['wikisource']  = 
"//upload.wikimedia.org/wikipedia/commons/4/4c/Wikisource-logo.svg";
+    vm.icons['wikiversity'] = 
"//upload.wikimedia.org/wikipedia/commons/9/91/Wikiversity-logo.svg";
+    vm.icons['foundation']  = 
"//upload.wikimedia.org/wikipedia/commons/c/c4/Wikimedia_Foundation_RGB_logo_with_text.svg";
+    vm.icons['mediawiki']   = 
"//upload.wikimedia.org/wikipedia/commons/3/3d/Mediawiki-logo.png";
+    vm.icons['meta']        = 
"//upload.wikimedia.org/wikipedia/commons/7/75/Wikimedia_Community_Logo.svg";
+    vm.icons['wikidata']    = 
"//upload.wikimedia.org/wikipedia/commons/f/ff/Wikidata-logo.svg";
+    vm.icons['commons']     = 
"//upload.wikimedia.org/wikipedia/commons/4/4a/Commons-logo.svg";
+    vm.icons['species']     = 
"//upload.wikimedia.org/wikipedia/en/b/bf/Wikispecies-logo-35px.png";
+    vm.icons['incubator']   = 
"//upload.wikimedia.org/wikipedia/commons/e/e3/Incubator-logo.svg";
+    vm.icons['test']        = 
"//upload.wikimedia.org/wikipedia/commons/4/4a/Wikipedia_logo_v2_%28black%29.svg";
 
-    function updatePeriodList () {
-      var hours = [0.5, 1, 1.5];
-      var days = [2, 3, 7, 14, 21, 30];
-
-      vm.periodList = [];
-      var addItem = function (value) {
-        return function (label) {
-          vm.periodList.push({value: value, label: label})
-        }
-      };
-      for (var i=0; i<hours.length; i++) {
-        $translate('HOURS', {number: 24*hours[i]}).then(addItem(hours[i]));
-      }
-      for (var j=0; j<days.length; j++) {
-        $translate('DAYS', {number: days[j]}).then(addItem(days[j]));
-      }
-    }
+    vm.flags = ["ad", "ae", "af", "ag", "ai", "al", "am", "an", "ao", "ar", 
"as", "at", "au", "aw", "ax", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", 
"bi", "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by", "bz", "ca", 
"cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl", "cm", "cn", "co", "cr", "cs", 
"cu", "cv", "cx", "cy", "cz", "da", "de", "dj", "dk", "dm", "do", "dz", "ec", 
"ee", "eg", "eh", "en", "er", "es", "et", "fam", "fi", "fj", "fk", "fm", "fo", 
"fr", "ga", "gb", "gd", "ge", "gf", "gh", "gi", "gl", "gm", "gn", "gp", "gq", 
"gr", "gs", "gt", "gu", "gw", "gy", "he", "hk", "hm", "hn", "hr", "ht", "hu", 
"id", "ie", "il", "in", "io", "iq", "ir", "is", "it", "jm", "jo", "jp", "ke", 
"kg", "kh", "ki", "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc", 
"li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc", "md", "me", "mg", 
"mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq", "mr", "ms", "mt", "mu", "mv", 
"mw", "mx", "my", "mz", "na", "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", 
"nr", "nu", "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm", "pn", 
"pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "rs", "ru", "rw", "sa", "sb", 
"scotland", "sc", "sd", "se", "sg", "sh", "si", "sj", "sk", "sl", "sm", "sn", 
"so", "sr", "st", "sv", "sy", "sz", "tc", "td", "tf", "tg", "th", "tj", "tk", 
"tl", "tm", "tn", "to", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "um", "us", 
"uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu", "wales", "wf", "ws", 
"ye", "yt", "za", "zh", "zm", "zw"];
 
     vm.resetWatchlist = function () {
+      $log.info('resetting watchlist');
       dataService.resetWatchlist();
       vm.watchlist = dataService.watchlist;
     };
diff --git a/frontend/src/components/watchlist/watchlist.html 
b/frontend/src/components/watchlist/watchlist.html
index 6c0d8bf..3fde4b2 100644
--- a/frontend/src/components/watchlist/watchlist.html
+++ b/frontend/src/components/watchlist/watchlist.html
@@ -1,84 +1,31 @@
-<div ng-controller="WatchlistCtrl as ctrl">
-  <form class="form-horizontal" role="form">
-    <div class="form-group">
-      <label class="control-label col-md-1">{{'SEARCH' | translate}}:</label>
-      <div class="col-md-2">
-        <input type="text" class="form-control" ng-model="ctrl.searchText">
+<div ng-controller="WatchlistCtrl as ctrl" layout="row" layout-align="center 
center" class="md-padding">
+  <md-whiteframe class="md-whiteframe-z5 whitebox">
+    <md-toolbar class='md-small-tall'>
+      <div class="md-toolbar-tools" ng-hide="showSearch">
+        <h1>
+          <span translate="WATCHLIST"></span>
+        </h1>
+        <div flex></div>
+        <md-button class="md-icon-button" title="{{'SEARCH_WATCHLIST' | 
translate}}" ng-click="showSearch = !showSearch">
+          <md-icon md-font-library="material-icons">search</md-icon>
+        </md-button>
       </div>
-      <label class="control-label col-md-2">{{'TIMEPERIOD' | 
translate}}:</label>
-      <div class="col-md-1">
-        <button type="button" class="btn btn-default"
-                ng-model="ctrl.config.watchlistperiod"
-                bs-options="item.value as item.label for item in 
ctrl.periodList"
-                ng-change="ctrl.resetWatchlist()"
-                bs-select></button>
+      <div class="md-toolbar-tools" ng-show="showSearch">
+        <md-input-container flex="flex" md-no-float>
+          <input type="text" placeholder="{{'SEARCH_WATCHLIST' | translate}}" 
ng-model="ctrl.searchText">
+        </md-input-container>
       </div>
-    </div>
-    <div class="form-group">
-      <label class="control-label col-md-1">Wikis:</label>
-      <div class="col-md-2">
-        <button type="button" class="btn btn-default"
-                ng-model="ctrl.config.projectsSelected"
-                bs-options="item for item in ctrl.config.projectsList"
-                ng-change="ctrl.saveConfig()"
-                data-multiple="1"
-                ng-model-options="{ debounce: 333 }"
-                bs-select></button>
-      </div>
-    </div>
-    <div class="form-group">
-      <label class="control-label col-md-1">{{'OPTIONS' | translate}}:</label>
-      <div class="col-md-4">
-        <div class="checkbox">
-          <label data-placement="bottom-left" data-type="info" 
data-animation="am-fade-and-scale"
-                 bs-tooltip="tooltip" bs-enabled="ctrl.config.lastrevonly">
-            <input type="checkbox" ng-model="ctrl.config.lastrevonly" 
ng-change="ctrl.resetWatchlist()">
-            {{'LASTREVONLY' | translate}}
-          </label>
-        </div>
-        <div class="checkbox">
-          <label data-placement="bottom-left" data-type="info" 
data-animation="am-fade-and-scale"
-                 bs-tooltip="tooltip" bs-enabled="ctrl.config.flagsenable">
-            <input type="checkbox" ng-model="ctrl.config.flagsenable" 
ng-change="ctrl.saveConfig()">
-            Show flags instead of language names
-          </label>
-        </div>
-        <div class="checkbox">
-          <label data-placement="bottom-left" data-type="info" 
data-animation="am-fade-and-scale"
-                 bs-tooltip="tooltip" bs-enabled="ctrl.config.editflags.minor">
-            <input type="checkbox" ng-model="ctrl.config.editflags.minor" 
ng-change="ctrl.saveConfig()">
-            {{'MINOR_EDITS' | translate}}
-          </label>
-        </div>
-        <div class="checkbox">
-          <label data-placement="bottom-left" data-type="info" 
data-animation="am-fade-and-scale"
-                 bs-tooltip="tooltip" bs-enabled="ctrl.config.editflags.bot">
-            <input type="checkbox" ng-model="ctrl.config.editflags.bot" 
ng-change="ctrl.saveConfig()">
-            {{'BOT_EDITS' | translate}}
-          </label>
-        </div>
-        <div class="checkbox">
-          <label data-placement="bottom-left" data-type="info" 
data-animation="am-fade-and-scale"
-                 bs-tooltip="tooltip" bs-enabled="ctrl.config.editflags.anon">
-            <input type="checkbox" ng-model="ctrl.config.editflags.anon" 
ng-change="ctrl.saveConfig()">
-            {{'ANON_EDITS' | translate}}
-          </label>
-        </div>
-        <div class="checkbox">
-          <label data-placement="bottom-left" data-type="info" 
data-animation="am-fade-and-scale"
-                 bs-tooltip="tooltip" 
bs-enabled="ctrl.config.editflags.registered">
-            <input type="checkbox" ng-model="ctrl.config.editflags.registered" 
ng-change="ctrl.saveConfig()">
-            {{'USER_EDITS' | translate}}
-          </label>
-        </div>
-      </div>
-    </div>
-  </form>
-  <hr>
-  <ul class="example-animate-container">
-    <li class="watchlist"
-        ng-repeat="event in ctrl.watchlist | 
projects:ctrl.config.projectsSelected | editflags:ctrl.config.editflags | 
filter:ctrl.searchText | orderBy:'-timestamp'">
-      <watchlist-entry></watchlist-entry>
-    </li>
-  </ul>
+    </md-toolbar>
+    <md-list>
+      <md-list-item ng-show="ctrl.watchlist.length === 0" layout="row" 
layout-align="center center">
+        <md-progress-circular md-mode="indeterminate"></md-progress-circular>
+      </md-list-item>
+      <md-list-item layout="row"
+                    class="watchlist" id="watchlist"
+                    ng-repeat="event in ctrl.watchlist | 
projects:ctrl.config.projectsSelected | editflags:ctrl.config.editflags | 
filter:ctrl.searchText | orderBy:'-timestamp'">
+        <watchlist-entry></watchlist-entry>
+        <md-divider></md-divider>
+      </md-list-item>
+    </md-list>
+  </md-whiteframe>
 </div>
diff --git a/frontend/src/i18n/locale-de.json b/frontend/src/i18n/locale-de.json
index fce36d1..a88f7bc 100644
--- a/frontend/src/i18n/locale-de.json
+++ b/frontend/src/i18n/locale-de.json
@@ -6,16 +6,12 @@
   "SIGN_OUT": "Abmelden",
   "DESCRIPTION": "Eine erweiterte Beobachtungsliste für Wikimedia Projekte.",
   "SIGN_IN_BUTTON": "Mit OAuth anmelden",
-  "SEARCH": "Suchen",
-  "SIGNED_IN_MSG": "Willkommen {{username}}, du bist angemeldet. Unter 
Umständen dauert es kurz, bis deine Beobachtungsliste vollständig geladen ist.",
   "MINOREDIT_FLAG": "K",
   "BOTEDIT_FLAG": "B",
   "NEWPAGE_FLAG": "N",
-  "OPTIONS": "Optionen",
   "LASTREVONLY": "Zeige nur die letzte Änderung pro Seite",
-  "TIMEPERIOD": "Zeige letzte",
-  "HOURS": "{{number}} Stunden",
-  "DAYS": "{{number}} Tage",
+  "SHOW_LAST_HOURS": "Zeige letzte {{number}} Stunden",
+  "SHOW_LAST_DAYS": "Zeige letzte {{number}} Tage",
   "LOGEVENT_DELETE_DELETE": "<user> löschte Seite <page>",
   "LOGEVENT_DELETE_REVISION": "<user> änderte die Sichtbarkeit von Versionen 
der Seite <page>",
   "LOGEVENT_DELETE_RESTORE": "<user> stellte Seite <page> wieder her",
diff --git a/frontend/src/i18n/locale-en.json b/frontend/src/i18n/locale-en.json
index c6cf1c6..ea99aab 100644
--- a/frontend/src/i18n/locale-en.json
+++ b/frontend/src/i18n/locale-en.json
@@ -6,20 +6,22 @@
   "SIGN_OUT": "Logout",
   "DESCRIPTION": "An enhanced watchlist for Wikimedia projects.",
   "SIGN_IN_BUTTON": "Sign in with OAuth",
-  "SEARCH": "Search",
-  "SIGNED_IN_MSG": "Welcome {{username}}, you are signed in. It may take a bit 
until your watchlist is fully loaded.",
   "MINOREDIT_FLAG": "m",
   "BOTEDIT_FLAG": "b",
   "NEWPAGE_FLAG": "N",
-  "OPTIONS": "Options",
+  "PREFERENCES": "Preferences",
+  "FILTER": "Filter",
+  "WIKIS": "Wikis",
   "LASTREVONLY": "Show only the latest change per page",
-  "TIMEPERIOD": "Show last",
-  "HOURS": "{{number}} hours",
-  "DAYS": "{{number}} days",
-  "MINOR_EDITS": "Show minor edits",
-  "BOT_EDITS": "Show bot edits",
-  "ANON_EDITS": "Show anonymous user edits",
-  "USER_EDITS": "Show registered user edits",
+  "SHOW_LAST_HOURS": "Show last {{number}} hours",
+  "SHOW_LAST_DAYS": "Show last {{number}} days",
+  "MINOR_EDITS": "minor edits",
+  "BOT_EDITS": "bot edits",
+  "ANON_EDITS": "anonymous user edits",
+  "USER_EDITS": "registered user edits",
+  "FLAGS": "Show flags instead of language names",
+  "WATCHLIST": "Watchlist",
+  "SEARCH_WATCHLIST": "Search watchlist",
   "LOGEVENT_DELETE_DELETE": "<user> deleted page <page>",
   "LOGEVENT_DELETE_REVISION": "<user> changed visibility of a revision on page 
<page>",
   "LOGEVENT_DELETE_RESTORE": "<user> restored <page>",
@@ -42,5 +44,10 @@
   "LOGEVENT_NEWUSERS_CREATE": "User account <user name={{title}}> was created",
   "LOGEVENT_NEWUSERS_CREATE2": "User account <user name={{title}}> was created 
by <user>",
   "LOGEVENT_NEWUSERS_BYEMAIL": "User account <user name={{title}}> was created 
by <user> and password was sent by email",
-  "LOGEVENT_RENAMEUSER": "<user> renamed user <user name={{olduser}}> 
({{edits}} edits) to <user name={{newuser}}>"
+  "LOGEVENT_RENAMEUSER": "<user> renamed user <user name={{olduser}}> 
({{edits}} edits) to <user name={{newuser}}>",
+  "OAUTH_FAILURE_TITLE": "OAuth authorization failure",
+  "OAUTH_FAILURE_CONTENT": "You have revoked the authorization for this 
application. Please logout and sign in again.",
+  "CLOSE": "Close",
+  "SERVER_ERROR_TITLE": "Sorry!",
+  "SERVER_ERROR_CONTENT": "No watchlist could be retrieved. Either you have no 
watchlist events for the selected time period, or this is an internal server 
error."
 }
diff --git a/frontend/src/i18n/locale-pt.json b/frontend/src/i18n/locale-pt.json
index 1b90f32..18f8afc 100644
--- a/frontend/src/i18n/locale-pt.json
+++ b/frontend/src/i18n/locale-pt.json
@@ -6,8 +6,6 @@
   "SIGN_OUT": "Sair",
   "DESCRIPTION": "Uma lista de páginas vigiadas aprimorada para os projetos 
Wikimedia.",
   "SIGN_IN_BUTTON": "Enrar com OAuth",
-  "SEARCH": "Pesquisa",
-  "SIGNED_IN_MSG": "Bem-vindo(a) {{username}}, você já está conectado. Pode 
levar algum tempo até que a sua lista de páginas vigiadas seja completamente 
carregada.",
   "MINOREDIT_FLAG": "m",
   "BOTEDIT_FLAG": "b",
   "NEWPAGE_FLAG": "N"
diff --git a/frontend/src/index.html b/frontend/src/index.html
index 65d6ab0..7a2b0a0 100644
--- a/frontend/src/index.html
+++ b/frontend/src/index.html
@@ -4,7 +4,7 @@
     <meta charset="utf-8">
     <title>crosswatch</title>
     <meta name="description" content="">
-    <meta name="viewport" content="width=device-width">
+    <meta name="viewport" content="initial-scale=1">
     <base href="/" />
     <!-- Place favicon.ico and apple-touch-icon.png in the root directory -->
 

-- 
To view, visit https://gerrit.wikimedia.org/r/218006
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I3fa2dba3aac6da73e2f6e8fc03169d4f1e71feeb
Gerrit-PatchSet: 5
Gerrit-Project: labs/tools/crosswatch
Gerrit-Branch: master
Gerrit-Owner: Sitic <jan.leb...@online.de>
Gerrit-Reviewer: Sitic <jan.leb...@online.de>

_______________________________________________
MediaWiki-commits mailing list
MediaWiki-commits@lists.wikimedia.org
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to