<commit_before>const reglM = require('regl')
// use this one for server side render
// const regl = require('regl')(require('gl')(256, 256))
// use this one for rendering inside a specific canvas/element
// var regl = require('regl')(canvasOrElement)
import { bunnyData, bunnyData2, bunnyData3, sceneData } from '../common/data'
import { drawModel as _drawModel, draw as _draw, makeDrawCalls } from './draw'
import { params as cameraDefaults } from '../common/controls/orbitControls'
import camera from '../common/camera'

import normals from 'angle-normals'

const regl = reglM({extensions: 'oes_texture_float'})//FIXME: for shadows, is it widely supported
/*canvas: container,
  drawingBufferWidth: container.offsetWidth,
  drawingBufferHeight: container.offsetHeight})*/ //for editor

const {frame, clear} = regl
//const drawModel = _drawModel.bind(null, regl)
const draw = _draw.bind(null, regl)

import {controlsLoop as controlsLoop} from '../common/controls/controlsLoop'
import pickLoop from '../common/picking/pickLoop'

import computeBounds from '../common/bounds/computeBounds'// from 'vertices-bounding-box'
import computeTMatrixFromTransforms from '../common/utils/computeTMatrixFromTransforms'

import most from 'most'

import { interactionsFromEvents, pointerGestures } from '../common/interactions/pointerGestures'

/* --------------------- */

const container = document.querySelector('canvas')
//const container = document.querySelector('#drawHere')

import makeGrid from './grid'
import makeShadowPlane from './shadowPlane'
import makeTransformGizmo from './transformsGizmo'

const grid = makeGrid(160, 1)
const gizmo = makeTransformGizmo()
const shadowPlane = makeShadowPlane(160)

/* --------------------- */

function flatten (arr) {
  return arr.reduce(function (a, b) {
    return a.concat(b)
  }, [])
}

let fullData = {
  scene: sceneData,
  entities: flatten([bunnyData, bunnyData2, bunnyData3, grid, shadowPlane, gizmo])
}

// inject bounding box data
fullData.entities = fullData.entities.map(function (entity) {
  const bounds = computeBounds(entity)
  const result = Object.assign({}, entity, {bounds})
  console.log('data with bounds', result)
  return result
})

// inject object transformation matrix : costly : only do it when changes happened to objects
fullData.entities = fullData.entities.map(function (entity) {
  const modelMat = computeTMatrixFromTransforms(entity)
  const result = Object.assign({}, entity, {modelMat})
  console.log('result', result)
  return result
})

// compute normals when needed : costly : only do it when changes happened to objects
fullData.entities = fullData.entities.map(function (entity) {
  const buffer = regl.buffer
  const {geometry} = entity
  geometry.normals = geometry.cells && !geometry.normals ? normals(geometry.cells, geometry.positions) : geometry.normals
  const result = Object.assign({}, entity, {geometry})
  return result
})

//inject bactching/rendering data
const {hashStore, entities} = makeDrawCalls(regl, fullData)
fullData.entities = entities

/* ============================================ */

// main render function: data in, rendered frame out
function render (data) {
  clear({
    depth: 1,
    color: [1, 1, 1, 1]
  })

  draw(hashStore, data)
}

// dynamic drawing
/*frame((props, context) => {
  render(fullData)
})*/

// render one frame
// render(fullData)

// interactions : camera controls
const baseInteractions$ = interactionsFromEvents(container)
const gestures = pointerGestures(baseInteractions$)
const camMoves$ = controlsLoop({gestures}, {settings: cameraDefaults, camera}, fullData)

// interactions : picking
const picks$ = pickLoop({gestures}, fullData)
  .map(e => fullData)

// merge all the things that should trigger a re-render
most.merge(
  camMoves$,
  picks$
)
  .forEach(render)
<commit_msg>chore(proto1): added extra notes about overall pipeline<commit_after>const reglM = require('regl')
// use this one for server side render
// const regl = require('regl')(require('gl')(256, 256))
// use this one for rendering inside a specific canvas/element
// var regl = require('regl')(canvasOrElement)
import { bunnyData, bunnyData2, bunnyData3, sceneData } from '../common/data'
import { drawModel as _drawModel, draw as _draw, makeDrawCalls } from './draw'
import { params as cameraDefaults } from '../common/controls/orbitControls'
import camera from '../common/camera'
import normals from 'angle-normals'

const regl = reglM({extensions: 'oes_texture_float'})//FIXME: for shadows, is it widely supported
/*canvas: container,
  drawingBufferWidth: container.offsetWidth,
  drawingBufferHeight: container.offsetHeight})*/ //for editor

const {frame, clear} = regl
//const drawModel = _drawModel.bind(null, regl)
const draw = _draw.bind(null, regl)

import {controlsLoop as controlsLoop} from '../common/controls/controlsLoop'
import pickLoop from '../common/picking/pickLoop'

import computeBounds from '../common/bounds/computeBounds'// from 'vertices-bounding-box'
import computeTMatrixFromTransforms from '../common/utils/computeTMatrixFromTransforms'

import most from 'most'

import { interactionsFromEvents, pointerGestures } from '../common/interactions/pointerGestures'

/* --------------------- */

const container = document.querySelector('canvas')
//const container = document.querySelector('#drawHere')

import makeGrid from './grid'
import makeShadowPlane from './shadowPlane'
import makeTransformGizmo from './transformsGizmo'

const grid = makeGrid(160, 1)
const gizmo = makeTransformGizmo()
const shadowPlane = makeShadowPlane(160)

/* --------------------- */

/* Pipeline:
  - data => process (normals computation, color format conversion) => (drawCall generation) => drawCall

  - every object with a fundamentall different 'look' (beyond what can be done with shader parameters) => different (VS) & PS
  - even if regl can 'combine' various uniforms, attributes, props etc, the rule above still applies
*/

function flatten (arr) {
  return arr.reduce(function (a, b) {
    return a.concat(b)
  }, [])
}

let fullData = {
  scene: sceneData,
  entities: flatten([bunnyData, bunnyData2, bunnyData3, grid, shadowPlane, ])//gizmo])
}

// inject bounding box data
fullData.entities = fullData.entities.map(function (entity) {
  const bounds = computeBounds(entity)
  const result = Object.assign({}, entity, {bounds})
  console.log('data with bounds', result)
  return result
})

// inject object transformation matrix : costly : only do it when changes happened to objects
fullData.entities = fullData.entities.map(function (entity) {
  const modelMat = computeTMatrixFromTransforms(entity)
  const result = Object.assign({}, entity, {modelMat})
  console.log('result', result)
  return result
})

// compute normals when needed : costly : only do it when changes happened to objects
fullData.entities = fullData.entities.map(function (entity) {
  const buffer = regl.buffer
  const {geometry} = entity
  geometry.normals = geometry.cells && !geometry.normals ? normals(geometry.cells, geometry.positions) : geometry.normals
  const result = Object.assign({}, entity, {geometry})
  return result
})

//inject bactching/rendering data
const {hashStore, entities} = makeDrawCalls(regl, fullData)
fullData.entities = entities

/* ============================================ */

// main render function: data in, rendered frame out
function render (data) {
  clear({
    depth: 1,
    color: [1, 1, 1, 1]
  })

  draw(hashStore, data)
}

// dynamic drawing
/*frame((props, context) => {
  render(fullData)
})*/

// render one frame
// render(fullData)

// interactions : camera controls
const baseInteractions$ = interactionsFromEvents(container)
const gestures = pointerGestures(baseInteractions$)
const camMoves$ = controlsLoop({gestures}, {settings: cameraDefaults, camera}, fullData)

// interactions : picking
const picks$ = pickLoop({gestures}, fullData)
  .map(e => fullData)

// merge all the things that should trigger a re-render
most.merge(
  camMoves$,
  picks$
)
  .forEach(render)
<|endoftext|>


<commit_before>import LoggerInterface from './interface';
import Pool from '../lib/pool';
import * as util from '../lib/util';

/**
 * IndexedDB protocol
 * @class IndexedDBLogger
 */
export default class IndexedDBLogger extends LoggerInterface {
    /**
     * IndexedDB protocol constructor
     * @constructor
     * @param {String} namespace - namespace to use
     */
    constructor(...args) {
        super(...args);
    }

    /**
     * add a log record
     * @method _reocrd
     * @private
     * @parma {String} level - log level
     * @param {String} descriptor - to speed up search and improve understanding
     * @param {Mixed} [data] - additional data
     */
    _record(level, descriptor, data) {
        if (IndexedDBLogger.status !== LoggerInterface.STATUS.INITED) {
            IndexedDBLogger._pool.push(() => this._record(level, descriptor, data));
            if (IndexedDBLogger.status !== LoggerInterface.STATUS.INITING) {
                IndexedDBLogger.init();
            }
            return;
        }

        util.debug(this._namespace, level, descriptor, data);
        let transaction = IndexedDBLogger.db.transaction(['logs'], IDBTransaction.READ_WRITE || 'readwrite');
        transaction.onerror = event => util.throwError(event.target.error);

        let store = transaction.objectStore('logs');
        // should not contains any function in data
        // otherwise 'DOMException: Failed to execute 'add' on 'IDBObjectStore': An object could not be cloned.' will be thrown
        let request = store.add({
            time: Date.now(),
            level: level,
            namespace: this._namespace,
            descriptor: descriptor,
            data: util.filterFunction(data)
        });

        request.onerror = event => {
            IndexedDBLogger.status = LoggerInterface.STATUS.FAILED;
            util.throwError(event.target.error);
        };
    }

