<commit_before>if(typeof window === 'undefined') {
    window = {};
}
if(typeof location === 'undefined') {
    location = {};
}

window._httpinvoke = window.httpinvoke;

// basic
window._cfg = {
    dummyserverPort: 1337,
    host: location.hostname,
    port: location.port || (location.protocol === 'https:' ? 443 : 80),
    path: '/dummyserver/',
    /* # Statuses from RFC 2616
     *
     * ## Not tested statuses, with reasons
     *
     * 100 Continue: technical status, not semantic, irrelevant for practical use
     * 101 Switching Protocols: technical status, not semantic, irrelevant for practical use
     * 401 Unauthorized: Chrome 29.0 throws an authorization dialog, when the mandatory header 'WWW-Authenticate' is set
     * 407 Proxy Authentication Required: not useful, Chrome 29.0 sends "some type" of network error
     * 411 Length Required: Content-Length is always sent by browsers
     * 417 Expectation Failed: the required header 'Expect' is not allowed by browsers
     *
     * ## Statuses that should work, but should not be used, with reasons
     *
     * 408 GET:
     * - Firefox 24.0 sends "some type" of network error
     *
     * 300 GET with Location header set:
     * - same-origin: Firefox 24.0 sends "some type" of network error
     * - cross-origin: Firefox 24.0 redirects
     *
     * 301, 302, 303, 307 with Location header set:
     * - cross-origin: "some type" of network error
     * - GET same-origin: Chrome 29.0 redirects
     * - POST same-origin: Chrome 29.0 times out
     *
     * 305 GET with Location:
     * - Safari 5.1 sends "some type" of network error
     *
     * 301, 302, 303, 307 GET same-origin without Location header set:
     * - IE 8.0 returns status 12150, which is WinInet error code ERROR_HTTP_HEADER_NOT_FOUND The requested header could not be located.
     *
     * Fails ("write EPIPE" error) on node:
     * - 301, 302, 303, 307 POST without location header set
     *
     * Broken by proxy of Karma test runner:
     * - same-origin 300, 305 POST
     * - 412, 413, 414, 415
     */
    status: {
        // OK
        '200': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: true
            }]
        },
        // Created
        '201': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: true
            }]
        },
        // Accepted
        '202': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: true
            }]
        },
        // Non-Authoritative Information
        '203': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: true
            }]
        },
        // No Content
        '204': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Reset Content
        '205': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Partial Content
        '206': {
            GET: [{
                requestEntity: false,
                partialResponse: true,
                responseEntity: true
            }]
        },
        // Multiple Choices
        '300': {
            GET: [{
                name: 'without location',
                location: false,
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Not Modified
        '304': {
            GET: [{
                ifModified: true,
                location: false,
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Use Proxy
        '305': {
            GET: [{
                name: 'without location',
                location: false,
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Bad Request
        '400': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Payment Required
        '402': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Forbidden
        '403': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Not Found
        '404': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Method Not Allowed
        '405': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Not Acceptable
        '406': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Conflict
        '409': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Gone
        '410': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Requested Range Not Satisfiable
        '416': {
            GET: [{
                requestEntity: false,
                partialResponse: true,
                responseEntity: false
            }]
        },
        // Internal Server Error
        '500': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Not Implemented
        '501': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Bad Gateway
        '502': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Service Unavailable
        '503': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Gateway Timeout
        '504': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // HTTP Version Not Supported
        '505': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        }
    },
    makeTextFinished: function(done) {
        var cfg = require('./dummyserver-config');
        return function(err, output) {
            if(err) {
                return done(err);
            }
            if(typeof output !== 'string') {
                return done(new Error('Received output is not a string'));
            }
            if(output !== cfg.textTest()) {
                return done(new Error('Received output ' + output + ' is not equal to expected output ' + cfg.textTest()));
            }
            done();
        };
    },
    makeByteArrayFinished: function(done) {
        var cfg = require('./dummyserver-config');
        return function(err, output) {
            if(err) {
                return done(err);
            }
            var expected = cfg.bytearrayTest();
            if(typeof output !== 'object' || output === null) {
                return done(new Error('Received output is not a non-null object'));
            }
            if(output.length !== expected.length) {
                return done(new Error('Received output length ' + output.length + ' is not equal to expected output length ' + expected.length));
            }
            var failures = [];
            for(var i = 0; i < output.length; i += 1) {
                if(output[i] !== expected[i]) {
                    failures.push(i + 'th byte: ' + output[i] + ' !== ' + expected[i]);
                }
            }
            if(failures.length > 0) {
                return done(new Error('Some received bytes differ from expected: ' + failures.join(',')));
            }
            done();
        };
    },
    eachBase: function(fn) {
        var httpinvoke = require('./httpinvoke-node');
        if(httpinvoke.cors) {
            try {
                fn(' (cross-origin)', window._cfg.corsURL, true);
            } catch(_) {
            }
        }
        try {
            fn(' (same-origin)', window._cfg.url, false);
        } catch(_) {
        }
    },
    jsonTest: function() {
        return [{
            a: 0,
            b: false,
            c: 'false',
            d: null
        }];
    },
    jsonTestPasses: function(json) {
        if(typeof json !== 'object' || json === null) {
            return false;
        }
        if(!(json instanceof Array)) {
            return false;
        }
        if(typeof json[0] !== 'object' || json === null) {
            return false;
        }
        if(!(json[0] instanceof Object)) {
            return false;
        }
        if(json[0].a !== 0) {
            return false;
        }
        if(json[0].b !== false) {
            return false;
        }
        if(json[0].c !== 'false') {
            return false;
        }
        if(json[0].d !== null) {
            return false;
        }
        return true;
    },
    textTest: function() {
        return 'ąčęėįšųū„“–ž1234567890-=!@#$%^&*()_+´¬¿,./;[]';
    },
    bytearrayTest: function() {
        var i, bytes = [];
        for(i = 0; i < 64; i += 1) {
            bytes.push(0);
        }
        for(i = 255; i >= 0; i -= 1) {
            bytes.push(i);
        }
        for(i = 0; i <= 255; i += 1) {
            bytes.push(i);
        }
        return bytes;
    }
};
// generated
window._cfg.corsURL = 'http://' + window._cfg.host + ':' + window._cfg.dummyserverPort + '/';
window._cfg.url = 'http://' + window._cfg.host + ':' + window._cfg.port + window._cfg.path;

window.require = function(module) {
    if(module === '../dummyserver-config' || module === './dummyserver-config') {
        return window._cfg;
    }
    return window._httpinvoke;
};

if(typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
    module.exports = window._cfg;
}
<commit_msg>karma-mocha-requireHack.js: status: cover all codes with GET method<commit_after>if(typeof window === 'undefined') {
    window = {};
}
if(typeof location === 'undefined') {
    location = {};
}

window._httpinvoke = window.httpinvoke;

// basic
window._cfg = {
    dummyserverPort: 1337,
    host: location.hostname,
    port: location.port || (location.protocol === 'https:' ? 443 : 80),
    path: '/dummyserver/',
    /* # Statuses from RFC 2616
     *
     * ## Not tested statuses, with reasons
     *
     * 100 Continue: technical status, not semantic, irrelevant for practical use
     * 101 Switching Protocols: technical status, not semantic, irrelevant for practical use
     * 205 Reset Content: Roy T. Fielding: "The most common use of 205 is within custom HTTP systems, not browsers.": http://w3-org.9356.n7.nabble.com/p2-deprecating-205-Reset-Content-tp253298p253354.html
     * 401 Unauthorized: Chrome 29.0 throws an authorization dialog, when the mandatory header 'WWW-Authenticate' is set
     * 407 Proxy Authentication Required: not useful, Chrome 29.0 sends "some type" of network error
     * 411 Length Required: Content-Length is always sent by browsers
     * 417 Expectation Failed: the required header 'Expect' is not allowed by browsers
     *
     * ## Statuses that should work, but should not be used, with reasons
     *
     * ### 3XX with Location header set
     *
     * Causes various unexpected behaviors - browsers are supposed to redirect,
     * also Karma test runner proxy failure.
     *
     * ### 3XX without Location header set
     *
     * 301, 302, 303, 307 GET same-origin:
     * - IE 8.0 returns status 12150, which is WinInet error code ERROR_HTTP_HEADER_NOT_FOUND The requested header could not be located
     *
     * 301, 302, 303, 307 POST:
     * - fails ("write EPIPE" error) on node
     *
     * 300, 305 POST:
     * - Karma test runner proxy failure
     *
     * 305 GET:
     * - Opera 12.10 responseText: <TITLE>Redirect to alternative proxy</TITLE>The server tried to redirect Opera to the alternative proxy "". For security reasons this is no longer supported.

Generated by Opera ©
     *
     * 300 GET:
     * - Opera 12.10 times out
     *
     * 304 POST:
     * - By RFC2616 impossible
     *
     */
    // TODO PUT POST - verify headers and input on server
    // TODO HEAD POST PUT DELETE
    status: {
        // OK
        '200': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: true
            }]
        },
        // Created
        '201': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: true
            }]
        },
        // Accepted
        '202': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: true
            }]
        },
        // Non-Authoritative Information
        '203': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: true
            }]
        },
        // No Content
        '204': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Partial Content
        '206': {
            GET: [{
                requestEntity: false,
                partialResponse: true,
                responseEntity: true
            }]
        },
        // Not Modified
        '304': {
            GET: [{
                ifModified: true,
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Bad Request
        '400': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Payment Required
        '402': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Forbidden
        '403': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Not Found
        '404': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Method Not Allowed
        '405': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Not Acceptable
        '406': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Request Timeout
        '408': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Conflict
        '409': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Gone
        '410': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Precondition Failed
        '412': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Request Entity Too Large
        '413': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Request-URI Too Long
        '414': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Unsupported Media Type
        '415': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Requested Range Not Satisfiable
        '416': {
            GET: [{
                requestEntity: false,
                partialResponse: true,
                responseEntity: false
            }]
        },
        // Internal Server Error
        '500': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Not Implemented
        '501': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Bad Gateway
        '502': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Service Unavailable
        '503': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // Gateway Timeout
        '504': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        },
        // HTTP Version Not Supported
        '505': {
            GET: [{
                requestEntity: false,
                partialResponse: false,
                responseEntity: false
            }]
        }
    },
    makeTextFinished: function(done) {
        var cfg = require('./dummyserver-config');
        return function(err, output) {
            if(err) {
                return done(err);
            }
            if(typeof output !== 'string') {
                return done(new Error('Received output is not a string'));
            }
            if(output !== cfg.textTest()) {
                return done(new Error('Received output ' + output + ' is not equal to expected output ' + cfg.textTest()));
            }
            done();
        };
    },
    makeByteArrayFinished: function(done) {
        var cfg = require('./dummyserver-config');
        return function(err, output) {
            if(err) {
                return done(err);
            }
            var expected = cfg.bytearrayTest();
            if(typeof output !== 'object' || output === null) {
                return done(new Error('Received output is not a non-null object'));
            }
            if(output.length !== expected.length) {
                return done(new Error('Received output length ' + output.length + ' is not equal to expected output length ' + expected.length));
            }
            var failures = [];
            for(var i = 0; i < output.length; i += 1) {
                if(output[i] !== expected[i]) {
                    failures.push(i + 'th byte: ' + output[i] + ' !== ' + expected[i]);
                }
            }
            if(failures.length > 0) {
                return done(new Error('Some received bytes differ from expected: ' + failures.join(',')));
            }
            done();
        };
    },
    eachBase: function(fn) {
        var httpinvoke = require('./httpinvoke-node');
        if(httpinvoke.cors) {
            try {
                fn(' (cross-origin)', window._cfg.corsURL, true);
            } catch(_) {
            }
        }
        try {
            fn(' (same-origin)', window._cfg.url, false);
        } catch(_) {
        }
    },
    jsonTest: function() {
        return [{
            a: 0,
            b: false,
            c: 'false',
            d: null
        }];
    },
    jsonTestPasses: function(json) {
        if(typeof json !== 'object' || json === null) {
            return false;
        }
        if(!(json instanceof Array)) {
            return false;
        }
        if(typeof json[0] !== 'object' || json === null) {
            return false;
        }
        if(!(json[0] instanceof Object)) {
            return false;
        }
        if(json[0].a !== 0) {
            return false;
        }
        if(json[0].b !== false) {
            return false;
        }
        if(json[0].c !== 'false') {
            return false;
        }
        if(json[0].d !== null) {
            return false;
        }
        return true;
    },
    textTest: function() {
        return 'ąčęėįšųū„“–ž1234567890-=!@#$%^&*()_+´¬¿,./;[]';
    },
    bytearrayTest: function() {
        var i, bytes = [];
        for(i = 0; i < 64; i += 1) {
            bytes.push(0);
        }
        for(i = 255; i >= 0; i -= 1) {
            bytes.push(i);
        }
        for(i = 0; i <= 255; i += 1) {
            bytes.push(i);
        }
        return bytes;
    }
};
// generated
window._cfg.corsURL = 'http://' + window._cfg.host + ':' + window._cfg.dummyserverPort + '/';
window._cfg.url = 'http://' + window._cfg.host + ':' + window._cfg.port + window._cfg.path;

