Skip to content

Commit

Permalink
Merge pull request #742 from OpenGeoscience/tutorial-urls
Browse files Browse the repository at this point in the history
Make a tutorial that acts as an editor
  • Loading branch information
manthey authored Oct 13, 2017
2 parents 3a7f896 + 70b41e7 commit d1abb04
Show file tree
Hide file tree
Showing 10 changed files with 175 additions and 60 deletions.
37 changes: 1 addition & 36 deletions examples/common/examples.js
Original file line number Diff line number Diff line change
@@ -1,39 +1,4 @@
var exampleUtils = {
/* Decode query components into a dictionary of values.
*
* @returns {object}: the query parameters as a dictionary.
*/
getQuery: function () {
var query = document.location.search.replace(/(^\?)/, '').split(
'&').map(function (n) {
n = n.split('=');
if (n[0]) {
this[decodeURIComponent(n[0].replace(/\+/g, '%20'))] = decodeURIComponent(n[1].replace(/\+/g, '%20'));
}
return this;
}.bind({}))[0];
return query;
},

/* Encode a dictionary of parameters to the query string, setting the window
* location and history. This will also remove undefined values from the
* set properites of params.
*
* @param {object} params: the query parameters as a dictionary.
*/
setQuery: function (params) {
$.each(params, function (key, value) {
if (value === undefined) {
delete params[key];
}
});
var newurl = window.location.protocol + '//' + window.location.host +
window.location.pathname + '?' + $.param(params);
window.history.replaceState(params, '', newurl);
}
};

window.utils = exampleUtils;
window.utils = require('./utils');

/* Add a function to take a screenshot. Show the screenshot so that a user can
* click on it to save it or right-click to copy it. */
Expand Down
36 changes: 36 additions & 0 deletions examples/common/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
var exampleUtils = {
/* Decode query components into a dictionary of values.
*
* @returns {object}: the query parameters as a dictionary.
*/
getQuery: function () {
var query = document.location.search.replace(/(^\?)/, '').split(
'&').map(function (n) {
n = n.split('=');
if (n[0]) {
this[decodeURIComponent(n[0].replace(/\+/g, '%20'))] = decodeURIComponent(n[1].replace(/\+/g, '%20'));
}
return this;
}.bind({}))[0];
return query;
},

/* Encode a dictionary of parameters to the query string, setting the window
* location and history. This will also remove undefined values from the
* set properites of params.
*
* @param {object} params: the query parameters as a dictionary.
*/
setQuery: function (params) {
$.each(params, function (key, value) {
if (value === undefined) {
delete params[key];
}
});
var newurl = window.location.protocol + '//' + window.location.host +
window.location.pathname + '?' + $.param(params);
window.history.replaceState(params, '', newurl);
}
};

module.exports = exampleUtils;
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"mousetrap": "^1.6.0",
"nib": "^1.1.2",
"node-resemble": "^2.0.1",
"pako": "^1.0.6",
"phantomjs-prebuilt": "^2.1.5",
"proj4": "2.3.16",
"pug": "2.0.0-rc.2",
Expand Down
1 change: 1 addition & 0 deletions tutorials/basic/index.pug
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ block mainTutorial
height: 100%;
padding: 0;
margin: 0;
overflow: hidden;
}

:markdown-it
Expand Down
4 changes: 4 additions & 0 deletions tutorials/common/tutorials.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ html, body {
width: 100%;
height: 100%;
overflow: hidden;
display: flex;
}
.navbar #maincontent {
height: calc(100% - 60px);
Expand All @@ -28,9 +29,12 @@ html, body {
overflow-y: auto;
padding: 0.5em;
background: ivory;
resize: horizontal;
min-width: 10em;
}
#workframe {
overflow: hidden;
flex-grow: 100;
}
.codeblock .codeblock_entry {
width: calc(100% - 6em);
Expand Down
119 changes: 95 additions & 24 deletions tutorials/common/tutorials.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
var $ = require('jquery');
var pako = require('pako');
var utils = require('../../examples/common/utils');

/* Track the last selector used for generating a code block and also a timer so
* we can avoid rendering the same codeblock if it is actively being edited. */
Expand Down Expand Up @@ -26,6 +28,7 @@ var processBlockInfo = {
' height: 100%;\n' +
' padding: 0;\n' +
' margin: 0;\n' +
' overflow: hidden;\n' +
'}'
};

Expand Down Expand Up @@ -156,6 +159,7 @@ function process_block(selector) {
window.tutorial = targetelem[0].contentWindow;
window.tutorials = window.tutorials || {};
window.tutorials[target] = targetelem[0].contentWindow;
elem.trigger('geojs-tutorial-run');
}
}

Expand Down Expand Up @@ -187,13 +191,101 @@ function process_block_debounce(selector, debounce) {
}, 2000);
}

/**
* Run any default code blocks, then start listening for changes in code
* blocks.
*/
function run_tutorial() {
/* If any of the codeblocks is marked 'default', run them. Do this in a
* timeout so that other start up scripts can run */
$('.codeblock[initial="true"]').each(function (idx, elem) {
run_block(elem);
});
/* Whenever a code block changes, run it with its parents */
$('.codeblock textarea').bind('input propertychange', function (evt) {
run_block(evt.target, true, true);
});
$('.codeblock .CodeMirror').each(function () {
$(this)[0].CodeMirror.on('change', function (elem) {
run_block(elem.getWrapperElement(), true, true);
});
});
/* Bind run and reset buttons */
$('.codeblock_reset').click(function (evt) {
var elem = $('textarea', $(evt.target).closest('.codeblock'));
elem.val(elem.attr('defaultvalue'));
$('.CodeMirror', $(evt.target).closest('.codeblock'))[0].CodeMirror.setValue(elem.attr('defaultvalue'));
run_block(evt.target, true);
});
$('.codeblock_run').click(function (evt) {
run_block(evt.target, undefined, undefined, evt.shiftKey);
});
}