    /**
     * initialize protocol
     * @method init
     * @static
     * @param {String} database - database name to use
     */
    static init(database) {
        if (!IndexedDBLogger.support) {
            util.throwError('your platform does not support indexeddb protocol.');
        }

        if (IndexedDBLogger.status) {
            return false;
        }

        IndexedDBLogger._pool = IndexedDBLogger._pool || new Pool();
        IndexedDBLogger._database = database || 'logline';
        IndexedDBLogger.status = super.STATUS.INITING;

        IndexedDBLogger.request = window.indexedDB.open(IndexedDBLogger._database);
        IndexedDBLogger.request.onerror = event => util.throwError('protocol indexeddb is prevented.');
        IndexedDBLogger.request.onsuccess = event => {
            IndexedDBLogger.db = event.target.result;
            IndexedDBLogger.status = super.STATUS.INITED;
            IndexedDBLogger._pool.consume();
            // globally handle db request errors
            IndexedDBLogger.db.onerror = event => util.throwError(event.target.error);
        };
        IndexedDBLogger.request.onupgradeneeded = event => {
            // init dabasebase
            let db = event.target.result, store = db.createObjectStore('logs', { autoIncrement: true });
            store.createIndex('namespace', 'namespace', { unique: false });
            store.createIndex('level', 'level', { unique: false });
            store.createIndex('descriptor', 'descriptor', { unique: false });
            store.createIndex('data', 'data', { unique: false });
        };
    }

    /**
     * get logs in range
     * if from and end is not defined, will fetch full log
     * @method get
     * @static
     * @param {String} [from] - time from, unix time stamp or falsy
     * @param {String} [to] - time end, unix time stamp or falsy
     * @param {Function} readyFn - function to call back with logs as parameter
     */
    static get(from, to, readyFn) {
        if (IndexedDBLogger.status !== super.STATUS.INITED) {
            return IndexedDBLogger._pool.push(() => IndexedDBLogger.get(from, to, readyFn));
        }

        from = LoggerInterface.transTimeFormat(from);
        to = LoggerInterface.transTimeFormat(to);

        let store = IndexedDBLogger._getTransactionStore(IDBTransaction.READ_ONLY || 'readonly'),
            request = store.openCursor(),
            logs = [];

        request.onsuccess = event => {
            var cursor = event.target.result;
            if (cursor) {
                if ((from && cursor.value.time < from) || (to && cursor.value.time > to)) {
                    cursor.continue();
                }

                logs.push({
                    time: cursor.value.time,
                    level: cursor.value.level,
                    namespace: cursor.value.namespace,
                    descriptor: cursor.value.descriptor,
                    data: cursor.value.data
                });
                cursor.continue();
            }
            else {
                readyFn(logs);
            }
        };

        request.onerror = event => util.throwError('failed to literat on logs from database.');
    }

    /**
     * clean logs = keep limited logs
     * @method keep
     * @static
     * @param {Number} daysToMaintain - keep logs within days
     */
    static keep(daysToMaintain) {
        if (IndexedDBLogger.status !== super.STATUS.INITED) {
            return IndexedDBLogger._pool.push(() => IndexedDBLogger.keep(daysToMaintain));
        }

        let store = IndexedDBLogger._getTransactionStore(IDBTransaction.READ_WRITE);
        if (!daysToMaintain) {
            let request = store.clear().onerror = event => util.throwError(event.target.error);
        }
        else {
            let range = (Date.now() - (daysToMaintain || 2) * 24 * 3600 * 1000);
            let request = store.openCursor();
            request.onsuccess = event => {
                let cursor = event.target.result;
                if (cursor && cursor.value.time < range) {
                    store.delete(cursor.primaryKey);
                    cursor.continue();
                }
            };
            request.onerror = event => util.throwError('unable to locate logs earlier than ' + daysToMaintain + 'd.');
        }
    }

    /**
     * delete log database
     * @method clean
     * @static
     */
    static clean() {
        if (IndexedDBLogger.status !== super.STATUS.INITED) {
            return IndexedDBLogger._pool.push(() => IndexedDBLogger.clean());
        }

        // database can be removed only after all connections are closed
        IndexedDBLogger.db.close();
        let request = window.indexedDB.deleteDatabase(IndexedDBLogger._database);
        request.onerror = event => util.throwError(event.target.error);
        /* eslint no-unused-vars: "off" */
        request.onsuccess = event => {
            delete IndexedDBLogger.status;
            delete IndexedDBLogger.db;
        };
    }

    /**
     * get internal transaction store
     * @method _getTransactionStore
     * @private
     * @static
     * @param {String} mode - transaction mode
     * @return {Object} - internal object store
     */
    static _getTransactionStore(mode) {
        if (IndexedDBLogger.db) {
            let transaction = IndexedDBLogger.db.transaction(['logs'], mode || IDBTransaction.READ_WRITE || 'readwrite');
            transaction.onerror = event => util.throwError(event.target.error);
            return transaction.objectStore('logs');
        }
        else {
            util.throwError('log database is not created or connections are closed, considering init it.');
        }
    }

    /**
     * detect support situation
     * @prop {Boolean} support
     */
    static get support() {
        return !!(window.indexedDB && window.IDBTransaction && window.IDBKeyRange);
    }
}
<commit_msg>prefer getAll API instead cursor in IndexedDB store<commit_after>import LoggerInterface from './interface';
import Pool from '../lib/pool';
import * as util from '../lib/util';

/**
 * IndexedDB protocol
 * @class IndexedDBLogger
 */
export default class IndexedDBLogger extends LoggerInterface {
    /**
     * IndexedDB protocol constructor
     * @constructor
     * @param {String} namespace - namespace to use
     */
    constructor(...args) {
        super(...args);
    }

    /**
     * add a log record
     * @method _reocrd
     * @private
     * @parma {String} level - log level
     * @param {String} descriptor - to speed up search and improve understanding
     * @param {Mixed} [data] - additional data
     */
    _record(level, descriptor, data) {
        if (IndexedDBLogger.status !== LoggerInterface.STATUS.INITED) {
            IndexedDBLogger._pool.push(() => this._record(level, descriptor, data));
            if (IndexedDBLogger.status !== LoggerInterface.STATUS.INITING) {
                IndexedDBLogger.init();
            }
            return;
        }

        util.debug(this._namespace, level, descriptor, data);
        let transaction = IndexedDBLogger.db.transaction(['logs'], IDBTransaction.READ_WRITE || 'readwrite');
        transaction.onerror = event => util.throwError(event.target.error);

        let store = transaction.objectStore('logs');
        // should not contains any function in data
        // otherwise 'DOMException: Failed to execute 'add' on 'IDBObjectStore': An object could not be cloned.' will be thrown
        let request = store.add({
            time: Date.now(),
            level: level,
            namespace: this._namespace,
            descriptor: descriptor,
            data: util.filterFunction(data)
        });

        request.onerror = event => {
            IndexedDBLogger.status = LoggerInterface.STATUS.FAILED;
            util.throwError(event.target.error);
        };
    }

    /**
     * initialize protocol
     * @method init
     * @static
     * @param {String} database - database name to use
     */
    static init(database) {
        if (!IndexedDBLogger.support) {
            util.throwError('your platform does not support indexeddb protocol.');
        }

        if (IndexedDBLogger.status) {
            return false;
        }

        IndexedDBLogger._pool = IndexedDBLogger._pool || new Pool();
        IndexedDBLogger._database = database || 'logline';
        IndexedDBLogger.status = super.STATUS.INITING;

        IndexedDBLogger.request = window.indexedDB.open(IndexedDBLogger._database);
        IndexedDBLogger.request.onerror = event => util.throwError('protocol indexeddb is prevented.');
        IndexedDBLogger.request.onsuccess = event => {
            IndexedDBLogger.db = event.target.result;
            IndexedDBLogger.status = super.STATUS.INITED;
            IndexedDBLogger._pool.consume();
            // globally handle db request errors
            IndexedDBLogger.db.onerror = event => util.throwError(event.target.error);
        };
        IndexedDBLogger.request.onupgradeneeded = event => {
            // init dabasebase
            let db = event.target.result, store = db.createObjectStore('logs', { autoIncrement: true });
            store.createIndex('namespace', 'namespace', { unique: false });
            store.createIndex('level', 'level', { unique: false });
            store.createIndex('descriptor', 'descriptor', { unique: false });
            store.createIndex('data', 'data', { unique: false });
        };
    }

    /**
     * get logs in range
     * if from and end is not defined, will fetch full log
     * @method get
     * @static
     * @param {String} [from] - time from, unix time stamp or falsy
     * @param {String} [to] - time end, unix time stamp or falsy
     * @param {Function} readyFn - function to call back with logs as parameter
     */
    static get(from, to, readyFn) {
        if (IndexedDBLogger.status !== super.STATUS.INITED) {
            return IndexedDBLogger._pool.push(() => IndexedDBLogger.get(from, to, readyFn));
        }

        from = LoggerInterface.transTimeFormat(from);
        to = LoggerInterface.transTimeFormat(to);

        let store = IndexedDBLogger._getTransactionStore(IDBTransaction.READ_ONLY);

        // IDBObjectStore.getAll is a non-standard API
        if (store.getAll) {
            let result, logs = [];
            store.getAll().onsuccess = event => result = event.target.result;
            for (let i = 0; i < result.length; i++) {
                if ((from && result[i].time < from) || (to && result[i].time > to)) {
                    continue;
                }
                logs.push(result[i]);
            }
            readyFn(logs);
        } else {
            let request = store.openCursor(), logs = [];
            request.onsuccess = event => {
                var cursor = event.target.result;
                if (cursor) {
                    if ((from && cursor.value.time < from) || (to && cursor.value.time > to)) {
                        cursor.continue();
                    }

                    logs.push({
                        time: cursor.value.time,
                        level: cursor.value.level,
                        namespace: cursor.value.namespace,
                        descriptor: cursor.value.descriptor,
                        data: cursor.value.data
                    });
                    cursor.continue();
                }
                else {
                    readyFn(logs);
                }
            };
        }
    }

    /**
     * clean logs = keep limited logs
     * @method keep
     * @static
     * @param {Number} daysToMaintain - keep logs within days
     */
    static keep(daysToMaintain) {
        if (IndexedDBLogger.status !== super.STATUS.INITED) {
            return IndexedDBLogger._pool.push(() => IndexedDBLogger.keep(daysToMaintain));
        }

        let store = IndexedDBLogger._getTransactionStore(IDBTransaction.READ_WRITE);
        if (!daysToMaintain) {
            let request = store.clear().onerror = event => util.throwError(event.target.error);
        }
        else {
            let range = (Date.now() - (daysToMaintain || 2) * 24 * 3600 * 1000);
            let request = store.openCursor();
            request.onsuccess = event => {
                let cursor = event.target.result;
                if (cursor && cursor.value.time < range) {
                    store.delete(cursor.primaryKey);
                    cursor.continue();
                }
            };
            request.onerror = event => util.throwError('unable to locate logs earlier than ' + daysToMaintain + 'd.');
        }
    }