window.require = function(module) {
    if(module === '../dummyserver-config' || module === './dummyserver-config') {
        return window._cfg;
    }
    return window._httpinvoke;
};

if(typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
    module.exports = window._cfg;
}
<|endoftext|>


<commit_before>const {app, BrowserWindow, ipcMain} = require('electron');
const url = require('url');
const path = require('path');
const jayson = require('jayson');
const semver = require('semver');
const https = require('https');
// tree-kill has better cross-platform handling of
// killing a process.  child-process.kill was unreliable
const kill = require('tree-kill');
const child_process = require('child_process');
const assert = require('assert');
const {version: localVersion} = require(app.getAppPath() + '/package.json');

const VERSION_CHECK_INTERVAL = 30 * 60 * 1000;
const LATEST_RELEASE_API_URL = 'https://api.github.com/repos/lbryio/lbry-app/releases/latest';

let client = jayson.client.http('http://localhost:5279/lbryapi');
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win;
// Also keep the daemon subprocess alive
let daemonSubprocess;

// This is set to true right before we try to shut the daemon subprocess --
// if it dies when we didn't ask it to shut down, we want to alert the user.
let daemonStopRequested = false;

// When a quit is attempted, we cancel the quit, do some preparations, then
// this is set to true and app.quit() is called again to quit for real.
let readyToQuit = false;

// If we receive a URI to open from an external app but there's no window to
// send it to, it's cached in this variable.
let openUri = null;

function checkForNewVersion(callback) {
  function formatRc(ver) {
    // Adds dash if needed to make RC suffix semver friendly
    return ver.replace(/([^-])rc/, '$1-rc');
  }

  let result = '';
  const opts = {
    headers: {
      'User-Agent': `LBRY/${localVersion}`,
    }
  };
  const req = https.get(Object.assign(opts, url.parse(LATEST_RELEASE_API_URL)), (res) => {
    res.on('data', (data) => {
      result += data;
    });
    res.on('end', () => {
      console.log('Local version:', localVersion);
      const tagName = JSON.parse(result).tag_name;
      const [_, remoteVersion] = tagName.match(/^v([\d.]+(?:-?rc\d+)?)$/);
      if (!remoteVersion) {
        console.log('Malformed remote version string:', tagName);
        win.webContents.send('version-info-received', null);
      } else {
        console.log('Remote version:', remoteVersion);
        const upgradeAvailable = semver.gt(formatRc(remoteVersion), formatRc(localVersion));
        console.log(upgradeAvailable ? 'Upgrade available' : 'No upgrade available');
        win.webContents.send('version-info-received', {remoteVersion, localVersion, upgradeAvailable});
      }
    })
  });

  req.on('error', (err) => {
    console.log('Failed to get current version from GitHub. Error:', err);
    win.webContents.send('version-info-received', null);
  });
}

ipcMain.on('version-info-requested', checkForNewVersion);

/*
 * Replacement for Electron's shell.openItem. The Electron version doesn't
 * reliably work from the main process, and we need to be able to run it
 * when no windows are open.
 */
function openItem(fullPath) {
    const subprocOptions = {
      detached: true,
      stdio: 'ignore',
    };

    let child;
    if (process.platform == 'darwin') {
      child = child_process.spawn('open', [fullPath], subprocOptions);
    } else if (process.platform == 'linux') {
      child = child_process.spawn('xdg-open', [fullPath], subprocOptions);
    } else if (process.platform == 'win32') {
      child = child_process.spawn(fullPath, [], subprocOptions);
    }

    // Causes child process reference to be garbage collected, allowing main process to exit
    child.unref();
}

function getPidsForProcessName(name) {
  if (process.platform == 'win32') {
    const tasklistOut = child_process.execSync(`tasklist /fi "Imagename eq ${name}.exe" /nh`, {encoding: 'utf8'});
    if (tasklistOut.startsWith('INFO')) {
      return [];
    } else {
      return tasklistOut.match(/[^\r\n]+/g).map((line) => line.split(/\s+/)[1]); // Second column of every non-empty line
    }
  } else {
    const pgrepOut = child_process.spawnSync('pgrep', ['-x', name], {encoding: 'utf8'}).stdout;
    return pgrepOut.match(/\d+/g);
  }
}

function createWindow () {
  win = new BrowserWindow({backgroundColor: '#155B4A', minWidth: 800, minHeight: 600 }) //$color-primary

  win.maximize()
  // win.webContents.openDevTools();
  win.loadURL(`file://${__dirname}/dist/index.html`)
  if (openUri) { // We stored and received a URI that an external app requested before we had a window object
    win.webContents.on('did-finish-load', () => {
      win.webContents.send('open-uri-requested', openUri);
    });
  }

  win.on('closed', () => {
    win = null
  })
};

function handleDaemonSubprocessExited() {
  console.log('The daemon has exited.');
  daemonSubprocess = null;
  if (!daemonStopRequested) {
    // We didn't request to stop the daemon, so display a
    // warning and schedule a quit.
    //
    // TODO: maybe it would be better to restart the daemon?
    if (win) {
      console.log('Did not request daemon stop, so quitting in 5 seconds.');
      win.loadURL(`file://${__dirname}/dist/warning.html`);
      setTimeout(quitNow, 5000);
    } else {
      console.log('Did not request daemon stop, so quitting.');
      quitNow();
    }
  }
}

function launchDaemon() {
  assert(!daemonSubprocess, 'Tried to launch daemon twice');

  if (process.env.LBRY_DAEMON) {
    executable = process.env.LBRY_DAEMON;
  } else {
    executable = path.join(__dirname, 'dist', 'lbrynet-daemon');
  }
  console.log('Launching daemon:', executable)
  daemonSubprocess = child_process.spawn(executable)
  // Need to handle the data event instead of attaching to
  // process.stdout because the latter doesn't work. I believe on
  // windows it buffers stdout and we don't get any meaningful output
  daemonSubprocess.stdout.on('data', (buf) => {console.log(String(buf).trim());});
  daemonSubprocess.stderr.on('data', (buf) => {console.log(String(buf).trim());});
  daemonSubprocess.on('exit', handleDaemonSubprocessExited);
}

/*
 * Quits without any preparation. When a quit is requested (either through the
 * interface or through app.quit()), we abort the quit, try to shut down the daemon,
 * and then call this to quit for real.
 */
function quitNow() {
  readyToQuit = true;
  app.quit();
}

app.on('ready', function(){
  launchDaemonIfNotRunning();
  createWindow();
});

function launchDaemonIfNotRunning() {
  // Check if the daemon is already running. If we get
  // an error its because its not running
  console.log('Checking for lbrynet daemon');
  client.request(
    'status', [],
    function (err, res) {
      if (err) {
        console.log('lbrynet daemon needs to be launched')
        launchDaemon();
      } else {
        console.log('lbrynet daemon is already running')
      }
    }
  );
}

/*
 * Last resort for killing unresponsive daemon instances.
 * Looks for any processes called "lbrynet-daemon" and
 * tries to force kill them.
 */
function forceKillAllDaemonsAndQuit() {
  console.log('Attempting to force kill any running lbrynet-daemon instances...');

  const daemonPids = getPidsForProcessName('lbrynet-daemon');
  if (!daemonPids) {
    console.log('No lbrynet-daemon found running.');
    quitNow();
  } else {
    console.log(`Found ${daemonPids.length} running daemon instances. Attempting to force kill...`);

    for (const pid of daemonPids) {
      let daemonKillAttemptsComplete = 0;
      kill(pid, 'SIGKILL', (err) => {
        daemonKillAttemptsComplete++;
        if (err) {
          console.log(`Failed to force kill daemon task with pid ${pid}. Error message: ${err.message}`);
        } else {
          console.log(`Force killed daemon task with pid ${pid}.`);
        }
        if (daemonKillAttemptsComplete >= daemonPids.length - 1) {
          quitNow();
        }
      });
    }
  }
}

// Quit when all windows are closed.
app.on('window-all-closed', () => {
  // On macOS it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('before-quit', (event) => {
  if (!readyToQuit) {
    // We need to shutdown the daemon before we're ready to actually quit. This
    // event will be triggered re-entrantly once preparation is done.
    event.preventDefault();
    shutdownDaemonAndQuit();
  } else {
    console.log('Quitting.')
  }
});

app.on('activate', () => {
  // On macOS it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (win === null) {
    createWindow()
  }
});

