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 ''; }); var defaultUrl = "https://learngitbranching.js.org/?locale=" + LocaleStore.getDefaultLocale(); altLinks.push(''); $('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;