<commit_before>'use strict';
const os = require('os');
const path = require('path');
const electron = require('electron');
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;
const shell = electron.shell;
const appName = app.getName();

function sendAction(action, data) {
	const win = BrowserWindow.getAllWindows()[0];

	if (process.platform === 'darwin') {
		win.restore();
	}

	win.webContents.send(action, data);
}

const viewSubmenu = [
	{
		label: 'Reset Text Size',
		accelerator: 'CmdOrCtrl+0',
		click() {
			sendAction('zoom-reset');
		}
	},
	{
		label: 'Increase Text Size',
		accelerator: 'CmdOrCtrl+Plus',
		click() {
			sendAction('zoom-in');
		}
	},
	{
		label: 'Decrease Text Size',
		accelerator: 'CmdOrCtrl+-',
		click() {
			sendAction('zoom-out');
		}
	}
];

const helpSubmenu = [
	{
		label: `${appName} Website...`,
		click() {
			shell.openExternal('https://github.com/sindresorhus/anatine');
		}
	},
	{
		label: 'Report an Issue...',
		click() {
			const body = `
<!-- Please succinctly describe your issue and steps to reproduce it. -->

-

${app.getName()} ${app.getVersion()}
Electron ${process.versions.electron}
${process.platform} ${process.arch} ${os.release()}`;

			shell.openExternal(`https://github.com/sindresorhus/anatine/issues/new?body=${encodeURIComponent(body)}`);
		}
	}
];

if (process.platform !== 'darwin') {
	helpSubmenu.push({
		type: 'separator'
	}, {
		label: `About ${appName}`,
		click() {
			electron.dialog.showMessageBox({
				title: `About ${appName}`,
				message: `${appName} ${app.getVersion()}`,
				detail: 'Created by Sindre Sorhus',
				// because of https://github.com/electron/electron/issues/5465
				icon: path.join(__dirname, 'static', process.platform === 'linux' ? 'Icon-linux-about.png' : 'Icon.png'),
				buttons: []
			});
		}
	});
}

const darwinTpl = [
	{
		label: appName,
		submenu: [
			{
				label: `About ${appName}`,
				role: 'about'
			},
			{
				type: 'separator'
			},
			{
				label: 'Toggle Dark Mode',
				accelerator: 'CmdOrCtrl+D',
				click() {
					sendAction('toggle-dark-mode');
				}
			},
			{
				type: 'separator'
			},
			{
				label: 'Log Out',
				click() {
					sendAction('log-out');
				}
			},
			{
				type: 'separator'
			},
			{
				label: 'Services',
				role: 'services',
				submenu: []
			},
			{
				type: 'separator'
			},
			{
				label: `Hide ${appName}`,
				accelerator: 'Cmd+H',
				role: 'hide'
			},
			{
				label: 'Hide Others',
				accelerator: 'Cmd+Shift+H',
				role: 'hideothers'
			},
			{
				label: 'Show All',
				role: 'unhide'
			},
			{
				type: 'separator'
			},
			{
				label: `Quit ${appName}`,
				accelerator: 'Cmd+Q',
				click() {
					app.quit();
				}
			}
		]
	},
	{
		label: 'File',
		submenu: [
			{
				label: 'New Tweet',
				accelerator: 'Cmd+N',
				click() {
					sendAction('new-tweet');
				}
			}
		]
	},
	{
		label: 'Edit',
		submenu: [
			{
				label: 'Undo',
				accelerator: 'CmdOrCtrl+Z',
				role: 'undo'
			},
			{
				label: 'Redo',
				accelerator: 'Shift+CmdOrCtrl+Z',
				role: 'redo'
			},
			{
				type: 'separator'
			},
			{
				label: 'Cut',
				accelerator: 'CmdOrCtrl+X',
				role: 'cut'
			},
			{
				label: 'Copy',
				accelerator: 'CmdOrCtrl+C',
				role: 'copy'
			},
			{
				label: 'Paste',
				accelerator: 'CmdOrCtrl+V',
				role: 'paste'
			},
			{
				label: 'Select All',
				accelerator: 'CmdOrCtrl+A',
				role: 'selectall'
			}
		]
	},
	{
		label: 'View',
		submenu: viewSubmenu
	},
	{
		label: 'Window',
		role: 'window',
		submenu: [
			{
				label: 'Minimize',
				accelerator: 'CmdOrCtrl+M',
				role: 'minimize'
			},
			{
				label: 'Close',
				accelerator: 'CmdOrCtrl+W',
				role: 'close'
			},
			{
				type: 'separator'
			},
			{
				label: 'Next Tab',
				accelerator: 'Ctrl+Tab',
				click() {
					sendAction('next-tab');
				}
			},
			{
				label: 'Previous Tab',
				accelerator: 'Ctrl+Shift+Tab',
				click() {
					sendAction('previous-tab');
				}
			},
			{
				type: 'separator'
			},
			{
				label: 'Bring All to Front',
				role: 'front'
			},

			// temp workaround for:
			// https://github.com/sindresorhus/caprine/issues/5
			{
				label: 'Toggle Full Screen',
				accelerator: 'Ctrl+Cmd+F',
				click() {
					const win = BrowserWindow.getAllWindows()[0];
					win.setFullScreen(!win.isFullScreen());
				}
			}
		]
	},
	{
		label: 'Help',
		role: 'help',
		submenu: helpSubmenu
	}
];

const otherTpl = [
	{
		label: 'File',
		submenu: [
			{
				label: 'New Tweet',
				accelerator: 'Ctrl+N',
				click() {
					sendAction('new-tweet');
				}
			},
			{
				type: 'separator'
			},
			{
				label: 'Next Tab',
				accelerator: 'Ctrl+Tab',
				click() {
					sendAction('next-tab');
				}
			},
			{
				label: 'Previous Tab',
				accelerator: 'Ctrl+Shift+Tab',
				click() {
					sendAction('previous-tab');
				}
			},
			{
				type: 'separator'
			},
			{
				label: 'Toggle Dark Mode',
				accelerator: 'CmdOrCtrl+D',
				click() {
					sendAction('toggle-dark-mode');
				}
			},
			{
				type: 'separator'
			},
			{
				label: 'Log Out',
				click() {
					sendAction('log-out');
				}
			},
			{
				label: 'Quit',
				click() {
					app.quit();
				}
			}
		]
	},
	{
		label: 'Edit',
		submenu: [
			{
				label: 'Cut',
				accelerator: 'CmdOrCtrl+X',
				role: 'cut'
			},
			{
				label: 'Copy',
				accelerator: 'CmdOrCtrl+C',
				role: 'copy'
			},
			{
				label: 'Paste',
				accelerator: 'CmdOrCtrl+V',
				role: 'paste'
			}
		]
	},
	{
		label: 'View',
		submenu: viewSubmenu
	},
	{
		label: 'Help',
		role: 'help',
		submenu: helpSubmenu
	}
];

const tpl = process.platform === 'darwin' ? darwinTpl : otherTpl;

module.exports = electron.Menu.buildFromTemplate(tpl);
<commit_msg>simplify menu shortcuts<commit_after>'use strict';
const os = require('os');
const path = require('path');
const electron = require('electron');
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;
const shell = electron.shell;
const appName = app.getName();

function sendAction(action, data) {
	const win = BrowserWindow.getAllWindows()[0];

	if (process.platform === 'darwin') {
		win.restore();
	}

	win.webContents.send(action, data);
}

const viewSubmenu = [
	{
		label: 'Reset Text Size',
		accelerator: 'CmdOrCtrl+0',
		click() {
			sendAction('zoom-reset');
		}
	},
	{
		label: 'Increase Text Size',
		accelerator: 'CmdOrCtrl+Plus',
		click() {
			sendAction('zoom-in');
		}
	},
	{
		label: 'Decrease Text Size',
		accelerator: 'CmdOrCtrl+-',
		click() {
			sendAction('zoom-out');
		}
	}
];

const helpSubmenu = [
	{
		label: `${appName} Website...`,
		click() {
			shell.openExternal('https://github.com/sindresorhus/anatine');
		}
	},
	{
		label: 'Report an Issue...',
		click() {
			const body = `
<!-- Please succinctly describe your issue and steps to reproduce it. -->

-

${app.getName()} ${app.getVersion()}
Electron ${process.versions.electron}
${process.platform} ${process.arch} ${os.release()}`;

			shell.openExternal(`https://github.com/sindresorhus/anatine/issues/new?body=${encodeURIComponent(body)}`);
		}
	}
];

if (process.platform !== 'darwin') {
	helpSubmenu.push({
		type: 'separator'
	}, {
		label: `About ${appName}`,
		click() {
			electron.dialog.showMessageBox({
				title: `About ${appName}`,
				message: `${appName} ${app.getVersion()}`,
				detail: 'Created by Sindre Sorhus',
				// because of https://github.com/electron/electron/issues/5465
				icon: path.join(__dirname, 'static', process.platform === 'linux' ? 'Icon-linux-about.png' : 'Icon.png'),
				buttons: []
			});
		}
	});
}

const darwinTpl = [
	{
		label: appName,
		submenu: [
			{
				label: `About ${appName}`,
				role: 'about'
			},
			{
				type: 'separator'
			},
			{
				label: 'Toggle Dark Mode',
				accelerator: 'Cmd+D',
				click() {
					sendAction('toggle-dark-mode');
				}
			},
			{
				type: 'separator'
			},
			{
				label: 'Log Out',
				click() {
					sendAction('log-out');
				}
			},
			{
				type: 'separator'
			},
			{
				label: 'Services',
				role: 'services',
				submenu: []
			},
			{
				type: 'separator'
			},
			{
				label: `Hide ${appName}`,
				accelerator: 'Cmd+H',
				role: 'hide'
			},
			{
				label: 'Hide Others',
				accelerator: 'Cmd+Shift+H',
				role: 'hideothers'
			},
			{
				label: 'Show All',
				role: 'unhide'
			},
			{
				type: 'separator'
			},
			{
				label: `Quit ${appName}`,
				accelerator: 'Cmd+Q',
				click() {
					app.quit();
				}
			}
		]
	},
	{
		label: 'File',
		submenu: [
			{
				label: 'New Tweet',
				accelerator: 'Cmd+N',
				click() {
					sendAction('new-tweet');
				}
			}
		]
	},
	{
		label: 'Edit',
		submenu: [
			{
				label: 'Undo',
				accelerator: 'Cmd+Z',
				role: 'undo'
			},
			{
				label: 'Redo',
				accelerator: 'Shift+Cmd+Z',
				role: 'redo'
			},
			{
				type: 'separator'
			},
			{
				label: 'Cut',
				accelerator: 'Cmd+X',
				role: 'cut'
			},
			{
				label: 'Copy',
				accelerator: 'Cmd+C',
				role: 'copy'
			},
			{
				label: 'Paste',
				accelerator: 'Cmd+V',
				role: 'paste'
			},
			{
				label: 'Select All',
				accelerator: 'Cmd+A',
				role: 'selectall'
			}
		]
	},
	{
		label: 'View',
		submenu: viewSubmenu
	},
	{
		label: 'Window',
		role: 'window',
		submenu: [
			{
				label: 'Minimize',
				accelerator: 'Cmd+M',
				role: 'minimize'
			},
			{
				label: 'Close',
				accelerator: 'Cmd+W',
				role: 'close'
			},
			{
				type: 'separator'
			},
			{
				label: 'Next Tab',
				accelerator: 'Ctrl+Tab',
				click() {
					sendAction('next-tab');
				}
			},
			{
				label: 'Previous Tab',
				accelerator: 'Ctrl+Shift+Tab',
				click() {
					sendAction('previous-tab');
				}
			},
			{
				type: 'separator'
			},
			{
				label: 'Bring All to Front',
				role: 'front'
			},

			// temp workaround for:
			// https://github.com/sindresorhus/caprine/issues/5
			{
				label: 'Toggle Full Screen',
				accelerator: 'Ctrl+Cmd+F',
				click() {
					const win = BrowserWindow.getAllWindows()[0];
					win.setFullScreen(!win.isFullScreen());
				}
			}
		]
	},
	{
		label: 'Help',
		role: 'help',
		submenu: helpSubmenu
	}
];