// When a quit is attempted, this is called. It attempts to shutdown the daemon,
// then calls quitNow() to quit for real.
function shutdownDaemonAndQuit(evenIfNotStartedByApp = false) {
  function doShutdown() {
    console.log('Asking daemon to shut down down');
    daemonStopRequested = true;
    client.request('daemon_stop', [], (err, res) => {
      if (err) {
        console.log(`received error when stopping lbrynet-daemon. Error message: ${err.message}\n`);
        console.log('You will need to manually kill the daemon.');
      } else {
        console.log('Successfully stopped daemon via RPC call.')
        quitNow();
      }
    });
  }

  if (daemonSubprocess) {
    doShutdown();
  } else if (!evenIfNotStartedByApp) {
    console.log('Not killing lbrynet-daemon because app did not start it');
    quitNow();
  } else {
    doShutdown();
  }

  // Is it safe to start the installer before the daemon finishes running?
  // If not, we should wait until the daemon is closed before we start the install.
}

function upgrade(event, installerPath) {
  app.on('quit', () => {
    console.log('Launching upgrade installer at', installerPath);
    // This gets triggered called after *all* other quit-related events, so
    // we'll only get here if we're fully prepared and quitting for real.
    openItem(installerPath);
  });

  if (win) {
    win.loadURL(`file://${__dirname}/dist/upgrade.html`);
  }

  shutdownDaemonAndQuit(true);
  // wait for daemon to shut down before upgrading
  // what to do if no shutdown in a long time?
  console.log('Update downloaded to', installerPath);
  console.log('The app will close, and you will be prompted to install the latest version of LBRY.');
  console.log('After the install is complete, please reopen the app.');
}

ipcMain.on('upgrade', upgrade);

if (process.platform == 'darwin') {
  app.on('open-url', (event, uri) => {
    if (!win) {
      // Window not created yet, so store up requested URI for when it is
      openUri = uri;
    } else {
      win.webContents.send('open-uri-requested', uri);
    }
  });
} else if (process.argv.length >= 3) {
  // No open-url event on Win, but we can still handle URIs provided at launch time
  win.webContents.send('open-uri-requested', process.argv[2]);
}
<commit_msg>Add setAsDefaultProtocolClient call for Windows<commit_after>const {app, BrowserWindow, ipcMain} = require('electron');
const url = require('url');
const path = require('path');
const jayson = require('jayson');
const semver = require('semver');
const https = require('https');
// tree-kill has better cross-platform handling of
// killing a process.  child-process.kill was unreliable
const kill = require('tree-kill');
const child_process = require('child_process');
const assert = require('assert');
const {version: localVersion} = require(app.getAppPath() + '/package.json');

const VERSION_CHECK_INTERVAL = 30 * 60 * 1000;
const LATEST_RELEASE_API_URL = 'https://api.github.com/repos/lbryio/lbry-app/releases/latest';

let client = jayson.client.http('http://localhost:5279/lbryapi');
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win;
// Also keep the daemon subprocess alive
let daemonSubprocess;

// This is set to true right before we try to shut the daemon subprocess --
// if it dies when we didn't ask it to shut down, we want to alert the user.
let daemonStopRequested = false;

// When a quit is attempted, we cancel the quit, do some preparations, then
// this is set to true and app.quit() is called again to quit for real.
let readyToQuit = false;

// If we receive a URI to open from an external app but there's no window to
// send it to, it's cached in this variable.
let openUri = null;

function checkForNewVersion(callback) {
  function formatRc(ver) {
    // Adds dash if needed to make RC suffix semver friendly
    return ver.replace(/([^-])rc/, '$1-rc');
  }

  let result = '';
  const opts = {
    headers: {
      'User-Agent': `LBRY/${localVersion}`,
    }
  };
  const req = https.get(Object.assign(opts, url.parse(LATEST_RELEASE_API_URL)), (res) => {
    res.on('data', (data) => {
      result += data;
    });
    res.on('end', () => {
      console.log('Local version:', localVersion);
      const tagName = JSON.parse(result).tag_name;
      const [_, remoteVersion] = tagName.match(/^v([\d.]+(?:-?rc\d+)?)$/);
      if (!remoteVersion) {
        console.log('Malformed remote version string:', tagName);
        win.webContents.send('version-info-received', null);
      } else {
        console.log('Remote version:', remoteVersion);
        const upgradeAvailable = semver.gt(formatRc(remoteVersion), formatRc(localVersion));
        console.log(upgradeAvailable ? 'Upgrade available' : 'No upgrade available');
        win.webContents.send('version-info-received', {remoteVersion, localVersion, upgradeAvailable});
      }
    })
  });

  req.on('error', (err) => {
    console.log('Failed to get current version from GitHub. Error:', err);
    win.webContents.send('version-info-received', null);
  });
}

ipcMain.on('version-info-requested', checkForNewVersion);

/*
 * Replacement for Electron's shell.openItem. The Electron version doesn't
 * reliably work from the main process, and we need to be able to run it
 * when no windows are open.
 */
function openItem(fullPath) {
    const subprocOptions = {
      detached: true,
      stdio: 'ignore',
    };

    let child;
    if (process.platform == 'darwin') {
      child = child_process.spawn('open', [fullPath], subprocOptions);
    } else if (process.platform == 'linux') {
      child = child_process.spawn('xdg-open', [fullPath], subprocOptions);
    } else if (process.platform == 'win32') {
      child = child_process.spawn(fullPath, [], subprocOptions);
    }

    // Causes child process reference to be garbage collected, allowing main process to exit
    child.unref();
}

function getPidsForProcessName(name) {
  if (process.platform == 'win32') {
    const tasklistOut = child_process.execSync(`tasklist /fi "Imagename eq ${name}.exe" /nh`, {encoding: 'utf8'});
    if (tasklistOut.startsWith('INFO')) {
      return [];
    } else {
      return tasklistOut.match(/[^\r\n]+/g).map((line) => line.split(/\s+/)[1]); // Second column of every non-empty line
    }
  } else {
    const pgrepOut = child_process.spawnSync('pgrep', ['-x', name], {encoding: 'utf8'}).stdout;
    return pgrepOut.match(/\d+/g);
  }
}

function createWindow () {
  win = new BrowserWindow({backgroundColor: '#155B4A', minWidth: 800, minHeight: 600 }) //$color-primary

  win.maximize()
  // win.webContents.openDevTools();
  win.loadURL(`file://${__dirname}/dist/index.html`)
  if (openUri) { // We stored and received a URI that an external app requested before we had a window object
    win.webContents.on('did-finish-load', () => {
      win.webContents.send('open-uri-requested', openUri);
    });
  }

  win.on('closed', () => {
    win = null
  })
};

function handleDaemonSubprocessExited() {
  console.log('The daemon has exited.');
  daemonSubprocess = null;
  if (!daemonStopRequested) {
    // We didn't request to stop the daemon, so display a
    // warning and schedule a quit.
    //
    // TODO: maybe it would be better to restart the daemon?
    if (win) {
      console.log('Did not request daemon stop, so quitting in 5 seconds.');
      win.loadURL(`file://${__dirname}/dist/warning.html`);
      setTimeout(quitNow, 5000);
    } else {
      console.log('Did not request daemon stop, so quitting.');
      quitNow();
    }
  }
}

function launchDaemon() {
  assert(!daemonSubprocess, 'Tried to launch daemon twice');

  if (process.env.LBRY_DAEMON) {
    executable = process.env.LBRY_DAEMON;
  } else {
    executable = path.join(__dirname, 'dist', 'lbrynet-daemon');
  }
  console.log('Launching daemon:', executable)
  daemonSubprocess = child_process.spawn(executable)
  // Need to handle the data event instead of attaching to
  // process.stdout because the latter doesn't work. I believe on
  // windows it buffers stdout and we don't get any meaningful output
  daemonSubprocess.stdout.on('data', (buf) => {console.log(String(buf).trim());});
  daemonSubprocess.stderr.on('data', (buf) => {console.log(String(buf).trim());});
  daemonSubprocess.on('exit', handleDaemonSubprocessExited);
}

/*
 * Quits without any preparation. When a quit is requested (either through the
 * interface or through app.quit()), we abort the quit, try to shut down the daemon,
 * and then call this to quit for real.
 */
function quitNow() {
  readyToQuit = true;
  app.quit();
}

app.on('ready', function(){
  launchDaemonIfNotRunning();
  createWindow();
});

function launchDaemonIfNotRunning() {
  // Check if the daemon is already running. If we get
  // an error its because its not running
  console.log('Checking for lbrynet daemon');
  client.request(
    'status', [],
    function (err, res) {
      if (err) {
        console.log('lbrynet daemon needs to be launched')
        launchDaemon();
      } else {
        console.log('lbrynet daemon is already running')
      }
    }
  );
}

/*
 * Last resort for killing unresponsive daemon instances.
 * Looks for any processes called "lbrynet-daemon" and
 * tries to force kill them.
 */
function forceKillAllDaemonsAndQuit() {
  console.log('Attempting to force kill any running lbrynet-daemon instances...');

  const daemonPids = getPidsForProcessName('lbrynet-daemon');
  if (!daemonPids) {
    console.log('No lbrynet-daemon found running.');
    quitNow();
  } else {
    console.log(`Found ${daemonPids.length} running daemon instances. Attempting to force kill...`);

    for (const pid of daemonPids) {
      let daemonKillAttemptsComplete = 0;
      kill(pid, 'SIGKILL', (err) => {
        daemonKillAttemptsComplete++;
        if (err) {
          console.log(`Failed to force kill daemon task with pid ${pid}. Error message: ${err.message}`);
        } else {
          console.log(`Force killed daemon task with pid ${pid}.`);
        }
        if (daemonKillAttemptsComplete >= daemonPids.length - 1) {
          quitNow();
        }
      });
    }
  }
}

// Quit when all windows are closed.
app.on('window-all-closed', () => {
  // On macOS it is common for applications and their menu bar
  // to stay active until the user quits explicitly with Cmd + Q
  if (process.platform !== 'darwin') {
    app.quit()
  }
})

app.on('before-quit', (event) => {
  if (!readyToQuit) {
    // We need to shutdown the daemon before we're ready to actually quit. This
    // event will be triggered re-entrantly once preparation is done.
    event.preventDefault();
    shutdownDaemonAndQuit();
  } else {
    console.log('Quitting.')
  }
});

app.on('activate', () => {
  // On macOS it's common to re-create a window in the app when the
  // dock icon is clicked and there are no other windows open.
  if (win === null) {
    createWindow()
  }
});

// When a quit is attempted, this is called. It attempts to shutdown the daemon,
// then calls quitNow() to quit for real.
function shutdownDaemonAndQuit(evenIfNotStartedByApp = false) {
  function doShutdown() {
    console.log('Asking daemon to shut down down');
    daemonStopRequested = true;
    client.request('daemon_stop', [], (err, res) => {
      if (err) {
        console.log(`received error when stopping lbrynet-daemon. Error message: ${err.message}\n`);
        console.log('You will need to manually kill the daemon.');
      } else {
        console.log('Successfully stopped daemon via RPC call.')
        quitNow();
      }
    });
  }

  if (daemonSubprocess) {
    doShutdown();
  } else if (!evenIfNotStartedByApp) {
    console.log('Not killing lbrynet-daemon because app did not start it');
    quitNow();
  } else {
    doShutdown();
  }

  // Is it safe to start the installer before the daemon finishes running?
  // If not, we should wait until the daemon is closed before we start the install.
}