    /**
     * delete log database
     * @method clean
     * @static
     */
    static clean() {
        if (IndexedDBLogger.status !== super.STATUS.INITED) {
            return IndexedDBLogger._pool.push(() => IndexedDBLogger.clean());
        }

        // database can be removed only after all connections are closed
        IndexedDBLogger.db.close();
        let request = window.indexedDB.deleteDatabase(IndexedDBLogger._database);
        request.onerror = event => util.throwError(event.target.error);
        /* eslint no-unused-vars: "off" */
        request.onsuccess = event => {
            delete IndexedDBLogger.status;
            delete IndexedDBLogger.db;
        };
    }

    /**
     * get internal transaction store
     * @method _getTransactionStore
     * @private
     * @static
     * @param {String} mode - transaction mode
     * @return {Object} - internal object store
     */
    static _getTransactionStore(mode) {
        if (IndexedDBLogger.db) {
            let transaction = IndexedDBLogger.db.transaction(['logs'], mode || IDBTransaction.READ_WRITE);
            transaction.onerror = event => util.throwError(event.target.error);
            return transaction.objectStore('logs');
        }
        else {
            util.throwError('log database is not created or connections are closed, considering init it.');
        }
    }

    /**
     * detect support situation
     * @prop {Boolean} support
     */
    static get support() {
        const support = !!(window.indexedDB && window.IDBTransaction && window.IDBKeyRange);
        if (support) {
            window.IDBTransaction.READ_WRITE = 'readwrite';
            window.IDBTransaction.READ_ONLY = 'readonly';
        }
        return support;
    }
}
<|endoftext|>


<commit_before>import { range } from 'lodash';
import moment from 'moment-timezone';

context('Assignment Edit', () => {
  const format = 'MMM D | hh:mm A';

  const fillDetails = () => {
    cy.get('.heading').should('contain.text', 'STEP 1')
    cy.get('.controls .btn-primary').should('be.disabled')
    cy.get('input[name="title"]').type('test assignment #1')
    cy.get('.controls .btn-primary').should('not.be.disabled')
  }
  const addTemplate = ({ name, dueDateOffsetDays = '1', dueTimeHour = '5',
    dueTimeMinutes = '30', closesDateOffsetDays = '30', isAM = false, doSelect = false }) => {
    cy.get('[data-test-id="grading-templates"]').click()
    cy.get('[data-test-id="add-template"]').click()
    cy.get('.modal').should('be.visible')
    cy.get('.modal input[name="name"]').type(name)
    cy.get('.modal select[name="default_due_date_offset_days"]').select(dueDateOffsetDays);
    cy.get('.modal select[name="default_due_time_hour"]').select(dueTimeHour);
    cy.get('.modal select[name="default_due_time_minute"]').select(dueTimeMinutes);
    cy.get('.modal select[name="default_close_date_offset_days"]').select(closesDateOffsetDays);
    cy.get('.modal input[name="default_due_time_ampm"]').check(isAM ? 'am' : 'pm', { force: true });
    cy.get('.modal [type="submit"]').click()

    if(doSelect) {
      cy.get('[data-test-id="grading-templates"]').click()
      cy.get(`[data-test-id="${name}"]`).click()
    }
  }

  it('validates', () => {
    cy.visit('/course/2/assignment/edit/homework/new')
    cy.disableTours();
    cy.get('input[name="title"]').type(' ').blur()
    cy.get('[aria-invalid][data-target="title"]').should('exist')

    cy.get('textarea[name="description"]').type(
      'Call me Ishmael. Some years ago—never mind how long precisely—having little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the world. It is a way I have of driving off the spleen and regulating the circulation. Whenever I find myself growing grim about the mouth; whenever it is a damp, drizzly November in my soul; whenever I find myself involuntarily pausing before coffin warehouses, and bringing up the rear of every funeral I meet;',
      { delay: 0, force: true },
    ).blur()
    cy.get('[aria-invalid][data-target="description"]').should('contain.text', 'Cannot be longer')
  })

  it('loads and advances homework', () => {
    cy.visit('/course/2/assignment/edit/homework/new')
    cy.disableTours();
    fillDetails()
    cy.get('.controls .btn-primary').click()
    cy.get('.heading').should('contain.text', 'STEP 2')
    cy.get('.controls .btn-primary').should('be.disabled')
    cy.get('.chapter[data-is-expanded="false"]').first().click()
    cy.get('[data-chapter-section="2.1"]').click();
    cy.get('[data-chapter-section="2.2"]').click();
    cy.get('.controls .btn-primary').should('not.be.disabled')

    cy.get('.controls .btn-primary').click()
    cy.get('.heading').should('contain.text', 'STEP 3')
    cy.get('.controls .btn-primary').should('be.disabled')
    range(3).map(i => cy.get('.exercise-card').eq(i).find('.action.include').trigger('click', { force: true }))
    cy.get('.controls .btn-primary').should('not.be.disabled')

    cy.get('.controls .btn-primary').click()
    cy.get('.heading').should('contain.text', 'STEP 4')
    cy.get('.openstax-exercise-preview').should('have.length', 3)

    cy.get('button.ox-icon-arrow-up').eq(1).click({ force: true })
  });

  it('loads and advances reading', () => {
    cy.visit('/course/1/assignment/edit/reading/new')
    cy.disableTours();
    fillDetails()
    cy.get('.controls .btn-primary').click()
    cy.get('.heading').should('contain.text', 'STEP 2')
    cy.get('.controls .btn-primary').should('be.disabled')
    cy.get('.chapter[data-is-expanded="false"]').first().click()
    cy.get('[data-chapter-section="2.1"]').click();
    cy.get('[data-chapter-section="2.2"]').click();
    cy.get('[data-chapter-section="2.3"]').click();
    cy.get('.controls .btn-primary').should('not.be.disabled')

    cy.get('.controls .btn-primary').click()
    cy.get('.heading').should('contain.text', 'STEP 3')

    cy.get('.selected-section .chapter-section').invoke('text').then(labels => {
      expect(labels).to.eq(['2.1', '2.2', '2.3'].join(''))
    })
    cy.get('button.ox-icon-arrow-up').eq(1).click()
    cy.get('.selected-section .chapter-section').invoke('text').then(labels => {
      expect(labels).to.eq(['2.1', '2.3', '2.2'].join(''))
    })
  });

  it('increases selections', () => {
    cy.visit('/course/2/assignment/edit/homework/new')
    cy.disableTours()
    fillDetails()
    cy.get('.controls .btn-primary').click()
    cy.get('.chapter-checkbox .btn').first().click()
    cy.get('.controls .btn-primary').click()
    cy.get('[data-test-id="selection-count"]').should('contain.text', '0')
    cy.get('[data-test-id="selection-count-footer"]').should('contain.text', '0')

    cy.get('.action.include:first').click({ force: true })

    cy.get('[data-test-id="selection-count"]').should('contain.text', '1')
    cy.get('[data-test-id="selection-count-footer"]').should('contain.text', '1')
    cy.get('[data-test-id="tutor-count"]').should('contain.text', '3')
    cy.get('[data-test-id="tutor-count-footer"]').should('contain.text', '3')
    cy.get('[data-test-id="total-count"]').should('contain.text', '4')
    cy.get('[data-test-id="total-count-footer"]').should('contain.text', '4')
  });

  it('filters question types', () => {
    cy.visit('/course/2/assignment/edit/homework/new')
    cy.disableTours()
    fillDetails()
    cy.get('.controls .btn-primary').click()
    cy.get('.chapter-checkbox .btn').first().click()
    cy.get('.controls .btn-primary').click()
    cy.get('[name="filter"][value="oe"]').click({ force: true })
    cy.get('[data-section]').should('not.contain.text', 'Multiple Choice Questions')
    cy.get('[name="filter"][value="mc"]').click({ force: true })
    cy.get('[data-section]').should('not.contain.text', 'Written Response Questions')
  });

  it('can change course timezone', () => {
    cy.visit('/course/2/assignment/edit/homework/new')
    cy.disableTours()
    fillDetails()
    cy.get('[data-test-id="change-timezone"]').click()
    cy.get('.settings-edit-course-modal').should('be.visible')
    cy.get('.settings-edit-course-modal [value="Hawaii"]').click({ force: true })
    cy.get('.settings-edit-course-modal .async-button').click()
    cy.get('.settings-edit-course-modal').should('not.exist')
    cy.get('[data-test-id="change-timezone"').should( 'contain.text', 'Hawaii')
  });

  it('renders external assignment', () => {
    cy.visit('/course/2/assignment/edit/external/new')
    cy.disableTours()
    cy.get('.heading').should('not.contain.text', 'STEP 1')
    cy.get('[name="external_url"').type('url')
  });

  it('can add a new template', () => {
    const templateName = 'This is a new template'
    cy.visit('/course/2/assignment/edit/homework/new')
    cy.disableTours()
    fillDetails()
    addTemplate({ name: templateName })
    cy.get('[data-test-id="grading-templates"]').should('contain.text', templateName)
  });

  // skipping; test is flaky in the morning on travis, some date math is incorrect here
  it.skip('can select another template and update dates, and change pivot dates to update other dates', () => {
    const templateName = 'Template to update dates'
    const dueDateOffsetDays = '3', dueTimeHour = '7', dueTimeMinutes = '15', closesDateOffsetDays = '10', isAM = false
    cy.visit('/course/2/assignment/edit/homework/new')
    cy.disableTours()
    fillDetails()
    addTemplate({ name: templateName, dueDateOffsetDays, dueTimeHour, dueTimeMinutes, closesDateOffsetDays, isAM, doSelect: true })
    cy.get('input[name="tasking_plans[0].opens_at"]').then(o => {
      const openDate = o[0].defaultValue
      let updatedDueDate;
      // Due date should change
      cy.get('input[name="tasking_plans[0].due_at"]').then(d => {
        const dueDate = moment(d[0].defaultValue, format).toISOString();
        updatedDueDate = dueDate;
        // Compute the due date from the open date
        const hour = isAM ? parseInt(dueTimeHour, 10) : parseInt(dueTimeHour, 10) + 12
        const expectedDueDate = moment(openDate, format)
          .add(parseInt(dueDateOffsetDays, 10), 'days')
          .set({ hour, minutes: parseInt(dueTimeMinutes, 10) })
          .toISOString();
        expect(dueDate).eq(expectedDueDate)
      })
      // Closes date should change
      cy.get('input[name="tasking_plans[0].closes_at"]').then(c => {
        const closesDate = moment(c[0].defaultValue, format).toISOString();
        // Compute the closes date from the due date
        const expectedDueDate = moment(updatedDueDate)
          .add(parseInt(closesDateOffsetDays, 10), 'days')
          .toISOString();
        expect(closesDate).eq(expectedDueDate)
      })
    });
  })

  it('changes open dates to update other dates', () => {
    const templateName = 'Template to update dates'
    const dueDateOffsetDays = '3', dueTimeHour = '7', dueTimeMinutes = '15', closesDateOffsetDays = '10', isAM = false
    const typedOpenDate = 'Jun 15 05:00 PM'
    cy.visit('/course/2/assignment/edit/homework/new')
    cy.disableTours()
    fillDetails()
    addTemplate({ name: templateName, dueDateOffsetDays, dueTimeHour, dueTimeMinutes, closesDateOffsetDays, isAM, doSelect: true })
    cy.get('input[name="tasking_plans[0].opens_at"]').clear({ force: true }).type(typedOpenDate, { force: true })
    cy.get('.oxdt-ok button').click()
    cy.get('input[name="tasking_plans[0].opens_at"]').then(o => {
      const openDate = o[0].defaultValue
      let updatedDueDate;
      // Due date should change
      cy.get('input[name="tasking_plans[0].due_at"]').then(d => {
        const dueDate = moment(d[0].defaultValue).toISOString();
        updatedDueDate = dueDate;
        // Compute the due date from the open date
        const hour = isAM ? parseInt(dueTimeHour, 10) : parseInt(dueTimeHour, 10) + 12
        const expectedDueDate = moment(openDate)
          .add(parseInt(dueDateOffsetDays, 10), 'days')
          .set({ hour, minutes: parseInt(dueTimeMinutes, 10) })
          .toISOString();
        expect(dueDate).eq(expectedDueDate)
      })
      // Closes date should change
      cy.get('input[name="tasking_plans[0].closes_at"]').then(c => {
        const closesDate = moment(c[0].defaultValue).toISOString();
        // Compute the closes date from the due date
        const expectedDueDate = moment(updatedDueDate)
          .add(parseInt(closesDateOffsetDays, 10), 'days')
          .toISOString();
        expect(closesDate).eq(expectedDueDate)
      })
    });
  })

  it('updates open date and time manually', () => {
    cy.visit('/course/2/assignment/edit/homework/new')
    cy.disableTours()

    cy.get('input[name="tasking_plans[0].opens_at"]').click();
    cy.get('.oxdt-cell-inner').contains('1').click();
    cy.get('.oxdt-time-panel-column:nth-child(1) .oxdt-time-panel-cell-inner').contains('08').click();
    cy.get('.oxdt-time-panel-column:nth-child(2) .oxdt-time-panel-cell-inner').contains('10').click();
    cy.get('.oxdt-time-panel-column:nth-child(3) .oxdt-time-panel-cell-inner').contains('PM').click();
    cy.get('.oxdt-ok button').click();
    cy.get('input[name="tasking_plans[0].opens_at"]').invoke('val').should('contain', '1 | 08:10 PM')
  })

  it('updates date when pivot date is updated', () => {
    const typedOpenDate = 'Jun 10 05:00 PM'
    const typedClosesDate = 'Jun 22 05:00 PMM'
    cy.visit('/course/2/assignment/edit/homework/new')
    cy.disableTours()
    let currentClosesDate;
    // force update the closes date
    cy.get('input[name="tasking_plans[0].closes_at"]').clear({ force: true }).type(typedClosesDate, { force: true }).blur();
    cy.get('.oxdt-ok button').click({ force: true })
    cy.get('input[name="tasking_plans[0].closes_at"]').then(c => {
      currentClosesDate = c[0].defaultValue;
    })
    // force update the open date
    cy.get('input[name="tasking_plans[0].opens_at"]').clear({ force: true }).type(typedOpenDate, { force: true })
    // After opening the closes date time picker modal, it gets the two OK buttons
    cy.get('.oxdt-ok button').last().click({ force: true })
    // after changing the open date, no dates should be changed because the interval between open/due/close date has changed
    cy.get('input[name="tasking_plans[0].closes_at"]').then(d => {
      expect(d[0].defaultValue).eq(currentClosesDate)
    })
  });
});
<commit_msg>fix test element name<commit_after>import { range } from 'lodash';
import moment from 'moment-timezone';