const otherTpl = [
	{
		label: 'File',
		submenu: [
			{
				label: 'New Tweet',
				accelerator: 'Ctrl+N',
				click() {
					sendAction('new-tweet');
				}
			},
			{
				type: 'separator'
			},
			{
				label: 'Next Tab',
				accelerator: 'Ctrl+Tab',
				click() {
					sendAction('next-tab');
				}
			},
			{
				label: 'Previous Tab',
				accelerator: 'Ctrl+Shift+Tab',
				click() {
					sendAction('previous-tab');
				}
			},
			{
				type: 'separator'
			},
			{
				label: 'Toggle Dark Mode',
				accelerator: 'Ctrl+D',
				click() {
					sendAction('toggle-dark-mode');
				}
			},
			{
				type: 'separator'
			},
			{
				label: 'Log Out',
				click() {
					sendAction('log-out');
				}
			},
			{
				label: 'Quit',
				click() {
					app.quit();
				}
			}
		]
	},
	{
		label: 'Edit',
		submenu: [
			{
				label: 'Cut',
				accelerator: 'Ctrl+X',
				role: 'cut'
			},
			{
				label: 'Copy',
				accelerator: 'Ctrl+C',
				role: 'copy'
			},
			{
				label: 'Paste',
				accelerator: 'Ctrl+V',
				role: 'paste'
			}
		]
	},
	{
		label: 'View',
		submenu: viewSubmenu
	},
	{
		label: 'Help',
		role: 'help',
		submenu: helpSubmenu
	}
];

const tpl = process.platform === 'darwin' ? darwinTpl : otherTpl;

module.exports = electron.Menu.buildFromTemplate(tpl);
<|endoftext|>


<commit_before>//check if need relogin
if(document.getElementById('UserID')){
    window.location.href = 'https://cas.coloradocollege.edu/cas/login?service=https%3A%2F%2Fbanssop.coloradocollege.edu%3A443%2Fssomanager%2Fc%2FSSB';
}

//create menu table
{
    var table = document.createElement('table');
    table.setAttribute('class', 'menu-table');
    var table_container = document.createElement('div');
    document.body.appendChild(table_container);
    table_container.appendChild(table);
    table.appendChild(document.createElement('tbody'));

    var fst = false;
    $('.menuplaintable').eq(0).find('tr').each(function() {
        tr = $(this);
        var a = tr.find('a').last();
        var href = a.attr('href');
        var title = a.text();
        var desc = tr.find('span.menulinkdesctext').first().text().replace(/$\s*/, '');

        var row;
        if(fst){
            fst = false;
            row = table.rows[table.rows.length - 1];
        }else{
            fst = true;
            row = table.insertRow(-1);
        }
        var div = document.createElement('div');
        div.innerHTML = `<a class="list" href="${href}"> <div class="list-container"> <div class="list-title">${title}</div> <div class="list-description">${desc}</div> </div> </a>`;
        var cell = row.insertCell(0);
        cell.setAttribute('class', 'menu-cell');
        cell.appendChild(div);
    });
}