function upgrade(event, installerPath) {
  app.on('quit', () => {
    console.log('Launching upgrade installer at', installerPath);
    // This gets triggered called after *all* other quit-related events, so
    // we'll only get here if we're fully prepared and quitting for real.
    openItem(installerPath);
  });

  if (win) {
    win.loadURL(`file://${__dirname}/dist/upgrade.html`);
  }

  shutdownDaemonAndQuit(true);
  // wait for daemon to shut down before upgrading
  // what to do if no shutdown in a long time?
  console.log('Update downloaded to', installerPath);
  console.log('The app will close, and you will be prompted to install the latest version of LBRY.');
  console.log('After the install is complete, please reopen the app.');
}

ipcMain.on('upgrade', upgrade);

app.setAsDefaultProtocolClient('lbry');
if (process.platform == 'darwin') {
  app.on('open-url', (event, uri) => {
    if (!win) {
      // Window not created yet, so store up requested URI for when it is
      openUri = uri;
    } else {
      win.webContents.send('open-uri-requested', uri);
    }
  });
} else if (process.argv.length >= 3) {
  // No open-url event on Win, but we can still handle URIs provided at launch time
  win.webContents.send('open-uri-requested', process.argv[2]);
}
<|endoftext|>


<commit_before>/**
 * Usage: npm run importIdPresets -- --dir DIR
 */

import fs from 'fs';
import path from 'path';
import mkdirp from 'mkdirp';
import presetsBuilder from 'id-presets-builder';
import argp from 'argp';
import logger from '../lib/logger';

const argv = argp.createParser({ once: true })
    .description ('Import presets, categories, fields and defaults from an iD reporitory clone')
    .on('end', function (argv) {
        if ( !argv.dir ) {
            this.printUsage(1);
        }
    })
    .on('error', function (error) {
        this.fail(error);
    })
    .body ()
        .text(' Options:')
        .option({
            description: 'iD\'s repository clone directory',
            short: 'd',
            long: 'dir',
            metavar: 'DIR',
            optional: false,
            type: String
        })
        .usage()
        .argv();

const iDDirectoryPath = path.resolve(argv.dir);
const iDPresetsDirectoryPath = path.join(iDDirectoryPath, 'data/presets');
const iDLocalesDirectoryPath = path.join(iDDirectoryPath, 'dist/locales');
const finalPresetsDirectoryPath = path.join(__dirname, '../public/data/iD-presets');
const finalLocalesDirectoryPath = path.join(finalPresetsDirectoryPath, 'locales');

logger.debug('final directories creation');
mkdirp.sync(finalPresetsDirectoryPath);
mkdirp.sync(finalLocalesDirectoryPath);

logger.debug('old locale files purge');
fs.readdir(finalLocalesDirectoryPath, (err, localeFiles) => {
    if (err) throw err;

    for (const localeFile of localeFiles) {
        fs.unlinkSync(
            path.join(finalLocalesDirectoryPath, localeFile)
        );
    }
});

logger.info('Generation of presets');
presetsBuilder.generatePresets(iDPresetsDirectoryPath, (err, data) => {
    if (err) throw err;

    const finalPresetsPath = path.join(finalPresetsDirectoryPath, 'presets.json');
    const newData = {
        defaults: data.defaults,
        presets:  data.presets,
        fields:   data.fields,
    };

    logger.debug(`creating ${finalPresetsPath}`);
    fs.writeFile(finalPresetsPath, JSON.stringify(newData));
});

logger.info('Generation of locale files');
fs.readdir(iDLocalesDirectoryPath, (err, iDLocaleFiles) => {
    if (err) throw err;

    for (const iDLocaleFile of iDLocaleFiles) {
        const iDLocaleFilePath = path.join(iDLocalesDirectoryPath, iDLocaleFile);
        const finalLocaleFilePath = path.join(finalLocalesDirectoryPath, iDLocaleFile);

        fs.readFile(iDLocaleFilePath, 'utf-8', (err, data) => {
            if (err) throw err;

            const json = JSON.parse(data);
            const newData = {};

            if (typeof json.presets === 'undefined') {
                return false;
            }

            if (typeof json.presets.presets !== 'undefined') {
                newData.presets = json.presets.presets;
            }

            if (typeof json.presets.fields !== 'undefined') {
                newData.fields = json.presets.fields;
            }

            logger.debug(`creating ${finalLocaleFilePath}`);
            fs.writeFile(
                finalLocaleFilePath,
                JSON.stringify(newData)
            );
        });
    }
});

logger.info('End of the generation');
<commit_msg>Fixes an error when no options given to the script<commit_after>/**
 * Usage: npm run importIdPresets -- --dir DIR
 */

import fs from 'fs';
import path from 'path';
import mkdirp from 'mkdirp';
import presetsBuilder from 'id-presets-builder';
import argp from 'argp';
import logger from '../lib/logger';

const argv = argp.createParser({ once: true })
    .description ('Import presets, categories, fields and defaults from an iD reporitory clone')
    .on('end', function (argv) {
        if ( !argv.dir ) {
            this.printUsage(1);
            process.exit(1);
        }
    })
    .on('error', function (error) {
        this.fail(error);
    })
    .body ()
        .text(' Options:')
        .option({
            description: 'iD\'s repository clone directory',
            short: 'd',
            long: 'dir',
            metavar: 'DIR',
            optional: false,
            type: String
        })
        .usage()
        .argv();

const iDDirectoryPath = path.resolve(argv.dir);
const iDPresetsDirectoryPath = path.join(iDDirectoryPath, 'data/presets');
const iDLocalesDirectoryPath = path.join(iDDirectoryPath, 'dist/locales');
const finalPresetsDirectoryPath = path.join(__dirname, '../public/data/iD-presets');
const finalLocalesDirectoryPath = path.join(finalPresetsDirectoryPath, 'locales');

logger.debug('final directories creation');
mkdirp.sync(finalPresetsDirectoryPath);
mkdirp.sync(finalLocalesDirectoryPath);

logger.debug('old locale files purge');
fs.readdir(finalLocalesDirectoryPath, (err, localeFiles) => {
    if (err) throw err;

    for (const localeFile of localeFiles) {
        fs.unlinkSync(
            path.join(finalLocalesDirectoryPath, localeFile)
        );
    }
});

logger.info('Generation of presets');
presetsBuilder.generatePresets(iDPresetsDirectoryPath, (err, data) => {
    if (err) throw err;

    const finalPresetsPath = path.join(finalPresetsDirectoryPath, 'presets.json');
    const newData = {
        defaults: data.defaults,
        presets:  data.presets,
        fields:   data.fields,
    };

    logger.debug(`creating ${finalPresetsPath}`);
    fs.writeFile(finalPresetsPath, JSON.stringify(newData));
});

logger.info('Generation of locale files');
fs.readdir(iDLocalesDirectoryPath, (err, iDLocaleFiles) => {
    if (err) throw err;

    for (const iDLocaleFile of iDLocaleFiles) {
        const iDLocaleFilePath = path.join(iDLocalesDirectoryPath, iDLocaleFile);
        const finalLocaleFilePath = path.join(finalLocalesDirectoryPath, iDLocaleFile);

        fs.readFile(iDLocaleFilePath, 'utf-8', (err, data) => {
            if (err) throw err;

            const json = JSON.parse(data);
            const newData = {};

            if (typeof json.presets === 'undefined') {
                return false;
            }

            if (typeof json.presets.presets !== 'undefined') {
                newData.presets = json.presets.presets;
            }

            if (typeof json.presets.fields !== 'undefined') {
                newData.fields = json.presets.fields;
            }

            logger.debug(`creating ${finalLocaleFilePath}`);
            fs.writeFile(
                finalLocaleFilePath,
                JSON.stringify(newData)
            );
        });
    }
});

logger.info('End of the generation');
<|endoftext|>


<commit_before>/** Represents a single project in the work carousel */
import Parallax3D from '../parallax';

function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }

export class CarouselTag {
  constructor(elem) {
    this.elem = elem;
    this.name = elem.textContent;
    this.carousel = undefined; // Will be set when added to a carousel
    this.selected = false;

    this.elem.addEventListener('click', () => this.toggle());
  }

  select() {
    this.selected = true;
    if (this.carousel) this.carousel.filter(this);
  }
  deselect() {
    if (this.selected) {
      this.selected = false;
      if (this.carousel) this.carousel.clearFilter();
    }
  }
  toggle() {
    this[this.selected ? 'deselect' : 'select']();
  }

  fade() { this.elem.classList.add('faded'); }
  opacify() { this.elem.classList.remove('faded'); }
}

export class CarouselDots {
  constructor(elem) {
    this.elem = elem;
  }
}

export class CarouselCard {
  constructor(elem, tagNames) {
    this.elem = elem;
    this.elem.classList.add('carousel-card');
    // Move elem into a wrapper div
    this.wrapper = document.createElement('div');
    this.wrapper.className = 'carousel-card-wrapper';
    this.elem.parentNode.insertBefore(this.wrapper, this.elem);
    this.wrapper.appendChild(this.elem);

    this.tagNames = tagNames || elem.dataset.tags.split(',').map(t => t.trim());
    this.titleElem = elem.getElementsByTagName('h1')[0];

    this.carousel = undefined; // Will be set when added to a carousel
    this.hidden = false;
    this.transitionTime = this.baseTransitionTime = 0.5;
    this.parallax = new Parallax3D(this.elem);

    this.elem.addEventListener('click', () => this.clickHandler());
    this.wrapper.addEventListener('scroll', () => this.scrollHandler());

    // Title manipulation interface

    const _this2 = this;
    /**
     * Interface for updating styles on the title.
     * Examples:
     *   this.title.translate = '-10px'
     *   this.title.opacity = 0.5
     */
    this.title = {
      _elem: _this2.titleElem,
      display() { this._elem.className = 'in-initial'; },
      fadeIn() { this._elem.className = 'fade-in'; },
      fadeOut() { this._elem.className = 'fade-out'; },
      fadeInLeft() { this._elem.className = 'in-from-left'; },
      fadeInRight() { this._elem.className = 'in-from-right'; },
      fadeInTop() { this._elem.className = 'in-from-top'; },
      fadeOutLeft() { this._elem.className = 'out-to-left'; },
      fadeOutRight() { this._elem.className = 'out-to-right'; },
      fadeOutTop() { this._elem.className = 'out-to-top'; },
      moveDown() { this._elem.className = 'down'; },
      moveUp() { this._elem.className = 'up'; },
    };
  }

