<commit_before>var data = {
    contacts: {
        "Github": "https://github.com/AlanMorel",
        "LinkedIn": "https://linkedin.com/in/AlanMorel/",
        "Facebook": "https://www.facebook.com/SharpAceX",
        "Twitter": "https://www.twitter.com/SharpAceX",
        "Email": "mailto:alan.morel@nyu.edu",
        "Resume": "downloads/resume.pdf",
		"Medium": "https://medium.com/@alanmorel"
    },
    projects: {
        "trumpulse": {
            name: "Trumpulse",
            background: "#2f61de",
            color: "#ffffff",
            date: "April 2017",
            description: "Trumpulse uses data from multiple news sources so you can get the pulse of America under Trump. It is a single-page application built using Angular. Using data from various news sources and social networks like Facebook and Twitter, you can get a diverse picture of the state of America under the Trump administration.",
            images: ["image1", "image2", "image3"],
            technologies: ["Angular.js", "Electron", "Cordova", "PHP", "News API", "Facebook API", "Twitter API"],
            platforms: ["Android", "Web", "Windows", "Mac"],
            links: {
                "Google Play": "https://play.google.com/store/apps/details?id=alanmorel.trumpulse",
				"Download for Windows": "downloads/trumpulse.zip",
                "Demo": "http://alanmorel.com/trumpulse",
                "Github": "https://github.com/AlanMorel/trumpulse"
            },
        },
        "wasdwithme": {
            name: "WASD With Me",
            background: "#5a26a5",
            color: "#ffffff",
            date: "February 2017",
            description: "WASD With Me is the online destination to meet and play with gamers. Discover and connect with gamers, set up gaming sessions and browse other gamers' libraries.",
            images: ["image1", "image2", "image3"],
            technologies: ["Node.js", "Express.js", "MondoDB", "Handlebars.js", "Socket.io", "Stylus", "IGDB API", "News API", "Steam API", "Twitch API"],
            platforms: ["Web"],
            links: {
                "Live Demo": "http://wasdwithme.herokuapp.com/",
                "Github": "https://github.com/AlanMorel/wasdWithMe",
            },
        },
        "zhigly": {
            name: "Zhigly",
            background: "#db3030",
            color: "#ffffff",
            date: "March 2016",
            description: "Zhigly is a hub where you can connect with your college campus. It allows you to look for items you'd like to buy or sell, browse upcoming on-campus events, search for jobs/internships/opportunities, look for housing, and much more.",
            images: ["image1", "image2", "image3"],
            technologies: ["ASP.NET", "C#", "Facebook SDK", "Twitter SDK", "Google+ API", "Imgur API"],
            platforms: ["Web"],
            links: {
                "Live Demo": "http://zhigly.com/",
                "Github": "https://github.com/AlanMorel/zhigly",
            },
        },
        "chainball": {
            name: "Chainball",
            background: "#f5577c",
            color: "#ffffff",
            date: "July 2014",
            description: "Use your fast reflexives to build yourself a chain as you navigate the endless stream of circles. Hit the blue and green circles while avoiding the red and gray ones to score as high as you can. When you’re finished, brag to your friends and challenge them to score higher than you in Chainball!",
            images: ["image1", "image2", "image3"],
            technologies: ["Java", "libGDX"],
            platforms: ["Android"],
            links: {
                "Google Play Store": "https://play.google.com/store/apps/details?id=com.luminositygames.chainball.android",
                "Github": "https://github.com/AlanMorel/chainball",
            },
        },
        "metronavigator": {
            name: "Metro Navigator",
            background: "#1d4f88",
            color: "#ffffff",
            date: "February 2014",
            description: "You’ll be able to find out the shortest routes between two stations, read the statuses of train lines and view the official map. It’s extremely useful for both experienced riders and newcomers to New York’s transportation system.",
            images: ["image1", "image2"],
            technologies: ["C#", ".NET"],
            platforms: ["Windows", "Web"],
            links: {
                "Download for Windows": "downloads/metronavigator.zip",
            },
        },
        "wavewarriors": {
            name: "Wave Warriors",
            background: "#f2ba00",
            color: "#000000",
            date: "December 2015",
            description: "WaveWarriors is a local multiplayer zombie-style survival game played using Xbox Controllers. Play for as long as you can as the waves of enemies get larger, faster and stronger. Various power ups are scattered around the place to aid in your survival. It supports up to 4 players so grab a few friends, connect your controllers and start working together!",
            images: ["image1", "image2", "image3"],
            technologies: ["Java", "Processing"],
            platforms: ["Windows", "Mac", "Linux"],
            links: {
                "Github": "https://github.com/AlanMorel/WaveWarriors",
            },
        },
        "pokedex": {
            name: "Pokédex",
            background: "#252525",
            color: "#ffffff",
            date: "April 2016",
            description: "Pokédex is a fun web app that you can use to learn more about Pokémon and play the famous 'Who's that Pokémon?'' game. It is a Flask fullstack web app powered by data provided by PokéAPI.",
            images: ["image1", "image2", "image3"],
            technologies: ["Python", "Flask", "PIL", "Requests", "PokéAPI"],
            platforms: ["Web"],
            links: {
                "Live Demo": "http://alanmorel.pythonanywhere.com/",
                "Github": "https://github.com/AlanMorel/pokedex",
            },
        },
        "sonar": {
            name: "Sonar",
            background: "#0b6cb3",
            color: "#ffffff",
            date: "August 2015",
            description: "Sonar allows you to easily see performance attributes of websites as you browse the web. This helps both end-users and developers understand why a webpage might be slow or experiencing problems. With Sonar, you can quickly identify how long a page took to load, the size of the page in bytes, if any requests caused bottlenecks, the number of hosts that served content, and details about the type of files delivered.",
            images: ["image1"],
            technologies: ["Chrome API", "HTML", "CSS", "JavaScript"],
            platforms: ["Chrome"],
            links: {
                "Promotional Page": "http://pages.catchpoint.com/sonar.html",
                "Chrome Web Store": "https://chrome.google.com/webstore/detail/sonar/dibilcjfahbokhiodajibcajcabfjein"
            },
        },
        "smoothietycoon": {
            name: "Smoothie Tycoon",
            background: "#ffa200",
            color: "#000000",
            date: "September 2014",
            description: "Start off small as you sell smoothies on the streets of New York to build your smoothie stand. Buy supplies and set your own price, and determine your own destiny!",
            images: ["image1", "image2", "image3"],
            technologies: ["Java", "libGDX"],
            platforms: ["Windows", "Mac", "Linux"],
            links: {
                "Download": "downloads/smoothietycoon.zip",
                "Github": "https://github.com/AlanMorel/smoothie-tycoon",
            },
        },
        "mars2o": {
            name: "Mars2O",
            background: "#bab9cc",
            color: "#000000",
            date: "October 2015",
            description: "Mars2O is a fun arcade game for Windows and Mac. You're tasked with returning back water from Mars, but there's just one problem: the asteroid belt is abnormally turbulent! Dodge asteroids and refuel your spaceship throughout your journey back to Earth.",
            images: ["image1", "image2", "image3"],
            technologies: ["Java", "Processing"],
            platforms: ["Windows", "Mac", "Linux"],
            links: {
                "Github": "https://github.com/AlanMorel/Mars2O",
            },
        },
        "balloonfrenzy": {
            name: "Balloon Frenzy!",
            background: "#78b7ff",
            color: "#000000",
            date: "August 2011",
            description: "Start off with one balloon and soon end up with many! Every 5 levels unlocks yet another balloon so things will become interesting real quick! Your goal is to obtain as high of a score as possible without missing too many balloons.",
            images: ["image1", "image2", "image3"],
            technologies: ["Java", "PHP"],
            platforms: ["Android"],
            links: {
                "Google Play Store": "https://play.google.com/store/apps/details?id=com.sharpacex.BalloonFrenzy",
            },
        },
        "dangerzone": {
            name: "Danger Zone",
            background: "#e2e2e2",
            color: "#000000",
            date: "June 2012",
            description: "Your job is simple. Just survive. With just your jumping and ducking skills at your disposal, you will need to avoid incoming missiles to keep yourself alive. The longer you can stay alive, the more score you’ll earn! At the end of the game your performance will be ranked from an (A+) to and (F-)! Be warned, this can get addicting!",
            images: ["image1", "image2", "image3"],
            technologies: ["Java", "PHP"],
            platforms: ["Android", "Windows"],
            links: {
                "Download for Windows": "downloads/dangerzone.zip",
                "Google Play Store": "https://play.google.com/store/apps/details?id=com.sharpacex.dangerzone",
            },
        },
        "flash": {
            name: "Flash",
            background: "#781ab5",
            color: "#ffffff",
            date: "March 2014",
            description: "If you want to have the benefits of physical flash cards with the flexibility and convenience of a program on your computer then Flash is the right program for you. Using Flash, you will be able to create flash cards in which you can use to study for your next final, midterm, or anything you’d like!",
            images: ["image1", "image2", "image3"],
            technologies: ["C#", ".NET"],
            platforms: ["Windows"],
            links: {
              "Download for Windows": "downloads/flash.zip",
            },
        },
        "sacredfire": {
            name: "Pokémon: SacredFire",
            background: "#c84000",
            color: "#ffffff",
            date: "October 2009",
            description: "You’re a teenage kid living in Wingpoint Town. Life was all good until about 3 weeks ago. Your Mom has had enough of Team Rocket. After weeks of news reports of Team Rocket abusing wild and legendary Pokémon, your Mom decides to do her best to protect you. From the start, you were destined to take action. Now you are thrown into a world where it’s you against the most powerful, evil and well-known corporation in the world. Your mission is to save the abused Pokémon, your family, and defend the honor of your hometown.",
            images: ["image1", "image2", "image3", "image4", "image5", "image6", "image7", "image8", "image9", "image10"],
            technologies: ["Assembly"],
            platforms: ["Game Boy Advance", "Windows", "Mac"],
            links: {
                "Pokémon: SacredFire Patch File": "/downloads/sacredfire.zip",
                "Lunar IPS (LIPS)": "http://fusoya.eludevisibility.org/lips/",
                "Pokémon: FireRed ROM": "https://www.loveroms.com/download/gameboy-advance/pokemon-fire-red-version-u-/39",
                "Visual Boy Advance": "https://sourceforge.net/projects/vba/",
            },
        },
        "khaos": {
            name: "Khaos Map",
            background: "#548845",
            color: "#ffffff",
            date: "June 2013",
            description: "This program allows you to easily to create and modify maps for the now-canceled game, Khaos Online. Places tiles, switch tilesets, save and load maps with ease.",
            images: ["image1"],
            technologies: ["C#", ".NET"],
            platforms: ["Windows"],
            links: {
                "Download for Windows": "downloads/khaos.zip",
            },
        },
        "quadratix": {
            name: "Quadratix",
            background: "#d6d6d6",
            color: "#000000",
            date: "October 2013",
            description: "Need help FOILing? Do you have a terrible math teacher? Wish you could easily factor your quadratics? Want to figure out the distance and slope between two points? Look no further than Quadratix! This program does exactly that! Enter either your factors or your quadratic and it will instantly solve it for you! Enter two points on the right and it will give you the slope and distance between those two points!",
            images: ["image1"],
            technologies: ["C#", ".NET"],
            platforms: ["Windows"],
            links: {
                "Download for Windows": "downloads/quadratix.zip",
            },
        },
        "alancryption": {
            name: "Alancryption",
            background: "#e69806",
            color: "#000000",
            date: "June 2013",
            description: "Tired of the NSA reading your conversations on Skype? Want a way to ensure your text is unreadable to humans? Look no further than Alancryption! This neat little program allows you to instantaneously encrypt any text. The encrypted text can then easily be converted back to its original form.",
            images: ["image1"],
            technologies: ["C#", ".NET"],
            platforms: ["Windows"],
            links: {
                "Download for Windows": "downloads/alancryption.zip",
            },
        },
        "prime": {
            name: "Prime Factorizer",
            background: "#d6d6d6",
            color: "#000000",
            date: "September 2013",
            description: "This program does what its name implies; it prime factorizes any number you put into it. It’s that simple. Enter a number and it spits out all the factors that make up that number. Pretty neat, huh?",
            images: ["image1"],
            technologies: ["C#", ".NET"],
            platforms: ["Windows"],
            links: {
                "Download for Windows": "downloads/prime.zip",
            },
        },
    }
};
<commit_msg>Minor text clean up<commit_after>var data = {
    contacts: {
        "Github": "https://github.com/AlanMorel",
        "LinkedIn": "https://linkedin.com/in/AlanMorel/",
        "Facebook": "https://www.facebook.com/SharpAceX",
        "Twitter": "https://www.twitter.com/SharpAceX",
        "Email": "mailto:alan.morel@nyu.edu",
        "Resume": "downloads/resume.pdf",
		"Medium": "https://medium.com/@alanmorel"
    },
    projects: {
        "trumpulse": {
            name: "Trumpulse",
            background: "#2f61de",
            color: "#ffffff",
            date: "April 2017",
            description: "Trumpulse uses data from multiple news sources so you can get the pulse of America under Trump. Using data from various news sources and social networks like Facebook and Twitter, you can get a diverse picture of the state of America under the Trump administration.",
            images: ["image1", "image2", "image3"],
            technologies: ["Angular.js", "Electron", "Cordova", "PHP", "News API", "Facebook API", "Twitter API"],
            platforms: ["Android", "Web", "Windows", "Mac"],
            links: {
                "Google Play": "https://play.google.com/store/apps/details?id=alanmorel.trumpulse",
				"Download for Windows": "downloads/trumpulse.zip",
                "Demo": "http://alanmorel.com/trumpulse",
                "Github": "https://github.com/AlanMorel/trumpulse"
            },
        },
        "wasdwithme": {
            name: "WASD With Me",
            background: "#5a26a5",
            color: "#ffffff",
            date: "February 2017",
            description: "WASD With Me is the online destination to meet and play with gamers. Discover and connect with gamers, set up gaming sessions and browse other gamers' libraries.",
            images: ["image1", "image2", "image3"],
            technologies: ["Node.js", "Express.js", "MondoDB", "Handlebars.js", "Socket.io", "Stylus", "IGDB API", "News API", "Steam API", "Twitch API"],
            platforms: ["Web"],
            links: {
                "Live Demo": "http://wasdwithme.herokuapp.com/",
                "Github": "https://github.com/AlanMorel/wasdWithMe",
            },
        },
        "zhigly": {
            name: "Zhigly",
            background: "#db3030",
            color: "#ffffff",
            date: "March 2016",
            description: "Zhigly is a hub where you can connect with your college campus. It allows you to look for items you'd like to buy or sell, browse upcoming on-campus events, search for jobs/internships/opportunities, look for housing, and much more.",
            images: ["image1", "image2", "image3"],
            technologies: ["ASP.NET", "C#", "Facebook SDK", "Twitter SDK", "Google+ API", "Imgur API"],
            platforms: ["Web"],
            links: {
                "Live Demo": "http://zhigly.com/",
                "Github": "https://github.com/AlanMorel/zhigly",
            },
        },
        "chainball": {
            name: "Chainball",
            background: "#f5577c",
            color: "#ffffff",
            date: "July 2014",
            description: "Use your fast reflexives to build yourself a chain as you navigate the endless stream of circles. Hit the blue and green circles while avoiding the red and gray ones to score as high as you can. When you’re finished, brag to your friends and challenge them to score higher than you in Chainball!",
            images: ["image1", "image2", "image3"],
            technologies: ["Java", "libGDX"],
            platforms: ["Android"],
            links: {
                "Google Play Store": "https://play.google.com/store/apps/details?id=com.luminositygames.chainball.android",
                "Github": "https://github.com/AlanMorel/chainball",
            },
        },
        "metronavigator": {
            name: "Metro Navigator",
            background: "#1d4f88",
            color: "#ffffff",
            date: "February 2014",
            description: "You’ll be able to find out the shortest routes between two stations, read the statuses of train lines and view the official map. It’s extremely useful for both experienced riders and newcomers to New York’s transportation system.",
            images: ["image1", "image2"],
            technologies: ["C#", ".NET"],
            platforms: ["Windows", "Web"],
            links: {
                "Download for Windows": "downloads/metronavigator.zip",
            },
        },
        "wavewarriors": {
            name: "Wave Warriors",
            background: "#f2ba00",
            color: "#000000",
            date: "December 2015",
            description: "WaveWarriors is a local multiplayer zombie-style survival game played using Xbox Controllers. Play for as long as you can as the waves of enemies get larger, faster and stronger. It supports up to 4 players so grab a few friends, connect your controllers and start working together!",
            images: ["image1", "image2", "image3"],
            technologies: ["Java", "Processing"],
            platforms: ["Windows", "Mac", "Linux"],
            links: {
                "Github": "https://github.com/AlanMorel/WaveWarriors",
            },
        },
        "pokedex": {
            name: "Pokédex",
            background: "#252525",
            color: "#ffffff",
            date: "April 2016",
            description: "Pokédex is a fun web app that you can use to learn more about Pokémon and play the famous 'Who's that Pokémon?'' game. It is a Flask fullstack web app powered by data provided by PokéAPI.",
            images: ["image1", "image2", "image3"],
            technologies: ["Python", "Flask", "PIL", "Requests", "PokéAPI"],
            platforms: ["Web"],
            links: {
                "Live Demo": "http://alanmorel.pythonanywhere.com/",
                "Github": "https://github.com/AlanMorel/pokedex",
            },
        },
        "sonar": {
            name: "Sonar",
            background: "#0b6cb3",
            color: "#ffffff",
            date: "August 2015",
            description: "Sonar allows you to easily see performance attributes of websites as you browse the web. This helps both end-users and developers understand why a webpage might be slow or experiencing problems. With Sonar, you can quickly identify how long a page took to load, the size of the page in bytes, if any requests caused bottlenecks, the number of hosts that served content, and details about the type of files delivered.",
            images: ["image1"],
            technologies: ["Chrome API", "HTML", "CSS", "JavaScript"],
            platforms: ["Chrome"],
            links: {
                "Promotional Page": "http://pages.catchpoint.com/sonar.html",
                "Chrome Web Store": "https://chrome.google.com/webstore/detail/sonar/dibilcjfahbokhiodajibcajcabfjein"
            },
        },
        "smoothietycoon": {
            name: "Smoothie Tycoon",
            background: "#ffa200",
            color: "#000000",
            date: "September 2014",
            description: "Start off small as you sell smoothies on the streets of New York to build your smoothie stand. Buy supplies and set your own price, and determine your own destiny!",
            images: ["image1", "image2", "image3"],
            technologies: ["Java", "libGDX"],
            platforms: ["Windows", "Mac", "Linux"],
            links: {
                "Download": "downloads/smoothietycoon.zip",
                "Github": "https://github.com/AlanMorel/smoothie-tycoon",
            },
        },
        "mars2o": {
            name: "Mars2O",
            background: "#bab9cc",
            color: "#000000",
            date: "October 2015",
            description: "Mars2O is a fun arcade game for Windows and Mac. You're tasked with returning back water from Mars, but there's just one problem: the asteroid belt is abnormally turbulent! Dodge asteroids and refuel your spaceship throughout your journey back to Earth.",
            images: ["image1", "image2", "image3"],
            technologies: ["Java", "Processing"],
            platforms: ["Windows", "Mac", "Linux"],
            links: {
                "Github": "https://github.com/AlanMorel/Mars2O",
            },
        },
        "balloonfrenzy": {
            name: "Balloon Frenzy!",
            background: "#78b7ff",
            color: "#000000",
            date: "August 2011",
            description: "Start off with one balloon and soon end up with many! Every 5 levels unlocks yet another balloon so things will become interesting real quick! Your goal is to obtain as high of a score as possible without missing too many balloons.",
            images: ["image1", "image2", "image3"],
            technologies: ["Java", "PHP"],
            platforms: ["Android"],
            links: {
                "Google Play Store": "https://play.google.com/store/apps/details?id=com.sharpacex.BalloonFrenzy",
            },
        },
        "dangerzone": {
            name: "Danger Zone",
            background: "#e2e2e2",
            color: "#000000",
            date: "June 2012",
            description: "Your job is simple. Just survive. With just your jumping and ducking skills at your disposal, you will need to avoid incoming missiles to keep yourself alive. The longer you can stay alive, the more score you’ll earn! At the end of the game your performance will be ranked from an (A+) to and (F-)! Be warned, this can get addicting!",
            images: ["image1", "image2", "image3"],
            technologies: ["Java", "PHP"],
            platforms: ["Android", "Windows"],
            links: {
                "Download for Windows": "downloads/dangerzone.zip",
                "Google Play Store": "https://play.google.com/store/apps/details?id=com.sharpacex.dangerzone",
            },
        },
        "flash": {
            name: "Flash",
            background: "#781ab5",
            color: "#ffffff",
            date: "March 2014",
            description: "If you want to have the benefits of physical flash cards with the flexibility and convenience of a program on your computer then Flash is the right program for you. Using Flash, you will be able to create flash cards in which you can use to study for your next final, midterm, or anything you’d like!",
            images: ["image1", "image2", "image3"],
            technologies: ["C#", ".NET"],
            platforms: ["Windows"],
            links: {
              "Download for Windows": "downloads/flash.zip",
            },
        },
        "sacredfire": {
            name: "Pokémon: SacredFire",
            background: "#c84000",
            color: "#ffffff",
            date: "October 2009",
            description: "You’re a teenage kid living in Wingpoint Town. Life was all good until about 3 weeks ago. Your Mom has had enough of Team Rocket. After weeks of news reports of Team Rocket abusing wild and legendary Pokémon, your Mom decides to do her best to protect you. From the start, you were destined to take action. Now you are thrown into a world where it’s you against the most powerful, evil and well-known corporation in the world. Your mission is to save the abused Pokémon, your family, and defend the honor of your hometown.",
            images: ["image1", "image2", "image3", "image4", "image5", "image6", "image7", "image8", "image9", "image10"],
            technologies: ["Assembly"],
            platforms: ["Game Boy Advance", "Windows", "Mac"],
            links: {
                "Pokémon: SacredFire Patch File": "/downloads/sacredfire.zip",
                "Lunar IPS (LIPS)": "http://fusoya.eludevisibility.org/lips/",
                "Pokémon: FireRed ROM": "https://www.loveroms.com/download/gameboy-advance/pokemon-fire-red-version-u-/39",
                "Visual Boy Advance": "https://sourceforge.net/projects/vba/",
            },
        },
        "khaos": {
            name: "Khaos Map",
            background: "#548845",
            color: "#ffffff",
            date: "June 2013",
            description: "This program allows you to easily to create and modify maps for the now-canceled game, Khaos Online. Places tiles, switch tilesets, save and load maps with ease.",
            images: ["image1"],
            technologies: ["C#", ".NET"],
            platforms: ["Windows"],
            links: {
                "Download for Windows": "downloads/khaos.zip",
            },
        },
        "quadratix": {
            name: "Quadratix",
            background: "#d6d6d6",
            color: "#000000",
            date: "October 2013",
            description: "Need help FOILing? Do you have a terrible math teacher? Wish you could easily factor your quadratics? Want to figure out the distance and slope between two points? Look no further than Quadratix! This program does exactly that! Enter either your factors or your quadratic and it will instantly solve it for you! Enter two points on the right and it will give you the slope and distance between those two points!",
            images: ["image1"],
            technologies: ["C#", ".NET"],
            platforms: ["Windows"],
            links: {
                "Download for Windows": "downloads/quadratix.zip",
            },
        },
        "alancryption": {
            name: "Alancryption",
            background: "#e69806",
            color: "#000000",
            date: "June 2013",
            description: "Tired of the NSA reading your conversations on Skype? Want a way to ensure your text is unreadable to humans? Look no further than Alancryption! This neat little program allows you to instantaneously encrypt any text. The encrypted text can then easily be converted back to its original form.",
            images: ["image1"],
            technologies: ["C#", ".NET"],
            platforms: ["Windows"],
            links: {
                "Download for Windows": "downloads/alancryption.zip",
            },
        },
        "prime": {
            name: "Prime Factorizer",
            background: "#d6d6d6",
            color: "#000000",
            date: "September 2013",
            description: "This program does what its name implies; it prime factorizes any number you put into it. It’s that simple. Enter a number and it spits out all the factors that make up that number. Pretty neat, huh?",
            images: ["image1"],
            technologies: ["C#", ".NET"],
            platforms: ["Windows"],
            links: {
                "Download for Windows": "downloads/prime.zip",
            },
        },
    }
};
<|endoftext|>