//load easier cc tool -> on_tool_loaded()
function load_tool(){
    var container = document.createElement('div');
    var cell = table.insertRow(0).insertCell(0);
    $(cell).attr('colspan', 2).html('

');
    $('#cceasier-tool').toggle().load(chrome.runtime.getURL('html/tool_div.html'), on_tool_loaded);
}

load_tool();

function download(filename, text) {
    var element = document.createElement('a');
    element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
    element.setAttribute('download', filename);

    element.style.display = 'none';
    document.body.appendChild(element);

    element.click();

    document.body.removeChild(element);
}

function show_modal_window(schedule) {
    //preprocess data
    {
        //sieve out duplicated courses
        var courses = $('#course_pool').val().split(',');
        var hash = {};
        $.each(courses, function () { hash[this] = true; });
        courses = [];
        $.each(hash, function (k, v) { courses.push(k); });

        var departs = {};
        $.each(courses, function () {
            var course_code = this;
            if (schedule[course_code] == null) return;
            var depart_code = course_code.slice(0, 2);
            if (departs[depart_code] == null) departs[depart_code] = [[], [], [], [], [], [], [], []];
            $.each(schedule[course_code], function () {
                var block = parseInt(this['block']);
                if (isNaN(block)) return;
                departs[depart_code][block - 1].push(this);
            });
        });
    }

    //if no valid courses
    if(Object.keys(departs).length == 0) {
        $('#course_pool').css('transition', '200ms');
        $('#course_pool').css('background-color', '#ffcdd2');
        setTimeout(function(){
            $('#course_pool').css('transition', '200ms');
            $('#course_pool').css('background-color', 'transparent');
        }, 200);
        return;
    }

    //build table, build export array
    {
        var modal_table = $('#result_modal_table')[0];
        modal_table.children[0].innerHTML = '';
        for (i = 0; i < 9; i++) modal_table.insertRow(-1);
        for (i = 1; i < 9; i++) {
            var cell = modal_table.rows[i].insertCell(-1);
            cell.innerText = i;
        }
        modal_table.rows[0].insertCell(-1);

        var export_arr = [[''],['1'],['2'],['3'],['4'],['5'],['6'],['7'],['8']];

        var depart2name = { "AN": "Anthropology", "AR": "Arabic", "AH": "Art History", "AS": "Art Studio", "PA": "Asian Studies", "BY": "Biology", "CH": "Chemistry", "CN": "Chinese Language", "CL": "Classics", "CO": "Comparative Literature", "CP": "Computer Science", "DS": "Dance Studio", "DA": "Dance Theory", "EC": "Economics", "ED": "Education", "EN": "English", "EV": "Environmental Program", "FG": "Feminist & Gender Studies", "FM": "Film and Media Studies", "FS": "Film Studies", "FR": "French", "GS": "General Studies", "GY": "Geology", "GR": "German", "HE": "Hebrew", "HY": "History", "HK": "Human Biology and Kinesiology", "IT": "Italian", "JA": "Japanese", "MA": "Mathematics", "MB": "Molecular Biology", "MU": "Music", "BE": "Organismal Biology and Ecology", "PH": "Philosophy", "PC": "Physics", "PS": "Political Science", "PG": "Portuguese", "PY": "Psychology", "RM": "Race, Ethnicity, and Migration", "RE": "Religion", "RU": "Russian", "RS": "Russian & Eurasian Studies", "SO": "Sociology", "SW": "Southwest Studies", "SP": "Spanish", "HS": "Studies in Humanities", "NS": "Studies in Natural Science", "TH": "Theatre" };
        $.each(departs, function (depart, blocks) {
            var cell = modal_table.rows[0].insertCell(-1);
            var depart_name = depart2name[depart];
            cell.innerText = depart_name;
            export_arr[0].push(depart_name);
            var max_len = Math.max(...$(blocks).map((_,x)=>x.length));
            for(i=1;i<max_len;i++) export_arr[0].push('');
            for (j = 0; j < 8; j++) {
                var cell = modal_table.rows[j + 1].insertCell(-1);
                cell.innerHTML = '

';
                var container_row = cell.children[0].children[0].children[0];
                $.each(blocks[j], function () {
                    export_arr[j+1].push(`${this['course_no']} (${this['available']}/${this['limit']}, ${this['waitlist']}, ${this['reserved']})`);
                    var card_container = container_row.insertCell(-1);
                    card_container.innerHTML = `<h3>${this['course_no']}<h3><br>${this['available']}/${this['limit']} (<small>${this['waitlist']}</small>, <small>${this['reserved']}</small>)`
                });
                for(i=blocks[j].length;i<max_len;i++) export_arr[j+1].push('');
            }
        });
    }
    //export
    {
        $('#btn_export').click(function(){
            csv = new CSV(export_arr).encode();
            download('course-schedule.csv', csv);
        });
    }
    $('[data-remodal-id=result_modal]').remodal().open();
}

function on_tool_loaded(){
    //create cc easer tool toggler
    {
        var container = document.createElement('div');
        var cell = table.insertRow(0).insertCell(0);
        $(cell).attr('colspan', 2).attr('class', 'menu-cell');
        cell.appendChild(container);
        container.innerHTML = `

Easier CC Banner Course Scheduler

Scheduling courses has never been easier.

 `;
        $(container.children[0]).click(function () {
            $('#cceasier-tool').slideToggle();
        });
    }

    //course scheduler style
    {
        $("#course_pool").focus(function () {
            $(".dropdown-menu").css('display', 'block');
            $(".dropdown-menu").addClass("hover-over-boxshadow");
            $("#course_pool").addClass("hover-over-boxshadow");
        });
        $("#course_pool").blur(function () {
            $(".dropdown-menu").css('display', 'none');
            $(".dropdown-menu").removeClass("hover-over-boxshadow");
            $("#course_pool").removeClass("hover-over-boxshadow");
        });
        var arrowImgUrl = chrome.runtime.getURL('img/arrow.png');
        $("#course_pool_confirm").append('');
    }

    //confirm button positioning
    $('#course_pool_confirm').css('left',($(window).width()*0.75-35-25).toString()+'px' );
    //console.log('b'+$(document.body).width());
    //console.log("before: "+($($(document.body).width()).toString()+'px'));
    //console.log( ($(document.body).width()*0.75-20-25).toString());

    $( window ).resize(function() {
        $('#course_pool_confirm').css('left',($(window).width()*0.75-35-25).toString()+'px' );
        console.log('a'+$(document.body).width());
        //console.log("after: "+($($(document.body).width()).toString()+'px'));
    });

    //transition & progress bar
    {
        $('#course_pool').css('background-color', '#b5b5b5 !important');
        var start_time = new Date().getTime();
        window.progressTimer = setInterval(function () {
            var t = (new Date().getTime() - start_time) / 1000;
            var p = 100 * (1 - Math.pow(0.85, t));
            $('#loading_bar').css('width', p + "%").css('width', '+=5px');
        }, 20);
        $('#cceasier-tool *').prop('disabled', true);
    }

    get_schedule(on_schedule_loaded);

    $(document.head).append(`<link rel="stylesheet" href="${chrome.runtime.getURL('style/main.css')}">`); //TODO: change injection
}

function on_schedule_loaded(schedule) {
    //transition & progress bar
    {
        clearInterval(window.progressTimer);
        setTimeout(function () {
            $('#loading_bar').css('width', '100%').css('width', '+=5px');
            $('#loading_bar').css('transition', '0.3s');
            $('#loading_bar').css('background-color', 'transparent');
            $('#course_pool').css('transition', '1s');
            $('#course_pool').css('background-color', 'transparent');
        }, 1000);
        $('#cceasier-tool *').prop('disabled', false);
    }

    //preprocess schedule data
    {
        var course_list = [];
        var instructor_sum = {};
        $.each(schedule, function () {
            var prof = '';
            var hash = {};
            $.each(schedule[this[0]['course_no']], function () {
                if (hash[this['instructor']] == null) {
                    prof += this['instructor'] + ', ';
                    hash[this['instructor']] = true;
                }
            });
            prof = prof.slice(0, -2);
            course_list.push({
                "course_code": this[0]["course_no"],
                "course_title": this[0]["course_title"],
                "instructor": prof
            });
            instructor_sum[this[0]['course_no']] = prof;
        });
    }

    var fuse = new Fuse(course_list, {
        shouldSort: true,
        threshold: 0.4,
        location: 0,
        distance: 100,
        maxPatternLength: 32,
        minMatchCharLength: 2,
        keys: [
            "course_code",
            "instructor",
            "course_title"]
    });

    new Textcomplete(new Textcomplete.editors.Textarea(document.getElementById('course_pool'))).register([
        {
            match: /(^|,)([^,]+)$/,
            search: function (term, callback) {
                var result = fuse.search(term);
                result = result.slice(0, 5);
                var match = [];
                $.each(result, function () {
                    match.push(this['course_code']);
                });
                callback(match);
            },
            template: function (course_code) {
                var title = schedule[course_code][0]['course_title'].replace(/(\[.*\])/g, '$1').replace(/(\(.*\))/g, '$1');
                return `<b>${course_code}</b> <small>(${instructor_sum[course_code]})</small><br>${title}`
            },
            replace: function (course_code) {
                return '$1' + course_code + ',';
            }
        }
    ]);

    //modal window event
    {
        $('#course_pool_confirm').click(function () { show_modal_window(schedule) });
        key('ctrl+enter', function () {
            if ($('#course_pool').is(':focus')) show_modal_window(schedule);
        });
        key.filter = ev => true;
    }
}

//remove the orignal menu
$(document.body.children[1]).remove();

<commit_msg>working on table style<commit_after>//check if need relogin
if(document.getElementById('UserID')){
    window.location.href = 'https://cas.coloradocollege.edu/cas/login?service=https%3A%2F%2Fbanssop.coloradocollege.edu%3A443%2Fssomanager%2Fc%2FSSB';
}

//create menu table
{
    var table = document.createElement('table');
    table.setAttribute('class', 'menu-table');
    var table_container = document.createElement('div');
    document.body.appendChild(table_container);
    table_container.appendChild(table);
    table.appendChild(document.createElement('tbody'));

    var fst = false;
    $('.menuplaintable').eq(0).find('tr').each(function() {
        tr = $(this);
        var a = tr.find('a').last();
        var href = a.attr('href');
        var title = a.text();
        var desc = tr.find('span.menulinkdesctext').first().text().replace(/$\s*/, '');

        var row;
        if(fst){
            fst = false;
            row = table.rows[table.rows.length - 1];
        }else{
            fst = true;
            row = table.insertRow(-1);
        }
        var div = document.createElement('div');
        div.innerHTML = `<a class="list" href="${href}"> <div class="list-container"> <div class="list-title">${title}</div> <div class="list-description">${desc}</div> </div> </a>`;
        var cell = row.insertCell(0);
        cell.setAttribute('class', 'menu-cell');
        cell.appendChild(div);
    });
}

//load easier cc tool -> on_tool_loaded()
function load_tool(){
    var container = document.createElement('div');
    var cell = table.insertRow(0).insertCell(0);
    $(cell).attr('colspan', 2).html('

');
    $('#cceasier-tool').toggle().load(chrome.runtime.getURL('html/tool_div.html'), on_tool_loaded);
}

load_tool();

function download(filename, text) {
    var element = document.createElement('a');
    element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
    element.setAttribute('download', filename);

    element.style.display = 'none';
    document.body.appendChild(element);

    element.click();

    document.body.removeChild(element);
}

function show_modal_window(schedule) {
    //preprocess data
    {
        //sieve out duplicated courses
        var courses = $('#course_pool').val().split(',');
        var hash = {};
        $.each(courses, function () { hash[this] = true; });
        courses = [];
        $.each(hash, function (k, v) { courses.push(k); });

        var departs = {};
        $.each(courses, function () {
            var course_code = this;
            if (schedule[course_code] == null) return;
            var depart_code = course_code.slice(0, 2);
            if (departs[depart_code] == null) departs[depart_code] = [[], [], [], [], [], [], [], []];
            $.each(schedule[course_code], function () {
                var block = parseInt(this['block']);
                if (isNaN(block)) return;
                departs[depart_code][block - 1].push(this);
            });
        });
    }

    //if no valid courses
    if(Object.keys(departs).length == 0) {
        $('#course_pool').css('transition', '200ms');
        $('#course_pool').css('background-color', '#ffcdd2');
        setTimeout(function(){
            $('#course_pool').css('transition', '200ms');
            $('#course_pool').css('background-color', 'transparent');
        }, 200);
        return;
    }

    //build table, build export array
    {
        var modal_table = $('#result_modal_table')[0];
        modal_table.children[0].innerHTML = '';
        for (i = 0; i < 9; i++) modal_table.insertRow(-1);
        for (i = 1; i < 9; i++) {
            var cell = modal_table.rows[i].insertCell(-1);
            cell.innerText = i;
        }
        modal_table.rows[0].insertCell(-1);

        var export_arr = [[''],['1'],['2'],['3'],['4'],['5'],['6'],['7'],['8']];

        var depart2name = { "AN": "Anthropology", "AR": "Arabic", "AH": "Art History", "AS": "Art Studio", "PA": "Asian Studies", "BY": "Biology", "CH": "Chemistry", "CN": "Chinese Language", "CL": "Classics", "CO": "Comparative Literature", "CP": "Computer Science", "DS": "Dance Studio", "DA": "Dance Theory", "EC": "Economics", "ED": "Education", "EN": "English", "EV": "Environmental Program", "FG": "Feminist & Gender Studies", "FM": "Film and Media Studies", "FS": "Film Studies", "FR": "French", "GS": "General Studies", "GY": "Geology", "GR": "German", "HE": "Hebrew", "HY": "History", "HK": "Human Biology and Kinesiology", "IT": "Italian", "JA": "Japanese", "MA": "Mathematics", "MB": "Molecular Biology", "MU": "Music", "BE": "Organismal Biology and Ecology", "PH": "Philosophy", "PC": "Physics", "PS": "Political Science", "PG": "Portuguese", "PY": "Psychology", "RM": "Race, Ethnicity, and Migration", "RE": "Religion", "RU": "Russian", "RS": "Russian & Eurasian Studies", "SO": "Sociology", "SW": "Southwest Studies", "SP": "Spanish", "HS": "Studies in Humanities", "NS": "Studies in Natural Science", "TH": "Theatre" };
        $.each(departs, function (depart, blocks) {
            var cell = modal_table.rows[0].insertCell(-1);
            var depart_name = depart2name[depart];
            cell.innerText = depart_name;
            export_arr[0].push(depart_name);
            var max_len = Math.max(...$(blocks).map((_,x)=>x.length));
            for(i=1;i<max_len;i++) export_arr[0].push('');
            for (j = 0; j < 8; j++) {
                var cell = modal_table.rows[j + 1].insertCell(-1);
                cell.innerHTML = '

';
                var container_row = cell.children[0].children[0].children[0];
                $.each(blocks[j], function () {
                    export_arr[j+1].push(`${this['course_no']} (${this['available']}/${this['limit']}, ${this['waitlist']}, ${this['reserved']})`);
                    var card_container = container_row.insertCell(-1);
                    card_container.innerHTML = `<h3>${this['course_no']}<h3><br>${this['available']}/${this['limit']} (<small>${this['waitlist']}</small>, <small>${this['reserved']}</small>)`
                });
                for(i=blocks[j].length;i<max_len;i++) export_arr[j+1].push('');
            }
        });
    }

    //modal table style
    $('#result_modal_table').css('width','100%');
    var modalTableTrs = $('#result_modal_table').find('tr');
    console.log(modalTableTrs);
    var modalTableTds = modalTableTrs.find('td');
    $(modalTableTrs[0]).css('height','auto');
    $(modalTableTds[0]).css('width','5%');
    for(var i=0; i<(modalTableTds.length-modalTableTds.length)/modalTableTrs.length; i++)
    {
        var x = (modalTableTds.length-modalTableTds.length)/modalTableTds.length;
        $(modalTableTds[i+1]).css('width',(x-5/(modalTableTds.length-modalTableTds.length)/modalTableTrs.length).toString()+'%');
    }

    //export
    {
        $('#btn_export').click(function(){
            csv = new CSV(export_arr).encode();
            download('course-schedule.csv', csv);
        });
    }
    $('[data-remodal-id=result_modal]').remodal().open();
}

function on_tool_loaded(){
    //create cc easer tool toggler
    {
        var container = document.createElement('div');
        var cell = table.insertRow(0).insertCell(0);
        $(cell).attr('colspan', 2).attr('class', 'menu-cell');
        cell.appendChild(container);
        container.innerHTML = `

Easier CC Banner Course Scheduler

Scheduling courses has never been easier.

 `;
        $(container.children[0]).click(function () {
            $('#cceasier-tool').slideToggle();
        });
    }

    //course scheduler style
    {
        $("#course_pool").focus(function () {
            $(".dropdown-menu").css('display', 'block');
            $(".dropdown-menu").addClass("hover-over-boxshadow");
            $("#course_pool").addClass("hover-over-boxshadow");
        });
        $("#course_pool").blur(function () {
            $(".dropdown-menu").css('display', 'none');
            $(".dropdown-menu").removeClass("hover-over-boxshadow");
            $("#course_pool").removeClass("hover-over-boxshadow");
        });
        var arrowImgUrl = chrome.runtime.getURL('img/arrow.png');
        $("#course_pool_confirm").append('');
    }

    //confirm button positioning
    $('#course_pool_confirm').css('left',($(window).width()*0.75-35-25).toString()+'px' );
    $( window ).resize(function() {
        $('#course_pool_confirm').css('left',($(window).width()*0.75-35-25).toString()+'px' );
    });

    //transition & progress bar
    {
        $('#course_pool').css('background-color', '#b5b5b5 !important');
        var start_time = new Date().getTime();
        window.progressTimer = setInterval(function () {
            var t = (new Date().getTime() - start_time) / 1000;
            var p = 100 * (1 - Math.pow(0.85, t));
            $('#loading_bar').css('width', p + "%").css('width', '+=5px');
        }, 20);
        $('#cceasier-tool *').prop('disabled', true);
    }

    get_schedule(on_schedule_loaded);

    $(document.head).append(`<link rel="stylesheet" href="${chrome.runtime.getURL('style/main.css')}">`); //TODO: change injection
}

function on_schedule_loaded(schedule) {
    //transition & progress bar
    {
        clearInterval(window.progressTimer);
        setTimeout(function () {
            $('#loading_bar').css('width', '100%').css('width', '+=5px');
            $('#loading_bar').css('transition', '0.3s');
            $('#loading_bar').css('background-color', 'transparent');
            $('#course_pool').css('transition', '1s');
            $('#course_pool').css('background-color', 'transparent');
        }, 1000);
        $('#cceasier-tool *').prop('disabled', false);
    }

    //preprocess schedule data
    {
        var course_list = [];
        var instructor_sum = {};
        $.each(schedule, function () {
            var prof = '';
            var hash = {};
            $.each(schedule[this[0]['course_no']], function () {
                if (hash[this['instructor']] == null) {
                    prof += this['instructor'] + ', ';
                    hash[this['instructor']] = true;
                }
            });
            prof = prof.slice(0, -2);
            course_list.push({
                "course_code": this[0]["course_no"],
                "course_title": this[0]["course_title"],
                "instructor": prof
            });
            instructor_sum[this[0]['course_no']] = prof;
        });
    }

    var fuse = new Fuse(course_list, {
        shouldSort: true,
        threshold: 0.4,
        location: 0,
        distance: 100,
        maxPatternLength: 32,
        minMatchCharLength: 2,
        keys: [
            "course_code",
            "instructor",
            "course_title"]
    });

    new Textcomplete(new Textcomplete.editors.Textarea(document.getElementById('course_pool'))).register([
        {
            match: /(^|,)([^,]+)$/,
            search: function (term, callback) {
                var result = fuse.search(term);
                result = result.slice(0, 5);
                var match = [];
                $.each(result, function () {
                    match.push(this['course_code']);
                });
                callback(match);
            },
            template: function (course_code) {
                var title = schedule[course_code][0]['course_title'].replace(/(\[.*\])/g, '$1').replace(/(\(.*\))/g, '$1');
                return `<b>${course_code}</b> <small>(${instructor_sum[course_code]})</small><br>${title}`
            },
            replace: function (course_code) {
                return '$1' + course_code + ',';
            }
        }
    ]);

    //modal window event
    {
        $('#course_pool_confirm').click(function () { show_modal_window(schedule) });
        key('ctrl+enter', function () {
            if ($('#course_pool').is(':focus')) show_modal_window(schedule);
        });
        key.filter = ev => true;
    }

}

//remove the orignal menu
$(document.body.children[1]).remove();

<|endoftext|>


<commit_before>var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res) {
  //res.render('public/index.html');
  res.render('index', {
  	'title': 'Low Pass'
  });
});