  /**
   * Update the card wrapper's CSS in order to reflect changes that were made to its JS
   * representation.
   */
  _applyTransform() {
    this.wrapper.style.transform = [
      'translateX(-50%)',
      `translateX(${this._translate})`,
    ].join(' ');
  }

  // Translation

  get translate() { return this._translate; }
  set translate(val) { this._translate = val; this._applyTransform(); }

  // Transition time

  get transitionTime() { return this._transitionTime; }
  set transitionTime(secs) {
    this._transitionTime = secs;
    this.wrapper.style.transition = `transform ${secs}s`;
  }

  setEnabled(enable = true) {
    const totalTime = this.transitionTime * 1000;
    const startTime = new Date();

    const existingRot = this.parallax.rotmax;
    const goal = enable ? 10 : 0;
    const changeNeeded = goal - existingRot;

    // Recursively adjust using setAnimationFrame until transitionTime is reached
    function step(parallax) {
      const timeElapsed = new Date() - startTime;
      // Still going
      if (timeElapsed < totalTime) {
        const progress = (timeElapsed / totalTime);
        parallax.rotmax = existingRot + (progress * changeNeeded);
        parallax.rotate();
        requestAnimationFrame(() => step(parallax));
      } else parallax.rotmax = goal;
    }
    step(this.parallax); // Start
  }
  disable() { this.setEnabled(false); }
  enable() { this.setEnabled(true); }

  hide() {
    this.hidden = true;
    this.elem.classList.add('hidden');
    // If this is centered
    const thisIndex = this.carousel.cards.indexOf(this);
    if (this.carousel.position === this.carousel.adjustIndex(thisIndex)) this.title.fadeOutTop();
  }
  show() {
    this.hidden = false;
    this.elem.classList.remove('hidden');
  }

  // Move the carousel so that this card is in the center
  centerSelf() {
    const thisIndex = this.carousel.cards.indexOf(this);
    this.carousel.position = this.carousel.adjustIndex(thisIndex);
  }

  // Bigger!
  expand() {
    // Prevents incorrect result when expanding and collapsing rapidly. Ensures that a pre-existing
    // timeout won't remove the expanded-x class after it's been added by this method.
    clearInterval(this._expansionTimeout);

    this.elem.classList.add('expanded-x');
    this.title.moveDown();
    this.disable();
    this._expansionTimeout = setTimeout(() => {
      this.elem.classList.add('expanded-y');
      this._expansionTimeout = setTimeout(() => { this.elem.style.maxHeight = 'none'; this.elem.classList.remove('expanded-y'); }, 500);
      window.nav.disableScrollBinding = true;
    }, 500);

    this.expanded = true;
    if (this.carousel) this.carousel.expanded(this);
  }
  collapse() {
    clearInterval(this._expansionTimeout);

    this.elem.style.maxHeight = '';
    this.elem.classList.add('expanded-y');
    setTimeout(() => this.elem.classList.remove('expanded-y'), 10);
    document.getElementById('email').classList.remove('hidden');
    window.nav.disableScrollBinding = false;

    this._expansionTimeout = setTimeout(() => {
      this.elem.classList.remove('expanded-x');
      this.title.moveUp();
      this.enable();
    }, 500);

    this.expanded = false;
    if (this.carousel) this.carousel.collapsed(this);
  }
  toggleExpand() {
    this[this.expanded ? 'collapse' : 'expand']();
  }

  fade() { if (!this.hidden) this.elem.style.opacity = 0.5; }
  opacify() { this.elem.style.opacity = ''; }

  clickHandler() {
    if (this.carousel && !this.hidden) {
      const i1 = this.carousel.cards.indexOf(this);
      const i2 = this.carousel.adjustIndex(i1);
      if (this.carousel.position !== i2) this.centerSelf();
      else this.toggleExpand();
    } else if (!this.hidden) {
      this.toggleExpand();
    }
  }

  scrollHandler() {
    const transitionDistance = window.innerHeight / 10;
    const progress = Math.min(this.wrapper.scrollTop / transitionDistance, 1);

    const nav = document.getElementById('navigation');
    const tags = document.querySelector('.tags');
    nav.style.opacity = 0.5 * (1 - progress);
    tags.style.opacity = 1 - progress;

    // Turn off pointer events when halfway faded
    nav.style.pointerEvents = tags.style.pointerEvents = progress < 0.5 ? '' : 'none';
  }
}

/** Collects and orchestrates multiple CarouselCards */
export class Carousel {
  constructor(cards, dots, tags, emptyElem) {
    this.dots = dots;
    this.tags = tags;
    this.tags.forEach((t) => { t.carousel = this; });
    this.cards = cards;
    this.cards.forEach((c) => { c.carousel = this; });
    this.emptyElem = emptyElem;
    this.hiddenIndices = [];

    this._position = 0;
    this.cards[this._position].title.display();
    // Trigger immediate layout of cards
    this.position = this._position;

    window.addEventListener('keydown', (e) => {
      // Carousel left/right with arrow keys, but only if carousel is expanded.
      // Carousel is expanded when the parent is #work-wrapper, the parent is .screen if it's not
      const projectsContainer = document.getElementsByClassName('projects-container')[0];
      if (projectsContainer.parentNode.id === 'work-wrapper') {
        if (e.key === 'ArrowRight') this.right();
        if (e.key === 'ArrowLeft') this.left();
      }
    });
  }

  // Transition time logic

  get baseTransitionTime() {
    const answer = this.cards[0].baseTransitionTime;
    // Warn if not every card has the same baseTransitionTime
    if (!this.cards.every(card => card.baseTransitionTime === answer)) console.warn('Carousel.baseTransitionTime called but baseTransitionTime of cards is inconsistent');
    return answer;
  }
  get transitionTime() {
    const answer = this.cards[0].transitionTime;
    // Warn if not every card has the same transitionTime
    if (!this.cards.every(card => card.transitionTime === answer)) console.warn('Carousel.transitionTime called but transitionTime of cards is inconsistent');
    return answer;
  }
  set transitionTime(secs) {
    this.cards.forEach((c) => { c.transitionTime = secs; });
  }

  // Rearrange

  // Adjust an index based on the number of hidden cards that come before it
  adjustIndex(index) {
    return index - this.hiddenIndices.filter(i => i < index).length;
  }

  get position() { return this._position; }
  set position(pos) {
    (async () => {
      const delta = Math.abs(this._position - pos);

      // Adjust transition time based on how many cards we're jumping
      const btt = this.baseTransitionTime;
      this.transitionTime = ((0.5 * btt) * ((delta || 1) - 1)) + btt;

      // If any card is open, collapse it and wait
      if (delta > 0 && typeof this.expandedIndex !== 'undefined') {
        this.cards[this.expandedIndex].collapse();
        await delay(1000);
      }

      // Apply certain changes to each card
      this.cards.forEach((card, index) => {
        const adj = i => this.adjustIndex(i);
        // Calculate position we're going to, and parse old position from old transform
        const cardPosition = adj(index) - pos;
        const oldTranslation = (card.translate || `${index * 48}vw`).slice(0, -2);
        const oldPosition = [
          oldTranslation / 48, // Could be in its normal location
          (oldTranslation - 12.5) / 48, // Could be to the right of an expanded card
          (oldTranslation + 12.5) / 48, // Could be to the left of an expanded card
        ].find(i => Math.floor(i) === i); // Whichever case yields an integer solution for position
        // Calculate an offset based on position relative to any expanded card
        let cardOffset = 0;
        if (adj(index) > adj(this.expandedIndex)) cardOffset = 12.5;
        if (adj(index) < adj(this.expandedIndex)) cardOffset = -12.5;
        // Set new translation
        const cardTranslation = `${(48 * cardPosition) + cardOffset}vw`;
        card.translate = cardTranslation;

        // Expand / disable cards based on whether they're centered
        if (cardPosition === 0 && !card.expanded) card.enable();
        else card.disable(); // Not center card

        // Apply animations to titles
        if (cardPosition === 0) { // Center card
          if (oldPosition < 0) card.title.fadeInLeft(); // coming from left
          if (oldPosition > 0) card.title.fadeInRight(); // coming from right
        } else if (oldPosition === 0) {
          if (cardPosition < 0) card.title.fadeOutLeft(); // going left
          if (cardPosition > 0) card.title.fadeOutRight(); // going right
        }
      });
      // Update internally-stored position tracker
      this._position = pos;
    })();
  }

  expanded(card) {
    this.expandedIndex = this.cards.indexOf(card);
    this.position = this.position; // Re-layout
    this.cards.forEach((c) => {
      if (c !== card) c.fade();
    });
  }
  collapsed() {
    this.expandedIndex = undefined;
    setTimeout(() => {
      this.position = this.position; // Re-layout
      this.cards.forEach(c => c.opacify());
    }, 500);
  }

  get minIndex() { return 0; } // eslint-disable-line class-methods-use-this
  get maxIndex() { return Math.max(this.cards.length - this.hiddenIndices.length - 1, 0); }

  left() {
    if (this.position > this.minIndex) this.position -= 1;
  }
  right() {
    if (this.position < this.maxIndex) this.position += 1;
  }

  async _filter(cardsToRemove) {
    // If the expanded card needs to be hidden, collapse it first
    if (typeof this.expandedIndex !== 'undefined') {
      this.cards[this.expandedIndex].collapse();
      await delay(1000);
    }
    // Make sure the empty indicator isn't displayed if it shouldn't be
    if (cardsToRemove.length < this.cards.length) this.emptyElem.classList.remove('displayed');

    const selectedCard = this.cards.filter(c => !c.hidden)[this.position];
    // Hide cards
    cardsToRemove.forEach(c => c.hide()); // Play removed animation on affected cards
    if (cardsToRemove.length) await delay(1000);
    // Minimal movement (first non-hidden card after previously selected card)
    const newCard = this.cards.slice(this.cards.indexOf(selectedCard)).find(c => !c.hidden);
    // Re-layout
    this.hiddenIndices = cardsToRemove.map(c => this.cards.indexOf(c));
    this.position = this.adjustIndex(Math.max(this.cards.indexOf(newCard), 0));
    // Show message if no cards are displayed
    if (this.hiddenIndices.length === this.cards.length) this.emptyElem.classList.add('displayed');
    // Show non-hidden cards
    await delay(500);
    const previouslyHidden = this.cards.filter(c => c.hidden);
    this.cards.filter(c => !cardsToRemove.includes(c)).forEach(c => c.show());
    await delay(1000);
    const displayed = this.cards.filter(c => !c.hidden);
    const currentCard = displayed[this.position];
    if (previouslyHidden.includes(currentCard)) currentCard.title.fadeInTop();
  }

  filter(tag) {
    tag.opacify();
    this.tags.filter(t => t !== tag).forEach((t) => { t.selected = false; t.fade(); });
    const cardsToRemove = this.cards.filter(c => !c.tagNames.includes(tag.name));
    this._filter(cardsToRemove);
  }

