diff --git a/.gitmodules b/.gitmodules
index beb37f01f7..51e1374b1a 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -28,3 +28,6 @@
[submodule "public/vendor/multiple-select"]
path = public/vendor/multiple-select
url = https://github.com/ornicar/multiple-select
+[submodule "public/vendor/hopscotch"]
+ path = public/vendor/hopscotch
+ url = https://github.com/linkedin/hopscotch
diff --git a/app/templating/AssetHelper.scala b/app/templating/AssetHelper.scala
index 42df3eb7f7..10fa418703 100644
--- a/app/templating/AssetHelper.scala
+++ b/app/templating/AssetHelper.scala
@@ -68,6 +68,9 @@ trait AssetHelper { self: I18nHelper =>
jsAt(s"vendor/moment/locale/$l.js", static = true)
}
+ def hopscotchJsTag = jsAt("""vendor/hopscotch/dist/js/hopscotch.min.js""")
+ def hopscotchCssTag = cssVendorTag("""hopscotch/dist/css/hopscotch.min.css""")
+
val tagmanagerTag = cdnOrLocal(
cdn = "http://cdnjs.cloudflare.com/ajax/libs/tagmanager/3.0.0/tagmanager.js",
test = "$.tagsManager",
diff --git a/app/views/insight/index.scala.html b/app/views/insight/index.scala.html
index fe8534c46f..624a8cb50a 100644
--- a/app/views/insight/index.scala.html
+++ b/app/views/insight/index.scala.html
@@ -2,12 +2,15 @@
@moreJs = {
@highchartsLatestTag
+@hopscotchJsTag
@jsAt("vendor/multiple-select/multiple-select.js")
@jsAt(s"compiled/lichess.insight${isProd??(".min")}.js")
@jsTag("insight-refresh.js")
+@jsTag("insight-tour.js")
@embedJs {
$(function() {
-LichessInsight(document.getElementById('insight'), {
+lichess = lichess || {};
+lichess.insight = LichessInsight(document.getElementById('insight'), {
ui: @Html(toJson(ui)),
initialQuestion: @Html(toJson(question)),
i18n: @jsI18n(),
@@ -27,6 +30,7 @@ postUrl: "@routes.Insight.json(u.username)"
}
@moreCss = {
+@hopscotchCssTag
@cssTag("insight.css")
@cssVendorTag("multiple-select/multiple-select.css")
@ctx.currentBg match {
diff --git a/public/javascripts/insight-tour.js b/public/javascripts/insight-tour.js
new file mode 100644
index 0000000000..e28520bcaf
--- /dev/null
+++ b/public/javascripts/insight-tour.js
@@ -0,0 +1,80 @@
+$(function() {
+
+ setTimeout(function() {
+ var tour = {
+ id: "insights",
+ showPrevButton: true,
+ steps: [{
+ title: "Welcome to chess insights!",
+ content: "Know your strengths and weaknesses!
" +
+ "Insights let you analyse your playing style,
" +
+ "using pertinent metrics and dimensions.
" +
+ "It's a powerful tool, let's take some time to see how it works.",
+ target: "#insight header h2",
+ placement: "bottom"
+ }, {
+ title: "Insights answer questions",
+ content: "Here are a few examples of the questions you can ask. Try clicking them!",
+ target: "#insight .panel-tabs",
+ placement: "right",
+ onShow: function() {
+ lichess.insight.setPanel('preset');
+ }
+ }, {
+ title: "Answers are graphs",
+ content: "Colorful bars represent the answer to the question posed.
" +
+ "Gray bars represent the size of each data sample, like the number of moves.",
+ target: "#insight .chart",
+ placement: "left"
+ }, {
+ title: "The same data, in a table",
+ content: "This table provides an alternative way to read the answer.
" +
+ "Farther down the page are a few of the games used to answer the question.",
+ target: "#insight table.slist",
+ placement: "top"
+ }, {
+ title: "Ask a question: metric",
+ content: "To start asking your own questions, start by selecting a metric.
" +
+ "For instance, let's ask a question about move times.",
+ target: "#insight div.ms.metric",
+ placement: "left",
+ onShow: function() {
+ lichess.insight.clearFilters();
+ lichess.insight.setPanel('filter');
+ }
+ }, {
+ title: "Ask a question: dimension",
+ content: "Now select a dimension to compare move times with.
" +
+ "For instance, try seeing your move times per variant, or per piece moved.",
+ target: "#insight div.ms.dimension",
+ placement: "left"
+ }, {
+ title: "Ask a question: filters",
+ content: "Make your question more precise by filtering the results.
" +
+ "For instance, select games where you play black and castle kingside.",
+ target: "#insight .panel-tabs",
+ placement: "top",
+ onShow: function() {
+ lichess.insight.clearFilters();
+ lichess.insight.setPanel('filter');
+ }
+ }, {
+ title: "Thank you for your time!",
+ content: "Now be inventive and find the right questions to ask!
" +
+ "You can copy the URL at any time to share the results you're seeing.
" +
+ "Oh and one last thing...",
+ target: "#insight header h2",
+ placement: "bottom"
+ }, {
+ title: "Share your insights data",
+ content: "By default, your data is visible to your lichess friends only.
" +
+ "You can make it public or private from your privacy settings.
" +
+ "Have fun :)",
+ target: "#insight .share",
+ placement: "right"
+ }]
+ };
+ var t = hopscotch.startTour(tour);
+ console.log(t);
+ }, 1000);
+});
diff --git a/public/vendor/hopscotch b/public/vendor/hopscotch
new file mode 160000
index 0000000000..5a09436783
--- /dev/null
+++ b/public/vendor/hopscotch
@@ -0,0 +1 @@
+Subproject commit 5a09436783db849e1a2d1a9551c2ce3acf045fa9
diff --git a/ui/analyse/src/forecast/forecastCtrl.js b/ui/analyse/src/forecast/forecastCtrl.js
index 37d721cd8f..6131acceac 100644
--- a/ui/analyse/src/forecast/forecastCtrl.js
+++ b/ui/analyse/src/forecast/forecastCtrl.js
@@ -65,6 +65,7 @@ module.exports = function(cfg, saveUrl) {
var reloadToLastPly = function() {
loading(true);
+ m.redraw();
if (window.history.replaceState) window.history.replaceState(null, null, '#last');
location.reload();
};
diff --git a/ui/insight/src/ctrl.js b/ui/insight/src/ctrl.js
index 751df581a7..dd858419b4 100644
--- a/ui/insight/src/ctrl.js
+++ b/ui/insight/src/ctrl.js
@@ -34,6 +34,12 @@ module.exports = function(env, domElement) {
panel: !!Object.keys(env.initialQuestion.filters).length ? 'filter' : 'preset'
};
+ this.setPanel = function(p) {
+ this.vm.panel = p;
+ m.redraw();
+ }.bind(this);
+
+
var reset = function() {
this.vm.metric = this.metrics[0];
this.vm.dimension = this.dimensions[0];
diff --git a/ui/insight/src/main.js b/ui/insight/src/main.js
index 509f1d2c38..1a3fe60e85 100644
--- a/ui/insight/src/main.js
+++ b/ui/insight/src/main.js
@@ -13,4 +13,6 @@ module.exports = function(element, opts) {
},
view: view
});
+
+ return controller;
};
diff --git a/ui/insight/src/view.js b/ui/insight/src/view.js
index 884b56c97f..48c40619af 100644
--- a/ui/insight/src/view.js
+++ b/ui/insight/src/view.js
@@ -24,13 +24,13 @@ module.exports = function(ctrl) {
m('a[data-panel=preset]', {
class: 'tab' + (ctrl.vm.panel === 'preset' ? ' active' : ''),
onclick: function() {
- ctrl.vm.panel = 'preset';
+ ctrl.setPanel('preset');
}
}, 'Presets'),
m('a[data-panel=filter]', {
class: 'tab' + (ctrl.vm.panel === 'filter' ? ' active' : ''),
onclick: function() {
- ctrl.vm.panel = 'filter';
+ ctrl.setPanel('filter');
}
}, 'Filters'), !!Object.keys(ctrl.vm.filters).length ? m('a.clear.hint--top', {
'data-hint': 'Clear all filters',