<commit_before>var groupKeys = ['step1','step2','step3','step4'],
    groupIDs = ['step1','step2','step3','step4'],
    s1 = document.getElementById('s1'),
    s2 = document.getElementById('s2'),
    s3 = document.getElementById('s3'),
    s4 = document.getElementById('s4');
/*
s.init(groupKeys, groupIDs);
s.switchr('step1',null,false);

s1.onclick = function() {
    s.switchr('step1',null,false);
};

s2.onclick = function() {
    s.switchr('step2',null,false);
};

s3.onclick = function() {
    s.switchr('step3',null,false);
};

s4.onclick = function() {
    s.switchr('step4',null,false);
};
*/

/* New Format */
NS = NDAYSwitchr;
NS.init();
var group = NS.group();
group.add(groupKeys,groupIDs);
group.hideAll();
group.show('step1');

s1.onclick = function() {
    group.hide('step1');
};

s2.onclick = function() {
    group.hide('step2');
};

s3.onclick = function() {
    group.hide('step3');
};

s4.onclick = function() {
    group.hide();
};<commit_msg>Changes to main.js<commit_after>var groupKeys = ['step1','step2','step3','step4'],
    groupIDs = ['step1','step2','step3','step4'],
    s1 = document.getElementById('s1'),
    s2 = document.getElementById('s2'),
    s3 = document.getElementById('s3'),
    s4 = document.getElementById('s4');