  clearFilter() {
    this.tags.forEach(t => t.opacify());
    this._filter([]);
  }
}
<commit_msg>Create a .dot for each item in carousel<commit_after>/** Represents a single project in the work carousel */
import Parallax3D from '../parallax';

function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }

export class CarouselTag {
  constructor(elem) {
    this.elem = elem;
    this.name = elem.textContent;
    this.carousel = undefined; // Will be set when added to a carousel
    this.selected = false;

    this.elem.addEventListener('click', () => this.toggle());
  }

  select() {
    this.selected = true;
    if (this.carousel) this.carousel.filter(this);
  }
  deselect() {
    if (this.selected) {
      this.selected = false;
      if (this.carousel) this.carousel.clearFilter();
    }
  }
  toggle() {
    this[this.selected ? 'deselect' : 'select']();
  }

  fade() { this.elem.classList.add('faded'); }
  opacify() { this.elem.classList.remove('faded'); }
}

export class CarouselDots {
  constructor(elem) {
    this.elem = elem;
    this.carousel = undefined; // Will be set when added to a carousel
  }

  init() {
    this.dots = this.carousel.cards.map((c, i) => {
      const e = document.createElement('div');
      e.dataset.index = i;
      e.className = 'dot';
      return e;
    });
    this.dots.forEach(d => this.elem.appendChild(d));
  }
}

export class CarouselCard {
  constructor(elem, tagNames) {
    this.elem = elem;
    this.elem.classList.add('carousel-card');
    // Move elem into a wrapper div
    this.wrapper = document.createElement('div');
    this.wrapper.className = 'carousel-card-wrapper';
    this.elem.parentNode.insertBefore(this.wrapper, this.elem);
    this.wrapper.appendChild(this.elem);

    this.tagNames = tagNames || elem.dataset.tags.split(',').map(t => t.trim());
    this.titleElem = elem.getElementsByTagName('h1')[0];

    this.carousel = undefined; // Will be set when added to a carousel
    this.hidden = false;
    this.transitionTime = this.baseTransitionTime = 0.5;
    this.parallax = new Parallax3D(this.elem);

    this.elem.addEventListener('click', () => this.clickHandler());
    this.wrapper.addEventListener('scroll', () => this.scrollHandler());

    // Title manipulation interface

    const _this2 = this;
    /**
     * Interface for updating styles on the title.
     * Examples:
     *   this.title.translate = '-10px'
     *   this.title.opacity = 0.5
     */
    this.title = {
      _elem: _this2.titleElem,
      display() { this._elem.className = 'in-initial'; },
      fadeIn() { this._elem.className = 'fade-in'; },
      fadeOut() { this._elem.className = 'fade-out'; },
      fadeInLeft() { this._elem.className = 'in-from-left'; },
      fadeInRight() { this._elem.className = 'in-from-right'; },
      fadeInTop() { this._elem.className = 'in-from-top'; },
      fadeOutLeft() { this._elem.className = 'out-to-left'; },
      fadeOutRight() { this._elem.className = 'out-to-right'; },
      fadeOutTop() { this._elem.className = 'out-to-top'; },
      moveDown() { this._elem.className = 'down'; },
      moveUp() { this._elem.className = 'up'; },
    };
  }

  /**
   * Update the card wrapper's CSS in order to reflect changes that were made to its JS
   * representation.
   */
  _applyTransform() {
    this.wrapper.style.transform = [
      'translateX(-50%)',
      `translateX(${this._translate})`,
    ].join(' ');
  }

  // Translation

  get translate() { return this._translate; }
  set translate(val) { this._translate = val; this._applyTransform(); }

  // Transition time

  get transitionTime() { return this._transitionTime; }
  set transitionTime(secs) {
    this._transitionTime = secs;
    this.wrapper.style.transition = `transform ${secs}s`;
  }

  setEnabled(enable = true) {
    const totalTime = this.transitionTime * 1000;
    const startTime = new Date();

    const existingRot = this.parallax.rotmax;
    const goal = enable ? 10 : 0;
    const changeNeeded = goal - existingRot;

    // Recursively adjust using setAnimationFrame until transitionTime is reached
    function step(parallax) {
      const timeElapsed = new Date() - startTime;
      // Still going
      if (timeElapsed < totalTime) {
        const progress = (timeElapsed / totalTime);
        parallax.rotmax = existingRot + (progress * changeNeeded);
        parallax.rotate();
        requestAnimationFrame(() => step(parallax));
      } else parallax.rotmax = goal;
    }
    step(this.parallax); // Start
  }
  disable() { this.setEnabled(false); }
  enable() { this.setEnabled(true); }

  hide() {
    this.hidden = true;
    this.elem.classList.add('hidden');
    // If this is centered
    const thisIndex = this.carousel.cards.indexOf(this);
    if (this.carousel.position === this.carousel.adjustIndex(thisIndex)) this.title.fadeOutTop();
  }
  show() {
    this.hidden = false;
    this.elem.classList.remove('hidden');
  }

  // Move the carousel so that this card is in the center
  centerSelf() {
    const thisIndex = this.carousel.cards.indexOf(this);
    this.carousel.position = this.carousel.adjustIndex(thisIndex);
  }

  // Bigger!
  expand() {
    // Prevents incorrect result when expanding and collapsing rapidly. Ensures that a pre-existing
    // timeout won't remove the expanded-x class after it's been added by this method.
    clearInterval(this._expansionTimeout);

    this.elem.classList.add('expanded-x');
    this.title.moveDown();
    this.disable();
    this._expansionTimeout = setTimeout(() => {
      this.elem.classList.add('expanded-y');
      this._expansionTimeout = setTimeout(() => { this.elem.style.maxHeight = 'none'; this.elem.classList.remove('expanded-y'); }, 500);
      window.nav.disableScrollBinding = true;
    }, 500);

    this.expanded = true;
    if (this.carousel) this.carousel.expanded(this);
  }
  collapse() {
    clearInterval(this._expansionTimeout);

    this.elem.style.maxHeight = '';
    this.elem.classList.add('expanded-y');
    setTimeout(() => this.elem.classList.remove('expanded-y'), 10);
    document.getElementById('email').classList.remove('hidden');
    window.nav.disableScrollBinding = false;

    this._expansionTimeout = setTimeout(() => {
      this.elem.classList.remove('expanded-x');
      this.title.moveUp();
      this.enable();
    }, 500);

    this.expanded = false;
    if (this.carousel) this.carousel.collapsed(this);
  }
  toggleExpand() {
    this[this.expanded ? 'collapse' : 'expand']();
  }

  fade() { if (!this.hidden) this.elem.style.opacity = 0.5; }
  opacify() { this.elem.style.opacity = ''; }

  clickHandler() {
    if (this.carousel && !this.hidden) {
      const i1 = this.carousel.cards.indexOf(this);
      const i2 = this.carousel.adjustIndex(i1);
      if (this.carousel.position !== i2) this.centerSelf();
      else this.toggleExpand();
    } else if (!this.hidden) {
      this.toggleExpand();
    }
  }

  scrollHandler() {
    const transitionDistance = window.innerHeight / 10;
    const progress = Math.min(this.wrapper.scrollTop / transitionDistance, 1);

    const nav = document.getElementById('navigation');
    const tags = document.querySelector('.tags');
    nav.style.opacity = 0.5 * (1 - progress);
    tags.style.opacity = 1 - progress;

    // Turn off pointer events when halfway faded
    nav.style.pointerEvents = tags.style.pointerEvents = progress < 0.5 ? '' : 'none';
  }
}

/** Collects and orchestrates multiple CarouselCards */
export class Carousel {
  constructor(cards, dots, tags, emptyElem) {
    this.dots = dots;
    this.dots.carousel = this;
    this.tags = tags;
    this.tags.forEach((t) => { t.carousel = this; });
    this.cards = cards;
    this.cards.forEach((c) => { c.carousel = this; });
    this.emptyElem = emptyElem;
    this.dots.init();
    this.hiddenIndices = [];

    this._position = 0;
    this.cards[this._position].title.display();
    // Trigger immediate layout of cards
    this.position = this._position;

    window.addEventListener('keydown', (e) => {
      // Carousel left/right with arrow keys, but only if carousel is expanded.
      // Carousel is expanded when the parent is #work-wrapper, the parent is .screen if it's not
      const projectsContainer = document.getElementsByClassName('projects-container')[0];
      if (projectsContainer.parentNode.id === 'work-wrapper') {
        if (e.key === 'ArrowRight') this.right();
        if (e.key === 'ArrowLeft') this.left();
      }
    });
  }

  // Transition time logic

  get baseTransitionTime() {
    const answer = this.cards[0].baseTransitionTime;
    // Warn if not every card has the same baseTransitionTime
    if (!this.cards.every(card => card.baseTransitionTime === answer)) console.warn('Carousel.baseTransitionTime called but baseTransitionTime of cards is inconsistent');
    return answer;
  }
  get transitionTime() {
    const answer = this.cards[0].transitionTime;
    // Warn if not every card has the same transitionTime
    if (!this.cards.every(card => card.transitionTime === answer)) console.warn('Carousel.transitionTime called but transitionTime of cards is inconsistent');
    return answer;
  }
  set transitionTime(secs) {
    this.cards.forEach((c) => { c.transitionTime = secs; });
  }

  // Rearrange

  // Adjust an index based on the number of hidden cards that come before it
  adjustIndex(index) {
    return index - this.hiddenIndices.filter(i => i < index).length;
  }

  get position() { return this._position; }
  set position(pos) {
    (async () => {
      const delta = Math.abs(this._position - pos);

      // Adjust transition time based on how many cards we're jumping
      const btt = this.baseTransitionTime;
      this.transitionTime = ((0.5 * btt) * ((delta || 1) - 1)) + btt;

      // If any card is open, collapse it and wait
      if (delta > 0 && typeof this.expandedIndex !== 'undefined') {
        this.cards[this.expandedIndex].collapse();
        await delay(1000);
      }

      // Apply certain changes to each card
      this.cards.forEach((card, index) => {
        const adj = i => this.adjustIndex(i);
        // Calculate position we're going to, and parse old position from old transform
        const cardPosition = adj(index) - pos;
        const oldTranslation = (card.translate || `${index * 48}vw`).slice(0, -2);
        const oldPosition = [
          oldTranslation / 48, // Could be in its normal location
          (oldTranslation - 12.5) / 48, // Could be to the right of an expanded card
          (oldTranslation + 12.5) / 48, // Could be to the left of an expanded card
        ].find(i => Math.floor(i) === i); // Whichever case yields an integer solution for position
        // Calculate an offset based on position relative to any expanded card
        let cardOffset = 0;
        if (adj(index) > adj(this.expandedIndex)) cardOffset = 12.5;
        if (adj(index) < adj(this.expandedIndex)) cardOffset = -12.5;
        // Set new translation
        const cardTranslation = `${(48 * cardPosition) + cardOffset}vw`;
        card.translate = cardTranslation;

        // Expand / disable cards based on whether they're centered
        if (cardPosition === 0 && !card.expanded) card.enable();
        else card.disable(); // Not center card

        // Apply animations to titles
        if (cardPosition === 0) { // Center card
          if (oldPosition < 0) card.title.fadeInLeft(); // coming from left
          if (oldPosition > 0) card.title.fadeInRight(); // coming from right
        } else if (oldPosition === 0) {
          if (cardPosition < 0) card.title.fadeOutLeft(); // going left
          if (cardPosition > 0) card.title.fadeOutRight(); // going right
        }
      });
      // Update internally-stored position tracker
      this._position = pos;
    })();
  }