context('Assignment Edit', () => {
  const format = 'MMM D | hh:mm A';

  const fillDetails = () => {
    cy.get('.heading').should('contain.text', 'STEP 1')
    cy.get('.controls .btn-primary').should('be.disabled')
    cy.get('input[name="title"]').type('test assignment #1')
    cy.get('.controls .btn-primary').should('not.be.disabled')
  }
  const addTemplate = ({ name, dueDateOffsetDays = '1', dueTimeHour = '5',
    dueTimeMinutes = '30', closesDateOffsetDays = '30', isAM = false, doSelect = false }) => {
    cy.get('[data-test-id="grading-templates"]').click()
    cy.get('[data-test-id="add-template"]').click()
    cy.get('.modal').should('be.visible')
    cy.get('.modal input[name="name"]').type(name)
    cy.get('.modal select[name="default_due_date_offset_days"]').select(dueDateOffsetDays);
    cy.get('.modal select[name="default_due_time_hour"]').select(dueTimeHour);
    cy.get('.modal select[name="default_due_time_minute"]').select(dueTimeMinutes);
    cy.get('.modal select[name="default_close_date_offset_days"]').select(closesDateOffsetDays);
    cy.get('.modal input[name="default_due_time_ampm"]').check(isAM ? 'am' : 'pm', { force: true });
    cy.get('.modal [type="submit"]').click()

    if(doSelect) {
      cy.get('[data-test-id="grading-templates"]').click()
      cy.get(`[data-test-id="${name}"]`).click()
    }
  }

  it('validates', () => {
    cy.visit('/course/2/assignment/edit/homework/new')
    cy.disableTours();
    cy.get('input[name="title"]').type(' ').blur()
    cy.get('[aria-invalid][data-target="title"]').should('exist')

    cy.get('textarea[name="description"]').type(
      'Call me Ishmael. Some years ago—never mind how long precisely—having little or no money in my purse, and nothing particular to interest me on shore, I thought I would sail about a little and see the watery part of the world. It is a way I have of driving off the spleen and regulating the circulation. Whenever I find myself growing grim about the mouth; whenever it is a damp, drizzly November in my soul; whenever I find myself involuntarily pausing before coffin warehouses, and bringing up the rear of every funeral I meet;',
      { delay: 0, force: true },
    ).blur()
    cy.get('[aria-invalid][data-target="description"]').should('contain.text', 'Cannot be longer')
  })

  it('loads and advances homework', () => {
    cy.visit('/course/2/assignment/edit/homework/new')
    cy.disableTours();
    fillDetails()
    cy.get('.controls .btn-primary').click()
    cy.get('.heading').should('contain.text', 'STEP 2')
    cy.get('.controls .btn-primary').should('be.disabled')
    cy.get('.chapter[data-is-expanded="false"]').first().click()
    cy.get('[data-chapter-section="2.1"]').click();
    cy.get('[data-chapter-section="2.2"]').click();
    cy.get('.controls .btn-primary').should('not.be.disabled')

    cy.get('.controls .btn-primary').click()
    cy.get('.heading').should('contain.text', 'STEP 3')
    cy.get('.controls .btn-primary').should('be.disabled')
    range(3).map(i => cy.get('.exercise-card').eq(i).find('.action.include').trigger('click', { force: true }))
    cy.get('.controls .btn-primary').should('not.be.disabled')

    cy.get('.controls .btn-primary').click()
    cy.get('.heading').should('contain.text', 'STEP 4')
    cy.get('.openstax-exercise-preview').should('have.length', 3)

    cy.get('button.ox-icon-arrow-up').eq(1).click({ force: true })
  });

  it('loads and advances reading', () => {
    cy.visit('/course/1/assignment/edit/reading/new')
    cy.disableTours();
    fillDetails()
    cy.get('.controls .btn-primary').click()
    cy.get('.heading').should('contain.text', 'STEP 2')
    cy.get('.controls .btn-primary').should('be.disabled')
    cy.get('.chapter[data-is-expanded="false"]').first().click()
    cy.get('[data-chapter-section="2.1"]').click();
    cy.get('[data-chapter-section="2.2"]').click();
    cy.get('[data-chapter-section="2.3"]').click();
    cy.get('.controls .btn-primary').should('not.be.disabled')

    cy.get('.controls .btn-primary').click()
    cy.get('.heading').should('contain.text', 'STEP 3')

    cy.get('.selected-section .chapter-section').invoke('text').then(labels => {
      expect(labels).to.eq(['2.1', '2.2', '2.3'].join(''))
    })
    cy.get('button.ox-icon-arrow-up').eq(1).click()
    cy.get('.selected-section .chapter-section').invoke('text').then(labels => {
      expect(labels).to.eq(['2.1', '2.3', '2.2'].join(''))
    })
  });

  it('increases selections', () => {
    cy.visit('/course/2/assignment/edit/homework/new')
    cy.disableTours()
    fillDetails()
    cy.get('.controls .btn-primary').click()
    cy.get('.chapter-checkbox .btn').first().click()
    cy.get('.controls .btn-primary').click()
    cy.get('[data-test-id="selection-count"]').should('contain.text', '0')
    cy.get('[data-test-id="selection-count-footer"]').should('contain.text', '0')

    cy.get('.action.include:first').click({ force: true })

    cy.get('[data-test-id="selection-count"]').should('contain.text', '1')
    cy.get('[data-test-id="selection-count-footer"]').should('contain.text', '1')
    cy.get('[data-test-id="tutor-count"]').should('contain.text', '3')
    cy.get('[data-test-id="tutor-count-footer"]').should('contain.text', '3')
    cy.get('[data-test-id="total-count"]').should('contain.text', '4')
    cy.get('[data-test-id="total-count-footer"]').should('contain.text', '4')
  });

  it('filters question types', () => {
    cy.visit('/course/2/assignment/edit/homework/new')
    cy.disableTours()
    fillDetails()
    cy.get('.controls .btn-primary').click()
    cy.get('.chapter-checkbox .btn').first().click()
    cy.get('.controls .btn-primary').click()
    cy.get('[name="filter"][value="oe"]').click({ force: true })
    cy.get('[data-section]').should('not.contain.text', 'Multiple Choice Questions')
    cy.get('[name="filter"][value="mc"]').click({ force: true })
    cy.get('[data-section]').should('not.contain.text', 'Written Response Questions')
  });

  it('can change course timezone', () => {
    cy.visit('/course/2/assignment/edit/homework/new')
    cy.disableTours()
    fillDetails()
    cy.get('[data-test-id="change-timezone"]').click()
    cy.get('.settings-edit-course-modal').should('be.visible')
    cy.get('.settings-edit-course-modal [value="Hawaii"]').click({ force: true })
    cy.get('.settings-edit-course-modal .async-button').click()
    cy.get('.settings-edit-course-modal').should('not.exist')
    cy.get('[data-test-id="change-timezone"').should( 'contain.text', 'Hawaii')
  });

  it('renders external assignment', () => {
    cy.visit('/course/2/assignment/edit/external/new')
    cy.disableTours()
    cy.get('.heading').should('not.contain.text', 'STEP 1')
    cy.get('[name="settings.external_url"').type('url')
  });

  it('can add a new template', () => {
    const templateName = 'This is a new template'
    cy.visit('/course/2/assignment/edit/homework/new')
    cy.disableTours()
    fillDetails()
    addTemplate({ name: templateName })
    cy.get('[data-test-id="grading-templates"]').should('contain.text', templateName)
  });

  // skipping; test is flaky in the morning on travis, some date math is incorrect here
  it.skip('can select another template and update dates, and change pivot dates to update other dates', () => {
    const templateName = 'Template to update dates'
    const dueDateOffsetDays = '3', dueTimeHour = '7', dueTimeMinutes = '15', closesDateOffsetDays = '10', isAM = false
    cy.visit('/course/2/assignment/edit/homework/new')
    cy.disableTours()
    fillDetails()
    addTemplate({ name: templateName, dueDateOffsetDays, dueTimeHour, dueTimeMinutes, closesDateOffsetDays, isAM, doSelect: true })
    cy.get('input[name="tasking_plans[0].opens_at"]').then(o => {
      const openDate = o[0].defaultValue
      let updatedDueDate;
      // Due date should change
      cy.get('input[name="tasking_plans[0].due_at"]').then(d => {
        const dueDate = moment(d[0].defaultValue, format).toISOString();
        updatedDueDate = dueDate;
        // Compute the due date from the open date
        const hour = isAM ? parseInt(dueTimeHour, 10) : parseInt(dueTimeHour, 10) + 12
        const expectedDueDate = moment(openDate, format)
          .add(parseInt(dueDateOffsetDays, 10), 'days')
          .set({ hour, minutes: parseInt(dueTimeMinutes, 10) })
          .toISOString();
        expect(dueDate).eq(expectedDueDate)
      })
      // Closes date should change
      cy.get('input[name="tasking_plans[0].closes_at"]').then(c => {
        const closesDate = moment(c[0].defaultValue, format).toISOString();
        // Compute the closes date from the due date
        const expectedDueDate = moment(updatedDueDate)
          .add(parseInt(closesDateOffsetDays, 10), 'days')
          .toISOString();
        expect(closesDate).eq(expectedDueDate)
      })
    });
  })

  it('changes open dates to update other dates', () => {
    const templateName = 'Template to update dates'
    const dueDateOffsetDays = '3', dueTimeHour = '7', dueTimeMinutes = '15', closesDateOffsetDays = '10', isAM = false
    const typedOpenDate = 'Jun 15 05:00 PM'
    cy.visit('/course/2/assignment/edit/homework/new')
    cy.disableTours()
    fillDetails()
    addTemplate({ name: templateName, dueDateOffsetDays, dueTimeHour, dueTimeMinutes, closesDateOffsetDays, isAM, doSelect: true })
    cy.get('input[name="tasking_plans[0].opens_at"]').clear({ force: true }).type(typedOpenDate, { force: true })
    cy.get('.oxdt-ok button').click()
    cy.get('input[name="tasking_plans[0].opens_at"]').then(o => {
      const openDate = o[0].defaultValue
      let updatedDueDate;
      // Due date should change
      cy.get('input[name="tasking_plans[0].due_at"]').then(d => {
        const dueDate = moment(d[0].defaultValue).toISOString();
        updatedDueDate = dueDate;
        // Compute the due date from the open date
        const hour = isAM ? parseInt(dueTimeHour, 10) : parseInt(dueTimeHour, 10) + 12
        const expectedDueDate = moment(openDate)
          .add(parseInt(dueDateOffsetDays, 10), 'days')
          .set({ hour, minutes: parseInt(dueTimeMinutes, 10) })
          .toISOString();
        expect(dueDate).eq(expectedDueDate)
      })
      // Closes date should change
      cy.get('input[name="tasking_plans[0].closes_at"]').then(c => {
        const closesDate = moment(c[0].defaultValue).toISOString();
        // Compute the closes date from the due date
        const expectedDueDate = moment(updatedDueDate)
          .add(parseInt(closesDateOffsetDays, 10), 'days')
          .toISOString();
        expect(closesDate).eq(expectedDueDate)
      })
    });
  })

  it('updates open date and time manually', () => {
    cy.visit('/course/2/assignment/edit/homework/new')
    cy.disableTours()

    cy.get('input[name="tasking_plans[0].opens_at"]').click();
    cy.get('.oxdt-cell-inner').contains('1').click();
    cy.get('.oxdt-time-panel-column:nth-child(1) .oxdt-time-panel-cell-inner').contains('08').click();
    cy.get('.oxdt-time-panel-column:nth-child(2) .oxdt-time-panel-cell-inner').contains('10').click();
    cy.get('.oxdt-time-panel-column:nth-child(3) .oxdt-time-panel-cell-inner').contains('PM').click();
    cy.get('.oxdt-ok button').click();
    cy.get('input[name="tasking_plans[0].opens_at"]').invoke('val').should('contain', '1 | 08:10 PM')
  })

  it('updates date when pivot date is updated', () => {
    const typedOpenDate = 'Jun 10 05:00 PM'
    const typedClosesDate = 'Jun 22 05:00 PMM'
    cy.visit('/course/2/assignment/edit/homework/new')
    cy.disableTours()
    let currentClosesDate;
    // force update the closes date
    cy.get('input[name="tasking_plans[0].closes_at"]').clear({ force: true }).type(typedClosesDate, { force: true }).blur();
    cy.get('.oxdt-ok button').click({ force: true })
    cy.get('input[name="tasking_plans[0].closes_at"]').then(c => {
      currentClosesDate = c[0].defaultValue;
    })
    // force update the open date
    cy.get('input[name="tasking_plans[0].opens_at"]').clear({ force: true }).type(typedOpenDate, { force: true })
    // After opening the closes date time picker modal, it gets the two OK buttons
    cy.get('.oxdt-ok button').last().click({ force: true })
    // after changing the open date, no dates should be changed because the interval between open/due/close date has changed
    cy.get('input[name="tasking_plans[0].closes_at"]').then(d => {
      expect(d[0].defaultValue).eq(currentClosesDate)
    })
  });
});
<|endoftext|>