module.exports = router;
<commit_msg>statically serve index<commit_after>var express = require('express');
var router = express.Router();

/* GET home page. */
router.get('/', function(req, res) {
  res.sendfile('index.html', {root: './public'});
  /*
  res.render('index', {
  	'title': 'Low Pass'
  });
  */
});

module.exports = router;
<|endoftext|>


<commit_before>this.recline = this.recline || {};
this.recline.Backend = this.recline.Backend || {};

(function($, my) {
  // ## DataProxy Backend
  //
  // For connecting to [DataProxy-s](http://github.com/okfn/dataproxy).
  //
  // When initializing the DataProxy backend you can set the following attributes:
  //
  // * dataproxy: {url-to-proxy} (optional). Defaults to http://jsonpdataproxy.appspot.com
  //
  // Datasets using using this backend should set the following attributes:
  //
  // * url: (required) url-of-data-to-proxy
  // * format: (optional) csv | xls (defaults to csv if not specified)
  //
  // Note that this is a **read-only** backend.
  my.DataProxy = Backbone.Model.extend({
    defaults: {
      dataproxy_url: 'http://jsonpdataproxy.appspot.com'
    },
    sync: function(method, model, options) {
      var self = this;
      if (method === "read") {
        if (model.__type__ == 'Dataset') {
          var base = self.get('dataproxy_url');
          // TODO: should we cache for extra efficiency
          var data = {
            url: model.get('url')
            , 'max-results':  1
            , type: model.get('format') || 'csv'
          };
          var jqxhr = $.ajax({
            url: base
            , data: data
            , dataType: 'jsonp'
          });
          var dfd = $.Deferred();
          my.wrapInTimeout(jqxhr).done(function(results) {
            model.fields.reset(_.map(results.fields, function(fieldId) {
              return {id: fieldId};
              })
            );
            dfd.resolve(model, jqxhr);
          })
          .fail(function(arguments) {
            dfd.reject(arguments);
          });
          return dfd.promise();
        }
      } else {
        alert('This backend only supports read operations');
      }
    },
    query: function(dataset, queryObj) {
      var base = this.get('dataproxy_url');
      var data = {
        url: dataset.get('url')
        , 'max-results':  queryObj.size
        , type: dataset.get('format')
      };
      var jqxhr = $.ajax({
        url: base
        , data: data
        , dataType: 'jsonp'
      });
      var dfd = $.Deferred();
      jqxhr.done(function(results) {
        var _out = _.map(results.data, function(doc) {
          var tmp = {};
          _.each(results.fields, function(key, idx) {
            tmp[key] = doc[idx];
          });
          return tmp;
        });
        dfd.resolve(_out);
      });
      return dfd.promise();
    }
  });
  recline.Model.backends['dataproxy'] = new my.DataProxy();

}(jQuery, this.recline.Backend));
<commit_msg>[backend/dataproxy][s]: simplify by not bothering to query proxy on Dataset fetch and instead just set fields as part of query.<commit_after>this.recline = this.recline || {};
this.recline.Backend = this.recline.Backend || {};

(function($, my) {
  // ## DataProxy Backend
  //
  // For connecting to [DataProxy-s](http://github.com/okfn/dataproxy).
  //
  // When initializing the DataProxy backend you can set the following attributes:
  //
  // * dataproxy: {url-to-proxy} (optional). Defaults to http://jsonpdataproxy.appspot.com
  //
  // Datasets using using this backend should set the following attributes:
  //
  // * url: (required) url-of-data-to-proxy
  // * format: (optional) csv | xls (defaults to csv if not specified)
  //
  // Note that this is a **read-only** backend.
  my.DataProxy = Backbone.Model.extend({
    defaults: {
      dataproxy_url: 'http://jsonpdataproxy.appspot.com'
    },
    sync: function(method, model, options) {
      var self = this;
      if (method === "read") {
        if (model.__type__ == 'Dataset') {
          // Do nothing as we will get fields in query step (and no metadata to
          // retrieve)
          var dfd = $.Deferred();
          dfd.resolve(model);
          return dfd.promise();
        }
      } else {
        alert('This backend only supports read operations');
      }
    },
    query: function(dataset, queryObj) {
      var base = this.get('dataproxy_url');
      var data = {
        url: dataset.get('url')
        , 'max-results':  queryObj.size
        , type: dataset.get('format')
      };
      var jqxhr = $.ajax({
        url: base
        , data: data
        , dataType: 'jsonp'
      });
      var dfd = $.Deferred();
      my.wrapInTimeout(jqxhr).done(function(results) {
        dataset.fields.reset(_.map(results.fields, function(fieldId) {
          return {id: fieldId};
          })
        );
        var _out = _.map(results.data, function(doc) {
          var tmp = {};
          _.each(results.fields, function(key, idx) {
            tmp[key] = doc[idx];
          });
          return tmp;
        });
        dfd.resolve(_out);
      })
      .fail(function(arguments) {
        dfd.reject(arguments);
      });
      return dfd.promise();
    }
  });
  recline.Model.backends['dataproxy'] = new my.DataProxy();

}(jQuery, this.recline.Backend));
<|endoftext|>


<commit_before>import path from "path";
import config from "config";

import NarrowsStore from "./NarrowsStore";

const store = new NarrowsStore(config.db.path, config.files.path);
store.connect();

export function getNarration(req, res) {
    const narrationId = parseInt(req.params.narrId, 10);

    store.getNarration(narrationId).then(narrationData => {
        res.json(narrationData);
    }).catch(err => {
        res.statusCode = 404;
        res.json({ errorMessage: "Cannot find narration " + narrationId });
    });
}

export function getFragment(req, res) {
    const fragmentId = parseInt(req.params.fgmtId, 10);

    store.getFragment(fragmentId).then(fragmentData => {
        res.json(fragmentData);
    }).catch(err => {
        res.statusCode = 404;
        res.json({
            errorMessage: `Cannot find fragment ${ fragmentId }: ${ err }`
        });
    });
}

export function getNarrationFragments(req, res) {
    const narrationId = parseInt(req.params.narrId, 10);

    store.getNarrationFragments(narrationId).then(fragmentListData => {
        res.json({ fragments: fragmentListData });
    }).catch(err => {
        res.statusCode = 404;
        res.json({
            errorMessage: `Cannot find fragments for narration ${ narrationId }: ${ err }`
        });
    });
}

export function getFragmentCharacter(req, res) {
    return getFragment(req, res);
}

export function postFragment(req, res) {
    const fragmentId = parseInt(req.params.fgmtId, 10);

    req.body.text = JSON.stringify(req.body.text);
    store.updateFragment(fragmentId, req.body).then(fragment => {
        res.send(fragment);
    }).catch(err => {
        res.send("There was a problem updating: " + err);
    });
}

export function postNewFragment(req, res) {
    const narrationId = parseInt(req.params.narrId, 10);

    req.body.text = JSON.stringify(req.body.text);
    store.createFragment(narrationId, req.body).then(fragmentData => {
        res.json(fragmentData);
    }).catch(err => {
        res.send("There was a problem: " + err);
    });
}