  expanded(card) {
    this.expandedIndex = this.cards.indexOf(card);
    this.position = this.position; // Re-layout
    this.cards.forEach((c) => {
      if (c !== card) c.fade();
    });
  }
  collapsed() {
    this.expandedIndex = undefined;
    setTimeout(() => {
      this.position = this.position; // Re-layout
      this.cards.forEach(c => c.opacify());
    }, 500);
  }

  get minIndex() { return 0; } // eslint-disable-line class-methods-use-this
  get maxIndex() { return Math.max(this.cards.length - this.hiddenIndices.length - 1, 0); }

  left() {
    if (this.position > this.minIndex) this.position -= 1;
  }
  right() {
    if (this.position < this.maxIndex) this.position += 1;
  }

  async _filter(cardsToRemove) {
    // If the expanded card needs to be hidden, collapse it first
    if (typeof this.expandedIndex !== 'undefined') {
      this.cards[this.expandedIndex].collapse();
      await delay(1000);
    }
    // Make sure the empty indicator isn't displayed if it shouldn't be
    if (cardsToRemove.length < this.cards.length) this.emptyElem.classList.remove('displayed');

    const selectedCard = this.cards.filter(c => !c.hidden)[this.position];
    // Hide cards
    cardsToRemove.forEach(c => c.hide()); // Play removed animation on affected cards
    if (cardsToRemove.length) await delay(1000);
    // Minimal movement (first non-hidden card after previously selected card)
    const newCard = this.cards.slice(this.cards.indexOf(selectedCard)).find(c => !c.hidden);
    // Re-layout
    this.hiddenIndices = cardsToRemove.map(c => this.cards.indexOf(c));
    this.position = this.adjustIndex(Math.max(this.cards.indexOf(newCard), 0));
    // Show message if no cards are displayed
    if (this.hiddenIndices.length === this.cards.length) this.emptyElem.classList.add('displayed');
    // Show non-hidden cards
    await delay(500);
    const previouslyHidden = this.cards.filter(c => c.hidden);
    this.cards.filter(c => !cardsToRemove.includes(c)).forEach(c => c.show());
    await delay(1000);
    const displayed = this.cards.filter(c => !c.hidden);
    const currentCard = displayed[this.position];
    if (previouslyHidden.includes(currentCard)) currentCard.title.fadeInTop();
  }

  filter(tag) {
    tag.opacify();
    this.tags.filter(t => t !== tag).forEach((t) => { t.selected = false; t.fade(); });
    const cardsToRemove = this.cards.filter(c => !c.tagNames.includes(tag.name));
    this._filter(cardsToRemove);
  }

  clearFilter() {
    this.tags.forEach(t => t.opacify());
    this._filter([]);
  }
}
<|endoftext|>


<commit_before>import Ember from 'ember';
import C from 'ui/utils/constants';
import Util from 'ui/utils/util';

export default Ember.Mixin.create({
  type: 'user_or_org',
  login: null, // This can't be called id because Ember uses that property..
  size: 40,
  fallback: null,

  name: null,
  description: 'Loading...',
  org: null,

  _avatarUrl: null,

  isTeam: Ember.computed.equal('type','team'),

  loginOrTypeChanged: function() {
    var login = this.get('login');
    var type = this.get('type');
    var fallback = this.get('fallback');

    if ( !type || !login )
    {
      return;
    }

    this.setProperties({
      'name': login,
      'description': 'Loading...',
      '_avatarUrl': null,
    });

    this.get('github').find(type, login).then((entry) => {
      this.setProperties({
        name: entry.name,
        description: entry.description,
        _avatarUrl: entry.avatarUrl,
        org: entry.org,
      });
    }).catch((err) => {
      if ( fallback && this.get('type') === 'team' )
      {
        this.setProperties({
          name: "(A team you don't have access to)",
          org: fallback,
          _avatarUrl: null
        });
      }
      else
      {
        this.setProperties({
          name: login,
          description: 'Error: ' + err,
          org: null,
          _avatarUrl: null
        });
      }
    });
  }.observes('login','type','fallback').on('init'),

  avatarUrl: function(){
    var url = this.get('_avatarUrl');
    if ( url )
    {
      url = Util.addQueryParam(url, 's', this.get('size'));
    }
    return url;
  }.property('_avatarUrl','size'),

  orgUrl: function() {
    var org = this.get('org');
    if ( org && this.get('type') === 'team' )
    {
      return C.GITHUB.URL + 'orgs/' + encodeURIComponent(org);
    }
  }.property('type','org'),

  url: function() {
    if ( this.get('type') === 'team' )
    {
      var entry = this.get('github').teamById(this.get('login'));
      if ( entry && entry.slug )
      {
        return C.GITHUB.URL + 'orgs/' + encodeURIComponent(entry.org) + '/teams/' + encodeURIComponent(entry.slug);
      }
    }
    else
    {
      return C.GITHUB.URL + encodeURIComponent(this.get('login'));
    }
  }.property('login'),
});
<commit_msg>Resize github icon<commit_after>import Ember from 'ember';
import C from 'ui/utils/constants';
import Util from 'ui/utils/util';