/* New Format */
NS = NDAYSwitchr;
NS.init();
var group = NS.group();
group.add(groupKeys,groupIDs);
console.info(NS.Groups)
console.log(group.elements);
group.hideAll();
// group.show('step1');

s1.onclick = function() {
    group.show('step1');
};

// s2.onclick = function() {
//     group.show('step2');
// };

// s3.onclick = function() {
//     group.show('step3');
// };

// s4.onclick = function() {
//     group.show('step4');
// };<|endoftext|>


<commit_before>/*
 * View model for OctoPrint-Slicer
 *
 * Author: Kenneth Jiang
 * License: AGPLv3
 */
$(function() {
    function SlicerViewModel(parameters) {
        var self = this;

        // assign the injected parameters, e.g.:
        // self.loginStateViewModel = parameters[0];
        // self.settingsViewModel = parameters[1];
        self.slicingViewModel = parameters[0];

        // Override slicingViewModel.show to surpress default slicing behavior
        self.slicingViewModel.show = function(target, file, force) {
            self.slicingViewModel.requestData();
            self.slicingViewModel.target = target;
            self.slicingViewModel.file(file);
            self.slicingViewModel.destinationFilename(self.slicingViewModel.file().substr(0, self.slicingViewModel.file().lastIndexOf(".")));
            self.slicingViewModel.printerProfile(self.slicingViewModel.printerProfiles.currentProfile());

            $('a[href="#tab_plugin_slicer"]').tab('show');
            self.loadSTL(target, file, force);
        };

        var CANVAS_WIDTH = 588,
            CANVAS_HEIGHT = 588;

        self.init = function() {
            self.models = [];
            self.container = document.getElementById( 'slicer-canvas' );

            self.camera = new THREE.PerspectiveCamera( 45, 1.0, 0.1, 100 );
            self.camera.up.set( 0, 0, 1 );
            self.camera.position.set( 2, 6, 4 );
            self.scene = new THREE.Scene();
            self.drawBedFloor(4, 4);
            self.drawWalls(4, 4, 4);

            // Lights
            self.scene.add( new THREE.AmbientLight(0xffffff, 1.0) );

            // renderer

            self.renderer = new THREE.WebGLRenderer( { antialias: true } );
            self.renderer.setClearColor( 0xd8d8d8 );
            self.renderer.setSize( CANVAS_WIDTH, CANVAS_HEIGHT );
            self.renderer.setPixelRatio( window.devicePixelRatio );

            self.renderer.gammaInput = true;
            self.renderer.gammaOutput = true;

            $("#slicer-viewport").empty().append(`

                    <button class="translate disabled" title="Translate"><img src="` + PLUGIN_BASEURL + `slicer/static/img/translate.png"></button>
                    <button class="rotate" title="Rotate"><img src="` + PLUGIN_BASEURL + `slicer/static/img/rotate.png"></button>
                    <button class="scale" title="Scale"><img src="` + PLUGIN_BASEURL + `slicer/static/img/scale.png"></button>

X<input type="number" step="any" name="x" min="">mm

Y<input type="number" step="any" name="y" min="">mm

X<input type="number" step="any" name="x" min="">°

Y<input type="number" step="any" name="y" min="">°

Z<input type="number" step="any" name="z" min="">°

                    `);

            $("#slicer-viewport").append(self.renderer.domElement);
            self.orbitControls = new THREE.OrbitControls(self.camera, self.renderer.domElement);
            self.orbitControls.enableDamping = true;
            self.orbitControls.dampingFactor = 0.25;
            self.orbitControls.enablePan = false;
            self.orbitControls.addEventListener("change", self.render);

            self.transformControls = new THREE.TransformControls(self.camera, self.renderer.domElement);
            self.transformControls.space = "world";
            self.transformControls.setAllowedTranslation("XY");
            self.transformControls.setRotationDisableE(true);
            self.transformControls.addEventListener("change", self.render);
            self.transformControls.addEventListener("mouseDown", self.startTransform);
            self.transformControls.addEventListener("mouseUp", self.endTransform);
            self.transformControls.addEventListener("change", self.updateTransformInputs);
            self.scene.add(self.transformControls);

            $("#slicer-viewport button.translate").click(function(event) {
                // Set selection mode to translate
                self.transformControls.setMode("translate");
                $("#slicer-viewport button.translate").removeClass("disabled");
                $("#slicer-viewport .values div").removeClass("show")
                    $("#slicer-viewport .translate.values div").addClass("show").children('p').addClass("show");
            });
            $("#slicer-viewport button.rotate").click(function(event) {
                // Set selection mode to rotate
                self.transformControls.setMode("rotate");
                $("#slicer-viewport button.rotate").removeClass("disabled");
                $("#slicer-viewport .values div").removeClass("show")
                    $("#slicer-viewport .rotate.values div").addClass("show").children('p').addClass("show");
            });
            $("#slicer-viewport .values input").change(function() {
                self.applyChange($(this));
            });

            ko.applyBindings(self.slicingViewModel, $('#slicing-settings')[0]);

        };

        self.loadSTL = function(target, file, force=true) {
            if (force) {
                self.models.forEach((model) => {
                    self.scene.remove(model);
                });
            }

            var loader = new THREE.STLLoader();
            loader.load(BASEURL + "downloads/files/" + target + "/" + file, function ( geometry ) {
                var material = new THREE.MeshPhongMaterial( { color: 0xff5533, specular: 0x111111, shininess: 200 } );
                var mesh = new THREE.Mesh( geometry, material );
                mesh.scale.set( 0.02, 0.02, 0.02 );
                self.models.push(mesh);

                self.scene.add( mesh );
                self.transformControls.attach(mesh);
                self.updateTransformInputs();
                self.render();
            } );
        };

        self.applyChange = function(input) {
            input.blur();
            if(!isNaN(parseFloat(input.val()))) {
                input.val(parseFloat(input.val()).toFixed(3));
                var model = self.transformControls.object;

                if (input.closest(".values").hasClass("rotate")) {
                    switch(input.attr("name")) {
                        case "x":
                            model.rotation.x = THREE.Math.degToRad(parseFloat(input.val()));
                            break;
                        case "y":
                            model.rotation.y = THREE.Math.degToRad(parseFloat(input.val()));
                            break;
                        case "z":
                            model.rotation.z = THREE.Math.degToRad(parseFloat(input.val()));
                            break;
                    }
                } else if (input.closest(".values").hasClass("translate")) {
                    switch(input.attr("name")) {
                        case "x":
                            model.position.x = -parseFloat(input.val());
                            break;
                        case "y":
                            model.position.y = -parseFloat(input.val());
                            break;
                    }
                }
                self.fixZPosition(model);
                self.render();
            }
        };

        self.startTransform = function () {
            // Disable orbit controls
            self.orbitControls.enabled = false;
        };

        self.endTransform = function () {
            // Enable orbit controls
            self.orbitControls.enabled = true;
        };

        self.updateTransformInputs = function () {
            var model = self.transformControls.object;
            $("#slicer-viewport .translate.values input[name=\"x\"]").val((model.position.x.toFixed(3) == 0 ? 0 : -model.position.x).toFixed(3)).attr("min", '');
            $("#slicer-viewport .translate.values input[name=\"y\"]").val(model.position.y.toFixed(3)).attr("min", '');
            $("#slicer-viewport .rotate.values input[name=\"x\"]").val((model.rotation.x * 180 / Math.PI).toFixed(3)).attr("min", '');
            $("#slicer-viewport .rotate.values input[name=\"y\"]").val((model.rotation.y * 180 / Math.PI).toFixed(3)).attr("min", '');
            $("#slicer-viewport .rotate.values input[name=\"z\"]").val((model.rotation.z * 180 / Math.PI).toFixed(3)).attr("min", '');
            self.fixZPosition(model);
            self.render();
        };

        self.drawBedFloor = function ( width, depth, segments ) {
            segments = segments || 20;
            var geometry = new THREE.PlaneGeometry(width, depth, segments, segments);
            var materialEven = new THREE.MeshBasicMaterial({color: 0xccccfc});
            var materialOdd = new THREE.MeshBasicMaterial({color: 0x444464});
            var materials = [materialEven, materialOdd];
            for (var x = 0; x < segments; x++) {
              for (var y = 0; y < segments; y++) {
                var i = x * segments + y;
                var j = 2 * i;
                geometry.faces[ j ].materialIndex = geometry.faces[ j + 1 ].materialIndex = (x + y) % 2;
              }
            }
            self.scene.add(new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(materials)));
        };

        self.drawWalls = function ( width, depth, height ) {
            var wall1 = self.rectShape( depth, height, 0x8888fc );
            wall1.rotation.x = Math.PI / 2;
            wall1.position.set(0, depth/2, height/2);
            self.scene.add(wall1);

            var wall2 = self.rectShape( width, height, 0x8888dc );
            wall2.rotation.y = Math.PI / 2;
            wall2.position.set(-width/2, 0, height/2);
            self.scene.add(wall2);

            var wall3 = self.rectShape( depth, height, 0x8888fc );
            wall3.rotation.x = -Math.PI / 2;
            wall3.position.set(0, -depth/2, height/2);
            self.scene.add(wall3);

            var wall4 = self.rectShape( width, height, 0x8888dc );
            wall4.rotation.y = -Math.PI / 2;
            wall4.position.set(width/2, 0, height/2);
            self.scene.add(wall4);
        }

        self.rectShape = function ( rectLength, rectWidth, color ) {
            var rectShape = new THREE.Shape();
            rectShape.moveTo( -rectLength/2,-rectWidth/2 );
            rectShape.lineTo( -rectLength/2, rectWidth/2 );
            rectShape.lineTo( rectLength/2, rectWidth/2 );
            rectShape.lineTo( rectLength/2, -rectWidth/2 );
            rectShape.lineTo( -rectLength/2, -rectWidth/2 );
            var rectGeom = new THREE.ShapeGeometry( rectShape );
            return new THREE.Mesh( rectGeom, new THREE.MeshBasicMaterial( { color } ) ) ;
        }

        self.fixZPosition = function ( model ) {
            var bedLowMinZ = 0.0;
            var boundaryBox = new THREE.Box3().setFromObject(model);
            boundaryBox.min.sub(model.position);
            boundaryBox.max.sub(model.position);
            model.position.z -= model.position.z + boundaryBox.min.z - bedLowMinZ;
        }

        self.slice = function() {
            self.saveModel(self.models[0]);
        }

        self.saveModel = function( model ) {
            // Create request
            var form = new FormData();
            form.append("file", self.blobFromModel(model), self.slicingViewModel.file());
            // Send request
            $.ajax({
                url: API_BASEURL + "files/local",
                type: "POST",
                data: form,
                processData: false,
                contentType: false,
                // On success
                success: function(data) {
                }
            });
        };

        self.blobFromModel = function( model ) {
    	    var exporter = new THREE.STLBinaryExporter();
    	    return new Blob([exporter.parse(model)], {type: "text/plain"});
        }

        self.render = function() {
            self.orbitControls.update();
            self.transformControls.update();
            self.renderer.render( self.scene, self.camera );
        };

        self.init();
        self.render();
    }

    // view model class, parameters for constructor, container to bind to
    OCTOPRINT_VIEWMODELS.push([
        SlicerViewModel,

        // e.g. loginStateViewModel, settingsViewModel, ...
        [ "slicingViewModel", /* "loginStateViewModel", "settingsViewModel" */ ],

        // e.g. #settings_plugin_slicer, #tab_plugin_slicer, ...
        [ "#slicer" ]
    ]);
});
<commit_msg>slicing works, apparently.<commit_after>/*
 * View model for OctoPrint-Slicer
 *
 * Author: Kenneth Jiang
 * License: AGPLv3
 */
