study chapter tour, introduce shepherd

pull/1892/head
Thibault Duplessis 2016-05-21 16:05:16 +02:00
parent fa536fdb13
commit c691d3042b
13 changed files with 342 additions and 206 deletions

3
.gitmodules vendored
View File

@ -25,3 +25,6 @@
[submodule "public/vendor/hopscotch"]
path = public/vendor/hopscotch
url = https://github.com/linkedin/hopscotch
[submodule "public/vendor/shepherd"]
path = public/vendor/shepherd
url = https://github.com/HubSpot/shepherd

View File

@ -0,0 +1,96 @@
lichess.studyTourChapter = function(study) {
lichess.shepherd(function(theme) {
var setTab = function(tab) {
return function() {
console.log('set tab ' + tab);
study.setTab(tab);
};
};
var tour = new Shepherd.Tour({
defaults: {
classes: theme,
scrollTo: false
}
});
tour.addStep('create', {
title: "Let's create a study chapter",
text: "A study can have several chapters.<br>" +
"Each chapter has a distinct move tree,<br>" +
"and can be created in various ways.",
attachTo: '.study_overboard label[for=chapter-name] left'
});
tour.addStep('init', {
title: "From initial position",
text: "Just a board setup for a new game.<br>" +
"Suited to explore openings.",
attachTo: '.study_overboard .study_tabs .init top',
when: {
'before-show': setTab('init')
}
});
tour.addStep('edit', {
title: "Custom position",
text: "Setup the board your way.<br>" +
"Suited to explore endgames.",
attachTo: '.study_overboard .study_tabs .edit bottom',
when: {
'before-show': setTab('edit')
}
});
tour.addStep('game', {
title: "Load an existing lichess game",
text: "Paste a lichess game URL<br>" +
"(like http://lichess.org/7fHIU0XI)<br>" +
"to load the game moves in the chapter.",
attachTo: '.study_overboard .study_tabs .game top',
when: {
'before-show': setTab('game')
}
});
tour.addStep('fen', {
title: "From a FEN string",
text: "Paste a position in FEN format<br>" +
"<i>4k3/4rb2/8/7p/8/5Q2/1PP5/1K6 w</i><br>" +
"to start the chapter from a position.",
attachTo: '.study_overboard .study_tabs .fen top',
when: {
'before-show': setTab('fen')
}
});
tour.addStep('pgn', {
title: "From a PGN game",
text: "Paste a game in PGN format.<br>" +
"to load moves, comments and variations in the chapter.",
attachTo: '.study_overboard .study_tabs .pgn top',
when: {
'before-show': setTab('pgn')
}
});
tour.addStep('variant', {
title: "Studies support variants",
text: "Yes, you can study crazyhouse,<br>" +
"and all lichess supported chess variants!",
attachTo: '.study_overboard label[for=chapter-variant] left',
when: {
'before-show': setTab('init')
}
});
tour.addStep('puzzle', {
title: "Puzzle mode",
text: "When you enable the puzzle mode, only you can see the chapter moves.<br>" +
"Make the other members guess what the next move is!",
attachTo: '.study_overboard label[for=chapter-conceal] left',
when: {
'before-show': setTab('pgn')
}
});
tour.addStep('end', {
title: "Thanks for your time",
text: "Chapters are saved forever.<br>" +
"Have fun organizing your chess content!",
attachTo: '.study_overboard .help bottom'
});
tour.start();
});
};

View File