export default Ember.Mixin.create({
  type: 'user_or_org',
  login: null, // This can't be called id because Ember uses that property..
  size: 36,
  fallback: null,

  name: null,
  description: 'Loading...',
  org: null,

  _avatarUrl: null,

  isTeam: Ember.computed.equal('type','team'),

  loginOrTypeChanged: function() {
    var login = this.get('login');
    var type = this.get('type');
    var fallback = this.get('fallback');

    if ( !type || !login )
    {
      return;
    }

    this.setProperties({
      'name': login,
      'description': 'Loading...',
      '_avatarUrl': null,
    });

    this.get('github').find(type, login).then((entry) => {
      this.setProperties({
        name: entry.name,
        description: entry.description,
        _avatarUrl: entry.avatarUrl,
        org: entry.org,
      });
    }).catch((err) => {
      if ( fallback && this.get('type') === 'team' )
      {
        this.setProperties({
          name: "(A team you don't have access to)",
          org: fallback,
          _avatarUrl: null
        });
      }
      else
      {
        this.setProperties({
          name: login,
          description: 'Error: ' + err,
          org: null,
          _avatarUrl: null
        });
      }
    });
  }.observes('login','type','fallback').on('init'),

  avatarUrl: function(){
    var url = this.get('_avatarUrl');
    if ( url )
    {
      url = Util.addQueryParam(url, 's', this.get('size') * 2);`

    }
    return url;
  }.property('_avatarUrl','size'),

  orgUrl: function() {
    var org = this.get('org');
    if ( org && this.get('type') === 'team' )
    {
      return C.GITHUB.URL + 'orgs/' + encodeURIComponent(org);
    }
  }.property('type','org'),

  url: function() {
    if ( this.get('type') === 'team' )
    {
      var entry = this.get('github').teamById(this.get('login'));
      if ( entry && entry.slug )
      {
        return C.GITHUB.URL + 'orgs/' + encodeURIComponent(entry.org) + '/teams/' + encodeURIComponent(entry.slug);
      }
    }
    else
    {
      return C.GITHUB.URL + encodeURIComponent(this.get('login'));
    }
  }.property('login'),
});
<|endoftext|>


<commit_before>//**************************************************************************************
const Browser = {
	IE: "Internet Explorer",
	Safari: "Safari",
	Edge: "Edge",
	Chrome: "Chrome",
	Firefox: "Firefox Mozilla",
};
//**************************************************************************************
function BrowserInfo()
{
	const res = {
		name: "",
		version: ""
	};

	const userAgent = self.navigator.userAgent;

	switch(true)
	{
		case (/edge\/([\d\.]+)/i.test(userAgent)):
			res.name = Browser.Edge;
			res.version = /edge\/([\d\.]+)/i.exec(userAgent)[1];
			break;
		case (/msie/i.test(userAgent)):
			res.name = Browser.IE;
			res.version = /msie ([\d\.]+)/i.exec(userAgent)[1];
			break;
		case (/Trident/i.test(userAgent)):
			res.name = Browser.IE;
			res.version = /rv:([\d\.]+)/i.exec(userAgent)[1];
			break;
		case (/chrome/i.test(userAgent)):
			res.name = Browser.Chrome;
			res.version = /chrome\/([\d\.]+)/i.exec(userAgent)[1];
			break;
		case (/safari/i.test(userAgent)):
			res.name = Browser.Safari;
			res.version = /([\d\.]+) safari/i.exec(userAgent)[1];
			break;
		case (/firefox/i.test(userAgent)):
			res.name = Browser.Firefox;
			res.version = /firefox\/([\d\.]+)/i.exec(userAgent)[1];
			break;
		default:
			console.log("UNKNOWN BROWSER");
	}

	return res;
}
//**************************************************************************************
function getRandomArbitrary(min, max)
{
	return Math.random() * (max - min) + min;
}
//**************************************************************************************
function getRandomValues(buffer)
{
	Math.seedrandom(self.location.href, { entropy: true });

	const buf = new Uint8Array(buffer.buffer);
	let i = 0;

	while(i < buf.length)
		buf[i++] = getRandomArbitrary(0, 255);

	return buffer;
}
//**************************************************************************************
export default function linerWorkerInit(path)
{
	const _self = self;
	if(!(_self.crypto || _self.msCrypto))
	{
		importScripts("//cdnjs.cloudflare.com/ajax/libs/seedrandom/2.4.0/seedrandom.min.js");
		_self.crypto = { getRandomValues: getRandomValues };
		Object.freeze(_self.crypto);
	}

	importScripts(path + "webcrypto-liner.lib.min.js");

	switch(BrowserInfo().name)
	{
		case Browser.IE:
			importScripts(path + "promise.min.js");
		case Browser.Edge:
		case Browser.Safari:
			importScripts(path + "asmcrypto.min.js");
			importScripts(path + "elliptic.min.js");
		default:
			console.log("UNKNOWN BROWSER, CAN NOT INITIALIZE");
	}
}
//**************************************************************************************
<commit_msg>Solving problems with Math initialization for some complicated cases<commit_after>//**************************************************************************************
const Browser = {
	IE: "Internet Explorer",
	Safari: "Safari",
	Edge: "Edge",
	Chrome: "Chrome",
	Firefox: "Firefox Mozilla",
};
//**************************************************************************************
function BrowserInfo()
{
	const res = {
		name: "",
		version: ""
	};

	const userAgent = self.navigator.userAgent;

	switch(true)
	{
		case (/edge\/([\d\.]+)/i.test(userAgent)):
			res.name = Browser.Edge;
			res.version = /edge\/([\d\.]+)/i.exec(userAgent)[1];
			break;
		case (/msie/i.test(userAgent)):
			res.name = Browser.IE;
			res.version = /msie ([\d\.]+)/i.exec(userAgent)[1];
			break;
		case (/Trident/i.test(userAgent)):
			res.name = Browser.IE;
			res.version = /rv:([\d\.]+)/i.exec(userAgent)[1];
			break;
		case (/chrome/i.test(userAgent)):
			res.name = Browser.Chrome;
			res.version = /chrome\/([\d\.]+)/i.exec(userAgent)[1];
			break;
		case (/safari/i.test(userAgent)):
			res.name = Browser.Safari;
			res.version = /([\d\.]+) safari/i.exec(userAgent)[1];
			break;
		case (/firefox/i.test(userAgent)):
			res.name = Browser.Firefox;
			res.version = /firefox\/([\d\.]+)/i.exec(userAgent)[1];
			break;
		default:
			console.log("UNKNOWN BROWSER");
	}

	return res;
}
//**************************************************************************************
function getRandomArbitrary(min, max)
{
	return Math.random() * (max - min) + min;
}
//**************************************************************************************
function getRandomValues(buffer)
{
	self.Math.seedrandom(self.location.href, { entropy: true });

	const buf = new Uint8Array(buffer.buffer);
	let i = 0;

	while(i < buf.length)
		buf[i++] = getRandomArbitrary(0, 255);

	return buffer;
}
//**************************************************************************************
export default function linerWorkerInit(path)
{
	const _self = self;
	if(!(_self.crypto || _self.msCrypto))
	{
		importScripts("//cdnjs.cloudflare.com/ajax/libs/seedrandom/2.4.0/seedrandom.min.js");
		_self.crypto = { getRandomValues: getRandomValues };
		Object.freeze(_self.crypto);
	}

	importScripts(path + "webcrypto-liner.lib.min.js");

	switch(BrowserInfo().name)
	{
		case Browser.IE:
			importScripts(path + "promise.min.js");
		case Browser.Edge:
		case Browser.Safari:
			importScripts(path + "asmcrypto.min.js");
			importScripts(path + "elliptic.min.js");
		default:
	}
}
//**************************************************************************************
<|endoftext|>


<commit_before>
var DomTable = (function(){
    "use strict";
    /***
     *
     * @param {Array[]} data ordered data to pump into table body
     * @param {Object} metadata object containing metadata of table
     * @param {String[]} metadata.cols cols of table
     * @param {String} metadata.id table id
     * @param {String[]} [metadata.tableClass] class used in html
     * @param {String[]} [header] string to be used as Header in table, if not stated then table will not have Header
     * @param {String[]} [footer] string to be used as footer, to have empty footer, simply use an empty element in array
     * eg. ["", "halo", ""] will have 3 elements in footer, 2 of them being empty
     */
    function createFlexTable(body, metadata, header, footer){

        var tableClasses = (metadata.tableClass) ? metadata.tableClass + " flex-table" : "flex-table";

        var $tableContainer = $("

", {class: "flex-table-container"});
        var $table = $("

", {class: tableClasses, id: metadata.id});
        var $body = createFlexTableTopGroup(body, metadata.cols, "body");

        if (header) {
            var $header = createFlexTableTopGroup([header], metadata.cols, "header");
            $header.appendTo($table);
        }

        $body.appendTo($table);

        if (footer) {
            var $footer = createFlexTableTopGroup([footer], metadata.cols, "footer");
            $footer.appendTo($table);
        }

        $table.appendTo($tableContainer);

        return $tableContainer;
    }

    /***
     *
     * @param {object[][]} data header strings
     * @param {String[]} metadata cols name
     * @param {"header"\"footer"|"body"} opt optional arg to specify which type of element to create, default to header
     */
    function createFlexTableTopGroup(data, metadata, opt){

        var $outer = function(){
            switch (opt) {
                case "body":
                    return $("

");
                case "footer":
                    return $("

");
                default :
                    return $("

");
            }
        }();

        for (var i = 0 ; i < data.length ; i++){
            var innerType = (opt === "body") ? "data" : "header";
            var $tr = createFlexTableRow(data[i], metadata, innerType);
            $tr.appendTo($outer);
        }

        return $outer;
    }

    /***
     *
     * @param {object[]} data
     * @param {String[]} metadata cols name
     * @param {"header"|"data"} opt optional, default to "header"
     */
    function createFlexTableRow(data, metadata, opt){
        if (data.length !== metadata.length) {
            throw new Error("metadata and data does not match");
        }

        var isData = (opt === "data");

        var $tr = $("

", {class: "flex-tr"});
        for (var i = 0 ; i < data.length ; i++){
            var className = metadata[i].toLowerCase().replace(/\s/g, "-") + " flex-tr-child";
            var rowElement = (isData) ?
                $("	", {class: className, text: text.localize(data[i])}) :
                $("	", {class: className, text: text.localize(data[i])});
            rowElement.appendTo($tr);
        }

        return $tr;
    }

    return {
        createFlexTable: createFlexTable,
        createFlexTableRow: createFlexTableRow
    };
}());<commit_msg>should not translate backend data on client side<commit_after>
var DomTable = (function(){
    "use strict";
    /***
     *
     * @param {Array[]} data ordered data to pump into table body
     * @param {Object} metadata object containing metadata of table
     * @param {String[]} metadata.cols cols of table
     * @param {String} metadata.id table id
     * @param {String[]} [metadata.tableClass] class used in html
     * @param {String[]} [header] string to be used as Header in table, if not stated then table will not have Header
     * @param {String[]} [footer] string to be used as footer, to have empty footer, simply use an empty element in array
     * eg. ["", "halo", ""] will have 3 elements in footer, 2 of them being empty
     */
    function createFlexTable(body, metadata, header, footer){

        var tableClasses = (metadata.tableClass) ? metadata.tableClass + " flex-table" : "flex-table";

        var $tableContainer = $("

", {class: "flex-table-container"});
        var $table = $("

", {class: tableClasses, id: metadata.id});
        var $body = createFlexTableTopGroup(body, metadata.cols, "body");

        if (header) {
            var $header = createFlexTableTopGroup([header], metadata.cols, "header");
            $header.appendTo($table);
        }

        $body.appendTo($table);

        if (footer) {
            var $footer = createFlexTableTopGroup([footer], metadata.cols, "footer");
            $footer.appendTo($table);
        }

        $table.appendTo($tableContainer);

        return $tableContainer;
    }

    /***
     *
     * @param {object[][]} data header strings
     * @param {String[]} metadata cols name
     * @param {"header"\"footer"|"body"} opt optional arg to specify which type of element to create, default to header
     */
    function createFlexTableTopGroup(data, metadata, opt){

        var $outer = function(){
            switch (opt) {
                case "body":
                    return $("

");
                case "footer":
                    return $("

");
                default :
                    return $("

");
            }
        }();

        for (var i = 0 ; i < data.length ; i++){
            var innerType = (opt === "body") ? "data" : "header";
            var $tr = createFlexTableRow(data[i], metadata, innerType);
            $tr.appendTo($outer);
        }

        return $outer;
    }

    /***
     *
     * @param {object[]} data
     * @param {String[]} metadata cols name
     * @param {"header"|"data"} opt optional, default to "header"
     */
    function createFlexTableRow(data, metadata, opt){
        if (data.length !== metadata.length) {
            throw new Error("metadata and data does not match");
        }

        var isData = (opt === "data");

        var $tr = $("

", {class: "flex-tr"});
        for (var i = 0 ; i < data.length ; i++){
            var className = metadata[i].toLowerCase().replace(/\s/g, "-") + " flex-tr-child";
            var rowElement = (isData) ?
                $("	", {class: className, text: data[i]}) :
                $("	", {class: className, text: data[i]});
            rowElement.appendTo($tr);
        }

        return $tr;
    }

    return {
        createFlexTable: createFlexTable,
        createFlexTableRow: createFlexTableRow
    };
}());<|endoftext|>


<commit_before>/**
 * ByYearSpineList: SpineList extended for holding Spines by year
 */
 window.ByYearSpineList = window.SpineList.extend({
    model: Spine, // same

    initialize: function(models, options) {
    	// Backbone.Model.prototype.set.call(this, attributes, options);
    	window.SpineList.prototype.initialize.call(this, models, options);
        // additional bindings
        _.bindAll(this, 'getAvailableYears');
    },

    url: function(){
	    return '/simpleshelf/_design/simpleshelf/_view/by_year'
	},

    parse: function(response){
        var results = [], row, values;
        if (response.rows){
            for (var x = 0; x < response.rows.length; x++){
                row = response.rows[x];
                values = {
                	'_rev': row.value._rev,
                	'id': row.id,
                	'latestDateFinished': row.value.latestDateFinished,
                	'title': row.value.title,
                	'year': row.key
                };
                results.push(values);
            }
       }
       return results;
    },

    /**
     * Return sorted list of years with data
     */
    getAvailableYears: function(){
        var allYears = this.map(function(model){
            return model.get('year');
        });
        // sort, make uniq
        allYears.sort();
        return _.uniq(allYears, true);
    }

});
<commit_msg>Customize for handling years<commit_after>/**
 * ByYearSpineList: SpineList extended for holding Spines by year
 */
 window.ByYearSpineList = window.SpineList.extend({
    model: Spine, // same

    initialize: function(models, options) {
    	// Backbone.Model.prototype.set.call(this, attributes, options);
    	window.SpineList.prototype.initialize.call(this, models, options);
        // additional bindings
        _.bindAll(this, 'getAvailableYears');
    },

    url: function(){
	    return '/simpleshelf/_design/simpleshelf/_view/by_year'
	},

    parse: function(response){
        var results = [], row, values, year;
        if (response.rows){
            for (var x = 0; x < response.rows.length; x++){
                row = response.rows[x];
                year = parseInt(row.key);
                if (_.isNaN(year)){
                    year = null;
                }
                values = {
                	'_rev': row.value._rev,
                	'id': row.id,
                	'latestDateFinished': row.value.latestDateFinished,
                	'title': row.value.title,
                    'year': year
                };
                results.push(values);
            }
       }
       return results;
    },

    /**
     * Return sorted list of years with data
     */
    getAvailableYears: function(){
        var allYears = this.map(function(model){
            return model.get('year');
        });
        // sort, make uniq
        allYears.sort();
        return _.uniq(allYears, true);
    },

    /**
     * Return list of spines within given year
     */
    getSpinesByYear: function(year){
        return this.filter(function(model){
            if (model.get('year') == year) {
                return true;
            }
        });
    }

});
<|endoftext|>