$(function() {
    function SlicerViewModel(parameters) {
        var self = this;

        // assign the injected parameters, e.g.:
        // self.loginStateViewModel = parameters[0];
        // self.settingsViewModel = parameters[1];
        self.slicingViewModel = parameters[0];

        // Override slicingViewModel.show to surpress default slicing behavior
        self.slicingViewModel.show = function(target, file, force) {
            self.slicingViewModel.requestData();
            self.slicingViewModel.target = target;
            self.slicingViewModel.file(file);
            self.slicingViewModel.destinationFilename(self.slicingViewModel.file().substr(0, self.slicingViewModel.file().lastIndexOf(".")));
            self.slicingViewModel.printerProfile(self.slicingViewModel.printerProfiles.currentProfile());

            $('a[href="#tab_plugin_slicer"]').tab('show');
            self.loadSTL(target, file, force);
        };

        var CANVAS_WIDTH = 588,
            CANVAS_HEIGHT = 588;

        self.init = function() {
            self.models = [];
            self.container = document.getElementById( 'slicer-canvas' );

            self.camera = new THREE.PerspectiveCamera( 45, 1.0, 0.1, 100 );
            self.camera.up.set( 0, 0, 1 );
            self.camera.position.set( 2, 6, 4 );
            self.scene = new THREE.Scene();
            self.drawBedFloor(4, 4);
            self.drawWalls(4, 4, 4);

            // Lights
            self.scene.add( new THREE.AmbientLight(0xffffff, 1.0) );

            // renderer

            self.renderer = new THREE.WebGLRenderer( { antialias: true } );
            self.renderer.setClearColor( 0xd8d8d8 );
            self.renderer.setSize( CANVAS_WIDTH, CANVAS_HEIGHT );
            self.renderer.setPixelRatio( window.devicePixelRatio );

            self.renderer.gammaInput = true;
            self.renderer.gammaOutput = true;

            $("#slicer-viewport").empty().append(`

                    <button class="translate disabled" title="Translate"><img src="` + PLUGIN_BASEURL + `slicer/static/img/translate.png"></button>
                    <button class="rotate" title="Rotate"><img src="` + PLUGIN_BASEURL + `slicer/static/img/rotate.png"></button>
                    <button class="scale" title="Scale"><img src="` + PLUGIN_BASEURL + `slicer/static/img/scale.png"></button>

X<input type="number" step="any" name="x" min="">mm

Y<input type="number" step="any" name="y" min="">mm

X<input type="number" step="any" name="x" min="">°

Y<input type="number" step="any" name="y" min="">°

Z<input type="number" step="any" name="z" min="">°

                    `);

            $("#slicer-viewport").append(self.renderer.domElement);
            self.orbitControls = new THREE.OrbitControls(self.camera, self.renderer.domElement);
            self.orbitControls.enableDamping = true;
            self.orbitControls.dampingFactor = 0.25;
            self.orbitControls.enablePan = false;
            self.orbitControls.addEventListener("change", self.render);

            self.transformControls = new THREE.TransformControls(self.camera, self.renderer.domElement);
            self.transformControls.space = "world";
            self.transformControls.setAllowedTranslation("XY");
            self.transformControls.setRotationDisableE(true);
            self.transformControls.addEventListener("change", self.render);
            self.transformControls.addEventListener("mouseDown", self.startTransform);
            self.transformControls.addEventListener("mouseUp", self.endTransform);
            self.transformControls.addEventListener("change", self.updateTransformInputs);
            self.scene.add(self.transformControls);

            $("#slicer-viewport button.translate").click(function(event) {
                // Set selection mode to translate
                self.transformControls.setMode("translate");
                $("#slicer-viewport button.translate").removeClass("disabled");
                $("#slicer-viewport .values div").removeClass("show")
                    $("#slicer-viewport .translate.values div").addClass("show").children('p').addClass("show");
            });
            $("#slicer-viewport button.rotate").click(function(event) {
                // Set selection mode to rotate
                self.transformControls.setMode("rotate");
                $("#slicer-viewport button.rotate").removeClass("disabled");
                $("#slicer-viewport .values div").removeClass("show")
                    $("#slicer-viewport .rotate.values div").addClass("show").children('p').addClass("show");
            });
            $("#slicer-viewport .values input").change(function() {
                self.applyChange($(this));
            });

            ko.applyBindings(self.slicingViewModel, $('#slicing-settings')[0]);

        };

        self.loadSTL = function(target, file, force=true) {
            if (force) {
                self.models.forEach((model) => {
                    self.scene.remove(model);
                });
            }

            var loader = new THREE.STLLoader();
            loader.load(BASEURL + "downloads/files/" + target + "/" + file, function ( geometry ) {
                var material = new THREE.MeshPhongMaterial( { color: 0xff5533, specular: 0x111111, shininess: 200 } );
                var mesh = new THREE.Mesh( geometry, material );
                mesh.scale.set( 0.02, 0.02, 0.02 );
                self.models.push(mesh);

                self.scene.add( mesh );
                self.transformControls.attach(mesh);
                self.updateTransformInputs();
                self.render();
            } );
        };

        self.applyChange = function(input) {
            input.blur();
            if(!isNaN(parseFloat(input.val()))) {
                input.val(parseFloat(input.val()).toFixed(3));
                var model = self.transformControls.object;

                if (input.closest(".values").hasClass("rotate")) {
                    switch(input.attr("name")) {
                        case "x":
                            model.rotation.x = THREE.Math.degToRad(parseFloat(input.val()));
                            break;
                        case "y":
                            model.rotation.y = THREE.Math.degToRad(parseFloat(input.val()));
                            break;
                        case "z":
                            model.rotation.z = THREE.Math.degToRad(parseFloat(input.val()));
                            break;
                    }
                } else if (input.closest(".values").hasClass("translate")) {
                    switch(input.attr("name")) {
                        case "x":
                            model.position.x = -parseFloat(input.val());
                            break;
                        case "y":
                            model.position.y = -parseFloat(input.val());
                            break;
                    }
                }
                self.fixZPosition(model);
                self.render();
            }
        };

        self.startTransform = function () {
            // Disable orbit controls
            self.orbitControls.enabled = false;
        };

        self.endTransform = function () {
            // Enable orbit controls
            self.orbitControls.enabled = true;
        };

        self.updateTransformInputs = function () {
            var model = self.transformControls.object;
            $("#slicer-viewport .translate.values input[name=\"x\"]").val((model.position.x.toFixed(3) == 0 ? 0 : -model.position.x).toFixed(3)).attr("min", '');
            $("#slicer-viewport .translate.values input[name=\"y\"]").val(model.position.y.toFixed(3)).attr("min", '');
            $("#slicer-viewport .rotate.values input[name=\"x\"]").val((model.rotation.x * 180 / Math.PI).toFixed(3)).attr("min", '');
            $("#slicer-viewport .rotate.values input[name=\"y\"]").val((model.rotation.y * 180 / Math.PI).toFixed(3)).attr("min", '');
            $("#slicer-viewport .rotate.values input[name=\"z\"]").val((model.rotation.z * 180 / Math.PI).toFixed(3)).attr("min", '');
            self.fixZPosition(model);
            self.render();
        };

        self.drawBedFloor = function ( width, depth, segments ) {
            segments = segments || 20;
            var geometry = new THREE.PlaneGeometry(width, depth, segments, segments);
            var materialEven = new THREE.MeshBasicMaterial({color: 0xccccfc});
            var materialOdd = new THREE.MeshBasicMaterial({color: 0x444464});
            var materials = [materialEven, materialOdd];
            for (var x = 0; x < segments; x++) {
              for (var y = 0; y < segments; y++) {
                var i = x * segments + y;
                var j = 2 * i;
                geometry.faces[ j ].materialIndex = geometry.faces[ j + 1 ].materialIndex = (x + y) % 2;
              }
            }
            self.scene.add(new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(materials)));
        };

        self.drawWalls = function ( width, depth, height ) {
            var wall1 = self.rectShape( depth, height, 0x8888fc );
            wall1.rotation.x = Math.PI / 2;
            wall1.position.set(0, depth/2, height/2);
            self.scene.add(wall1);

            var wall2 = self.rectShape( width, height, 0x8888dc );
            wall2.rotation.y = Math.PI / 2;
            wall2.position.set(-width/2, 0, height/2);
            self.scene.add(wall2);

            var wall3 = self.rectShape( depth, height, 0x8888fc );
            wall3.rotation.x = -Math.PI / 2;
            wall3.position.set(0, -depth/2, height/2);
            self.scene.add(wall3);

            var wall4 = self.rectShape( width, height, 0x8888dc );
            wall4.rotation.y = -Math.PI / 2;
            wall4.position.set(width/2, 0, height/2);
            self.scene.add(wall4);
        }

        self.rectShape = function ( rectLength, rectWidth, color ) {
            var rectShape = new THREE.Shape();
            rectShape.moveTo( -rectLength/2,-rectWidth/2 );
            rectShape.lineTo( -rectLength/2, rectWidth/2 );
            rectShape.lineTo( rectLength/2, rectWidth/2 );
            rectShape.lineTo( rectLength/2, -rectWidth/2 );
            rectShape.lineTo( -rectLength/2, -rectWidth/2 );
            var rectGeom = new THREE.ShapeGeometry( rectShape );
            return new THREE.Mesh( rectGeom, new THREE.MeshBasicMaterial( { color } ) ) ;
        }

        self.fixZPosition = function ( model ) {
            var bedLowMinZ = 0.0;
            var boundaryBox = new THREE.Box3().setFromObject(model);
            boundaryBox.min.sub(model.position);
            boundaryBox.max.sub(model.position);
            model.position.z -= model.position.z + boundaryBox.min.z - bedLowMinZ;
        }

        self.slice = function() {
            self.saveModel(self.models[0]);
        }

        self.saveModel = function( model ) {
            // Create request
            var form = new FormData();
            form.append("file", self.blobFromModel(model), self.slicingViewModel.file());
            // Send request
            $.ajax({
                url: API_BASEURL + "files/local",
                type: "POST",
                data: form,
                processData: false,
                contentType: false,
                // On success
                success: function(data) {
                    self.slicingViewModel.slice();
                }
            });
        };

        self.blobFromModel = function( model ) {
    	    var exporter = new THREE.STLBinaryExporter();
    	    return new Blob([exporter.parse(model)], {type: "text/plain"});
        }

        self.render = function() {
            self.orbitControls.update();
            self.transformControls.update();
            self.renderer.render( self.scene, self.camera );
        };

        self.init();
        self.render();
    }

    // view model class, parameters for constructor, container to bind to
    OCTOPRINT_VIEWMODELS.push([
        SlicerViewModel,

        // e.g. loginStateViewModel, settingsViewModel, ...
        [ "slicingViewModel", /* "loginStateViewModel", "settingsViewModel" */ ],

        // e.g. #settings_plugin_slicer, #tab_plugin_slicer, ...
        [ "#slicer" ]
    ]);
});
<|endoftext|>


