mirror of
https://github.com/pcottle/learnGitBranching.git
synced 2025-06-22 22:23:29 +02:00
363 lines
11 KiB
JavaScript
363 lines
11 KiB
JavaScript
var Backbone = require('backbone');
|
|
var jQuery = require('jquery');
|
|
var EventEmitter = require('events').EventEmitter;
|
|
var React = require('react');
|
|
var ReactDOM = require('react-dom');
|
|
|
|
var util = require('../util');
|
|
var intl = require('../intl');
|
|
var LocaleStore = require('../stores/LocaleStore');
|
|
var LocaleActions = require('../actions/LocaleActions');
|
|
|
|
/**
|
|
* Globals
|
|
*/
|
|
|
|
Backbone.$ = jQuery;
|
|
|
|
// Bypass jasmine
|
|
if (util.isBrowser()) {
|
|
window.jQuery = jQuery;
|
|
window.$ = jQuery;
|
|
window.Raphael = require('raphael');
|
|
}
|
|
|
|
var events = Object.assign(
|
|
{},
|
|
EventEmitter.prototype,
|
|
{
|
|
trigger: function() {
|
|
// alias this for backwards compatibility
|
|
this.emit.apply(this, arguments);
|
|
}
|
|
}
|
|
);
|
|
// Allow unlimited listeners, so FF doesn't break
|
|
events.setMaxListeners(0);
|
|
var commandUI;
|
|
var sandbox;
|
|
var eventBaton;
|
|
var levelDropdown;
|
|
|
|
///////////////////////////////////////////////////////////////////////
|
|
|
|
var init = function() {
|
|
/**
|
|
* There is a decent amount of bootstrapping we need just to hook
|
|
* everything up. The init() method takes on these responsibilities,
|
|
* including but not limited to:
|
|
* - setting up Events and EventBaton
|
|
* - calling the constructor for the main visualization
|
|
* - initializing the command input bar
|
|
* - handling window.focus and zoom events
|
|
**/
|
|
var Sandbox = require('../sandbox/').Sandbox;
|
|
var EventBaton = require('../util/eventBaton').EventBaton;
|
|
var LevelDropdownView = require('../views/levelDropdownView').LevelDropdownView;
|
|
|
|
eventBaton = new EventBaton();
|
|
commandUI = new CommandUI();
|
|
sandbox = new Sandbox();
|
|
levelDropdown = new LevelDropdownView({
|
|
wait: true
|
|
});
|
|
|
|
LocaleStore.subscribe(function() {
|
|
if (LocaleStore.getLocale() !== LocaleStore.getDefaultLocale()) {
|
|
intlRefresh();
|
|
}
|
|
});
|
|
events.on('vcsModeChange', vcsModeRefresh);
|
|
|
|
initRootEvents(eventBaton);
|
|
initDemo(sandbox);
|
|
// unfortunate global export for casper tests
|
|
window.LocaleStore = LocaleStore;
|
|
window.LocaleActions = LocaleActions;
|
|
window.intl = intl;
|
|
};
|
|
|
|
var vcsModeRefresh = function(eventData) {
|
|
if (!window.$) { return; }
|
|
|
|
var mode = eventData.mode;
|
|
var isGit = eventData.mode === 'git';
|
|
|
|
var displayMode = mode.slice(0, 1).toUpperCase() + mode.slice(1);
|
|
var otherMode = (displayMode === 'Git') ? 'Hg' : 'Git';
|
|
var regex = new RegExp(otherMode, 'g');
|
|
|
|
document.title = intl.str('learn-git-branching').replace(regex, displayMode);
|
|
$('span.vcs-mode-aware').each(function(i, el) {
|
|
var text = $(el).text().replace(regex, displayMode);
|
|
$(el).text(text);
|
|
});
|
|
|
|
$('body').toggleClass('gitMode', isGit);
|
|
$('body').toggleClass('hgMode', !isGit);
|
|
};
|
|
|
|
var insertAlternateLinks = function(pageId) {
|
|
// For now pageId is null, which would link to the main page.
|
|
// In future if pageId is provided this method should link to a specific page
|
|
|
|
// The value of the hreflang attribute identifies the language (in ISO 639-1 format)
|
|
// and optionally a region (in ISO 3166-1 Alpha 2 format) of an alternate URL
|
|
|
|
var altLinks = LocaleStore.getSupportedLocales().map(function(langCode) {
|
|
var url = "https://learngitbranching.js.org/?locale=" + langCode;
|
|
return '<link rel="alternate" hreflang="'+langCode+'" href="' + url +'" />';
|
|
});
|
|
var defaultUrl = "https://learngitbranching.js.org/?locale=" + LocaleStore.getDefaultLocale();
|
|
altLinks.push('<link rel="alternate" hreflang="x-default" href="' + defaultUrl +'" />');
|
|
$('head').prepend(altLinks);
|
|
|
|
};
|
|
|
|
var intlRefresh = function() {
|
|
if (!window.$) { return; }
|
|
var countryCode = LocaleStore.getLocale().split("_")[0];
|
|
$("html").attr('lang', countryCode);
|
|
$("meta[http-equiv='content-language']").attr("content", countryCode);
|
|
$('span.intl-aware').each(function(i, el) {
|
|
var intl = require('../intl');
|
|
var key = $(el).attr('data-intl');
|
|
$(el).text(intl.str(key));
|
|
});
|
|
};
|
|
|
|
var initRootEvents = function(eventBaton) {
|
|
// we always want to focus the text area to collect input
|
|
var focusTextArea = function() {
|
|
$('#commandTextField').focus();
|
|
};
|
|
focusTextArea();
|
|
|
|
$(window).focus(function(e) {
|
|
eventBaton.trigger('windowFocus', e);
|
|
});
|
|
$(document).click(function(e) {
|
|
eventBaton.trigger('documentClick', e);
|
|
});
|
|
$(document).bind('keydown', function(e) {
|
|
eventBaton.trigger('docKeydown', e);
|
|
});
|
|
$(document).bind('keyup', function(e) {
|
|
eventBaton.trigger('docKeyup', e);
|
|
});
|
|
$(window).on('resize', function(e) {
|
|
events.trigger('resize', e);
|
|
});
|
|
|
|
eventBaton.stealBaton('docKeydown', function() { });
|
|
eventBaton.stealBaton('docKeyup', function() { });
|
|
|
|
// the default action on window focus and document click is to just focus the text area
|
|
eventBaton.stealBaton('windowFocus', focusTextArea);
|
|
eventBaton.stealBaton('documentClick', focusTextArea);
|
|
|
|
// but when the input is fired in the text area, we pipe that to whoever is
|
|
// listenining
|
|
var makeKeyListener = function(name) {
|
|
return function() {
|
|
var args = [name];
|
|
Array.prototype.slice.apply(arguments).forEach(function(arg) {
|
|
args.push(arg);
|
|
});
|
|
eventBaton.trigger.apply(eventBaton, args);
|
|
};
|
|
};
|
|
|
|
$('#commandTextField').on('keydown', makeKeyListener('keydown'));
|
|
$('#commandTextField').on('keyup', makeKeyListener('keyup'));
|
|
$(window).trigger('resize');
|
|
};
|
|
|
|
var initDemo = function(sandbox) {
|
|
var params = util.parseQueryString(window.location.href);
|
|
|
|
// being the smart programmer I am (not), I don't include a true value on demo, so
|
|
// I have to check if the key exists here
|
|
var commands;
|
|
if (/(iPhone|iPod|iPad).*AppleWebKit/i.test(navigator.userAgent) || /android/i.test(navigator.userAgent)) {
|
|
sandbox.mainVis.customEvents.on('gitEngineReady', function() {
|
|
eventBaton.trigger('commandSubmitted', 'mobile alert');
|
|
});
|
|
}
|
|
|
|
if (params.hasOwnProperty('demo')) {
|
|
commands = [
|
|
"git commit; git checkout -b bugFix C1; git commit; git merge master; git checkout master; git commit; git rebase bugFix;",
|
|
"delay 1000; reset;",
|
|
"level advanced1 --noFinishDialog --noStartCommand --noIntroDialog;",
|
|
"delay 2000; show goal; delay 1000; hide goal;",
|
|
"git checkout bugFix; git rebase master; git checkout side; git rebase bugFix;",
|
|
"git checkout another; git rebase side; git rebase another master;",
|
|
"help; levels"
|
|
];
|
|
} else if (params.hasOwnProperty('hgdemo')) {
|
|
commands = [
|
|
'importTreeNow {"branches":{"master":{"target":"C3","id":"master"},"feature":{"target":"C2","id":"feature"},"debug":{"target":"C4","id":"debug"}},"commits":{"C0":{"parents":[],"id":"C0","rootCommit":true},"C1":{"parents":["C0"],"id":"C1"},"C2":{"parents":["C1"],"id":"C2"},"C3":{"parents":["C1"],"id":"C3"},"C4":{"parents":["C2"],"id":"C4"}},"HEAD":{"target":"feature","id":"HEAD"}}',
|
|
'delay 1000',
|
|
'git rebase master',
|
|
'delay 1000',
|
|
'undo',
|
|
'hg book',
|
|
'delay 1000',
|
|
'hg rebase -d master'
|
|
];
|
|
commands = commands.join(';#').split('#'); // hax
|
|
} else if (params.hasOwnProperty('hgdemo2')) {
|
|
commands = [
|
|
'importTreeNow {"branches":{"master":{"target":"C3","id":"master"},"feature":{"target":"C2","id":"feature"},"debug":{"target":"C4","id":"debug"}},"commits":{"C0":{"parents":[],"id":"C0","rootCommit":true},"C1":{"parents":["C0"],"id":"C1"},"C2":{"parents":["C1"],"id":"C2"},"C3":{"parents":["C1"],"id":"C3"},"C4":{"parents":["C2"],"id":"C4"}},"HEAD":{"target":"debug","id":"HEAD"}}',
|
|
'delay 1000',
|
|
'git rebase master',
|
|
'delay 1000',
|
|
'undo',
|
|
'hg sum',
|
|
'delay 1000',
|
|
'hg rebase -d master'
|
|
];
|
|
commands = commands.join(';#').split('#'); // hax
|
|
} else if (params.hasOwnProperty('remoteDemo')) {
|
|
commands = [
|
|
'git clone',
|
|
'git commit',
|
|
'git fakeTeamwork',
|
|
'git pull',
|
|
'git push',
|
|
'git commit',
|
|
'git fakeTeamwork',
|
|
'git pull --rebase',
|
|
'git push',
|
|
'levels'
|
|
];
|
|
commands = commands.join(';#').split('#'); // hax
|
|
} else if (params.gist_level_id) {
|
|
$.ajax({
|
|
url: 'https://api.github.com/gists/' + params.gist_level_id,
|
|
type: 'GET',
|
|
dataType: 'jsonp',
|
|
success: function(response) {
|
|
var data = response.data || {};
|
|
var files = data.files || {};
|
|
if (!Object.keys(files).length) {
|
|
console.warn('no files found');
|
|
return;
|
|
}
|
|
var file = files[Object.keys(files)[0]];
|
|
if (!file.content) {
|
|
console.warn('file empty');
|
|
}
|
|
eventBaton.trigger(
|
|
'commandSubmitted',
|
|
'importLevelNow ' + escape(file.content) + '; clear'
|
|
);
|
|
}
|
|
});
|
|
} else if (!params.hasOwnProperty('NODEMO')) {
|
|
commands = [
|
|
"help;",
|
|
"levels"
|
|
];
|
|
}
|
|
if (params.hasOwnProperty('STARTREACT')) {
|
|
/*
|
|
ReactDOM.render(
|
|
React.createElement(CommandView, {}),
|
|
document.getElementById(params['STARTREACT'])
|
|
);*/
|
|
}
|
|
if (commands) {
|
|
sandbox.mainVis.customEvents.on('gitEngineReady', function() {
|
|
eventBaton.trigger('commandSubmitted', commands.join(''));
|
|
});
|
|
}
|
|
|
|
if (params.locale !== undefined && params.locale.length) {
|
|
LocaleActions.changeLocaleFromURI(params.locale);
|
|
} else {
|
|
tryLocaleDetect();
|
|
}
|
|
|
|
insertAlternateLinks();
|
|
|
|
if (params.command) {
|
|
var command = unescape(params.command);
|
|
sandbox.mainVis.customEvents.on('gitEngineReady', function() {
|
|
eventBaton.trigger('commandSubmitted', command);
|
|
});
|
|
}
|
|
|
|
};
|
|
|
|
function tryLocaleDetect() {
|
|
// use navigator to get the locale setting
|
|
changeLocaleFromHeaders(navigator.language || navigator.browserLanguage);
|
|
}
|
|
|
|
function changeLocaleFromHeaders(langString) {
|
|
LocaleActions.changeLocaleFromHeader(langString);
|
|
}
|
|
|
|
if (require('../util').isBrowser()) {
|
|
// this file gets included via node sometimes as well
|
|
$(document).ready(init);
|
|
}
|
|
|
|
/**
|
|
* the UI method simply bootstraps the command buffer and
|
|
* command prompt views. It only interacts with user input
|
|
* and simply pipes commands to the main events system
|
|
**/
|
|
function CommandUI() {
|
|
Backbone.$ = $; // lol WTF BACKBONE MANAGE YOUR DEPENDENCIES
|
|
var Views = require('../views');
|
|
var Collections = require('../models/collections');
|
|
var CommandViews = require('../views/commandViews');
|
|
var CommandHistoryView = require('../react_views/CommandHistoryView.jsx');
|
|
var MainHelperBarView = require('../react_views/MainHelperBarView.jsx');
|
|
|
|
this.commandCollection = new Collections.CommandCollection();
|
|
this.commandBuffer = new Collections.CommandBuffer({
|
|
collection: this.commandCollection
|
|
});
|
|
|
|
this.commandPromptView = new CommandViews.CommandPromptView({
|
|
el: $('#commandLineBar')
|
|
});
|
|
|
|
ReactDOM.render(
|
|
React.createElement(MainHelperBarView),
|
|
document.getElementById('helperBarMount')
|
|
);
|
|
ReactDOM.render(
|
|
React.createElement(
|
|
CommandHistoryView,
|
|
{ commandCollection: this.commandCollection }
|
|
),
|
|
document.getElementById('commandDisplay')
|
|
);
|
|
}
|
|
|
|
exports.getEvents = function() {
|
|
return events;
|
|
};
|
|
|
|
exports.getSandbox = function() {
|
|
return sandbox;
|
|
};
|
|
|
|
exports.getEventBaton = function() {
|
|
return eventBaton;
|
|
};
|
|
|
|
exports.getCommandUI = function() {
|
|
return commandUI;
|
|
};
|
|
|
|
exports.getLevelDropdown = function() {
|
|
return levelDropdown;
|
|
};
|
|
|
|
exports.init = init;
|