@ -3,6 +3,7 @@ lichess.studyTour = function(study) {
var tour = {
id: "study",
showPrevButton: true,
scrollDuration: 500,
steps: [{
title: "Welcome to lichess study!",
content: "This is a shared analysis board.<br><br>" +

View File

@ -103,6 +103,15 @@ lichess.hopscotch = function(f) {
lichess.slider = function() {
return lichess.loadScript('/assets/javascripts/vendor/jquery-ui.slider.min.js', true);
};
lichess.shepherd = function(f) {
var theme = 'shepherd-theme-' + ($('body').hasClass('dark') ? 'default' : 'dark');
lichess.loadCss('/assets/vendor/shepherd/dist/css/' + theme + '.css');
lichess.loadScript("/assets/vendor/shepherd/dist/js/tether.js").done(function() {
lichess.loadScript("/assets/vendor/shepherd/dist/js/shepherd.min.js").done(function() {
f(theme);
});
});
}
lichess.isPageVisible = document.visibilityState !== 'hidden';
lichess.notifications = [];

View File

@ -2499,6 +2499,9 @@ input.cmn-toggle-round:checked + label:after {
.hopscotch-content a {
text-decoration: underline;
}
.shepherd-step {
z-index: 5011;
}
body ::-webkit-scrollbar,
body ::-webkit-scrollbar-corner {
background: #eee;

View File

@ -0,0 +1,194 @@
.shepherd-element, .shepherd-element:after, .shepherd-element:before, .shepherd-element *, .shepherd-element *:after, .shepherd-element *:before {
box-sizing: border-box; }
.shepherd-element {
position: absolute;
display: none; }
.shepherd-element.shepherd-open {
display: block; }
.shepherd-element.shepherd-theme-arrows {
max-width: 100%;
max-height: 100%; }
.shepherd-element.shepherd-theme-arrows .shepherd-content {
border-radius: 5px;
position: relative;
font-family: inherit;
background: #fff;
color: #444;
padding: 1em;
font-size: 1.1em;
line-height: 1.5em;
transform: translateZ(0);
box-shadow: 0 1px 4px rgba(0,0,0,0.2);
}
.shepherd-element.shepherd-theme-arrows .shepherd-content:before {
content: "";
display: block;
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-width: 16px;
border-style: solid;
pointer-events: none; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-bottom.shepherd-element-attached-center .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-bottom.shepherd-element-attached-center .shepherd-content:before {
top: 100%;
left: 50%;
margin-left: -16px;
border-top-color: #fff; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-top.shepherd-element-attached-center .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-top.shepherd-element-attached-center .shepherd-content:before {
bottom: 100%;
left: 50%;
margin-left: -16px;
border-bottom-color: #fff; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-right.shepherd-element-attached-middle .shepherd-content {
margin-right: 16px; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-right.shepherd-element-attached-middle .shepherd-content:before {
left: 100%;
top: 50%;
margin-top: -16px;
border-left-color: #fff; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-left.shepherd-element-attached-middle .shepherd-content {
margin-left: 16px; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-left.shepherd-element-attached-middle .shepherd-content:before {
right: 100%;
top: 50%;
margin-top: -16px;
border-right-color: #fff; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom .shepherd-content:before {
bottom: 100%;
left: 16px;
border-bottom-color: #fff; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom .shepherd-content {
margin-top: 16px; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom .shepherd-content:before {
bottom: 100%;
right: 16px;
border-bottom-color: #fff; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-top .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-top .shepherd-content:before {
top: 100%;
left: 16px;
border-top-color: #fff; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-top .shepherd-content {
margin-bottom: 16px; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-top .shepherd-content:before {
top: 100%;
right: 16px;
border-top-color: #fff; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content {
margin-right: 16px; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content:before {
top: 16px;
left: 100%;
border-left-color: #fff; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content {
margin-left: 16px; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content:before {
top: 16px;
right: 100%;
border-right-color: #fff; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content {
margin-right: 16px; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-bottom.shepherd-element-attached-right.shepherd-target-attached-left .shepherd-content:before {
bottom: 16px;
left: 100%;
border-left-color: #fff; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content {
margin-left: 16px; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-bottom.shepherd-element-attached-left.shepherd-target-attached-right .shepherd-content:before {
bottom: 16px;
right: 100%;
border-right-color: #fff; }
.shepherd-element.shepherd-theme-arrows.shepherd-element-attached-top.shepherd-element-attached-center.shepherd-has-title .shepherd-content:before, .shepherd-element.shepherd-theme-arrows.shepherd-element-attached-top.shepherd-element-attached-right.shepherd-target-attached-bottom.shepherd-has-title .shepherd-content:before, .shepherd-element.shepherd-theme-arrows.shepherd-element-attached-top.shepherd-element-attached-left.shepherd-target-attached-bottom.shepherd-has-title .shepherd-content:before {
border-bottom-color: #eee; }
.shepherd-element.shepherd-theme-arrows.shepherd-has-title .shepherd-content header {
background: #eee;
padding: 1em; }
.shepherd-element.shepherd-theme-arrows.shepherd-has-title .shepherd-content header a.shepherd-cancel-link {
padding: 0;
margin-bottom: 0; }
.shepherd-element.shepherd-theme-arrows.shepherd-has-cancel-link .shepherd-content header h3 {
float: left; }
.shepherd-element.shepherd-theme-arrows .shepherd-content {
padding: 0; }
.shepherd-element.shepherd-theme-arrows .shepherd-content * {
font-size: inherit; }
.shepherd-element.shepherd-theme-arrows .shepherd-content header {
*zoom: 1;
border-radius: 5px 5px 0 0; }
.shepherd-element.shepherd-theme-arrows .shepherd-content header:after {
content: "";
display: table;
clear: both; }
.shepherd-element.shepherd-theme-arrows .shepherd-content header h3 {
margin: 0;
line-height: 1;
font-weight: normal; }
.shepherd-element.shepherd-theme-arrows .shepherd-content header a.shepherd-cancel-link {
float: right;
text-decoration: none;
font-size: 1.25em;
line-height: .8em;
font-weight: normal;
color: rgba(0, 0, 0, 0.5);
opacity: 0.25;
position: relative;
top: .1em;
padding: .8em;
margin-bottom: -.8em; }
.shepherd-element.shepherd-theme-arrows .shepherd-content header a.shepherd-cancel-link:hover {
opacity: 1; }
.shepherd-element.shepherd-theme-arrows .shepherd-content .shepherd-text {
padding: 1em; }
.shepherd-element.shepherd-theme-arrows .shepherd-content .shepherd-text p {
margin: 0 0 .5em 0;
line-height: 1.3em; }
.shepherd-element.shepherd-theme-arrows .shepherd-content .shepherd-text p:last-child {
margin-bottom: 0; }
.shepherd-element.shepherd-theme-arrows .shepherd-content footer {
padding: 0 1em 1em; }
.shepherd-element.shepherd-theme-arrows .shepherd-content footer .shepherd-buttons {
text-align: right;
list-style: none;
padding: 0;
margin: 0; }
.shepherd-element.shepherd-theme-arrows .shepherd-content footer .shepherd-buttons li {
display: inline;
padding: 0;
margin: 0; }
.shepherd-element.shepherd-theme-arrows .shepherd-content footer .shepherd-buttons li .shepherd-button {
display: inline-block;
vertical-align: middle;
*vertical-align: auto;
*zoom: 1;
*display: inline;
border-radius: 3px;
cursor: pointer;
border: 0;
margin: 0 .5em 0 0;
font-family: inherit;
text-transform: uppercase;
letter-spacing: .1em;
font-size: .8em;
line-height: 1em;
padding: .75em 2em;
background: #3288e6;
color: #fff; }
.shepherd-element.shepherd-theme-arrows .shepherd-content footer .shepherd-buttons li .shepherd-button.shepherd-button-secondary {
background: #eee;
color: #888; }
.shepherd-element.shepherd-theme-arrows .shepherd-content footer .shepherd-buttons li:last-child .shepherd-button {
margin-right: 0; }

View File

@ -299,6 +299,15 @@ div.underboard .notif.error {
.study_overboard .form .editor .spinner {
padding-top: 80px;
}
.study_overboard h2 i {
margin-left: 10px;
opacity: 0.5;
cursor: pointer;
}
.study_overboard h2 i:hover {
opacity: 0.7;
color: #3893E8;
}
.study_buttons {
display: flex;
justify-content: space-between;

1
public/vendor/shepherd vendored 160000

@ -0,0 +1 @@
Subproject commit e3ed0fcbf5c31137ece409d3ad6fea4e264ca1cc

View File

@ -1,201 +0,0 @@
var m = require('mithril');
var storedProp = require('../util').storedProp;
var partial = require('chessground').util.partial;
var xhr = require('./studyXhr');
var dialog = require('./dialog');
var concealChoices = [
['', "Reveal all moves at once"],
['1', "Conceal next moves (puzzle mode)"]
];
module.exports = {
ctrl: function(send, chapters, setTab, root) {
var vm = {
variants: [],
open: false,
initial: m.prop(false),
tab: storedProp('study.form.tab', 'init'),
editorFen: m.prop(null)
};
var loadVariants = function() {
if (!vm.variants.length) xhr.variants().then(function(vs) {
vm.variants = vs;
m.redraw();
});
};
var open = function() {
vm.open = true;
loadVariants();
vm.initial(false);
};
var close = function() {
vm.open = false;
};
return {
vm: vm,
open: open,
root: root,
openInitial: function() {
open();
vm.initial(true);
},
close: close,
toggle: function() {
if (vm.open) close();
else open();
},
initial: vm.initial,
submit: function(data) {
data.initial = vm.initial();
send("addChapter", data)
close();
setTab();
},
chapters: chapters
}
},
view: function(ctrl) {
var activeTab = ctrl.vm.tab();
var makeTab = function(key, name, title) {
return m('a.hint--top', {
class: key + (activeTab === key ? ' active' : ''),
'data-hint': title,
onclick: partial(ctrl.vm.tab, key),
}, name);
};
var fieldValue = function(e, id) {
var el = e.target.querySelector('#chapter-' + id);
return el ? el.value : null;
};
var gameOrPgn = activeTab === 'game' || activeTab === 'pgn';
return dialog.form({
onClose: ctrl.close,
content: [
activeTab === 'edit' ? null : m('h2', 'New chapter'),
m('form.material.form', {
onsubmit: function(e) {
ctrl.submit({
name: fieldValue(e, 'name'),
game: fieldValue(e, 'game'),
variant: fieldValue(e, 'variant'),
fen: fieldValue(e, 'fen') || (activeTab === 'edit' ? ctrl.vm.editorFen() : null),
pgn: fieldValue(e, 'pgn'),
orientation: fieldValue(e, 'orientation'),
conceal: !!fieldValue(e, 'conceal')
});
e.stopPropagation();
return false;
}
}, [
m('div.form-group', [
m('input#chapter-name', {
required: true,
minlength: 2,
maxlength: 80,
config: function(el, isUpdate) {
if (!isUpdate && !el.value) {
el.value = 'Chapter ' + (ctrl.initial() ? 1 : (ctrl.chapters().length + 1));
el.select();
el.focus();
}
}
}),
m('label.control-label[for=chapter-name]', 'Name'),
m('i.bar')
]),
m('div.study_tabs', [
makeTab('init', 'Init', 'Start from initial position'),
makeTab('edit', 'Edit', 'Start from custom position'),
makeTab('game', 'game', 'Load a lichess game'),
makeTab('fen', 'FEN', 'Load a FEN position'),
makeTab('pgn', 'PGN', 'Load a PGN game')
]),
activeTab === 'edit' ? m('div.editor_wrap', {
config: function(el, isUpdate, ctx) {
if (isUpdate) return;
$.when(
lichess.loadScript('/assets/compiled/lichess.editor.js'),
$.get('/editor.json', {
fen: ctrl.root.vm.node.fen
})
).then(function(a, b) {
var data = b[0];
data.embed = true;
data.options = {
inlineCastling: true,
onChange: function(fen) {
ctrl.vm.editorFen(fen);
m.redraw();
}
};
var editor = LichessEditor(el, data);
ctrl.vm.editorFen(editor.getFen());
});
}
}, m.trust(lichess.spinnerHtml)) : null,
activeTab === 'game' ? m('div.form-group', [
m('input#chapter-game', {
placeholder: 'Game ID or URL'
}),
m('label.control-label[for=chapter-game]', 'From played or imported game'),
m('i.bar')
]) : null,
activeTab === 'fen' ? m('div.form-group.no-label', [
m('input#chapter-fen', {
placeholder: 'Initial FEN position'
}),
m('i.bar')
]) : null,
activeTab === 'pgn' ? m('div.form-group.no-label', [
m('textarea#chapter-pgn', {
placeholder: 'Paste your PGN here, one game only'
}),
m('i.bar')
]) : null,
m('div', [
m('div.form-group.half', [
m('select#chapter-variant', {
disabled: gameOrPgn
}, gameOrPgn ? [
m('option', 'Automatic')
] :
ctrl.vm.variants.map(function(v) {
return m('option', {
value: v.key
}, v.name)
})),
m('label.control-label[for=chapter-variant]', 'Variant'),
m('i.bar')
]),
m('div.form-group.half', [
m('select#chapter-orientation', ['White', 'Black'].map(function(color) {
return m('option', {
value: color.toLowerCase()
}, color)
})),
m('label.control-label[for=chapter-orientation]', 'Orientation'),
m('i.bar')
])
]),
gameOrPgn ? m('div.form-group', [
m('select#chapter-conceal', concealChoices.map(function(c) {
return m('option', {
value: c[0]
}, c[1])
})),
m('label.control-label[for=chapter-conceal]', 'Progressive move display'),
m('i.bar')
]) : null,
dialog.button('Create chapter')
])
]
});
}
};

View File

@ -37,11 +37,23 @@ module.exports = {
vm.open = true;
loadVariants();
vm.initial(false);
if (lichess.once('insight-tour-chapter')) startTour();
};
var close = function() {
vm.open = false;
};
var startTour = function() {
lichess.loadScript('/assets/javascripts/study/tour-chapter.js').then(function() {
lichess.studyTourChapter({
setTab: function(tab) {
vm.tab(tab);
m.redraw();
}
});
});
};
return {
vm: vm,
open: open,
@ -62,7 +74,8 @@ module.exports = {
close();
setTab();
},
chapters: chapters
chapters: chapters,
startTour: startTour
}
},
view: function(ctrl) {
@ -80,7 +93,13 @@ module.exports = {
return dialog.form({
onClose: ctrl.close,
content: [
activeTab === 'edit' ? null : m('h2', 'New chapter'),
activeTab === 'edit' ? null : m('h2', [
'New chapter',
m('i.help', {
'data-icon': '',
onclick: ctrl.startTour
})
]),
m('form.material.form', {
onsubmit: function(e) {
ctrl.submit({

View File

@ -125,7 +125,7 @@ module.exports = {
});
var startTour = function() {
lichess.loadScript('/assets/javascripts/study-tour.js').then(function() {
lichess.loadScript('/assets/javascripts/study/tour.js').then(function() {
lichess.studyTour({
userId: ctrl.userId,
setTab: function(tab) {

View File

@ -73,7 +73,9 @@ function buttons(root) {
m('span.button.help.hint--top', {
'data-hint': 'Need help? Get the tour!',
onclick: ctrl.startTour
}, 'help')
}, m('i.text', {
'data-icon': ''
}, 'help'))
]);
}

View File

@ -28,7 +28,7 @@ function renderMove(ctrl, node, path, isMainline, conceal) {
if (path === ctrl.vm.contextMenuPath) classes.push('context_menu');
if (path === ctrl.vm.initialPath) classes.push('current');
if (conceal) classes.push(conceal);
if (!isMainline && node.comments) classes.push('annotated');
if (!isMainline && (node.comments || node.shapes)) classes.push('annotated');
if (classes.length) attrs.class = classes.join(' ');
return {
tag: 'move',