<commit_before>//----------------------------------------------------------------------------
//  Copyright (C) 2013 The IPython Development Team
//
//  Distributed under the terms of the BSD License.  The full license is in
//  the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------

//============================================================================
// Base Widget Model and View classes
//============================================================================

/**
 * @module IPython
 * @namespace IPython
 **/

define(["notebook/js/widgetmanager",
        "underscore",
        "backbone"],
function(WidgetManager, Underscore, Backbone){

    var WidgetModel = Backbone.Model.extend({
        constructor: function (widget_manager, model_id, comm) {
            // Construcctor
            //
            // Creates a WidgetModel instance.
            //
            // Parameters
            // ----------
            // widget_manager : WidgetManager instance
            // model_id : string
            //      An ID unique to this model.
            // comm : Comm instance (optional)
            this.widget_manager = widget_manager;
            this.pending_msgs = 0;
            this.msg_throttle = 2;
            this.msg_buffer = null;
            this.key_value_lock = null;
            this.id = model_id;
            this.views = [];

            if (comm !== undefined) {
                // Remember comm associated with the model.
                this.comm = comm;
                comm.model = this;

                // Hook comm messages up to model.
                comm.on_close($.proxy(this._handle_comm_closed, this));
                comm.on_msg($.proxy(this._handle_comm_msg, this));
            }
            return Backbone.Model.apply(this);
        },

        send: function (content, callbacks) {
            // Send a custom msg over the comm.
            if (this.comm !== undefined) {
                var data = {method: 'custom', custom_content: content};
                this.comm.send(data, callbacks);
            }
        },

        _handle_comm_closed: function (msg) {
            // Handle when a widget is closed.
            this.trigger('comm:close');
            delete this.comm.model; // Delete ref so GC will collect widget model.
            delete this.comm;
            delete this.model_id; // Delete id from model so widget manager cleans up.
            // TODO: Handle deletion, like this.destroy(), and delete views, etc.
        },

        _handle_comm_msg: function (msg) {
            // Handle incoming comm msg.
            var method = msg.content.data.method;
            switch (method) {
                case 'update':
                    this.apply_update(msg.content.data.state);
                    break;
                case 'custom':
                    this.trigger('msg:custom', msg.content.data.custom_content);
                    break;
                case 'display':
                    this.widget_manager.display_view(msg, this);
                    break;
            }
        },

        apply_update: function (state) {
            // Handle when a widget is updated via the python side.
            for (var key in state) {
                if (state.hasOwnProperty(key)) {
                    var value = state[key];
                    this.key_value_lock = [key, value];
                    try {
                        this.set(key, this._unpack_models(value));
                    } finally {
                        this.key_value_lock = null;
                    }
                }
            }
            //TODO: are there callbacks that make sense in this case?  If so, attach them here as an option
        },

        _handle_status: function (msg, callbacks) {
            // Handle status msgs.

            // execution_state : ('busy', 'idle', 'starting')
            if (this.comm !== undefined) {
                if (msg.content.execution_state ==='idle') {
                    // Send buffer if this message caused another message to be
                    // throttled.
                    if (this.msg_buffer !== null &&
                        this.msg_throttle === this.pending_msgs) {
                        var data = {method: 'backbone', sync_method: 'update', sync_data: this.msg_buffer};
                        this.comm.send(data, callbacks);
                        this.msg_buffer = null;
                    } else {
                        --this.pending_msgs;
                    }
                }
            }
        },

        callbacks: function(callbacks) {
            // Create msg callbacks for a comm msg.
            var that = this;
            if (callbacks.iopub === undefined) {callbacks.iopub = {};}
            callbacks.iopub.status = function (msg) {
                that._handle_status(msg, callbacks);
            }
            return callbacks;
        },

        sync: function (method, model, options) {
            var error = options.error || function() {console.error('Backbone sync error:', arguments);}
            if (this.comm === undefined) {
                error();
                return false;
            }

            var attrs = (method==='patch') ? options.attrs : model.toJSON(options);

            if (this.key_value_lock !== null) {
                var k = this.key_value_lock[0];
                var v = this.key_value_lock[1];
                if (attrs[k]===v) {
                    delete attrs[k];
                }
            }
            if (_.size(attrs) == 0) {
                error();
                return false;
            }
            var callbacks = model.callbacks(options.callbacks || {});
            if (this.pending_msgs >= this.msg_throttle) {
                // The throttle has been exceeded, buffer the current msg so
                // it can be sent once the kernel has finished processing
                // some of the existing messages.

                // combine updates if it is a 'patch' sync, otherwise replace updates
                switch (method) {
                    case 'patch':
                        this.msg_buffer = _.extend(this.msg_buffer || {}, attrs);
                        break;
                    case 'update':
                        this.msg_buffer = attrs;
                        break;
                    default:
                        error();
                        return false;
                }
                this.msg_buffer_callbacks = callbacks;

            } else {
                // We haven't exceeded the throttle, send the message like
                // normal.  If this is a patch operation, just send the
                // changes.
                var data = {method: 'backbone', sync_data: attrs};
                this.comm.send(data, callbacks);
                this.pending_msgs++;
            }

            // Since the comm is a one-way communication, assume the message
            // arrived.  Don't call success since we don't have a model back from the server
            // this means we miss out on the 'sync' event.
        },

        save_changes: function(callbacks) {
			// Push this model's state to the back-end
			//
			// This invokes a Backbone.Sync.
            this.save(this.changedAttributes(), {patch: true, callbacks: callbacks});
        },

        _pack_models: function(value) {
            // Replace models with model ids recursively.
            if (value instanceof Backbone.Model) {
                return value.id;
            } else if (value instanceof Object) {
                var packed = {};
                for (var key in value) {
                    packed[key] = this._pack_models(value[key]);
                }
                return packed;
            } else {
                return value;
            }
        },

        _unpack_models: function(value) {
            // Replace model ids with models recursively.
            if (value instanceof Object) {
                var unpacked = {};
                for (var key in value) {
                    unpacked[key] = this._unpack_models(value[key]);
                }
                return unpacked;
            } else {
                var model = this.widget_manager.get_model(value);
                if (model !== null) {
                    return model;
                } else {
                    return value;
                }
            }
        },

    });
    WidgetManager.register_widget_model('WidgetModel', WidgetModel);

    var WidgetView = Backbone.View.extend({
        initialize: function(parameters) {
            // Public constructor.
            this.model.on('change',this.update,this);
            this.options = parameters.options;
            this.child_views = [];
            this.model.views.push(this);
        },

        update: function(){
            // Triggered on model change.
            //
            // Update view to be consistent with this.model
        },

        create_child_view: function(child_model, options) {
            // Create and return a child view.
            //
            // -given a model and (optionally) a view name if the view name is
            // not given, it defaults to the model's default view attribute.

            // TODO: this is hacky, and makes the view depend on this cell attribute and widget manager behavior
	        // it would be great to have the widget manager add the cell metadata
	        // to the subview without having to add it here.
	        options = options || {};
            options.cell = this.options.cell;
            var child_view = this.model.widget_manager.create_view(child_model, options);
            this.child_views[child_model.id] = child_view;
            return child_view;
        },

        delete_child_view: function(child_model, options) {
            // Delete a child view that was previously created using create_child_view.
            var view = this.child_views[child_model.id];
            delete this.child_views[child_model.id];
            view.remove();
        },

        do_diff: function(old_list, new_list, removed_callback, added_callback) {
            // Difference a changed list and call remove and add callbacks for
            // each removed and added item in the new list.
            //
            // Parameters
            // ----------
            // old_list : array
            // new_list : array
            // removed_callback : Callback(item)
            //      Callback that is called for each item removed.
            // added_callback : Callback(item)
            //      Callback that is called for each item added.

            // removed items
            _.each(_.difference(old_list, new_list), function(item, index, list) {
                removed_callback(item);
            }, this);

            // added items
            _.each(_.difference(new_list, old_list), function(item, index, list) {
                added_callback(item);
            }, this);
        },

        callbacks: function(){
            // Create msg callbacks for a comm msg.
            return this.model.widget_manager.callbacks(this);
        },

        render: function(){
            // Render the view.
            //
            // By default, this is only called the first time the view is created
        },

        send: function (content) {
            // Send a custom msg associated with this view.
            this.model.send(content, this.callbacks());
        },

        touch: function () {
            this.model.save_changes(this.callbacks());
        },

    });

    var DOMWidgetView = WidgetView.extend({
        initialize: function (options) {
            // Public constructor

            // In the future we may want to make changes more granular
            // (e.g., trigger on visible:change).
            this.model.on('change', this.update, this);
            this.model.on('msg:custom', this.on_msg, this);
            DOMWidgetView.__super__.initialize.apply(this, arguments);
        },

        on_msg: function(msg) {
            // Handle DOM specific msgs.
            switch(msg.msg_type) {
                case 'add_class':
                    this.add_class(msg.selector, msg.class_list);
                    break;
                case 'remove_class':
                    this.remove_class(msg.selector, msg.class_list);
                    break;
            }
        },

        add_class: function (selector, class_list) {
            // Add a DOM class to an element.
            this._get_selector_element(selector).addClass(class_list);
        },

        remove_class: function (selector, class_list) {
            // Remove a DOM class from an element.
            this._get_selector_element(selector).removeClass(class_list);
        },

        update: function () {
            // Update the contents of this view
            //
            // Called when the model is changed.  The model may have been
            // changed by another view or by a state update from the back-end.
            //      The very first update seems to happen before the element is
            // finished rendering so we use setTimeout to give the element time
            // to render
            var e = this.$el;
            var visible = this.model.get('visible');
            setTimeout(function() {e.toggle(visible)},0);

            var css = this.model.get('_css');
            if (css === undefined) {return;}
            for (var selector in css) {
                if (css.hasOwnProperty(selector)) {
                    // Apply the css traits to all elements that match the selector.
                    var elements = this._get_selector_element(selector);
                    if (elements.length > 0) {
                        var css_traits = css[selector];
                        for (var css_key in css_traits) {
                            if (css_traits.hasOwnProperty(css_key)) {
                                elements.css(css_key, css_traits[css_key]);
                            }
                        }
                    }
                }
            }
        },

        _get_selector_element: function (selector) {
            // Get the elements via the css selector.

            // If the selector is blank, apply the style to the $el_to_style
            // element.  If the $el_to_style element is not defined, use apply
            // the style to the view's element.
            var elements;
            if (selector === undefined || selector === null || selector === '') {
                if (this.$el_to_style === undefined) {
                    elements = this.$el;
                } else {
                    elements = this.$el_to_style;
                }
            } else {
                elements = this.$el.find(selector);
            }
            return elements;
        },
    });

    IPython.WidgetModel = WidgetModel;
    IPython.WidgetView = WidgetView;
    IPython.DOMWidgetView = DOMWidgetView;

    // Pass through WidgetManager namespace.
    return WidgetManager;
});
<commit_msg>Change the callback logic so the order makes more sense: *Now* View calls Model which calls Widget Manager *instead of * View calls Widget Manager, which then the Model modifies right before sync<commit_after>//----------------------------------------------------------------------------
//  Copyright (C) 2013 The IPython Development Team
//
//  Distributed under the terms of the BSD License.  The full license is in
//  the file COPYING, distributed as part of this software.
//----------------------------------------------------------------------------