<commit_before>var minimist = require("minimist");
var _ = require("lodash");
var parsers = require("./parsers.js");

var availableOptionsText = function(options) {
  if(_.isEmpty(options)) {
    return '';
  } else {
    return "Available options: \n" + _.pluck(options, "helpText").join('\n') + '\n\n';
  }
};

var displayOptionNames = function(names) {
  return _.map(names, function(name) {
    if(name.length > 1) return '--' + name;
    else return '-' + name;
  }).join(", ");
};

var option = function(name, opts) {
  var options = opts || {};
  var names = [name].concat(options.aliases || []);
  var metavar = options.metavar || null;
  var parser = options.parser || parsers.stringParser;
  var helpT = options.helpT || "";
  var defaultValue = (typeof options.defaultValue !== 'undefined') ? options.defaultValue : null;

  return {
    names: names,
    getValue: function(cliOpts) {
        var value = _(names)
            .map(function(name) { return cliOpts[name]; })
            .find(function(v) { return typeof v !== 'undefined'; });
      if(value) {
        return parser(value);
      } else if(defaultValue !== null) {
        return { success: defaultValue };
      } else {
        return { error: "missing value" };
      }
    },
    helpText: function() {
      var name = displayOptionNames(names);
      if(metavar) name += ' ' + metavar.toUpperCase();
      return name + "\t\t\t\t" + helpT;
    }()
  };
};

var flag = function(name, opts) {
    var options = opts || {};
    options.parser = parsers.booleanParser;
    if(typeof options.defaultValue === 'undefined') options.defaultValue = false;
    return option(name, options);
};

var argument = function(name, opts) {
  var options = opts || {};
  var parser = options.parser || parsers.stringParser;
  var help = options.help || "";
  var defaultValue = typeof options.defaultValue !== "undefined" ? options.defaultValue : null;

  return {
    getValue: function(position, args) {
      var value = args[position];
      if(value) {
        return parser(value);
      } else if(defaultValue !== null) {
        return { success: defaultValue };
      } else {
        return { error: "missing value" };
      }
    },
    helpText: function() {
      if(defaultValue !== null) {
        return "[" + name.toUpperCase() + "]";
      } else {
        return name.toUpperCase();
      }
    }()
  };
};