/**
* Get query parameters for each step and update them as we start. Monitor the
* code blocks and update the url when they change if appropriate.
*
* @param {boolean} alwaysKeep If `true`, update the url even if the `keep` url
* parameter is not specified.
*/
function start_keeper(alwaysKeep) {
var query = utils.getQuery(),
keep = query.keep || (alwaysKeep === true);

$('.codeblock').each(function () {
var block = $(this),
key = 'src' + (block.attr('step') !== '1' ? block.attr('step') : '');
if (query[key]) {
try {
/* Strip out white space and pluses, then convert ., -, and _ to /, +,
* =. By removing whitespace, the url is more robust against email
* handling. The others keep things short. */
var src = atob(query[key].replace(/(\s|\+)/g, '').replace(/\./g, '/').replace(/-/g, '+').replace(/_/g, '='));
src = pako.inflate(src, {to: 'string', raw: true});
if ($('.CodeMirror', block).length) {
$('.CodeMirror', block)[0].CodeMirror.setValue(src);
} else {
$('textarea', block).val(src);
}
} catch (err) { }
}
});
if (keep) {
$(document).on('geojs-tutorial-run', '.codeblock', function () {
var newQuery = {};
if (query.keep && alwaysKeep !== true) {
newQuery.keep = query.keep;
}
$('.codeblock').each(function () {
var block = $(this),
defaultSrc = $('textarea', block).attr('defaultvalue'),
key = 'src' + (block.attr('step') !== '1' ? block.attr('step') : '');

var src = $('.CodeMirror', block).length ? $('.CodeMirror', block)[0].CodeMirror.getValue() : $('textarea', block).val().trim();
if (src !== defaultSrc) {
var comp = btoa(pako.deflate(src, {to: 'string', level: 9, raw: true}));
/* instead of using regular base64, convert /, +, and = to ., -, and _
* so that they don't need to be escaped on the url. This reduces the
* average length of the url by 6 percent. */
comp = comp.replace(/\//g, '.').replace(/\+/g, '-').replace(/=/g, '_');
newQuery[key] = comp;
}
});
utils.setQuery(newQuery);
});
}
}

/**
* Process code blocks to remove unwanted white space and store default values,
* set up event handling, and run any initial code blocks.
*
* @param {boolean} useCodeMirror If explicitly false, don't use CodeMirror.
* @param {boolean} alwaysKeep If `true`, update the url even if the `keep` url
* parameter is not specified.
*/
function start_tutorial(useCodeMirror) {
function start_tutorial(useCodeMirror, alwaysKeep) {
/* clean up whitespace and store a default value for each code block */
$('.codeblock').each(function () {
var elem = $('textarea', this),
Expand Down Expand Up @@ -225,29 +317,8 @@ function start_tutorial(useCodeMirror) {
}
/* Check if iframe srcdoc support is present */
processBlockInfo.srcdocSupport = !!('srcdoc' in document.createElement('iframe'));
/* If any of the codeblocks is marked 'default', run them */
$('.codeblock[initial="true"]').each(function (idx, elem) {
run_block(elem);
});
/* Whenever a code block changes, run it with its parents */
$('.codeblock textarea').bind('input propertychange', function (evt) {
run_block(evt.target, true, true);
});
$('.codeblock .CodeMirror').each(function () {
$(this)[0].CodeMirror.on('change', function (elem) {
run_block(elem.getWrapperElement(), true, true);
});
});
/* Bind run and reset buttons */
$('.codeblock_reset').click(function (evt) {
var elem = $('textarea', $(evt.target).closest('.codeblock'));
elem.val(elem.attr('defaultvalue'));
$('.CodeMirror', $(evt.target).closest('.codeblock'))[0].CodeMirror.setValue(elem.attr('defaultvalue'));
run_block(evt.target, true);
});
$('.codeblock_run').click(function (evt) {
run_block(evt.target, undefined, undefined, evt.shiftKey);
});
start_keeper(alwaysKeep);
run_tutorial();
}

module.exports = start_tutorial;
7 changes: 7 additions & 0 deletions tutorials/editor/editor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/* global start_tutorial */

$(function () {
/* start_tutorial has to be called explicitly when we are ready since we need
* to ask it to always keep url changes. */
start_tutorial(undefined, true);
});
21 changes: 21 additions & 0 deletions tutorials/editor/index.pug
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
extends ../common/index.pug

block mainTutorial
:markdown-it
# Editor
Any changes made will be stored in the URL whenever the code is run. This can be sent as a link, bookmarked, or otherwise shared.

You can interact with the code through the javascript console by accessing the top-level variables in the `tutorial` global parameter.

+codeblock('javascript', 1, undefined, true).
var map = geo.map({
node: "#map",
center: {x: 4.90, y: 52.37},
zoom: 14
});
var layer = map.createLayer('osm');
+codeblock_test('map has one osm layer from openstreetmap', [
'map.layers().length === 1',
'map.layers()[0] instanceof geo.osmLayer',
'layer.url().match(/openstreetmap/)'
])
Binary file added tutorials/editor/thumb.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions tutorials/editor/tutorial.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"title": "Editor",
"hideNavbar": true,
"level": 10,
"tutorialJs": ["editor.js"],
"about": {
"text": "Edit and save work in URL."
}
}

0 comments on commit d1abb04

Please sign in to comment.