//============================================================================
// Base Widget Model and View classes
//============================================================================

/**
 * @module IPython
 * @namespace IPython
 **/

define(["notebook/js/widgetmanager",
        "underscore",
        "backbone"],
function(WidgetManager, Underscore, Backbone){

    var WidgetModel = Backbone.Model.extend({
        constructor: function (widget_manager, model_id, comm) {
            // Construcctor
            //
            // Creates a WidgetModel instance.
            //
            // Parameters
            // ----------
            // widget_manager : WidgetManager instance
            // model_id : string
            //      An ID unique to this model.
            // comm : Comm instance (optional)
            this.widget_manager = widget_manager;
            this.pending_msgs = 0;
            this.msg_throttle = 2;
            this.msg_buffer = null;
            this.key_value_lock = null;
            this.id = model_id;
            this.views = [];

            if (comm !== undefined) {
                // Remember comm associated with the model.
                this.comm = comm;
                comm.model = this;

                // Hook comm messages up to model.
                comm.on_close($.proxy(this._handle_comm_closed, this));
                comm.on_msg($.proxy(this._handle_comm_msg, this));
            }
            return Backbone.Model.apply(this);
        },

        send: function (content, callbacks) {
            // Send a custom msg over the comm.
            if (this.comm !== undefined) {
                var data = {method: 'custom', custom_content: content};
                this.comm.send(data, callbacks);
            }
        },

        _handle_comm_closed: function (msg) {
            // Handle when a widget is closed.
            this.trigger('comm:close');
            delete this.comm.model; // Delete ref so GC will collect widget model.
            delete this.comm;
            delete this.model_id; // Delete id from model so widget manager cleans up.
            // TODO: Handle deletion, like this.destroy(), and delete views, etc.
        },

        _handle_comm_msg: function (msg) {
            // Handle incoming comm msg.
            var method = msg.content.data.method;
            switch (method) {
                case 'update':
                    this.apply_update(msg.content.data.state);
                    break;
                case 'custom':
                    this.trigger('msg:custom', msg.content.data.custom_content);
                    break;
                case 'display':
                    this.widget_manager.display_view(msg, this);
                    break;
            }
        },

        apply_update: function (state) {
            // Handle when a widget is updated via the python side.
            for (var key in state) {
                if (state.hasOwnProperty(key)) {
                    var value = state[key];
                    this.key_value_lock = [key, value];
                    try {
                        this.set(key, this._unpack_models(value));
                    } finally {
                        this.key_value_lock = null;
                    }
                }
            }
            //TODO: are there callbacks that make sense in this case?  If so, attach them here as an option
        },

        _handle_status: function (msg, callbacks) {
            // Handle status msgs.

            // execution_state : ('busy', 'idle', 'starting')
            if (this.comm !== undefined) {
                if (msg.content.execution_state ==='idle') {
                    // Send buffer if this message caused another message to be
                    // throttled.
                    if (this.msg_buffer !== null &&
                        this.msg_throttle === this.pending_msgs) {
                        var data = {method: 'backbone', sync_method: 'update', sync_data: this.msg_buffer};
                        this.comm.send(data, callbacks);
                        this.msg_buffer = null;
                    } else {
                        --this.pending_msgs;
                    }
                }
            }
        },

        callbacks: function(view) {
            // Create msg callbacks for a comm msg.
            var callbacks = this.widget_manager.callbacks(view);

            if (callbacks.iopub === undefined) {
                callbacks.iopub = {};
            }

            var that = this;
            callbacks.iopub.status = function (msg) {
                that._handle_status(msg, callbacks);
            }
            return callbacks;
        },

        sync: function (method, model, options) {

            // Make sure a comm exists.
            var error = options.error || function() {
                console.error('Backbone sync error:', arguments);
            }
            if (this.comm === undefined) {
                error();
                return false;
            }

            // Delete any key value pairs that the back-end already knows about.
            var attrs = (method === 'patch') ? options.attrs : model.toJSON(options);
            if (this.key_value_lock !== null) {
                var key = this.key_value_lock[0];
                var value = this.key_value_lock[1];
                if (attrs[key] === value) {
                    delete attrs[key];
                }
            }

            // Only sync if there are attributes to send to the back-end.
            if (_.size(attrs) !== 0) {
                var callbacks = options.callbacks || {};
                if (this.pending_msgs >= this.msg_throttle) {
                    // The throttle has been exceeded, buffer the current msg so
                    // it can be sent once the kernel has finished processing
                    // some of the existing messages.

                    // Combine updates if it is a 'patch' sync, otherwise replace updates
                    switch (method) {
                        case 'patch':
                            this.msg_buffer = _.extend(this.msg_buffer || {}, attrs);
                            break;
                        case 'update':
                            this.msg_buffer = attrs;
                            break;
                        default:
                            error();
                            return false;
                    }
                    this.msg_buffer_callbacks = callbacks;

                } else {
                    // We haven't exceeded the throttle, send the message like
                    // normal.  If this is a patch operation, just send the
                    // changes.
                    var data = {method: 'backbone', sync_data: attrs};
                    this.comm.send(data, callbacks);
                    this.pending_msgs++;
                }
            }
            // Since the comm is a one-way communication, assume the message
            // arrived.  Don't call success since we don't have a model back from the server
            // this means we miss out on the 'sync' event.
        },

        save_changes: function(callbacks) {
			// Push this model's state to the back-end
			//
			// This invokes a Backbone.Sync.
            this.save(this.changedAttributes(), {patch: true, callbacks: callbacks});
        },

        _pack_models: function(value) {
            // Replace models with model ids recursively.
            if (value instanceof Backbone.Model) {
                return value.id;
            } else if (value instanceof Object) {
                var packed = {};
                for (var key in value) {
                    packed[key] = this._pack_models(value[key]);
                }
                return packed;
            } else {
                return value;
            }
        },

        _unpack_models: function(value) {
            // Replace model ids with models recursively.
            if (value instanceof Object) {
                var unpacked = {};
                for (var key in value) {
                    unpacked[key] = this._unpack_models(value[key]);
                }
                return unpacked;
            } else {
                var model = this.widget_manager.get_model(value);
                if (model !== null) {
                    return model;
                } else {
                    return value;
                }
            }
        },

    });
    WidgetManager.register_widget_model('WidgetModel', WidgetModel);

    var WidgetView = Backbone.View.extend({
        initialize: function(parameters) {
            // Public constructor.
            this.model.on('change',this.update,this);
            this.options = parameters.options;
            this.child_views = [];
            this.model.views.push(this);
        },

        update: function(){
            // Triggered on model change.
            //
            // Update view to be consistent with this.model
        },

        create_child_view: function(child_model, options) {
            // Create and return a child view.
            //
            // -given a model and (optionally) a view name if the view name is
            // not given, it defaults to the model's default view attribute.

            // TODO: this is hacky, and makes the view depend on this cell attribute and widget manager behavior
	        // it would be great to have the widget manager add the cell metadata
	        // to the subview without having to add it here.
	        options = options || {};
            options.cell = this.options.cell;
            var child_view = this.model.widget_manager.create_view(child_model, options);
            this.child_views[child_model.id] = child_view;
            return child_view;
        },

        delete_child_view: function(child_model, options) {
            // Delete a child view that was previously created using create_child_view.
            var view = this.child_views[child_model.id];
            delete this.child_views[child_model.id];
            view.remove();
        },

        do_diff: function(old_list, new_list, removed_callback, added_callback) {
            // Difference a changed list and call remove and add callbacks for
            // each removed and added item in the new list.
            //
            // Parameters
            // ----------
            // old_list : array
            // new_list : array
            // removed_callback : Callback(item)
            //      Callback that is called for each item removed.
            // added_callback : Callback(item)
            //      Callback that is called for each item added.

            // removed items
            _.each(_.difference(old_list, new_list), function(item, index, list) {
                removed_callback(item);
            }, this);

            // added items
            _.each(_.difference(new_list, old_list), function(item, index, list) {
                added_callback(item);
            }, this);
        },

        callbacks: function(){
            // Create msg callbacks for a comm msg.
            return this.model.callbacks(this);
        },

        render: function(){
            // Render the view.
            //
            // By default, this is only called the first time the view is created
        },

        send: function (content) {
            // Send a custom msg associated with this view.
            this.model.send(content, this.callbacks());
        },

        touch: function () {
            this.model.save_changes(this.callbacks());
        },

    });

    var DOMWidgetView = WidgetView.extend({
        initialize: function (options) {
            // Public constructor

            // In the future we may want to make changes more granular
            // (e.g., trigger on visible:change).
            this.model.on('change', this.update, this);
            this.model.on('msg:custom', this.on_msg, this);
            DOMWidgetView.__super__.initialize.apply(this, arguments);
        },

        on_msg: function(msg) {
            // Handle DOM specific msgs.
            switch(msg.msg_type) {
                case 'add_class':
                    this.add_class(msg.selector, msg.class_list);
                    break;
                case 'remove_class':
                    this.remove_class(msg.selector, msg.class_list);
                    break;
            }
        },

        add_class: function (selector, class_list) {
            // Add a DOM class to an element.
            this._get_selector_element(selector).addClass(class_list);
        },

        remove_class: function (selector, class_list) {
            // Remove a DOM class from an element.
            this._get_selector_element(selector).removeClass(class_list);
        },

        update: function () {
            // Update the contents of this view
            //
            // Called when the model is changed.  The model may have been
            // changed by another view or by a state update from the back-end.
            //      The very first update seems to happen before the element is
            // finished rendering so we use setTimeout to give the element time
            // to render
            var e = this.$el;
            var visible = this.model.get('visible');
            setTimeout(function() {e.toggle(visible)},0);

            var css = this.model.get('_css');
            if (css === undefined) {return;}
            for (var selector in css) {
                if (css.hasOwnProperty(selector)) {
                    // Apply the css traits to all elements that match the selector.
                    var elements = this._get_selector_element(selector);
                    if (elements.length > 0) {
                        var css_traits = css[selector];
                        for (var css_key in css_traits) {
                            if (css_traits.hasOwnProperty(css_key)) {
                                elements.css(css_key, css_traits[css_key]);
                            }
                        }
                    }
                }
            }
        },

        _get_selector_element: function (selector) {
            // Get the elements via the css selector.

            // If the selector is blank, apply the style to the $el_to_style
            // element.  If the $el_to_style element is not defined, use apply
            // the style to the view's element.
            var elements;
            if (selector === undefined || selector === null || selector === '') {
                if (this.$el_to_style === undefined) {
                    elements = this.$el;
                } else {
                    elements = this.$el_to_style;
                }
            } else {
                elements = this.$el.find(selector);
            }
            return elements;
        },
    });

    IPython.WidgetModel = WidgetModel;
    IPython.WidgetView = WidgetView;
    IPython.DOMWidgetView = DOMWidgetView;

    // Pass through WidgetManager namespace.
    return WidgetManager;
});
<|endoftext|>