export function getStaticFile(req, res) {
    res.sendFile(path.join(req.params.narrId, req.params.filename),
                 { root: config.files.path });
}

export function postReaction(req, res) {
    const fragmentId = req.params.fgmtId,
          characterToken = req.params.charToken,
          reactionText = req.body.text;

    store.getCharacterId(characterToken).then(characterId => {
        store.saveReaction(fragmentId, characterId, reactionText).then(() => {
            res.json({ fragmentId, characterId, reactionText });
        });
    }).catch(err => {
        res.json({ errorMessage: "Could not save reaction: " + err});
    });
}
<commit_msg>Prevent non-participants from reading fragments<commit_after>import path from "path";
import config from "config";

import NarrowsStore from "./NarrowsStore";

const store = new NarrowsStore(config.db.path, config.files.path);
store.connect();

export function getNarration(req, res) {
    const narrationId = parseInt(req.params.narrId, 10);

    store.getNarration(narrationId).then(narrationData => {
        res.json(narrationData);
    }).catch(err => {
        res.statusCode = 404;
        res.json({ errorMessage: "Cannot find narration " + narrationId });
    });
}

export function getFragment(req, res) {
    const fragmentId = parseInt(req.params.fgmtId, 10);

    store.getFragment(fragmentId).then(fragmentData => {
        res.json(fragmentData);
    }).catch(err => {
        res.statusCode = 404;
        res.json({
            errorMessage: `Cannot find fragment ${ fragmentId }: ${ err }`
        });
    });
}

export function getFragmentCharacter(req, res) {
    const fragmentId = parseInt(req.params.fgmtId, 10);
    const characterToken = req.params.charToken;

    store.getCharacterId(characterToken).then(characterId => {
        return store.getFragment(fragmentId).then(fragmentData => {
            const participantIds = fragmentData.participants.map(p => p.id);
            if (participantIds.indexOf(characterId) === -1) {
                throw new Error("Character does not participate in fragment");
            }
            res.json(fragmentData);
        });
    }).catch(err => {
        res.statusCode = 404;
        res.json({
            errorMessage: `Cannot find fragment ${ fragmentId }` +
                ` with character ${ characterToken }`
        });
    });
}

export function getNarrationFragments(req, res) {
    const narrationId = parseInt(req.params.narrId, 10);

    store.getNarrationFragments(narrationId).then(fragmentListData => {
        res.json({ fragments: fragmentListData });
    }).catch(err => {
        res.statusCode = 404;
        res.json({
            errorMessage: `Cannot find fragments for narration ${ narrationId }: ${ err }`
        });
    });
}

export function postFragment(req, res) {
    const fragmentId = parseInt(req.params.fgmtId, 10);

    req.body.text = JSON.stringify(req.body.text);
    store.updateFragment(fragmentId, req.body).then(fragment => {
        res.send(fragment);
    }).catch(err => {
        res.send("There was a problem updating: " + err);
    });
}

export function postNewFragment(req, res) {
    const narrationId = parseInt(req.params.narrId, 10);

    req.body.text = JSON.stringify(req.body.text);
    store.createFragment(narrationId, req.body).then(fragmentData => {
        res.json(fragmentData);
    }).catch(err => {
        res.send("There was a problem: " + err);
    });
}

export function getStaticFile(req, res) {
    res.sendFile(path.join(req.params.narrId, req.params.filename),
                 { root: config.files.path });
}

export function postReaction(req, res) {
    const fragmentId = req.params.fgmtId,
          characterToken = req.params.charToken,
          reactionText = req.body.text;

    store.getCharacterId(characterToken).then(characterId => {
        store.saveReaction(fragmentId, characterId, reactionText).then(() => {
            res.json({ fragmentId, characterId, reactionText });
        });
    }).catch(err => {
        res.json({ errorMessage: "Could not save reaction: " + err});
    });
}
<|endoftext|>


<commit_before>var TablesortCore = require('tablesort')

// Basic dates in dd/mm/yy or dd-mm-yy format.
// Years can be 4 digits. Days and Months can be 1 or 2 digits.
{
  let parseDate = function (date) {
    date = date.replace(/\-/g, '/')
    date = date.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2}) .*/, '$1/$2/$3') // format before getTime

    return new Date(date).getTime() || -1
  }

  TablesortCore.extend('date', function (item) {
    return (
      item.search(/(Mon|Tue|Wed|Thu|Fri|Sat|Sun)\.?,?\s*/i) !== -1 ||
      item.search(/\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}/) !== -1 ||
      item.search(/(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)/i) !== -1
    ) && !isNaN(parseDate(item))
  }, function (a, b) {
    a = a.toLowerCase()
    b = b.toLowerCase()

    return parseDate(b) - parseDate(a)
  })
}

// Dot separated values. E.g. IP addresses or version numbers.
TablesortCore.extend('dotsep', function (item) {
  return /^(\d+\.)+\d+$/.test(item)
}, function (a, b) {
  a = a.split('.')
  b = b.split('.')

  for (var i = 0, len = a.length, ai, bi; i < len; i++) {
    ai = parseInt(a[i], 10)
    bi = parseInt(b[i], 10)

    if (ai === bi) continue
    if (ai > bi) return -1
    if (ai < bi) return 1
  }

  return 0
})

// Filesizes. e.g. '5.35 K', '10 MB', '12.45 GB', or '4.67 TiB'
{
  let compareNumber = function (a, b) {
    a = parseFloat(a)
    b = parseFloat(b)

    a = isNaN(a) ? 0 : a
    b = isNaN(b) ? 0 : b

    return a - b
  }

  let cleanNumber = function (i) {
    return i.replace(/[^\-?0-9.]/g, '')
  }

  // Returns suffix multiplier
  // Ex. suffix2num('KB') -> 1000
  // Ex. suffix2num('KiB') -> 1024
  let suffix2num = function (suffix) {
    suffix = suffix.toLowerCase()
    var base = suffix[1] === 'i' ? 1024 : 1000

    switch (suffix[0]) {
      case 'k':
        return Math.pow(base, 2)
      case 'm':
        return Math.pow(base, 3)
      case 'g':
        return Math.pow(base, 4)
      case 't':
        return Math.pow(base, 5)
      case 'p':
        return Math.pow(base, 6)
      case 'e':
        return Math.pow(base, 7)
      case 'z':
        return Math.pow(base, 8)
      case 'y':
        return Math.pow(base, 9)
      default:
        return base
    }
  }

  // Converts filesize to bytes
  // Ex. filesize2num('123 KB') -> 123000
  // Ex. filesize2num('123 KiB') -> 125952
  let filesize2num = function (filesize) {
    let matches = filesize.match(/^(\d+(\.\d+)?) ?((K|M|G|T|P|E|Z|Y|B$)i?B?)$/i)

    let num = parseFloat(cleanNumber(matches[1]))
    let suffix = matches[3]

    return num * suffix2num(suffix)
  }

  TablesortCore.extend('filesize', function (item) {
    return /^\d+(\.\d+)? ?(K|M|G|T|P|E|Z|Y|B$)i?B?$/i.test(item)
  }, function (a, b) {
    a = filesize2num(a)
    b = filesize2num(b)

    return compareNumber(b, a)
  })
}

TablesortCore.extend('monthname', function (item) {
  return (
    item.search(/(January|February|March|April|May|June|July|August|September|October|November|December)/i) !== -1
  )
}, function (a, b) {
  var monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
  return monthNames.indexOf(b) - monthNames.indexOf(a)
})

{
  let cleanNumber = function (i) {
    return i.replace(/[^\-?0-9.]/g, '')
  }

  let compareNumber = function (a, b) {
    a = parseFloat(a)
    b = parseFloat(b)

    a = isNaN(a) ? 0 : a
    b = isNaN(b) ? 0 : b

    return a - b
  }

  TablesortCore.extend('number', function (item) {
    return item.match(/^-?[£\x24Û¢´€]?\d+\s*([,\.]\d{0,2})/) || // Prefixed currency
      item.match(/^-?\d+\s*([,\.]\d{0,2})?[£\x24Û¢´€]/) || // Suffixed currency
      item.match(/^-?(\d)*-?([,\.]){0,1}-?(\d)+([E,e][\-+][\d]+)?%?$/) // Number
  }, function (a, b) {
    a = cleanNumber(a)
    b = cleanNumber(b)

    return compareNumber(b, a)
  })
}

module.exports = TablesortCore
<commit_msg>kill some duplication<commit_after>var TablesortCore = require('tablesort')

{
  let cleanNumber = function (i) {
    return i.replace(/[^\-?0-9.]/g, '')
  }

  let compareNumber = function (a, b) {
    a = parseFloat(a)
    b = parseFloat(b)

    a = isNaN(a) ? 0 : a
    b = isNaN(b) ? 0 : b

    return a - b
  }

  // Basic dates in dd/mm/yy or dd-mm-yy format.
  // Years can be 4 digits. Days and Months can be 1 or 2 digits.

  let parseDate = function (date) {
    date = date.replace(/\-/g, '/')
    date = date.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2}) .*/, '$1/$2/$3') // format before getTime

    return new Date(date).getTime() || -1
  }

  TablesortCore.extend('date', function (item) {
    return (
      item.search(/(Mon|Tue|Wed|Thu|Fri|Sat|Sun)\.?,?\s*/i) !== -1 ||
      item.search(/\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}/) !== -1 ||
      item.search(/(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)/i) !== -1
    ) && !isNaN(parseDate(item))
  }, function (a, b) {
    a = a.toLowerCase()
    b = b.toLowerCase()

    return parseDate(b) - parseDate(a)
  })

  // Dot separated values. E.g. IP addresses or version numbers.
  TablesortCore.extend('dotsep', function (item) {
    return /^(\d+\.)+\d+$/.test(item)
  }, function (a, b) {
    a = a.split('.')
    b = b.split('.')

    for (var i = 0, len = a.length, ai, bi; i < len; i++) {
      ai = parseInt(a[i], 10)
      bi = parseInt(b[i], 10)

      if (ai === bi) continue
      if (ai > bi) return -1
      if (ai < bi) return 1
    }

    return 0
  })

  // Filesizes. e.g. '5.35 K', '10 MB', '12.45 GB', or '4.67 TiB'
  // Returns suffix multiplier
  // Ex. suffix2num('KB') -> 1000
  // Ex. suffix2num('KiB') -> 1024
  let suffix2num = function (suffix) {
    suffix = suffix.toLowerCase()
    var base = suffix[1] === 'i' ? 1024 : 1000

    switch (suffix[0]) {
      case 'k':
        return Math.pow(base, 2)
      case 'm':
        return Math.pow(base, 3)
      case 'g':
        return Math.pow(base, 4)
      case 't':
        return Math.pow(base, 5)
      case 'p':
        return Math.pow(base, 6)
      case 'e':
        return Math.pow(base, 7)
      case 'z':
        return Math.pow(base, 8)
      case 'y':
        return Math.pow(base, 9)
      default:
        return base
    }
  }

  // Converts filesize to bytes
  // Ex. filesize2num('123 KB') -> 123000
  // Ex. filesize2num('123 KiB') -> 125952
  let filesize2num = function (filesize) {
    let matches = filesize.match(/^(\d+(\.\d+)?) ?((K|M|G|T|P|E|Z|Y|B$)i?B?)$/i)

    let num = parseFloat(cleanNumber(matches[1]))
    let suffix = matches[3]

    return num * suffix2num(suffix)
  }

  TablesortCore.extend('filesize', function (item) {
    return /^\d+(\.\d+)? ?(K|M|G|T|P|E|Z|Y|B$)i?B?$/i.test(item)
  }, function (a, b) {
    a = filesize2num(a)
    b = filesize2num(b)

    return compareNumber(b, a)
  })

  TablesortCore.extend('monthname', function (item) {
    return (
      item.search(/(January|February|March|April|May|June|July|August|September|October|November|December)/i) !== -1
    )
  }, function (a, b) {
    var monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
    return monthNames.indexOf(b) - monthNames.indexOf(a)
  })

  TablesortCore.extend('number', function (item) {
    return item.match(/^-?[£\x24Û¢´€]?\d+\s*([,\.]\d{0,2})/) || // Prefixed currency
      item.match(/^-?\d+\s*([,\.]\d{0,2})?[£\x24Û¢´€]/) || // Suffixed currency
      item.match(/^-?(\d)*-?([,\.]){0,1}-?(\d)+([E,e][\-+][\d]+)?%?$/) // Number
  }, function (a, b) {
    a = cleanNumber(a)
    b = cleanNumber(b)

    return compareNumber(b, a)
  })
}