var command = function(name, opts, cb) {
  var options = opts || {};
  var description = options.description || "";
  var args = options.args || [];
  var flags = options.options || [];
  return {
    name: name,
    args: args,
    options: flags,
    helpText: function(cliName) {
      var argList = _.pluck(args, "helpText").join(' ');
      return "Usage : " + cliName +"  " + name + " " + argList + "\n" +
          description + "\n\n" +
          availableOptionsText(flags);
    },
    singleLineHelp: "  " + name + " " + _.pluck(args, "helpText").join(' ') + "\t\t\t\t" + description,
    getValue: function(cliArgs, cliOpts, globalOpts) {
      var parsedOptions = parsers.parseOptions(flags, cliOpts);
      var parsedArgs = parsers.parseArgs(args, cliArgs);
      var globalOptions = globalOpts || {};

      if(parsers.isValid(parsedArgs) && parsers.isValid(parsedOptions)) {
          cb({
            args: parsedArgs.success,
            options: _.extend(parsedOptions.success, globalOpts)
          });
      } else {
        return { errors: {} };
      }
    }
  };
};

var getProcessName = function(args) {
  if(args[0] === 'node') {
    return _.take(args, 2).join(" ");
  } else {
    return args[0];
  }
};

var cli = function(opts) {
  var options = opts || {};
  var name = options.name || getProcessName(process.argv);
  var flags = options.options || [];
  var commands = options.commands || [];
  return {
    getValue: function(cliArgs, cliOpts) {
      var parsedOptions = parsers.parseOptions(flags, cliOpts);

      if(parsers.isValid(parsedOptions)) {
        var matchedCommand = _.find(commands, function(command) {
          return command.name === cliArgs[0];
        });

        if(matchedCommand) {
          var commandResult = matchedCommand.getValue(_.drop(cliArgs, 1), cliOpts, parsedOptions.success);
          if(parsers.isError(commandResult)) {
            console.log(matchedCommand.helpText(name));
          }
        } else {
          console.log(this.helpText());
        }
      } else {
        console.log(this.helpText());
      }

    },
    helpText: function() {
      return "Name + description\n\n" +
          availableOptionsText(flags) +
          "Available commands: \n" +
          _.pluck(commands, "singleLineHelp").join('\n');
    }
  };
};

var parseValues = function(cliApp, argv) {
  var aargv = (typeof argv === "undefined") ? process.argv : argv;

  var opts = {
      "boolean": true
  };

  var cliValues = minimist(aargv, opts);
  var options = _.omit(cliValues, "_");
  var args = cliValues._;

  if(args[0] === "node") {
    cliApp.getValue(_.drop(args, 2), options);
  } else {
    cliApp.getValue(_.drop(args, 1), options);
  }
};

module.exports = {
  parseValues: parseValues,
  cli: cli,
  command: command,
  argument: argument,
  option: option,
  flag: flag
};

<commit_msg>Support for sub*commands and top-level actions<commit_after>var minimist = require("minimist");
var _ = require("lodash");
var parsers = require("./parsers.js");

var availableOptionsText = function(options) {
  if(_.isEmpty(options)) {
    return '';
  } else {
    return "Available options: \n" + _.pluck(options, "helpText").join('\n') + '\n\n';
  }
};

var displayOptionNames = function(names) {
  return _.map(names, function(name) {
    if(name.length > 1) return '--' + name;
    else return '-' + name;
  }).join(", ");
};

var option = function(name, opts) {
  var options = opts || {};
  var names = [name].concat(options.aliases || []);
  var metavar = options.metavar || null;
  var parser = options.parser || parsers.stringParser;
  var helpT = options.helpT || "";
  var defaultValue = (typeof options.defaultValue !== 'undefined') ? options.defaultValue : null;

  return {
    names: names,
    getValue: function(cliOpts) {
        var value = _(names)
            .map(function(name) { return cliOpts[name]; })
            .find(function(v) { return typeof v !== 'undefined'; });
      if(value) {
        return parser(value);
      } else if(defaultValue !== null) {
        return { success: defaultValue };
      } else {
        return { error: "missing value" };
      }
    },
    helpText: function() {
      var name = displayOptionNames(names);
      if(metavar) name += ' ' + metavar.toUpperCase();
      return name + "\t\t\t\t" + helpT;
    }()
  };
};

var flag = function(name, opts) {
    var options = opts || {};
    options.parser = parsers.booleanParser;
    if(typeof options.defaultValue === 'undefined') options.defaultValue = false;
    return option(name, options);
};

var argument = function(name, opts) {
  var options = opts || {};
  var parser = options.parser || parsers.stringParser;
  var help = options.help || "";
  var defaultValue = typeof options.defaultValue !== "undefined" ? options.defaultValue : null;

  return {
    getValue: function(position, args) {
      var value = args[position];
      if(value) {
        return parser(value);
      } else if(defaultValue !== null) {
        return { success: defaultValue };
      } else {
        return { error: "missing value" };
      }
    },
    helpText: function() {
      if(defaultValue !== null) {
        return "[" + name.toUpperCase() + "]";
      } else {
        return name.toUpperCase();
      }
    }()
  };
};

var command = function(name, opts, cb) {
  var options = opts || {};
  var description = options.description || "";
  var args = options.args || [];
  var flags = options.options || [];
  var commands = options.commands || [];
  var action = typeof cb !== 'undefined' ? cb : null;
  return {
    name: name,
    args: args,
    options: flags,
    helpText: function(cliName) {
      var argList = _.pluck(args, "helpText").join(' ');
      return "Usage : " + cliName +"  " + name + " " + argList + "\n" +
          description + "\n\n" +
          availableOptionsText(flags);
    },
    singleLineHelp: "  " + name + " " + _.pluck(args, "helpText").join(' ') + "\t\t\t\t" + description,
    getValue: function(cliArgs, cliOpts, globalOpts) {
      var globalOptions = globalOpts || {};
      var parsedOptions = parsers.parseOptions(flags, cliOpts);

      if(parsers.isValid(parsedOptions)) {
        var matchedCommand = _.find(commands, function(command) {
          return command.name === cliArgs[0];
        });

        if(matchedCommand) {
          var commandResult = matchedCommand.getValue(_.drop(cliArgs, 1), cliOpts, parsedOptions.success);
          if(parsers.isError(commandResult)) {
            console.log(matchedCommand.helpText(name));
          }
        } else {
          var parsedArgs = parsers.parseArgs(args, cliArgs);

          if(action !== null && parsers.isValid(parsedArgs)) {
              action({
                args: parsedArgs.success,
                options: _.extend(parsedOptions.success, globalOpts)
              });
          } else {
            console.log(this.helpText());
          }
        }
      } else {
        return { errors: {} };
      }
    }
  };
};

var getProcessName = function(args) {
  if(args[0] === 'node') {
    return _.take(args, 2).join(" ");
  } else {
    return args[0];
  }
};

var cli = function(opts, cb) {
  var options = opts || {};
  var name = options.name || getProcessName(process.argv);
  var flags = options.options || [];
  var commands = options.commands || [];
  var args = options.args || [];
  var action = typeof cb !== 'undefined' ? cb : null;
  return {
    getValue: function(cliArgs, cliOpts) {
      var parsedOptions = parsers.parseOptions(flags, cliOpts);

      if(parsers.isValid(parsedOptions)) {
        var matchedCommand = _.find(commands, function(command) {
          return command.name === cliArgs[0];
        });

        if(matchedCommand) {
          var commandResult = matchedCommand.getValue(_.drop(cliArgs, 1), cliOpts, parsedOptions.success);
          if(parsers.isError(commandResult)) {
            console.log(matchedCommand.helpText(name));
          }
        } else {
          var parsedArgs = parsers.parseArgs(args, cliArgs);

          if(action !== null && parsers.isValid(parsedArgs)) {
              action({
                args: parsedArgs.success,
                options: parsedOptions.success
              });
          } else {
            console.log(this.helpText());
          }
        }
      } else {
        console.log(this.helpText());
      }

    },
    helpText: function() {
      return "Name + description\n\n" +
          availableOptionsText(flags) +
          "Available commands: \n" +
          _.pluck(commands, "singleLineHelp").join('\n');
    }
  };
};

var parseValues = function(cliApp, argv) {
  var aargv = (typeof argv === "undefined") ? process.argv : argv;

  var opts = {
      "boolean": true
  };

  var cliValues = minimist(aargv, opts);
  var options = _.omit(cliValues, "_");
  var args = cliValues._;

  if(args[0] === "node") {
    cliApp.getValue(_.drop(args, 2), options);
  } else {
    cliApp.getValue(_.drop(args, 1), options);
  }
};

module.exports = {
  parseValues: parseValues,
  cli: cli,
  command: command,
  argument: argument,
  option: option,
  flag: flag
};

<|endoftext|>


<commit_before>var num1 = 5;
var str = 'string';
var boo1 = true;
var boo2 = false;
var num2 = 5.6;

console.log(num1); // 5
console.log(str); // "string"
console.log(boo1); // true
console.log(boo2); // false
console.log(num2); // 5.6

console.log(typeof num1); // "number"
console.log(typeof str); // "string"
console.log(typeof boo1); // "boolean"
console.log(typeof boo2); // "boolean"
console.log(typeof num2); // "number"

var var1 = [1,2,4];
console.log(var1);
console.log(typeof var1);

var var2 = [1,2,4,'5'];
console.log(var2);
console.log(typeof var2);

var var2 = [1,2,4,'5',]; // After comma also it won't show error, ! declared
console.log(var2[0]); // 1
console.log(var2[1]); // 2
console.log(var2[2]); // 4
console.log(var2[3]); // "5"
console.log(var2[4]); // undefined

var undef = undefined;
console.log(undef); // undefined // not recommended
console.log(typeof undefined); // "undefined"

var nu = null;
console.log(nu); // null
console.log(typeof nu); // "object"
<commit_msg>== vs ===<commit_after>var num1 = 5;
var str = 'string';
var boo1 = true;
var boo2 = false;
var num2 = 5.6;

console.log(num1); // 5
console.log(str); // "string"
console.log(boo1); // true
console.log(boo2); // false
console.log(num2); // 5.6

console.log(typeof num1); // "number"
console.log(typeof str); // "string"
console.log(typeof boo1); // "boolean"
console.log(typeof boo2); // "boolean"
console.log(typeof num2); // "number"