<commit_before>var REGEX_NUMBER = /^(\-?[0-9]+|0[xX][0-9A-Fa-f]+)$/;
var REGEX_NUMBER_RANGE = /^([0-9]+|0[xX][0-9A-Fa-f]+)[ ]*\-[ ]*([0-9]+|0[xX][0-9A-Fa-f]+)$/;

const MIN = 0;
const MAX = 16777216;
const PARTITION_SIZE = 128;
const FILE_SIZE = 2048;
const FOLDER_DEPTH = 3;
const PARTITION_PADDING = 8;
//const BASE_PATH = "http://cors.io/?u=https://github.com/Mikescher/Befunge_Number_Representations/blob/master/output/"
const BASE_PATH = "https://github.com/Mikescher/Befunge_Number_Representations/blob/master/output/"

var repDict = {};
var reqLock = false;
var reqQueue = [];

function leftpad(n, width, z) { z = z || '0'; n = n + ''; return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n; }
function rightpad(str,l,c) {return str+Array(l-str.toString().length+1).join(c||" ")}

function getFilePath(number) {
  var n = Math.floor(number / FILE_SIZE) * FILE_SIZE;

  var path = "out_" + leftpad(n, PARTITION_PADDING) + ".tsv";

  var mod = FILE_SIZE;
  for(var i = 0; i < FOLDER_DEPTH; i++) {
      mod *= PARTITION_SIZE;
      n = Math.floor(n / mod) * mod;
      path = leftpad(n, PARTITION_PADDING) + "/" + path;
  }

  return BASE_PATH + path;
}