module.exports = TablesortCore
<|endoftext|>


<commit_before>/*******************************************************************************
 * @license
 * Copyright (c) 2012 VMware, Inc. All Rights Reserved.
 * THIS FILE IS PROVIDED UNDER THE TERMS OF THE ECLIPSE PUBLIC LICENSE
 * ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS FILE
 * CONSTITUTES RECIPIENTS ACCEPTANCE OF THE AGREEMENT.
 * You can obtain a current copy of the Eclipse Public License from
 * http://www.opensource.org/licenses/eclipse-1.0.php
 *
 * Contributors:
 *     Kris De Volder - initial API and implementation
 ******************************************************************************/

/*global $*/

///////////////////////////////////////////////////////////////////////////////////
// This module is supposed to provide a console-like api for displaying messages.
// as well as some basic operations to reveal/hide the console.

//TODO: We are now also using this console to display error messages. For example
// when problems parsing .scripted file.

//So 'exec-console' is not a good name for this module.

define(function (require) {

	var editorUtils = require('scripted/utils/editorUtils');
	require('jquery');
	require('jquery_ui');

	/**
	 * Maximum number of log entries in the console. If more entries are
	 * added then the oldest entries are automatically deleted.
	 */
	var MAX_ENTRIES = 10;

	/**
	 * The id of the dom element to which we append console output.
	 */
	var CONSOLE_DISPLAY = "#console_display";

	/**
	 * The id of the dom element that represents the entire console UI.
	 */
	var CONSOLE_WRAPPER = "#console_wrapper";

	var initialized = false;

	//This regexp defines what text snippets should be turned into links.
	//This regexp may need some more tweaking
	var urlRegexp = "http(s)?://[a-zA-Z,0-9\\.@:%_\\+~#=]+";

	/**
	 * A helper method that creates a dom element to display a given msg.
	 * Displays mostly plain text but create links for things that look
	 * like urls like http://blah.foo.com.
	 */
	function render(msg, cssClass) {

		function addText(elem, text) {
			if (text) {
				var textContent = document.createTextNode(text);
				elem.appendChild(textContent);
			}
		}

		function addLink(elem, url) {
			var link = document.createElement('a');
			link.href = url;
			link.setAttribute('target','_blank'); //In new tab/window
			addText(link, url);
			elem.appendChild(link);
		}

		var regexp = new RegExp(urlRegexp, 'g');
		var elem = document.createElement('div');
		if (cssClass) {
			elem.setAttribute('class', cssClass);
		}
		var index = 0; // start index of the 'unprocessed' part of the message
		while (index < msg.length) {
			var match = regexp.exec(msg);
			if (!match) {
				//no more urls in the msg. So plunk all remaining text into the node
				addText(elem, msg.substring(index));
				index = msg.length;
			} else {
				//First pick up any text before the url...
				addText(elem, msg.substring(index, match.index));
				index += match.index;

				//Now add a link for the found url
				var url = match[0];
				addLink(elem, url);
				index += url.length;
			}
		}
		return elem;
	}

	///// Managing the entries in the log (so we can limit their number) //////////////

	var entries = []; //Array with at most MAX_ENTRIES entries.
	var newEntryPos = 0; // next place to put an element, cycles around if running past the end.

	function addEntry(e) {
		var oldest = entries[newEntryPos];
		if (oldest) {
			oldest.remove();
		}
		entries[newEntryPos] = e;
		newEntryPos = (newEntryPos+1) % MAX_ENTRIES;
	}

	function clear() {
		for (var i = 0; i < entries.length; i++) {
			var e = entries[i];
			if (e) {
				//This doesn't work on FireFox: e.remove();
				e.parentNode.removeChild(e);
				entries[i] = null;
			}
		}
	}

	//////////////////// Public API ///////////////////////////////////////////////////

	/**
	 * Change the width and vertical position of the console UI to line up properly with
	 * the editor.
	 */
	function updateWidth() {
		var c = $(CONSOLE_WRAPPER);
		var e = $("#editor");
		function copyCss() {
			for (var i = 0; i < arguments.length; i++) {
				var name = arguments[i];
				c.css(name, e.css(name));
			}
		}
		copyCss('margin-left', 'margin-right', 'width');
	}

	/**
	 * This method should be called by the editor setup at a good time (i.e. when editor has
	 * mostly been setup.
	 */
	function initialize() {
		if (!initialized) {
			initialized = true;
			$(CONSOLE_WRAPPER).resizable({
				handles: "n"
			});
			$(CONSOLE_WRAPPER).resize(function (event, ui) {
				var console_height = ui.size.height;
				var breadcrumb_height = $('#breadcrumb').outerHeight();
				var editor_height = $('#main').height() - breadcrumb_height - console_height;
				$(CONSOLE_WRAPPER).css('top', '0px'); //I think JQuery resizable is changing this from 0 but it messes things up.
				$(CONSOLE_WRAPPER).height(console_height);
				$("#editor").height(editor_height);
				editorUtils.getMainEditor().getTextView()._update();
				updateWidth();
			});
			$("#navigator-wrapper").resize(updateWidth);
			$("#side_panel").resize(updateWidth);
		}
	}

	function isVisible() {
		return $(CONSOLE_WRAPPER).css('display')!=='none';
	}

	function show() {
		initialize();
		var c = $(CONSOLE_WRAPPER);
		var e = $("#editor");
		if (!isVisible()) {
			//TODO: should remember previous size not reset to 1/3 of the screen.
			//If the console is presently hidden...
			var editor_height = e.height();
			var console_height = $(CONSOLE_WRAPPER).height() || (editor_height / 3);
			editor_height = editor_height - console_height;

			var overhead = c.outerHeight() - c.height();

			c.css('display', 'block');
			c.height(console_height-overhead);
			e.height(editor_height);
			c.resizable({
				disabled: false
			});

			editorUtils.getMainEditor().getTextView()._update();
			updateWidth();
		}
	}

	function hide() {
		if (isVisible()) {
			var c = $(CONSOLE_WRAPPER);
			var e = $("#editor");
			var console_height = c.outerHeight();
			c.css('display', 'none');
			c.resizable({
				disabled: true
			});
			e.height(e.height()+console_height);
			editorUtils.getMainEditor().getTextView()._update();
		}
	}

	function toggle() {
		if (isVisible()) {
			hide();
		} else {
			show();
		}
	}

	function scrollToBottom() {
		var c = $(CONSOLE_DISPLAY);
		var scrollH = c.prop("scrollHeight");
		var H = c.height();
		if (H < scrollH) {
			c.scrollTop(scrollH-H);
		}
	}

	/**
	 * Append a message to the console.
	 * An optional cssClass parameter allows tagging the
	 * element in the view with a specific cssClass. (so that it's
	 * look can be customized via css).
	 *
	 * @param String msg
	 * @param String cssClass
	 */
	function log(msg, cssClass) {
		cssClass = cssClass || 'log';
		show();
		var e = render(msg, cssClass);
		$(CONSOLE_DISPLAY).append(e);
		addEntry(e);
		scrollToBottom();
	}

	$(document).ready(function () {
		$('#console_toggle').on('click', toggle);
		$('#side_panel').bind('open', updateWidth);
		$('#side_panel').bind('close', updateWidth);
		$(window).resize(updateWidth);
	});

	return {
		clear: clear,
		log: log,
		error: function (msg) {
			log(msg, 'error');
		}
	};

});

<commit_msg>Fix #252: exec-console dropping output after parsing urls.<commit_after>/*******************************************************************************
 * @license
 * Copyright (c) 2012 VMware, Inc. All Rights Reserved.
 * THIS FILE IS PROVIDED UNDER THE TERMS OF THE ECLIPSE PUBLIC LICENSE
 * ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THIS FILE
 * CONSTITUTES RECIPIENTS ACCEPTANCE OF THE AGREEMENT.
 * You can obtain a current copy of the Eclipse Public License from
 * http://www.opensource.org/licenses/eclipse-1.0.php
 *
 * Contributors:
 *     Kris De Volder - initial API and implementation
 ******************************************************************************/

/*global $*/

///////////////////////////////////////////////////////////////////////////////////
// This module is supposed to provide a console-like api for displaying messages.
// as well as some basic operations to reveal/hide the console.

//TODO: We are now also using this console to display error messages. For example
// when problems parsing .scripted file.

//So 'exec-console' is not a good name for this module.