var var1 = [1,2,4];
console.log(var1);
console.log(typeof var1);

var var2 = [1,2,4,'5'];
console.log(var2);
console.log(typeof var2);

var var2 = [1,2,4,'5',]; // After comma also it won't show error, ! declared
console.log(var2[0]); // 1
console.log(var2[1]); // 2
console.log(var2[2]); // 4
console.log(var2[3]); // "5"
console.log(var2[4]); // undefined

var undef = undefined;
console.log(undef); // undefined // not recommended
console.log(typeof undefined); // "undefined"

var nu = null;
console.log(nu); // null
console.log(typeof nu); // "object"

console.log( null == undefined ); // true
console.log( null === undefined ); // false
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2015-present, Alibaba Group Holding Limited.
 * All rights reserved.
 *
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * @providesModule ReactPortal
 * @flow
 */
'use strict';

import React from 'react';
import Platform from 'ReactPlatform';
import StyleSheet from 'ReactStyleSheet';
import View from 'ReactView';

var _portalRef: any;

// Unique identifiers for modals.
var lastUsedTag = 0;

/*
 * Note: Only intended for Android at the moment.  Just use Modal in your iOS
 * code.
 *
 * A container that renders all the modals on top of everything else in the application.
 *
 * Portal makes it possible for application code to pass modal views all the way up to
 * the root element created in `renderApplication`.
 *
 * Never use `<Portal>` in your code. There is only one Portal instance rendered
 * by the top-level `renderApplication`.
 */
var Portal = React.createClass({
  statics: {
    /**
     * Use this to create a new unique tag for your component that renders
     * modals. A good place to allocate a tag is in `componentWillMount`
     * of your component.
     * See `showModal` and `closeModal`.
     */
    allocateTag: function(): string {
      return '__modal_' + (++lastUsedTag);
    },

    /**
     * Render a new modal.
     * @param tag A unique tag identifying the React component to render.
     * This tag can be later used in `closeModal`.
     * @param component A React component to be rendered.
     */
    showModal: function(tag: string, component: any) {
      if (!_portalRef) {
        console.error('Calling showModal but no Portal has been rendered.');
        return;
      }
      _portalRef._showModal(tag, component);
    },

    /**
     * Remove a modal from the collection of modals to be rendered.
     * @param tag A unique tag identifying the React component to remove.
     * Must exactly match the tag previously passed to `showModal`.
     */
    closeModal: function(tag: string) {
      if (!_portalRef) {
        console.error('Calling closeModal but no Portal has been rendered.');
        return;
      }
      _portalRef._closeModal(tag);
    },

    /**
     * Get an array of all the open modals, as identified by their tag string.
     */
    getOpenModals: function(): Array<string> {
      if (!_portalRef) {
        console.error('Calling getOpenModals but no Portal has been rendered.');
        return [];
      }
      return _portalRef._getOpenModals();
    },
  },

  getInitialState: function() {
    return {modals: {}};
  },

  _showModal: function(tag: string, component: any) {
    // We are about to open first modal, so Portal will appear.
    // Let's disable accessibility for background view on Android.
    if (this._getOpenModals().length === 0 && this.props.onModalVisibilityChanged) {
      this.props.onModalVisibilityChanged(true);
    }
    // This way state is chained through multiple calls to
    // _showModal, _closeModal correctly.
    this.setState((state) => {
      var modals = state.modals;
      modals[tag] = component;
      return {modals};
    });
  },

  _closeModal: function(tag: string) {
    if (!this.state.modals.hasOwnProperty(tag)) {
      return;
    }
    // We are about to close last modal, so Portal will disappear.
    // Let's enable accessibility for application view on Android.
    if (this._getOpenModals().length === 1 && this.props.onModalVisibilityChanged) {
      this.props.onModalVisibilityChanged(false);
    }
    // This way state is chained through multiple calls to
    // _showModal, _closeModal correctly.
    this.setState((state) => {
      var modals = state.modals;
      delete modals[tag];
      return {modals};
    });
  },

  _getOpenModals: function(): Array<string> {
    return Object.keys(this.state.modals);
  },

  render: function() {
    _portalRef = this;
    if (!this.state.modals) {
      return null;
    }
    var modals = [];
    for (var tag in this.state.modals) {
      modals.push(this.state.modals[tag]);
    }
    if (modals.length === 0) {
      return null;
    }
    return (
      <View
        style={styles.modalsContainer}>
        {modals}
      </View>
    );
  }
});

var styles = StyleSheet.create({
  modalsContainer: {
    position: 'absolute',
    left: 0,
    top: 0,
    right: 0,
    bottom: 0,
  },
});

Portal.isReactNativeComponent = true;

export default Portal;
<commit_msg>Make ES6+ Portal<commit_after>/**
 * Copyright (c) 2015-present, Alibaba Group Holding Limited.
 * All rights reserved.
 *
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * @providesModule ReactPortal
 * @flow
 */
'use strict';

import React from 'react';
import Platform from 'ReactPlatform';
import StyleSheet from 'ReactStyleSheet';
import View from 'ReactView';
import autobind from 'autobind-decorator';

var _portalRef: any;

// Unique identifiers for modals.
var lastUsedTag = 0;

/*
 * Note: Only intended for Android at the moment.  Just use Modal in your iOS
 * code.
 *
 * A container that renders all the modals on top of everything else in the application.
 *
 * Portal makes it possible for application code to pass modal views all the way up to
 * the root element created in `renderApplication`.
 *
 * Never use `<Portal>` in your code. There is only one Portal instance rendered
 * by the top-level `renderApplication`.
 */
class Portal extends React.Component {
  static statics = {
    /**
     * Use this to create a new unique tag for your component that renders
     * modals. A good place to allocate a tag is in `componentWillMount`
     * of your component.
     * See `showModal` and `closeModal`.
     */
    allocateTag(): string {
      return '__modal_' + (++lastUsedTag);
    },

    /**
     * Render a new modal.
     * @param tag A unique tag identifying the React component to render.
     * This tag can be later used in `closeModal`.
     * @param component A React component to be rendered.
     */
    showModal(tag: string, component: any) {
      if (!_portalRef) {
        console.error('Calling showModal but no Portal has been rendered.');
        return;
      }
      _portalRef._showModal(tag, component);
    },

    /**
     * Remove a modal from the collection of modals to be rendered.
     * @param tag A unique tag identifying the React component to remove.
     * Must exactly match the tag previously passed to `showModal`.
     */
    closeModal(tag: string) {
      if (!_portalRef) {
        console.error('Calling closeModal but no Portal has been rendered.');
        return;
      }
      _portalRef._closeModal(tag);
    },

    /**
     * Get an array of all the open modals, as identified by their tag string.
     */
    getOpenModals(): Array<string> {
      if (!_portalRef) {
        console.error('Calling getOpenModals but no Portal has been rendered.');
        return [];
      }
      return _portalRef._getOpenModals();
    },
  }

  state = {modals: {}}

  _showModal(tag: string, component: any) {
    // We are about to open first modal, so Portal will appear.
    // Let's disable accessibility for background view on Android.
    if (this._getOpenModals().length === 0 && this.props.onModalVisibilityChanged) {
      this.props.onModalVisibilityChanged(true);
    }
    // This way state is chained through multiple calls to
    // _showModal, _closeModal correctly.
    this.setState((state) => {
      var modals = state.modals;
      modals[tag] = component;
      return {modals};
    });
  }

  _closeModal(tag: string) {
    if (!this.state.modals.hasOwnProperty(tag)) {
      return;
    }
    // We are about to close last modal, so Portal will disappear.
    // Let's enable accessibility for application view on Android.
    if (this._getOpenModals().length === 1 && this.props.onModalVisibilityChanged) {
      this.props.onModalVisibilityChanged(false);
    }
    // This way state is chained through multiple calls to
    // _showModal, _closeModal correctly.
    this.setState((state) => {
      var modals = state.modals;
      delete modals[tag];
      return {modals};
    });
  }

  _getOpenModals(): Array<string> {
    return Object.keys(this.state.modals);
  }

  render() {
    _portalRef = this;
    if (!this.state.modals) {
      return null;
    }
    var modals = [];
    for (var tag in this.state.modals) {
      modals.push(this.state.modals[tag]);
    }
    if (modals.length === 0) {
      return null;
    }
    return (
      <View
        style={styles.modalsContainer}>
        {modals}
      </View>
    );
  }

};

var styles = StyleSheet.create({
  modalsContainer: {
    position: 'absolute',
    left: 0,
    top: 0,
    right: 0,
    bottom: 0,
  },
});

Portal.isReactNativeComponent = true;

autobind(Portal);

export default Portal;
<|endoftext|>


<commit_before>register_namespace('ui.infoblock');

ui.infoblock.convert_wiki = function (elem) {
    elem.find('a').each(function(){
        var html = '' + $(this).text() + '';
        if ($(this).hasClass('bbcode_artist')) {
            var artist = $(this).attr('href');
            artist = artist.replace('http://www.last.fm/music/', '');
            artist = util.string.urldecode(artist);
            html = '' + $(this).text() + ''
        }
        if ($(this).hasClass('bbcode_tag')) {
            var tag = $(this).attr('href');
            tag = tag.replace('http://www.last.fm/tag/', '');
            tag = util.string.urldecode(tag);
            html = '' + $(this).text() + ''
        }
        $(html).insertAfter($(this));
        $(this).remove();
    });
};

ui.infoblock.add_comments = function(elem, type, name) {
    var hash = util.string.md5(type + ' ' + name)
    var id = 'infoblock_' + type + util.random.randint(0, 100500);
    elem.find('.infoblock__comments').attr('id', id + '__comments');
    network.vkontakte.Widgets.Comments(id + '__comments', {autoPublish: 0, limit: 5}, hash);
}