function parseData(data) {
    data.split('\n').forEach(function(line) {
        var split = line.split('\t');
        if (split.length != 2) return;

        repDict[parseInt(split[0], 16)] = split[1];
    }, this);
}

function work(command, term) {
    if (command.trim() == '') {
        term.echo("");
    } else if (command === 'help') {
        runHelp(term);
    } else if (command === 'clear') {
        term.clear();
    } else if (command === 'download') {
        window.open('https://www.mikescher.com/programs/view/BefunUtils', '_self');
    } else if (command === 'source') {
        window.open('https://github.com/Mikescher/BefunRep', '_self');
    } else if (command.match(REGEX_NUMBER)) {
       runRep(term, parseInt(command), false)
    } else if (command.match(REGEX_NUMBER_RANGE)) {
       runRepRange(term, parseInt(command.split('-')[0].trim()), parseInt(command.split('-')[1].trim()))
    } else {
       term.error('Command "' + command + '" not recognized. Try entering "help"');
    }
}

function runHelp(term) {
  term.echo(" ");
  term.echo("   ==========");
  term.echo("    BefunRep");
  term.echo("   ==========");
  term.echo(" ");
  term.echo("A common problem in befunge is writing literals for large numbers (with the least amount of code).");
  term.echo("BefunRep is a program by me (https://www.mikescher.de) that calculates representations for a big range of numbers.");
  term.echo("These calculations can take a while and are then saved in a binary file (called 'safe').");
  term.echo("For convenience I calculated the numbers from " + MIN + " to " + MAX + " and commited them to github (https://github.com/Mikescher/Befunge_Number_Representations).");
  term.echo("This is nothing more than a simple interface to get them.");
  term.echo("For bigger projects its probably advicable to download the files or better yet create a own safe with the newest BefunRep version (type the command 'source')");
  term.echo(" ");
  term.echo("BefunRep is part of the BefunUtils tool package for Befunge-93 development by me (Mikescher)");
  term.echo(" ");
  term.echo("Special thanks to:");
  term.echo("  - 'jcubic' for his jQuery terminal plugin");
  term.echo("  - 'github' for hosting all my crap");
}

function runRepRange(term, start, end) {
    if (end - start < 0 || end-start > 128) {
        term.error("You can maximally request 128 items at once");
        return;
    }

    if (start < MIN || end > MAX) {
        term.error("The numbers are not in the range of the precalculated safe ("+MIN+" - " + MAX + ")");
        term.error("You can download BefunRep and generate you own (bigger) safe");
        term.error("Enter 'download' to download the binary or 'source' to view the repository with the up-to-date code");
        return;
    }

    for (var i = start; i <= end; i++) {
        runRep(term, i, true);
    }
}

function runRep(term, number, ext) {

    if (number in repDict) {
        if (ext) term.echo(rightpad(number, 10, " ") + " => " + repDict[number]); else term.echo(repDict[number]);

        var next = reqQueue.shift();
        if (next !== undefined) next();

        return;
    }

    if (number < MIN || number > MAX) {
        term.error("The number is not in the range of the precalculated safe ("+MIN+" - " + MAX + ")");
        term.error("You can download BefunRep and generate you own (bigger) safe");
        term.error("Enter 'download' to download the binary or 'source' to view the repository with the up-to-date code");

        var next = reqQueue.shift();
        if (next !== undefined) next();

        return;
    }

    var fp = getFilePath(number);

    if (reqLock) {
        reqQueue.push(function() {runRep(term, number, ext)});
        return;
    }

    reqLock = true;
    jQuery.get(fp, undefined, function(data)
    {
        parseData(data);
        if (ext) term.echo(rightpad(number, 10, " ") + " => " + repDict[number]); else term.echo(repDict[number]);
    },
    "text").fail(function(jqXHR, textStatus)
    {
        term.error("Could not get data from " + fp);
        term.error(textStatus);
    })
    .always(function()
    {
        reqLock = false;
        var next = reqQueue.shift();
        if (next !== undefined) next();
    });
}

var config = {
    greetings: 'This is the online interface for BefunRep'+'\n'+
               'Enter a number to see a (nearly) optimal befunge-93 representation'+'\n'+
               'All values are loaded from this repository: https://github.com/Mikescher/Befunge_Number_Representations'+'\n'+
               'Enter "help" for more information',
    name: 'befunrep_online',
    prompt: 'bef93> '
}

function init($, undefined) {
    $('#befterminal').terminal(work, config);
}

jQuery(init);<commit_msg>[BefunRepTerminal] use CORS proxy<commit_after>var REGEX_NUMBER = /^(\-?[0-9]+|0[xX][0-9A-Fa-f]+)$/;
var REGEX_NUMBER_RANGE = /^([0-9]+|0[xX][0-9A-Fa-f]+)[ ]*\-[ ]*([0-9]+|0[xX][0-9A-Fa-f]+)$/;

const MIN = 0;
const MAX = 16777216;
const PARTITION_SIZE = 128;
const FILE_SIZE = 2048;
const FOLDER_DEPTH = 3;
const PARTITION_PADDING = 8;
const BASE_PATH = "http://cors.io/?u=https://github.com/Mikescher/Befunge_Number_Representations/blob/master/output/"
//const BASE_PATH = "https://github.com/Mikescher/Befunge_Number_Representations/blob/master/output/"

var repDict = {};
var reqLock = false;
var reqQueue = [];

function leftpad(n, width, z) { z = z || '0'; n = n + ''; return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n; }
function rightpad(str,l,c) {return str+Array(l-str.toString().length+1).join(c||" ")}

function getFilePath(number) {
  var n = Math.floor(number / FILE_SIZE) * FILE_SIZE;

  var path = "out_" + leftpad(n, PARTITION_PADDING) + ".tsv";

  var mod = FILE_SIZE;
  for(var i = 0; i < FOLDER_DEPTH; i++) {
      mod *= PARTITION_SIZE;
      n = Math.floor(n / mod) * mod;
      path = leftpad(n, PARTITION_PADDING) + "/" + path;
  }

  return BASE_PATH + path;
}

function parseData(data) {
    data.split('\n').forEach(function(line) {
        var split = line.split('\t');
        if (split.length != 2) return;

        repDict[parseInt(split[0], 16)] = split[1];
    }, this);
}

function work(command, term) {
    if (command.trim() == '') {
        term.echo("");
    } else if (command === 'help') {
        runHelp(term);
    } else if (command === 'clear') {
        term.clear();
    } else if (command === 'download') {
        window.open('https://www.mikescher.com/programs/view/BefunUtils', '_self');
    } else if (command === 'source') {
        window.open('https://github.com/Mikescher/BefunRep', '_self');
    } else if (command.match(REGEX_NUMBER)) {
       runRep(term, parseInt(command), false)
    } else if (command.match(REGEX_NUMBER_RANGE)) {
       runRepRange(term, parseInt(command.split('-')[0].trim()), parseInt(command.split('-')[1].trim()))
    } else {
       term.error('Command "' + command + '" not recognized. Try entering "help"');
    }
}

function runHelp(term) {
  term.echo(" ");
  term.echo("   ==========");
  term.echo("    BefunRep");
  term.echo("   ==========");
  term.echo(" ");
  term.echo("A common problem in befunge is writing literals for large numbers (with the least amount of code).");
  term.echo("BefunRep is a program by me (https://www.mikescher.de) that calculates representations for a big range of numbers.");
  term.echo("These calculations can take a while and are then saved in a binary file (called 'safe').");
  term.echo("For convenience I calculated the numbers from " + MIN + " to " + MAX + " and commited them to github (https://github.com/Mikescher/Befunge_Number_Representations).");
  term.echo("This is nothing more than a simple interface to get them.");
  term.echo("For bigger projects its probably advicable to download the files or better yet create a own safe with the newest BefunRep version (type the command 'source')");
  term.echo(" ");
  term.echo("BefunRep is part of the BefunUtils tool package for Befunge-93 development by me (Mikescher)");
  term.echo(" ");
  term.echo("Special thanks to:");
  term.echo("  - 'jcubic' for his jQuery terminal plugin");
  term.echo("  - 'github' for hosting all my crap");
}

function runRepRange(term, start, end) {
    if (end - start < 0 || end-start > 128) {
        term.error("You can maximally request 128 items at once");
        return;
    }

    if (start < MIN || end > MAX) {
        term.error("The numbers are not in the range of the precalculated safe ("+MIN+" - " + MAX + ")");
        term.error("You can download BefunRep and generate you own (bigger) safe");
        term.error("Enter 'download' to download the binary or 'source' to view the repository with the up-to-date code");
        return;
    }

    for (var i = start; i <= end; i++) {
        runRep(term, i, true);
    }
}

function runRep(term, number, ext) {

    if (number in repDict) {
        if (ext) term.echo(rightpad(number, 10, " ") + " => " + repDict[number]); else term.echo(repDict[number]);

        var next = reqQueue.shift();
        if (next !== undefined) next();

        return;
    }

    if (number < MIN || number > MAX) {
        term.error("The number is not in the range of the precalculated safe ("+MIN+" - " + MAX + ")");
        term.error("You can download BefunRep and generate you own (bigger) safe");
        term.error("Enter 'download' to download the binary or 'source' to view the repository with the up-to-date code");

        var next = reqQueue.shift();
        if (next !== undefined) next();

        return;
    }

    var fp = getFilePath(number);

    if (reqLock) {
        reqQueue.push(function() {runRep(term, number, ext)});
        return;
    }

    reqLock = true;
    jQuery.get(fp, undefined, function(data)
    {
        parseData(data);
        if (ext) term.echo(rightpad(number, 10, " ") + " => " + repDict[number]); else term.echo(repDict[number]);
    },
    "text").fail(function(jqXHR, textStatus)
    {
        term.error("Could not get data from " + fp);
        term.error(textStatus);
    })
    .always(function()
    {
        reqLock = false;
        var next = reqQueue.shift();
        if (next !== undefined) next();
    });
}

var config = {
    greetings: 'This is the online interface for BefunRep'+'\n'+
               'Enter a number to see a (nearly) optimal befunge-93 representation'+'\n'+
               'All values are loaded from this repository: https://github.com/Mikescher/Befunge_Number_Representations'+'\n'+
               'Enter "help" for more information',
    name: 'befunrep_online',
    prompt: 'bef93> '
}

function init($, undefined) {
    $('#befterminal').terminal(work, config);
}

jQuery(init);<|endoftext|>