define(function (require) {

	var editorUtils = require('scripted/utils/editorUtils');
	require('jquery');
	require('jquery_ui');

	/**
	 * Maximum number of log entries in the console. If more entries are
	 * added then the oldest entries are automatically deleted.
	 */
	var MAX_ENTRIES = 10;

	/**
	 * The id of the dom element to which we append console output.
	 */
	var CONSOLE_DISPLAY = "#console_display";

	/**
	 * The id of the dom element that represents the entire console UI.
	 */
	var CONSOLE_WRAPPER = "#console_wrapper";

	var initialized = false;

	//This regexp defines what text snippets should be turned into links.
	//This regexp may need some more tweaking
	var urlRegexp = "http(s)?://[a-zA-Z,0-9\\.@:%_\\+~#=/\\-]+";

	/**
	 * A helper method that creates a dom element to display a given msg.
	 * Displays mostly plain text but create links for things that look
	 * like urls like http://blah.foo.com.
	 */
	function render(msg, cssClass) {

		function addText(elem, text) {
			if (text) {
				var textContent = document.createTextNode(text);
				elem.appendChild(textContent);
			}
		}

		function addLink(elem, url) {
			var link = document.createElement('a');
			link.href = url;
			link.setAttribute('target','_blank'); //In new tab/window
			addText(link, url);
			elem.appendChild(link);
		}

		var regexp = new RegExp(urlRegexp, 'g');
		var elem = document.createElement('div');
		if (cssClass) {
			elem.setAttribute('class', cssClass);
		}
		var index = 0; // start index of the 'unprocessed' part of the message
		while (index < msg.length) {
			var match = regexp.exec(msg);
			if (!match) {
				//no more urls in the msg. So plunk all remaining text into the node
				addText(elem, msg.substring(index));
				index = msg.length;
			} else {
				//First pick up any text before the url...
				addText(elem, msg.substring(index, match.index));
				index = match.index;

				//Now add a link for the found url
				var url = match[0];
				addLink(elem, url);
				index += url.length;
			}
		}
		return elem;
	}

	///// Managing the entries in the log (so we can limit their number) //////////////

	var entries = []; //Array with at most MAX_ENTRIES entries.
	var newEntryPos = 0; // next place to put an element, cycles around if running past the end.

	function addEntry(e) {
		var oldest = entries[newEntryPos];
		if (oldest) {
			oldest.remove();
		}
		entries[newEntryPos] = e;
		newEntryPos = (newEntryPos+1) % MAX_ENTRIES;
	}

	function clear() {
		for (var i = 0; i < entries.length; i++) {
			var e = entries[i];
			if (e) {
				//This doesn't work on FireFox: e.remove();
				e.parentNode.removeChild(e);
				entries[i] = null;
			}
		}
	}

	//////////////////// Public API ///////////////////////////////////////////////////

	/**
	 * Change the width and vertical position of the console UI to line up properly with
	 * the editor.
	 */
	function updateWidth() {
		var c = $(CONSOLE_WRAPPER);
		var e = $("#editor");
		function copyCss() {
			for (var i = 0; i < arguments.length; i++) {
				var name = arguments[i];
				c.css(name, e.css(name));
			}
		}
		copyCss('margin-left', 'margin-right', 'width');
	}

	/**
	 * This method should be called by the editor setup at a good time (i.e. when editor has
	 * mostly been setup.
	 */
	function initialize() {
		if (!initialized) {
			initialized = true;
			$(CONSOLE_WRAPPER).resizable({
				handles: "n"
			});
			$(CONSOLE_WRAPPER).resize(function (event, ui) {
				var console_height = ui.size.height;
				var breadcrumb_height = $('#breadcrumb').outerHeight();
				var editor_height = $('#main').height() - breadcrumb_height - console_height;
				$(CONSOLE_WRAPPER).css('top', '0px'); //I think JQuery resizable is changing this from 0 but it messes things up.
				$(CONSOLE_WRAPPER).height(console_height);
				$("#editor").height(editor_height);
				editorUtils.getMainEditor().getTextView()._update();
				updateWidth();
			});
			$("#navigator-wrapper").resize(updateWidth);
			$("#side_panel").resize(updateWidth);
		}
	}

	function isVisible() {
		return $(CONSOLE_WRAPPER).css('display')!=='none';
	}

	function show() {
		initialize();
		var c = $(CONSOLE_WRAPPER);
		var e = $("#editor");
		if (!isVisible()) {
			//TODO: should remember previous size not reset to 1/3 of the screen.
			//If the console is presently hidden...
			var editor_height = e.height();
			var console_height = $(CONSOLE_WRAPPER).height() || (editor_height / 3);
			editor_height = editor_height - console_height;

			var overhead = c.outerHeight() - c.height();

			c.css('display', 'block');
			c.height(console_height-overhead);
			e.height(editor_height);
			c.resizable({
				disabled: false
			});

			editorUtils.getMainEditor().getTextView()._update();
			updateWidth();
		}
	}

	function hide() {
		if (isVisible()) {
			var c = $(CONSOLE_WRAPPER);
			var e = $("#editor");
			var console_height = c.outerHeight();
			c.css('display', 'none');
			c.resizable({
				disabled: true
			});
			e.height(e.height()+console_height);
			editorUtils.getMainEditor().getTextView()._update();
		}
	}

	function toggle() {
		if (isVisible()) {
			hide();
		} else {
			show();
		}
	}

	function scrollToBottom() {
		var c = $(CONSOLE_DISPLAY);
		var scrollH = c.prop("scrollHeight");
		var H = c.height();
		if (H < scrollH) {
			c.scrollTop(scrollH-H);
		}
	}

	/**
	 * Append a message to the console.
	 * An optional cssClass parameter allows tagging the
	 * element in the view with a specific cssClass. (so that it's
	 * look can be customized via css).
	 *
	 * @param String msg
	 * @param String cssClass
	 */
	function log(msg, cssClass) {
		cssClass = cssClass || 'log';
		show();
		var e = render(msg, cssClass);
		$(CONSOLE_DISPLAY).append(e);
		addEntry(e);
		scrollToBottom();
	}

	$(document).ready(function () {
		$('#console_toggle').on('click', toggle);
		$('#side_panel').bind('open', updateWidth);
		$('#side_panel').bind('close', updateWidth);
		$(window).resize(updateWidth);
	});

	return {
		clear: clear,
		log: log,
		error: function (msg) {
			log(msg, 'error');
		}
	};

});

<|endoftext|>


<commit_before>/**
 * Generic non bubling event system for core classes.
 */
(function() {

	core.Class("lowland.base.NativeEvents", {
		members : {
			__destructableEventsList : null,

			/**
			 * Add event listener on @event {String} to @element {Element}. If the event is fired the
			 * @callback {Function} is executed in @context {var?null}.
			 */
			addNativeListener : function(element, event, callback, context, capture) {
				var boundCallback;

				var destructableEventsList = this.__destructableEventsList;
				if (!destructableEventsList) {
					destructableEventsList = this.__destructableEventsList = [];
				}
				destructableEventsList.push([element, event, callback, context, capture]);

				if (!context) {
					context = this;
				}

				lowland.bom.Events.listen(element, event, core.Function.bind(callback, context), capture);
			},

			/**
			 * Remove event listener on @event {String} from @element {Element} that has @callback {Function?null} and @context {var?null} given.
			 */
			removeNativeListener : function(element, event, callback, context, capture) {
				var destructableEventsList = this.__destructableEventsList;
				if (destructableEventsList) {
					for (var i=0,ii=destructableEventsList.length; i<ii; i++) {
						var evt = destructableEventsList[i];
						if (evt[0] == element && evt[1] == event && evt[2] == callback && evt[3] == context && evt[4] == capture) {
							destructableEventsList.splice(i, 1);
							break;
						}
					}
				}

				if (!context) {
					context = this;
				}

				lowland.bom.Events.unlisten(element, event, core.Function.bind(callback, context), capture);
			},

			destruct : function() {
				var destructableEventsList = this.__destructableEventsList;
				while (destructableEventsList && destructableEventsList.length > 0) {
					var evt = destructableEventsList[0];
					this.removeNativeListener.apply(this, evt);
				}
			},

			debugNativeListener : jasy.Env.isSet("debug") ? lowland.bom.Events.debugEvents : undefined
		}
	});

})();
<commit_msg>Add removeAllNativeListeners<commit_after>/**
 * Generic non bubling event system for core classes.
 */
(function() {

	core.Class("lowland.base.NativeEvents", {
		members : {
			__destructableEventsList : null,

			/**
			 * Add event listener on @event {String} to @element {Element}. If the event is fired the
			 * @callback {Function} is executed in @context {var?null}.
			 */
			addNativeListener : function(element, event, callback, context, capture) {
				var boundCallback;

				var destructableEventsList = this.__destructableEventsList;
				if (!destructableEventsList) {
					destructableEventsList = this.__destructableEventsList = [];
				}
				destructableEventsList.push([element, event, callback, context, capture]);

				if (!context) {
					context = this;
				}

				lowland.bom.Events.listen(element, event, core.Function.bind(callback, context), capture);
			},

			/**
			 * Remove event listener on @event {String} from @element {Element} that has @callback {Function?null} and @context {var?null} given.
			 */
			removeNativeListener : function(element, event, callback, context, capture) {
				var destructableEventsList = this.__destructableEventsList;
				if (destructableEventsList) {
					for (var i=0,ii=destructableEventsList.length; i<ii; i++) {
						var evt = destructableEventsList[i];
						if (evt[0] == element && evt[1] == event && evt[2] == callback && evt[3] == context && evt[4] == capture) {
							destructableEventsList.splice(i, 1);
							break;
						}
					}
				}

				if (!context) {
					context = this;
				}

				lowland.bom.Events.unlisten(element, event, core.Function.bind(callback, context), capture);
			},

			removeAllNativeListeners : function() {
				var destructableEventsList = this.__destructableEventsList;
				while (destructableEventsList && destructableEventsList.length > 0) {
					var evt = destructableEventsList[0];
					this.removeNativeListener.apply(this, evt);
				}
			},

			destruct : function() {
				this.removeAllNativeListeners();
			},

			debugNativeListener : jasy.Env.isSet("debug") ? lowland.bom.Events.debugEvents : undefined
		}
	});

})();
<|endoftext|>


<commit_before>/**
 *
 *  Copyright 2016 Netflix, Inc.
 *
 *     Licensed under the Apache License, Version 2.0 (the "License");
 *     you may not use this file except in compliance with the License.
 *     You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 *     Unless required by applicable law or agreed to in writing, software
 *     distributed under the License is distributed on an "AS IS" BASIS,
 *     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *     See the License for the specific language governing permissions and
 *     limitations under the License.
 *
 */
import * as THREE from 'three';

import BaseView from './baseView';
import GlobalStyles from '../globalStyles';

function roundRect (context, x, y, w, h, radius, strokeColor, fillColor) {
  const r = x + w;
  const b = y + h;
  context.beginPath();
  context.strokeStyle = strokeColor;
  context.lineWidth = 2;
  context.moveTo(x + radius, y);
  context.lineTo(r - radius, y);
  context.quadraticCurveTo(r, y, r, y + radius);
  context.lineTo(r, (y + h) - radius);
  context.quadraticCurveTo(r, b, r - radius, b);
  context.lineTo(x + radius, b);
  context.quadraticCurveTo(x, b, x, b - radius);
  context.lineTo(x, y + radius);
  context.quadraticCurveTo(x, y, x + radius, y);
  context.fillStyle = fillColor;
  context.closePath();
  context.fill();
  context.stroke();
}