ui.infoblock.show_artist = function(elem, name) {
    network.lastfm.api(
        'artist.getInfo',
        {
            'artist': name,
            'lang': 'ru'
        },
        function(data) {
            var context = {
                'name': data.artist.name,
                'wiki_summary': data.artist.bio.summary,
                'wiki': data.artist.bio.content,
                'similar': network.lastfm.arrayize(data.artist.similar.artist),
                'tags': network.lastfm.arrayize(data.artist.tags.tag),
                'image': network.lastfm.select_image(data.artist.image, 'large'),
                'lastfm_url': data.artist.url,
                'stations': [
                    {
                        'type': 'similar',
                        'name': data.artist.name,
                        'html': player.station.similar.get_html(data.artist.name)
                    },
                    {
                        'type': 'artist',
                        'name': data.artist.name,
                        'html': player.station.artist.get_html(data.artist.name)
                    }
                ]
            };
            elem.html(ich.tpl_infoblock_artist(context));
            ui.infoblock.convert_wiki($('.infoblock__wiki'));
            ui.infoblock.add_comments(elem);
        }
    );
};

ui.infoblock.show_tag = function(elem, name) {
    network.lastfm.api(
        'tag.getInfo',
        {
            'tag': name,
            'lang': 'ru'
        },
        function(data) {
            var context = {
                'name': data.tag.name,
                'wiki_summary': data.tag.wiki.summary,
                'wiki': data.tag.wiki.content,
                'lastfm_url': data.tag.url,
                'stations': [
                    {
                        'type': 'tag',
                        'name': data.tag.name,
                        'html': player.station.tag.get_html(data.tag.name)
                    }
                ]
            };
            elem.html(ich.tpl_infoblock_tag(context));
            ui.infoblock.convert_wiki($('.infoblock__wiki'));
            ui.infoblock.add_comments(elem);

            network.lastfm.api(
                'tag.getTopArtists',
                {
                    'tag': name,
                    'limit': 25
                },
                function(data) {
                    context['top_artists'] = [];
                    var artists = context['artists'] = network.lastfm.arrayize(data.topartists.artist);
                    for (var i = 0; i < 4; i++) {
                        context['top_artists'].push({
                            'name': artists[i].name,
                            'image': network.lastfm.select_image(artists[i].image, 'medium', true)
                        });
                    }
                    var html = ich.tpl_infoblock_tag(context).find('.infoblock__artists').html();
                    elem.find('.infoblock__artists').html(html);
                    var html = ich.tpl_infoblock_tag(context).find('.infoblock__photos').html();
                    elem.find('.infoblock__photos').html(html);
                }
            );

            network.lastfm.api(
                'tag.getSimilar',
                {
                    'tag': name
                },
                function(data) {
                    context['tags'] = network.lastfm.arrayize(data.similartags.tag).splice(0, 10);
                    var html = ich.tpl_infoblock_tag(context).find('.infoblock__tags').html();
                    elem.find('.infoblock__tags').html(html);
                }
            );

        }
    );
};

ui.infoblock.show_user = function(elem, name) {
    network.lastfm.api(
        'user.getInfo',
        {
            'user': name
        },
        function(data) {
            var context = {
                'name': data.user.name,
                'image': network.lastfm.select_image(data.user.image, 'large'),
                'lastfm_url': data.user.url,
                'it_is_you': (data.user.name == network.lastfm.user),
                'stations': [
                    {
                        'type': 'library',
                        'name': data.user.name,
                        'html': player.station.library.get_html(data.user.name)
                    },
                    {
                        'type': 'loved',
                        'name': data.user.name,
                        'html': player.station.loved.get_html(data.user.name)
                    }
                ]
            };
            elem.html(ich.tpl_infoblock_user(context));

            network.lastfm.api(
                'user.getTopArtists',
                {
                    'user': name,
                    'period': 'overall',
                    'limit': 16
                },
                function(data) {
                    var artists = network.lastfm.arrayize(data.topartists.artist);
                    context['artists'] = [];
                    for (var i = 0; i < artists.length; i++) {
                        context['artists'].push({
                            'name': artists[i].name,
                            'image': network.lastfm.select_image(artists[i].image, 'medium', true)
                        });
                    }
                    elem.html(ich.tpl_infoblock_user(context));
                }
            );
        }
    );
};

ui.infoblock.show = function(elem, type, name) {
    elem.html('

');
    ui.infoblock['show_' + type](elem, name);
};

ui.infoblock.show_popup = function(type, name) {
    ui.popup.show(name);
    ui.infoblock.show($('.popup .popup__content'), type, name);
};

$('.nav-infoblock').live('click', function(e) {
    e.preventDefault();
    ui.infoblock.show_popup($(this).data('type'), (($(this).data('name'))||$(this).text()));
});

<commit_msg>И наконец... комментарии должны работать теперь.<commit_after>register_namespace('ui.infoblock');

ui.infoblock.convert_wiki = function (elem) {
    elem.find('a').each(function(){
        var html = '' + $(this).text() + '';
        if ($(this).hasClass('bbcode_artist')) {
            var artist = $(this).attr('href');
            artist = artist.replace('http://www.last.fm/music/', '');
            artist = util.string.urldecode(artist);
            html = '' + $(this).text() + ''
        }
        if ($(this).hasClass('bbcode_tag')) {
            var tag = $(this).attr('href');
            tag = tag.replace('http://www.last.fm/tag/', '');
            tag = util.string.urldecode(tag);
            html = '' + $(this).text() + ''
        }
        $(html).insertAfter($(this));
        $(this).remove();
    });
};

ui.infoblock.add_comments = function(elem, type, name) {
    var hash = util.string.md5(type + ' ' + name)
    var id = 'infoblock_' + type + util.random.randint(0, 100500);
    elem.find('.infoblock__comments').attr('id', id + '__comments');
    network.vkontakte.Widgets.Comments(id + '__comments', {autoPublish: 0, limit: 5}, hash);
}

ui.infoblock.show_artist = function(elem, name) {
    network.lastfm.api(
        'artist.getInfo',
        {
            'artist': name,
            'lang': 'ru'
        },
        function(data) {
            var context = {
                'name': data.artist.name,
                'wiki_summary': data.artist.bio.summary,
                'wiki': data.artist.bio.content,
                'similar': network.lastfm.arrayize(data.artist.similar.artist),
                'tags': network.lastfm.arrayize(data.artist.tags.tag),
                'image': network.lastfm.select_image(data.artist.image, 'large'),
                'lastfm_url': data.artist.url,
                'stations': [
                    {
                        'type': 'similar',
                        'name': data.artist.name,
                        'html': player.station.similar.get_html(data.artist.name)
                    },
                    {
                        'type': 'artist',
                        'name': data.artist.name,
                        'html': player.station.artist.get_html(data.artist.name)
                    }
                ]
            };
            elem.html(ich.tpl_infoblock_artist(context));
            ui.infoblock.convert_wiki($('.infoblock__wiki'));
            ui.infoblock.add_comments(elem, 'artist', name);
        }
    );
};

ui.infoblock.show_tag = function(elem, name) {
    network.lastfm.api(
        'tag.getInfo',
        {
            'tag': name,
            'lang': 'ru'
        },
        function(data) {
            var context = {
                'name': data.tag.name,
                'wiki_summary': data.tag.wiki.summary,
                'wiki': data.tag.wiki.content,
                'lastfm_url': data.tag.url,
                'stations': [
                    {
                        'type': 'tag',
                        'name': data.tag.name,
                        'html': player.station.tag.get_html(data.tag.name)
                    }
                ]
            };
            elem.html(ich.tpl_infoblock_tag(context));
            ui.infoblock.convert_wiki($('.infoblock__wiki'));
            ui.infoblock.add_comments(elem, 'tag', name);

            network.lastfm.api(
                'tag.getTopArtists',
                {
                    'tag': name,
                    'limit': 25
                },
                function(data) {
                    context['top_artists'] = [];
                    var artists = context['artists'] = network.lastfm.arrayize(data.topartists.artist);
                    for (var i = 0; i < 4; i++) {
                        context['top_artists'].push({
                            'name': artists[i].name,
                            'image': network.lastfm.select_image(artists[i].image, 'medium', true)
                        });
                    }
                    var html = ich.tpl_infoblock_tag(context).find('.infoblock__artists').html();
                    elem.find('.infoblock__artists').html(html);
                    var html = ich.tpl_infoblock_tag(context).find('.infoblock__photos').html();
                    elem.find('.infoblock__photos').html(html);
                }
            );

            network.lastfm.api(
                'tag.getSimilar',
                {
                    'tag': name
                },
                function(data) {
                    context['tags'] = network.lastfm.arrayize(data.similartags.tag).splice(0, 10);
                    var html = ich.tpl_infoblock_tag(context).find('.infoblock__tags').html();
                    elem.find('.infoblock__tags').html(html);
                }
            );

        }
    );
};

ui.infoblock.show_user = function(elem, name) {
    network.lastfm.api(
        'user.getInfo',
        {
            'user': name
        },
        function(data) {
            var context = {
                'name': data.user.name,
                'image': network.lastfm.select_image(data.user.image, 'large'),
                'lastfm_url': data.user.url,
                'it_is_you': (data.user.name == network.lastfm.user),
                'stations': [
                    {
                        'type': 'library',
                        'name': data.user.name,
                        'html': player.station.library.get_html(data.user.name)
                    },
                    {
                        'type': 'loved',
                        'name': data.user.name,
                        'html': player.station.loved.get_html(data.user.name)
                    }
                ]
            };
            elem.html(ich.tpl_infoblock_user(context));

            network.lastfm.api(
                'user.getTopArtists',
                {
                    'user': name,
                    'period': 'overall',
                    'limit': 16
                },
                function(data) {
                    var artists = network.lastfm.arrayize(data.topartists.artist);
                    context['artists'] = [];
                    for (var i = 0; i < artists.length; i++) {
                        context['artists'].push({
                            'name': artists[i].name,
                            'image': network.lastfm.select_image(artists[i].image, 'medium', true)
                        });
                    }
                    elem.html(ich.tpl_infoblock_user(context));
                }
            );
        }
    );
};

ui.infoblock.show = function(elem, type, name) {
    elem.html('

');
    ui.infoblock['show_' + type](elem, name);
};

ui.infoblock.show_popup = function(type, name) {
    ui.popup.show(name);
    ui.infoblock.show($('.popup .popup__content'), type, name);
};

$('.nav-infoblock').live('click', function(e) {
    e.preventDefault();
    ui.infoblock.show_popup($(this).data('type'), (($(this).data('name'))||$(this).text()));
});

<|endoftext|>