function truncate (name) {
  if (name.length > 18) {
    return `${name.substr(0, 7)}…${name.substr(-7)}`;
  }
  return name;
}

class NodeNameView extends BaseView {
  constructor (nodeView, fixedWidth) {
    super(nodeView.object);
    this.fixedWidth = fixedWidth;
    this.nodeName = nodeView.object.getDisplayName();
    this.nodeView = nodeView;

    // How far away from the node we want the label to begin
    this.buffer = Math.max(this.nodeView.radius * 0.3, 7);

    // Create the canvas to build a sprite
    this.nameCanvas = this.createCanvas(200, this.fontSize + 10);

    this.nameTexture = new THREE.Texture(this.nameCanvas);
    this.nameTexture.minFilter = THREE.LinearFilter;

    this.updateLabel();

    this.material = new THREE.MeshBasicMaterial({ map: this.nameTexture, side: THREE.DoubleSide, transparent: true });
    this.view = this.addChildElement(new THREE.PlaneBufferGeometry(this.nameCanvas.width, this.nameCanvas.height), this.material);
  }

  getDisplayName (getDefault) {
    const getDefaultDisplayName = () => truncate(this.nodeName);
    if (getDefault) { return getDefaultDisplayName(); }

    const showFullDisplayName = this.highlight || this.nodeView.focused;
    return showFullDisplayName ? this.nodeName : getDefaultDisplayName();
  }

  updateLabel () {
    const context = this.nameCanvas.getContext('2d');
    const fontSize = this.fixedWidth ? 22 : 18;

    const font = `${fontSize}px 'Source Sans Pro', sans-serif`;
    context.font = font;

    // Label Width
    this.defaultLabelWidth = this.fixedWidth ? 260 : context.measureText(this.getDisplayName(true)).width + 16;
    const labelWidth = this.fixedWidth ? 260 : context.measureText(this.getDisplayName()).width + 16;
    if (labelWidth !== this.labelWidth) { this.labelWidth = labelWidth; }
    this.resizeCanvas(this.nameCanvas, this.labelWidth, fontSize + 10);

    // label color
    const labelColor = this.highlight ? GlobalStyles.styles.colorTrafficHighlighted[this.object.getClass()] : GlobalStyles.getColorTraffic(this.object.getClass());
    roundRect(context, 0, 0, this.nameCanvas.width, this.nameCanvas.height, 3, GlobalStyles.styles.colorLabelBorder, labelColor);
    context.fillStyle = GlobalStyles.styles.colorLabelText;

    context.fillText(this.getDisplayName(), this.nameCanvas.width / 2, this.nameCanvas.height / 2);

    this.nameTexture.needsUpdate = true;

    if (this.view) {
      this.applyPosition();
      this.view.scale.x = this.labelWidth / this.defaultLabelWidth;
      this.view.geometry.width = this.nameCanvas.width;
      this.view.geometry.height = this.nameCanvas.height;
      this.view.geometry.needsUpdate = true;
    }
  }

  updatePosition () {
    // Update the bounding box
    this.boundingBox = {};
    // Add a little bit of fuzziness to the label height since we don't care if it overlaps a little...
    const yDelta = (this.nameCanvas.height * 0.6) / 2;
    this.boundingBox.top = this.nodeView.object.position.y - yDelta;
    this.boundingBox.bottom = this.nodeView.object.position.y + yDelta;
    if (this.nodeView.labelPositionLeft) {
      this.boundingBox.right = this.nodeView.object.boundingBox.left - this.buffer;
      this.boundingBox.left = this.boundingBox.right - this.nameCanvas.width;
    } else {
      this.boundingBox.left = this.nodeView.object.boundingBox.right + this.buffer;
      this.boundingBox.right = this.boundingBox.left + this.nameCanvas.width;
    }
  }

  applyPosition () {
    let x;
    const y = this.nodeView.object.graphRenderer === 'global' ? 80 : 0;

    // Prioritize left side if node is left of center, right side if node is right of center
    if (this.nodeView.labelPositionLeft) {
      // Put the label to the left of the node
      x = 0 - this.nodeView.radius - (this.labelWidth / 2) - this.buffer;
    } else {
      // Put the label to the right of the node
      x = this.nodeView.radius + (this.labelWidth / 2) + this.buffer;
    }

    this.container.position.set(x, y, 1);
  }

  setHighlight (highlight) {
    this.highlight = highlight;
  }

  refresh () {
    this.updateLabel();
  }

  setOpacity (opacity) {
    super.setOpacity(opacity);
    this.material.opacity = opacity;
  }

  cleanup () {
    this.nameTexture.dispose();
    this.material.dispose();
  }
}

export default NodeNameView;
<commit_msg>to use getColorTraffic<commit_after>/**
 *
 *  Copyright 2016 Netflix, Inc.
 *
 *     Licensed under the Apache License, Version 2.0 (the "License");
 *     you may not use this file except in compliance with the License.
 *     You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 *     Unless required by applicable law or agreed to in writing, software
 *     distributed under the License is distributed on an "AS IS" BASIS,
 *     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *     See the License for the specific language governing permissions and
 *     limitations under the License.
 *
 */
import * as THREE from 'three';

import BaseView from './baseView';
import GlobalStyles from '../globalStyles';

function roundRect (context, x, y, w, h, radius, strokeColor, fillColor) {
  const r = x + w;
  const b = y + h;
  context.beginPath();
  context.strokeStyle = strokeColor;
  context.lineWidth = 2;
  context.moveTo(x + radius, y);
  context.lineTo(r - radius, y);
  context.quadraticCurveTo(r, y, r, y + radius);
  context.lineTo(r, (y + h) - radius);
  context.quadraticCurveTo(r, b, r - radius, b);
  context.lineTo(x + radius, b);
  context.quadraticCurveTo(x, b, x, b - radius);
  context.lineTo(x, y + radius);
  context.quadraticCurveTo(x, y, x + radius, y);
  context.fillStyle = fillColor;
  context.closePath();
  context.fill();
  context.stroke();
}

function truncate (name) {
  if (name.length > 18) {
    return `${name.substr(0, 7)}…${name.substr(-7)}`;
  }
  return name;
}

class NodeNameView extends BaseView {
  constructor (nodeView, fixedWidth) {
    super(nodeView.object);
    this.fixedWidth = fixedWidth;
    this.nodeName = nodeView.object.getDisplayName();
    this.nodeView = nodeView;

    // How far away from the node we want the label to begin
    this.buffer = Math.max(this.nodeView.radius * 0.3, 7);

    // Create the canvas to build a sprite
    this.nameCanvas = this.createCanvas(200, this.fontSize + 10);

    this.nameTexture = new THREE.Texture(this.nameCanvas);
    this.nameTexture.minFilter = THREE.LinearFilter;

    this.updateLabel();

    this.material = new THREE.MeshBasicMaterial({ map: this.nameTexture, side: THREE.DoubleSide, transparent: true });
    this.view = this.addChildElement(new THREE.PlaneBufferGeometry(this.nameCanvas.width, this.nameCanvas.height), this.material);
  }

  getDisplayName (getDefault) {
    const getDefaultDisplayName = () => truncate(this.nodeName);
    if (getDefault) { return getDefaultDisplayName(); }

    const showFullDisplayName = this.highlight || this.nodeView.focused;
    return showFullDisplayName ? this.nodeName : getDefaultDisplayName();
  }

  updateLabel () {
    const context = this.nameCanvas.getContext('2d');
    const fontSize = this.fixedWidth ? 22 : 18;

    const font = `${fontSize}px 'Source Sans Pro', sans-serif`;
    context.font = font;

    // Label Width
    this.defaultLabelWidth = this.fixedWidth ? 260 : context.measureText(this.getDisplayName(true)).width + 16;
    const labelWidth = this.fixedWidth ? 260 : context.measureText(this.getDisplayName()).width + 16;
    if (labelWidth !== this.labelWidth) { this.labelWidth = labelWidth; }
    this.resizeCanvas(this.nameCanvas, this.labelWidth, fontSize + 10);

    // label color
    const labelColor = GlobalStyles.getColorTraffic(this.object.getClass(), this.highlight);
    roundRect(context, 0, 0, this.nameCanvas.width, this.nameCanvas.height, 3, GlobalStyles.styles.colorLabelBorder, labelColor);
    context.fillStyle = GlobalStyles.styles.colorLabelText;

    context.fillText(this.getDisplayName(), this.nameCanvas.width / 2, this.nameCanvas.height / 2);

    this.nameTexture.needsUpdate = true;

    if (this.view) {
      this.applyPosition();
      this.view.scale.x = this.labelWidth / this.defaultLabelWidth;
      this.view.geometry.width = this.nameCanvas.width;
      this.view.geometry.height = this.nameCanvas.height;
      this.view.geometry.needsUpdate = true;
    }
  }

  updatePosition () {
    // Update the bounding box
    this.boundingBox = {};
    // Add a little bit of fuzziness to the label height since we don't care if it overlaps a little...
    const yDelta = (this.nameCanvas.height * 0.6) / 2;
    this.boundingBox.top = this.nodeView.object.position.y - yDelta;
    this.boundingBox.bottom = this.nodeView.object.position.y + yDelta;
    if (this.nodeView.labelPositionLeft) {
      this.boundingBox.right = this.nodeView.object.boundingBox.left - this.buffer;
      this.boundingBox.left = this.boundingBox.right - this.nameCanvas.width;
    } else {
      this.boundingBox.left = this.nodeView.object.boundingBox.right + this.buffer;
      this.boundingBox.right = this.boundingBox.left + this.nameCanvas.width;
    }
  }

  applyPosition () {
    let x;
    const y = this.nodeView.object.graphRenderer === 'global' ? 80 : 0;

    // Prioritize left side if node is left of center, right side if node is right of center
    if (this.nodeView.labelPositionLeft) {
      // Put the label to the left of the node
      x = 0 - this.nodeView.radius - (this.labelWidth / 2) - this.buffer;
    } else {
      // Put the label to the right of the node
      x = this.nodeView.radius + (this.labelWidth / 2) + this.buffer;
    }

    this.container.position.set(x, y, 1);
  }

  setHighlight (highlight) {
    this.highlight = highlight;
  }

  refresh () {
    this.updateLabel();
  }

  setOpacity (opacity) {
    super.setOpacity(opacity);
    this.material.opacity = opacity;
  }

  cleanup () {
    this.nameTexture.dispose();
    this.material.dispose();
  }
}

export default NodeNameView;
<|endoftext|>