<commit_before>/* global cytoscape:true */
class MCProcessesWorkflowGraphComponentController {
    /*@ngInject*/
    constructor(processGraph, workflowService, mcbus, experimentsService, $stateParams, mcstate, $filter, $mdDialog) {
        this.cy = null;
        this.processGraph = processGraph;
        this.workflowService = workflowService;
        this.mcbus = mcbus;
        this.mcstate = mcstate;
        this.myName = 'MCProcessesWorkflowGraph';
        this.projectId = $stateParams.project_id;
        this.experimentId = $stateParams.experiment_id;
        this.experimentsService = experimentsService;
        this.$filter = $filter;
        this.$mdDialog = $mdDialog;
        this.removedNodes = null;
        this.navigator = null;
    }

    $onInit() {
        // To preserve this binding pass this.allProcessesGraph bound to an arrow function. Arrow
        // functions lexically scope, so this in the arrow function is the this for
        // MCProcessesWorkflowGraphComponentController
        let cb = (processes) => {
            this.processes = processes;
            this.allProcessesGraph();
        };

        this.mcbus.subscribe('PROCESSES$CHANGE', this.myName, cb);

        let searchcb = (search) => {
            if (search === '') {
                if (this.removedNodes !== null) {
                    this.removedNodes.restore();
                    this.cy.layout({name: 'dagre', fit: true});
                }
                return;
            }
            this.removedNodes = null;
            let matches = this.$filter('filter')(this.processes.plain(), search);
            if (!matches.length) {
                return;
            }
            let matchesById = _.indexBy(matches, 'id');
            let matchingNodes = this.cy.nodes().filter((i, ele) => {
                let processId = ele.data('details').id;
                if ((processId in matchesById)) {
                    return false;
                }
                return true;
            });
            this.removedNodes = this.cy.remove(matchingNodes.union(matchingNodes.connectedEdges()));
            this.cy.layout({name: 'dagre', fit: true});
        };

        this.mcstate.subscribe('WORKFLOW$SEARCH', this.myName, searchcb);
        this.mcbus.subscribe('WORKFLOW$RESET', this.myName, () => this.allProcessesGraph());
        this.mcbus.subscribe('WORKFLOW$NAVIGATOR', this.myName, () => {
            if (this.navigator === null) {
                this.navigator = this.cy.navigator();
            } else {
                this.navigator.destroy();
                this.navigator = null;
            }
        });

        this.mcbus.subscribe('WORKFLOW$FILTER$BYSAMPLES', this.myName, (samples) => {
            console.log('WORKFLOW$FILTER$BYSAMPLES', samples);
            if (!samples.length) {
                return;
            }

            let matchingProcesses = [];
            samples.forEach(sample => {
                let matches = this.$filter('filter')(this.processes.plain(), sample.id);
                matchingProcesses = matchingProcesses.concat(matches.map(p => ({id: p.id, seen: false})));
            });

            console.log('  number of matching processes', matchingProcesses.length);

            let matchesById = _.indexBy(matchingProcesses, 'id');
            let matchingNodes = this.cy.nodes().filter((i, ele) => {
                let processId = ele.data('details').id;
                if ((processId in matchesById)) {
                    return false;
                }
                return true;
            });
            this.removedNodes = this.cy.remove(matchingNodes.union(matchingNodes.connectedEdges()));
            this.cy.layout({name: 'dagre', fit: true});
        });
    }

    $onDestroy() {
        this.mcbus.leave('PROCESSES$CHANGE', this.myName);
        this.mcstate.leave('WORKFLOW$SEARCH', this.myName);
        this.mcbus.leave('WORKFLOW$RESET', this.myName);
        if (this.navigator !== null) {
            this.navigator.destroy();
        }
    }

    // This method will be called implicitly when the component is loaded.
    $onChanges(changes) {
        if (changes.processes) {
            this.processes = changes.processes.currentValue;
        }
        if (changes.highlightProcesses) {
            this.highlightProcesses = changes.highlightProcesses.currentValue;
        }
        this.allProcessesGraph();
    }

    allProcessesGraph() {
        let g = this.processGraph.build(this.processes, this.highlightProcesses);
        this.samples = g.samples;
        this.cy = cytoscape({
            container: document.getElementById('processesGraph'),
            elements: g.elements,
            style: [
                {
                    selector: 'node',
                    style: {
                        'content': 'data(name)',
                        'text-valign': 'center',
                        'text-halign': 'center',
                        'background-color': 'data(color)',
                        color: 'black',
                        'text-outline-color': 'data(color)',
                        'font-size': '18px',
                        'font-weight': 'bold',
                        'text-outline-width': '5px',
                        'text-outline-opacity': 1,
                        'border-width': '4px',
                        'border-color': 'data(highlight)',
                        shape: 'data(shape)',
                        width: '80px',
                        height: '80px'
                    }
                },
                {
                    selector: 'edge',
                    style: {
                        'width': 4,
                        'target-arrow-shape': 'triangle',
                        //'target-arrow-color': '#9dbaea',
                        'curve-style': 'bezier',
                        'content': 'data(name)',
                        'font-weight': 'bold',
                        'text-outline-color': '#555',
                        'text-outline-width': '3px',
                        'color': '#fff'
                    }
                },
                {
                    selector: 'node:selected',
                    style: {
                        'border-width': '8px',
                        'border-color': '#2196f3',
                        color: '#2196f3'
                    }
                },
                {
                    selector: 'edge:selected',
                    style: {
                        'background-color': '#2196f3',
                        'text-outline-color': '#2196f3'
                    }
                }
            ]
        });

        this.cy.on('click', event => {
            let target = event.cyTarget;
            if (!target.isNode && !target.isEdge) {
                this.mcstate.set(this.mcstate.SELECTED$PROCESS, null);
                this.mcProcessesWorkflow.setSelectedProcess(null);
            } else if (target.isNode()) {
                //let edges = target.connectedEdges();
                //edges.forEach((e) => console.log('source is ' + e.data('source')));
                //console.log(target.connectedEdges());
                let processId = target.data('id');
                let process = this.processes.filter((p) => p.id === processId)[0];
                this.mcstate.set(this.mcstate.SELECTED$PROCESS, process);
                this.mcProcessesWorkflow.setSelectedProcess(processId, (target.outgoers().length > 0));
            } else if (target.isEdge()) {
                this.mcstate.set(this.mcstate.SELECTED$PROCESS, null);
                this.mcProcessesWorkflow.setSelectedProcess(null);
            }
        });
        this.cy.on('cxttap', event => {
            console.log('cxttap');
            let target = event.cyTarget;
            if (target.isNode()) {
                console.log(' -- target is node', target.data('name'));
            }
        });
        //this.cy.on('mouseover', function(event) {
        //    let target = event.cyTarget;
        //    if (target.data) {
        //        //log('target', target.data('name'));
        //    }
        //    // Need to install qtip or some other
        //    //target.qtip({
        //    //    content: target.data('name')
        //    //});
        //});
        this.cy.layout({name: 'dagre', fit: true});
        this.cy.panzoom();
        this.ctxMenu = this.setupContextMenus()
    }

    setupContextMenus() {
        let options = {
            menuItems: [
                {
                    id: 'details',
                    title: 'Show Details',
                    selector: 'node, edge',
                    hasTrailingDivider: true,
                    onClickFunction: (event) => this._showDetails(event)
                },
                {
                    id: 'clone-process',
                    title: 'Clone Process',
                    selector: 'node',
                    onClickFunction: (event) => this._cloneProcess(event)
                },
                {
                    id: 'delete-process',
                    title: 'Delete Process',
                    selector: 'node',
                    onClickFunction: (event) => this._deleteProcess(event),
                }
            ]
        };
        this.cy.contextMenus(options);
    }

    _showDetails(event) {
        let target = event.cyTarget;
        if (target.isNode()) {
            let process = this.getProcessFromEvent(event);
            this.$mdDialog.show({
                templateUrl: 'app/project/experiments/experiment/components/workflow/mc-process-details-dialog.html',
                controller: MCProcessDetailsDialogController,
                controllerAs: '$ctrl',
                bindToController: true,
                locals: {
                    process: process
                }
            });
        }
    }

    _cloneProcess(event) {
        let process = this.getProcessFromEvent(event);
        this.workflowService.cloneProcess(this.projectId, this.experimentId, process);
    }

    _deleteProcess() {

    }

    getProcessFromEvent(event) {
        let target = event.cyTarget;
        let processId = target.data('id');
        let process = this.processes.filter((p) => p.id === processId)[0];
        process.hasChildren = (target.outgoers().length > 0);
        return process;
    }

}

class MCProcessDetailsDialogController {
    /*@ngInject*/
    constructor($mdDialog, workflowService, $stateParams) {
        this.$mdDialog = $mdDialog;
        this.projectId = $stateParams.project_id;
        this.experimentId = $stateParams.experiment_id;
        this.workflowService = workflowService;
    }

    done() {
        this.$mdDialog.hide();
    }

    deleteProcess() {
        this.workflowService.deleteNodeAndProcess(this.projectId, this.experimentId, this.process.id)
    }
}

angular.module('materialscommons').component('mcProcessesWorkflowGraph', {
    templateUrl: 'app/project/experiments/experiment/components/workflow/mc-processes-workflow-graph.html',
    controller: MCProcessesWorkflowGraphComponentController,
    bindings: {
        processes: '<',
        highlightProcesses: '<'
    },
    require: {
        mcProcessesWorkflow: '^mcProcessesWorkflow'
    }
});
<commit_msg>Remove right click for unimplemented delete.<commit_after>/* global cytoscape:true */
class MCProcessesWorkflowGraphComponentController {
    /*@ngInject*/
    constructor(processGraph, workflowService, mcbus, experimentsService, $stateParams, mcstate, $filter, $mdDialog) {
        this.cy = null;
        this.processGraph = processGraph;
        this.workflowService = workflowService;
        this.mcbus = mcbus;
        this.mcstate = mcstate;
        this.myName = 'MCProcessesWorkflowGraph';
        this.projectId = $stateParams.project_id;
        this.experimentId = $stateParams.experiment_id;
        this.experimentsService = experimentsService;
        this.$filter = $filter;
        this.$mdDialog = $mdDialog;
        this.removedNodes = null;
        this.navigator = null;
    }

    $onInit() {
        // To preserve this binding pass this.allProcessesGraph bound to an arrow function. Arrow
        // functions lexically scope, so this in the arrow function is the this for
        // MCProcessesWorkflowGraphComponentController
        let cb = (processes) => {
            this.processes = processes;
            this.allProcessesGraph();
        };

        this.mcbus.subscribe('PROCESSES$CHANGE', this.myName, cb);

        let searchcb = (search) => {
            if (search === '') {
                if (this.removedNodes !== null) {
                    this.removedNodes.restore();
                    this.cy.layout({name: 'dagre', fit: true});
                }
                return;
            }
            this.removedNodes = null;
            let matches = this.$filter('filter')(this.processes.plain(), search);
            if (!matches.length) {
                return;
            }
            let matchesById = _.indexBy(matches, 'id');
            let matchingNodes = this.cy.nodes().filter((i, ele) => {
                let processId = ele.data('details').id;
                if ((processId in matchesById)) {
                    return false;
                }
                return true;
            });
            this.removedNodes = this.cy.remove(matchingNodes.union(matchingNodes.connectedEdges()));
            this.cy.layout({name: 'dagre', fit: true});
        };

        this.mcstate.subscribe('WORKFLOW$SEARCH', this.myName, searchcb);
        this.mcbus.subscribe('WORKFLOW$RESET', this.myName, () => this.allProcessesGraph());
        this.mcbus.subscribe('WORKFLOW$NAVIGATOR', this.myName, () => {
            if (this.navigator === null) {
                this.navigator = this.cy.navigator();
            } else {
                this.navigator.destroy();
                this.navigator = null;
            }
        });

        this.mcbus.subscribe('WORKFLOW$FILTER$BYSAMPLES', this.myName, (samples) => {
            console.log('WORKFLOW$FILTER$BYSAMPLES', samples);
            if (!samples.length) {
                return;
            }

            let matchingProcesses = [];
            samples.forEach(sample => {
                let matches = this.$filter('filter')(this.processes.plain(), sample.id);
                matchingProcesses = matchingProcesses.concat(matches.map(p => ({id: p.id, seen: false})));
            });

            console.log('  number of matching processes', matchingProcesses.length);

            let matchesById = _.indexBy(matchingProcesses, 'id');
            let matchingNodes = this.cy.nodes().filter((i, ele) => {
                let processId = ele.data('details').id;
                if ((processId in matchesById)) {
                    return false;
                }
                return true;
            });
            this.removedNodes = this.cy.remove(matchingNodes.union(matchingNodes.connectedEdges()));
            this.cy.layout({name: 'dagre', fit: true});
        });
    }

    $onDestroy() {
        this.mcbus.leave('PROCESSES$CHANGE', this.myName);
        this.mcstate.leave('WORKFLOW$SEARCH', this.myName);
        this.mcbus.leave('WORKFLOW$RESET', this.myName);
        if (this.navigator !== null) {
            this.navigator.destroy();
        }
    }

    // This method will be called implicitly when the component is loaded.
    $onChanges(changes) {
        if (changes.processes) {
            this.processes = changes.processes.currentValue;
        }
        if (changes.highlightProcesses) {
            this.highlightProcesses = changes.highlightProcesses.currentValue;
        }
        this.allProcessesGraph();
    }

    allProcessesGraph() {
        let g = this.processGraph.build(this.processes, this.highlightProcesses);
        this.samples = g.samples;
        this.cy = cytoscape({
            container: document.getElementById('processesGraph'),
            elements: g.elements,
            style: [
                {
                    selector: 'node',
                    style: {
                        'content': 'data(name)',
                        'text-valign': 'center',
                        'text-halign': 'center',
                        'background-color': 'data(color)',
                        color: 'black',
                        'text-outline-color': 'data(color)',
                        'font-size': '18px',
                        'font-weight': 'bold',
                        'text-outline-width': '5px',
                        'text-outline-opacity': 1,
                        'border-width': '4px',
                        'border-color': 'data(highlight)',
                        shape: 'data(shape)',
                        width: '80px',
                        height: '80px'
                    }
                },
                {
                    selector: 'edge',
                    style: {
                        'width': 4,
                        'target-arrow-shape': 'triangle',
                        //'target-arrow-color': '#9dbaea',
                        'curve-style': 'bezier',
                        'content': 'data(name)',
                        'font-weight': 'bold',
                        'text-outline-color': '#555',
                        'text-outline-width': '3px',
                        'color': '#fff'
                    }
                },
                {
                    selector: 'node:selected',
                    style: {
                        'border-width': '8px',
                        'border-color': '#2196f3',
                        color: '#2196f3'
                    }
                },
                {
                    selector: 'edge:selected',
                    style: {
                        'background-color': '#2196f3',
                        'text-outline-color': '#2196f3'
                    }
                }
            ]
        });

        this.cy.on('click', event => {
            let target = event.cyTarget;
            if (!target.isNode && !target.isEdge) {
                this.mcstate.set(this.mcstate.SELECTED$PROCESS, null);
                this.mcProcessesWorkflow.setSelectedProcess(null);
            } else if (target.isNode()) {
                //let edges = target.connectedEdges();
                //edges.forEach((e) => console.log('source is ' + e.data('source')));
                //console.log(target.connectedEdges());
                let processId = target.data('id');
                let process = this.processes.filter((p) => p.id === processId)[0];
                this.mcstate.set(this.mcstate.SELECTED$PROCESS, process);
                this.mcProcessesWorkflow.setSelectedProcess(processId, (target.outgoers().length > 0));
            } else if (target.isEdge()) {
                this.mcstate.set(this.mcstate.SELECTED$PROCESS, null);
                this.mcProcessesWorkflow.setSelectedProcess(null);
            }
        });
        this.cy.on('cxttap', event => {
            console.log('cxttap');
            let target = event.cyTarget;
            if (target.isNode()) {
                console.log(' -- target is node', target.data('name'));
            }
        });
        //this.cy.on('mouseover', function(event) {
        //    let target = event.cyTarget;
        //    if (target.data) {
        //        //log('target', target.data('name'));
        //    }
        //    // Need to install qtip or some other
        //    //target.qtip({
        //    //    content: target.data('name')
        //    //});
        //});
        this.cy.layout({name: 'dagre', fit: true});
        this.cy.panzoom();
        this.ctxMenu = this.setupContextMenus()
    }

    setupContextMenus() {
        let options = {
            menuItems: [
                {
                    id: 'details',
                    title: 'Show Details',
                    selector: 'node, edge',
                    hasTrailingDivider: true,
                    onClickFunction: (event) => this._showDetails(event)
                },
                {
                    id: 'clone-process',
                    title: 'Clone Process',
                    selector: 'node',
                    onClickFunction: (event) => this._cloneProcess(event)
                }
                // ,
                // {
                //     id: 'delete-process',
                //     title: 'Delete Process',
                //     selector: 'node',
                //     onClickFunction: (event) => this._deleteProcess(event)
                // }
            ]
        };
        this.cy.contextMenus(options);
    }

    _showDetails(event) {
        let target = event.cyTarget;
        if (target.isNode()) {
            let process = this.getProcessFromEvent(event);
            this.$mdDialog.show({
                templateUrl: 'app/project/experiments/experiment/components/workflow/mc-process-details-dialog.html',
                controller: MCProcessDetailsDialogController,
                controllerAs: '$ctrl',
                bindToController: true,
                locals: {
                    process: process
                }
            });
        }
    }

    _cloneProcess(event) {
        let process = this.getProcessFromEvent(event);
        this.workflowService.cloneProcess(this.projectId, this.experimentId, process);
    }

    _deleteProcess() {

    }

    getProcessFromEvent(event) {
        let target = event.cyTarget;
        let processId = target.data('id');
        let process = this.processes.filter((p) => p.id === processId)[0];
        process.hasChildren = (target.outgoers().length > 0);
        return process;
    }

}

class MCProcessDetailsDialogController {
    /*@ngInject*/
    constructor($mdDialog, workflowService, $stateParams) {
        this.$mdDialog = $mdDialog;
        this.projectId = $stateParams.project_id;
        this.experimentId = $stateParams.experiment_id;
        this.workflowService = workflowService;
    }

    done() {
        this.$mdDialog.hide();
    }

    deleteProcess() {
        this.workflowService.deleteNodeAndProcess(this.projectId, this.experimentId, this.process.id)
    }
}

angular.module('materialscommons').component('mcProcessesWorkflowGraph', {
    templateUrl: 'app/project/experiments/experiment/components/workflow/mc-processes-workflow-graph.html',
    controller: MCProcessesWorkflowGraphComponentController,
    bindings: {
        processes: '<',
        highlightProcesses: '<'
    },
    require: {
        mcProcessesWorkflow: '^mcProcessesWorkflow'
    }
});
<|endoftext|>


<commit_before>const webpack = require('webpack')
const nodeExternals = require('webpack-node-externals')
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin')
const config = require('./paths')
const path = require('path')
const babelConfig = require('../babel')

// This is the Webpack configuration.
// It is focused on developer experience and fast rebuilds.
module.exports = (options) => ({
  // Webpack can target multiple environments such as `node`,
  // `browser`, and even `electron`. Since Backpack is focused on Node,
  // we set the default target accordingly.
  target: 'node',
  // The benefit of Webpack over just using babel-cli or babel-node
  // command is sourcemap support. Although it slows down compilation,
  // it makes debugging dramatically easier.
  devtool: 'source-map',
  // Webpack allows you to define externals - modules that should not be
  // bundled. When bundling with Webpack for the backend - you usually
  // don't want to bundle its node_modules dependencies. This creates an externals
  // function that ignores node_modules when bundling in Webpack.
  // @see https://github.com/liady/webpack-node-externals
  externals: nodeExternals(),
  // As of Webpack 2 beta, Webpack provides performance hints.
  // Since we are not targeting a browser, bundle size is not relevant.
  // Additionally, the performance hints clutter up our nice error messages.
  performance: {
    hints: false
  },
  // Since we are wrapping our own webpack config, we need to properly resolve
  // Backpack's and the given user's node_modules without conflict.
  resolve: {
    extensions: ['.js', '.json'],
    modules: [config.userNodeModulesPath, path.resolve(__dirname, '../node_modules')]
  },
  resolveLoader: {
    modules: [config.userNodeModulesPath, path.resolve(__dirname, '../node_modules')]
  },
  node: {
    __filename: false,
    __dirname: false
  },
  entry: {
    main: [
      `${config.serverSrcPath}/index.js`
    ],
  },
  // This sets the default output file path, name, and compile target
  // module type. Since we are focused on Node.js, the libraryTarget
  // is set to CommonJS2
  output: {
    path: config.serverBuildPath,
    filename: '[name].js',
    sourceMapFilename: '[name].map',
    publicPath: config.publicPath,
    libraryTarget: 'commonjs2'
  },
  // Define a few default Webpack loaders. Notice the use of the new
  // Webpack 2 configuration: module.rules instead of module.loaders
  module: {
    rules: [
      // This is the development configuration.
      // It is focused on developer experience and fast rebuilds.
      {
        test: /\.json$/,
        loader: 'json-loader'
      },
      // Process JS with Babel (transpiles ES6 code into ES5 code).
      {
        test: /\.(js|jsx)$/,
        loader: 'babel-loader',
        exclude: [
          /node_modules/,
          config.buildPath
        ],
        options: babelConfig
      }
    ]
  },
  plugins: [
    // We define some sensible Webpack flags. One for the Node environment,
    // and one for dev / production. These become global variables. Note if
    // you use something like eslint or standard in your editor, you will
    // want to configure __DEV__ as a global variable accordingly.
    new webpack.DefinePlugin({
      'process.env.NODE_ENV': JSON.stringify(options.env),
      '__DEV__': options.env === 'development'
    }),
    // In order to provide sourcemaps, we automagically insert this at the
    // top of each file using the BannerPlugin.
    new webpack.BannerPlugin({
      raw: true,
      banner: 'require("source-map-support").install();'
    }),
    // The FriendlyErrorsWebpackPlugin (when combined with source-maps)
    // gives Backpack its human-readable error messages.
    new FriendlyErrorsWebpackPlugin(),
    // This plugin is awkwardly named. Use to be called NoErrorsPlugin.
    // It does not actually swallow errors. Instead, it just prevents
    // Webpack from printing out compile time stats to the console.
    // @todo new webpack.NoEmitOnErrorsPlugin()
    new webpack.NoErrorsPlugin()
  ]
})
<commit_msg>Closes #20. Add support for custom .babelrc<commit_after>const fs = require('fs')
const webpack = require('webpack')
const nodeExternals = require('webpack-node-externals')
const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin')
const config = require('./paths')
const path = require('path')
const babelPreset = require('../babel')

// This is the Webpack configuration.
// It is focused on developer experience and fast rebuilds.
module.exports = (options) => {
  const babelRcPath = path.resolve('.babelrc')
  const hasBabelRc = fs.existsSync(babelRcPath)
  const mainBabelOptions = {
    babelrc: true,
    cacheDirectory: true,
    presets: []
  }

  if (hasBabelRc) {
    console.log('> Using .babelrc defined in your app root')
  } else {
    mainBabelOptions.presets.push(require.resolve('../babel'))
  }

  return {
    // Webpack can target multiple environments such as `node`,
    // `browser`, and even `electron`. Since Backpack is focused on Node,
    // we set the default target accordingly.
    target: 'node',
    // The benefit of Webpack over just using babel-cli or babel-node
    // command is sourcemap support. Although it slows down compilation,
    // it makes debugging dramatically easier.
    devtool: 'source-map',
    // Webpack allows you to define externals - modules that should not be
    // bundled. When bundling with Webpack for the backend - you usually
    // don't want to bundle its node_modules dependencies. This creates an externals
    // function that ignores node_modules when bundling in Webpack.
    // @see https://github.com/liady/webpack-node-externals
    externals: nodeExternals(),
    // As of Webpack 2 beta, Webpack provides performance hints.
    // Since we are not targeting a browser, bundle size is not relevant.
    // Additionally, the performance hints clutter up our nice error messages.
    performance: {
      hints: false
    },
    // Since we are wrapping our own webpack config, we need to properly resolve
    // Backpack's and the given user's node_modules without conflict.
    resolve: {
      extensions: ['.js', '.json'],
      modules: [config.userNodeModulesPath, path.resolve(__dirname, '../node_modules')]
    },
    resolveLoader: {
      modules: [config.userNodeModulesPath, path.resolve(__dirname, '../node_modules')]
    },
    node: {
      __filename: false,
      __dirname: false
    },
    entry: {
      main: [
        `${config.serverSrcPath}/index.js`
      ],
    },
    // This sets the default output file path, name, and compile target
    // module type. Since we are focused on Node.js, the libraryTarget
    // is set to CommonJS2
    output: {
      path: config.serverBuildPath,
      filename: '[name].js',
      sourceMapFilename: '[name].map',
      publicPath: config.publicPath,
      libraryTarget: 'commonjs2'
    },
    // Define a few default Webpack loaders. Notice the use of the new
    // Webpack 2 configuration: module.rules instead of module.loaders
    module: {
      rules: [
        // This is the development configuration.
        // It is focused on developer experience and fast rebuilds.
        {
          test: /\.json$/,
          loader: 'json-loader'
        },
        // Process JS with Babel (transpiles ES6 code into ES5 code).
        {
          test: /\.(js|jsx)$/,
          loader: 'babel-loader',
          exclude: [
            /node_modules/,
            config.buildPath
          ],
          options: mainBabelOptions
        }
      ]
    },
    plugins: [
      // We define some sensible Webpack flags. One for the Node environment,
      // and one for dev / production. These become global variables. Note if
      // you use something like eslint or standard in your editor, you will
      // want to configure __DEV__ as a global variable accordingly.
      new webpack.DefinePlugin({
        'process.env.NODE_ENV': JSON.stringify(options.env),
        '__DEV__': options.env === 'development'
      }),
      // In order to provide sourcemaps, we automagically insert this at the
      // top of each file using the BannerPlugin.
      new webpack.BannerPlugin({
        raw: true,
        banner: 'require("source-map-support").install();'
      }),
      // The FriendlyErrorsWebpackPlugin (when combined with source-maps)
      // gives Backpack its human-readable error messages.
      new FriendlyErrorsWebpackPlugin(),
      // This plugin is awkwardly named. Use to be called NoErrorsPlugin.
      // It does not actually swallow errors. Instead, it just prevents
      // Webpack from printing out compile time stats to the console.
      // @todo new webpack.NoEmitOnErrorsPlugin()
      new webpack.NoErrorsPlugin()
    ]
  }
}
<|endoftext|>


<commit_before>var round = require('../round');
var partial = require('chessground').util.partial;
var classSet = require('chessground').util.classSet;
var raf = require('chessground').util.requestAnimationFrame;
var game = require('game').game;
var util = require('../util');
var status = require('game').status;
var renderStatus = require('game').view.status;
var router = require('game').router;
var m = require('mithril');

var emptyMove = m('move.empty', '...');
var nullMove = m('move.empty', '');

function renderMove(step, curPly, orEmpty) {
  if (!step) return orEmpty ? emptyMove : nullMove;
  var san = step.san[0] === 'P' ? step.san.slice(1) : step.san;
  return {
    tag: 'move',
    attrs: step.ply !== curPly ? {} : {
      class: 'active'
    },
    children: [san]
  };
}

function renderResult(ctrl) {
  var result;
  if (status.finished(ctrl.data)) switch (ctrl.data.game.winner) {
    case 'white':
      result = '1-0';
      break;
    case 'black':
      result = '0-1';
      break;
    default:
      result = '½-½';
  }
  if (result || status.aborted(ctrl.data)) {
    var winner = game.getPlayer(ctrl.data, ctrl.data.game.winner);
    return [
      m('p.result', result),
      m('p.status', [
        renderStatus(ctrl),
        winner ? ', ' + ctrl.trans(winner.color == 'white' ? 'whiteIsVictorious' : 'blackIsVictorious') : null
      ])
    ];
  }
}

function renderMoves(ctrl) {
  var steps = ctrl.data.steps;
  var firstPly = round.firstPly(ctrl.data);
  var lastPly = round.lastPly(ctrl.data);
  if (typeof lastPly === 'undefined') return;

  var pairs = [];
  if (firstPly % 2 === 0)
    for (var i = 1, len = steps.length; i < len; i += 2) pairs.push([steps[i], steps[i + 1]]);
  else {
    pairs.push([null, steps[1]]);
    for (var i = 2, len = steps.length; i < len; i += 2) pairs.push([steps[i], steps[i + 1]]);
  }

  var rows = [];
  for (var i = 0, len = pairs.length; i < len; i++) rows.push({
    tag: 'turn',
    children: [{
        tag: 'index',
        children: [i + 1]
      },
      renderMove(pairs[i][0], ctrl.vm.ply, true),
      renderMove(pairs[i][1], ctrl.vm.ply, false)
    ]
  });
  rows.push(renderResult(ctrl));

  return rows;
}

var analyseButtonIcon = m('span[data-icon="A"]');

function analyseButton(ctrl) {
  var showInfo = ctrl.forecastInfo();
  var attrs = {
    class: classSet({
      'button analysis': true,
      'hint--top': !showInfo,
      'hint--bottom': showInfo,
      'glowed': showInfo,
      'text': ctrl.data.forecastCount
    }),
    'data-hint': ctrl.trans('analysis'),
    href: router.game(ctrl.data, ctrl.data.player.color) + '/analysis#' + ctrl.vm.ply,
  };
  if (showInfo) attrs.config = function(el) {
    setTimeout(function() {
      $(el).powerTip({
        manual: true,
        fadeInTime: 300,
        fadeOutTime: 300,
        placement: 'n'
      }).data('powertipjq', $(el).siblings('.forecast-info').clone().show()).powerTip('show');
    }, 1000);
  };
  return [
    m('a', attrs, [
      m('span', {
        'data-icon': 'A',
        class: ctrl.data.forecastCount ? 'text' : ''
      }),
      ctrl.data.forecastCount
    ]),
    showInfo ? m('div.forecast-info.info.none', [
      m('strong.title.text[data-icon=]', 'Speed up your game!'),
      m('span.content', [
        'Use the analysis board to create conditional premoves.',
        m('br'),
        'Now available on your turn!'
      ])
    ]) : null
  ];
}

var flipIcon = m('span[data-icon=B]');

function renderButtons(ctrl) {
  var d = ctrl.data;
  var firstPly = round.firstPly(d);
  var lastPly = round.lastPly(d);
  var flipAttrs = {
    class: 'button flip hint--top' + (ctrl.vm.flip ? ' active' : ''),
    'data-hint': ctrl.trans('flipBoard'),
    'data-act': 'flip'
  };
  return m('div.buttons', {
    config: util.bindOnce('mousedown', function(e) {
      var ply = parseInt(e.target.getAttribute('data-ply'));
      if (!isNaN(ply)) ctrl.userJump(ply);
      else {
        var action = e.target.getAttribute('data-act') || e.target.parentNode.getAttribute('data-act');
        if (action === 'flip') {
          if (d.tv) location.href = '/tv/' + d.tv.channel + (d.tv.flip ? '' : '?flip=1');
          else if (d.player.spectator) location.href = router.game(d, d.opponent.color);
          else ctrl.flip();
        }
      }
    })
  }, [
    m('a', flipAttrs, flipIcon), [
      ['W', firstPly],
      ['Y', ctrl.vm.ply - 1],
      ['X', ctrl.vm.ply + 1],
      ['V', lastPly]
    ].map(function(b, i) {
      var enabled = ctrl.vm.ply !== b[1] && b[1] >= firstPly && b[1] <= lastPly;
      return m('a', {
        class: 'button ' + classSet({
          disabled: (ctrl.broken || !enabled),
          glowed: i === 3 && ctrl.isLate() && !ctrl.vm.initializing
        }),
        'data-icon': b[0],
        'data-ply': enabled ? b[1] : '-'
      });
    }), game.userAnalysable(d) ? analyseButton(ctrl) : null
  ]);
}

function autoScroll(el, ctrl) {
  raf(function() {
    if (ctrl.data.steps.length < 7) return;
    var st;
    if (ctrl.vm.ply >= round.lastPly(ctrl.data) - 1) st = 9999;
    else {
      var plyEl = el.querySelector('.active') || el.querySelector('turn:first-child');
      if (plyEl) st = plyEl.offsetTop - el.offsetHeight / 2 + plyEl.offsetHeight / 2;
    }
    if (st !== undefined) el.scrollTop = st;
  });
}

function racingKingsInit(d) {
  if (d.game.variant.key === 'racingKings' && d.game.turns === 0 && !d.player.spectator)
    return m('div.message', {
      'data-icon': '',
    }, [
      "You have the " + d.player.color + " pieces",
      d.player.color === 'white' ? [',', m('br'), m('strong', "it's your turn!")] : null
    ]);
}

module.exports = function(ctrl) {
  var d = ctrl.data;
  var h = ctrl.vm.ply + ctrl.stepsHash(d.steps) + d.game.status.id + d.game.winner + ctrl.vm.flip;
  if (ctrl.vm.replayHash === h) return {
    subtree: 'retain'
  };
  ctrl.vm.replayHash = h;
  var message = (d.game.variant.key === 'racingKings' && d.game.turns === 0) ? racingKingsInit : null;
  return m('div.replay', [
    renderButtons(ctrl),
    racingKingsInit(ctrl.data) || (ctrl.replayEnabledByPref() ? m('div.moves', {
      config: function(el, isUpdate, ctx) {
        if (isUpdate) return;
        util.bindOnce('mousedown', function(e) {
          var turn = parseInt($(e.target).siblings('index').text());
          var ply = 2 * turn - 2 + $(e.target).index();
          if (ply) ctrl.userJump(ply);
        })(el, isUpdate, ctx);
        var scrollNow = partial(autoScroll, el, ctrl);
        ctrl.vm.autoScroll = {
          now: scrollNow,
          throttle: util.throttle(300, false, scrollNow)
        };
        scrollNow();
        window.addEventListener('load', scrollNow);
      }
    }, renderMoves(ctrl)) : renderResult(ctrl))
  ]);
}
<commit_msg>better scroll moves view when displaying result<commit_after>var round = require('../round');
var partial = require('chessground').util.partial;
var classSet = require('chessground').util.classSet;
var raf = require('chessground').util.requestAnimationFrame;
var game = require('game').game;
var util = require('../util');
var status = require('game').status;
var renderStatus = require('game').view.status;
var router = require('game').router;
var m = require('mithril');

var emptyMove = m('move.empty', '...');
var nullMove = m('move.empty', '');

function renderMove(step, curPly, orEmpty) {
  if (!step) return orEmpty ? emptyMove : nullMove;
  var san = step.san[0] === 'P' ? step.san.slice(1) : step.san;
  return {
    tag: 'move',
    attrs: step.ply !== curPly ? {} : {
      class: 'active'
    },
    children: [san]
  };
}

function renderResult(ctrl) {
  var result;
  if (status.finished(ctrl.data)) switch (ctrl.data.game.winner) {
    case 'white':
      result = '1-0';
      break;
    case 'black':
      result = '0-1';
      break;
    default:
      result = '½-½';
  }
  if (result || status.aborted(ctrl.data)) {
    var winner = game.getPlayer(ctrl.data, ctrl.data.game.winner);
    return [
      m('p.result', result),
      m('p.status', {
        config: function(el, isUpdate) {
          if (!isUpdate) ctrl.vm.autoScroll.now();
        }
      }, [
        renderStatus(ctrl),
        winner ? ', ' + ctrl.trans(winner.color == 'white' ? 'whiteIsVictorious' : 'blackIsVictorious') : null
      ])
    ];
  }
}

function renderMoves(ctrl) {
  var steps = ctrl.data.steps;
  var firstPly = round.firstPly(ctrl.data);
  var lastPly = round.lastPly(ctrl.data);
  if (typeof lastPly === 'undefined') return;

  var pairs = [];
  if (firstPly % 2 === 0)
    for (var i = 1, len = steps.length; i < len; i += 2) pairs.push([steps[i], steps[i + 1]]);
  else {
    pairs.push([null, steps[1]]);
    for (var i = 2, len = steps.length; i < len; i += 2) pairs.push([steps[i], steps[i + 1]]);
  }

  var rows = [];
  for (var i = 0, len = pairs.length; i < len; i++) rows.push({
    tag: 'turn',
    children: [{
        tag: 'index',
        children: [i + 1]
      },
      renderMove(pairs[i][0], ctrl.vm.ply, true),
      renderMove(pairs[i][1], ctrl.vm.ply, false)
    ]
  });
  rows.push(renderResult(ctrl));

  return rows;
}

var analyseButtonIcon = m('span[data-icon="A"]');

function analyseButton(ctrl) {
  var showInfo = ctrl.forecastInfo();
  var attrs = {
    class: classSet({
      'button analysis': true,
      'hint--top': !showInfo,
      'hint--bottom': showInfo,
      'glowed': showInfo,
      'text': ctrl.data.forecastCount
    }),
    'data-hint': ctrl.trans('analysis'),
    href: router.game(ctrl.data, ctrl.data.player.color) + '/analysis#' + ctrl.vm.ply,
  };
  if (showInfo) attrs.config = function(el) {
    setTimeout(function() {
      $(el).powerTip({
        manual: true,
        fadeInTime: 300,
        fadeOutTime: 300,
        placement: 'n'
      }).data('powertipjq', $(el).siblings('.forecast-info').clone().show()).powerTip('show');
    }, 1000);
  };
  return [
    m('a', attrs, [
      m('span', {
        'data-icon': 'A',
        class: ctrl.data.forecastCount ? 'text' : ''
      }),
      ctrl.data.forecastCount
    ]),
    showInfo ? m('div.forecast-info.info.none', [
      m('strong.title.text[data-icon=]', 'Speed up your game!'),
      m('span.content', [
        'Use the analysis board to create conditional premoves.',
        m('br'),
        'Now available on your turn!'
      ])
    ]) : null
  ];
}

var flipIcon = m('span[data-icon=B]');

function renderButtons(ctrl) {
  var d = ctrl.data;
  var firstPly = round.firstPly(d);
  var lastPly = round.lastPly(d);
  var flipAttrs = {
    class: 'button flip hint--top' + (ctrl.vm.flip ? ' active' : ''),
    'data-hint': ctrl.trans('flipBoard'),
    'data-act': 'flip'
  };
  return m('div.buttons', {
    config: util.bindOnce('mousedown', function(e) {
      var ply = parseInt(e.target.getAttribute('data-ply'));
      if (!isNaN(ply)) ctrl.userJump(ply);
      else {
        var action = e.target.getAttribute('data-act') || e.target.parentNode.getAttribute('data-act');
        if (action === 'flip') {
          if (d.tv) location.href = '/tv/' + d.tv.channel + (d.tv.flip ? '' : '?flip=1');
          else if (d.player.spectator) location.href = router.game(d, d.opponent.color);
          else ctrl.flip();
        }
      }
    })
  }, [
    m('a', flipAttrs, flipIcon), [
      ['W', firstPly],
      ['Y', ctrl.vm.ply - 1],
      ['X', ctrl.vm.ply + 1],
      ['V', lastPly]
    ].map(function(b, i) {
      var enabled = ctrl.vm.ply !== b[1] && b[1] >= firstPly && b[1] <= lastPly;
      return m('a', {
        class: 'button ' + classSet({
          disabled: (ctrl.broken || !enabled),
          glowed: i === 3 && ctrl.isLate() && !ctrl.vm.initializing
        }),
        'data-icon': b[0],
        'data-ply': enabled ? b[1] : '-'
      });
    }), game.userAnalysable(d) ? analyseButton(ctrl) : null
  ]);
}

function autoScroll(el, ctrl) {
  raf(function() {
    if (ctrl.data.steps.length < 7) return;
    var st;
    if (ctrl.vm.ply >= round.lastPly(ctrl.data) - 1) st = 9999;
    else {
      var plyEl = el.querySelector('.active') || el.querySelector('turn:first-child');
      if (plyEl) st = plyEl.offsetTop - el.offsetHeight / 2 + plyEl.offsetHeight / 2;
    }
    if (st !== undefined) el.scrollTop = st;
  });
}

function racingKingsInit(d) {
  if (d.game.variant.key === 'racingKings' && d.game.turns === 0 && !d.player.spectator)
    return m('div.message', {
      'data-icon': '',
    }, [
      "You have the " + d.player.color + " pieces",
      d.player.color === 'white' ? [',', m('br'), m('strong', "it's your turn!")] : null
    ]);
}

module.exports = function(ctrl) {
  var d = ctrl.data;
  var h = ctrl.vm.ply + ctrl.stepsHash(d.steps) + d.game.status.id + d.game.winner + ctrl.vm.flip;
  if (ctrl.vm.replayHash === h) return {
    subtree: 'retain'
  };
  ctrl.vm.replayHash = h;
  var message = (d.game.variant.key === 'racingKings' && d.game.turns === 0) ? racingKingsInit : null;
  return m('div.replay', [
    renderButtons(ctrl),
    racingKingsInit(ctrl.data) || (ctrl.replayEnabledByPref() ? m('div.moves', {
      config: function(el, isUpdate, ctx) {
        if (isUpdate) return;
        util.bindOnce('mousedown', function(e) {
          var turn = parseInt($(e.target).siblings('index').text());
          var ply = 2 * turn - 2 + $(e.target).index();
          if (ply) ctrl.userJump(ply);
        })(el, isUpdate, ctx);
        var scrollNow = partial(autoScroll, el, ctrl);
        ctrl.vm.autoScroll = {
          now: scrollNow,
          throttle: util.throttle(300, false, scrollNow)
        };
        scrollNow();
        window.addEventListener('load', scrollNow);
      }
    }, renderMoves(ctrl)) : renderResult(ctrl))
  ]);
}
<|endoftext|>


<commit_before>// ==UserScript==
// @name         CargoShorts
// @namespace    http://github.com/Fryguy/cargo_shorts
// @version      0.2
// @description  Auto-click through BlueJeans screens
// @author       Jason Frey
// @match        https://bluejeans.com/*
// @run-at       document-end
// @grant        GM_xmlhttpRequest
// @connect      localhost
// ==/UserScript==

(function() {
    'use strict';

    function isInitialPage() {
        return !isSubsequentPage();
    }

    function isSubsequentPage() {
        return location.pathname == "/xd-local-storage-iframe/";
    }

    function guestLoginInput() {
        return window.top.document.querySelector(".guestView.panel > div:nth-child(2) > input");
    }

    function guestLoginButton() {
        return window.top.document.querySelector(".guestView.panel > button");
    }

    function computerAudioButton() {
        return window.top.document.querySelector(".primaryConnectionsDialog[style*=\"visible\"] .joinComputer");
    }

    function joinMeetingButton() {
        return window.top.document.querySelector("#modalContentMain > div.options.dynamicTooltipEl > div.decisionsHolder.connectionSwitching > div > div.enterHolder > button");
    }

    function waitForEntrypoint() {
        if (isInitialPage()) {
            if (guestLoginInput()) {
                doLogin();
            } else {
                setTimeout(waitForEntrypoint, 1000);
            }
        } else {
            if (computerAudioButton()) {
                doAudioOptions();
                setTimeout(waitForJoinMeetingButton, 1000);
            } else {
                setTimeout(waitForEntrypoint, 1000);
            }
        }
    }

    function waitForJoinMeetingButton() {
        if (joinMeetingButton()) {
            doJoinMeeting();
        } else {
            setTimeout(waitForJoinMeetingButton, 1000);
        }
    }

    function doLogin() {
        GM_xmlhttpRequest({
            method: "GET",
            headers: {
                Accept: "application/json",
            },
            url: "http://localhost:3000/configuration.json",
            onload: function(response) {
                var json = JSON.parse(response.responseText);
                guestLoginInput().value = json.name;
                guestLoginButton().click();
            }
        });
    }

    function doAudioOptions() {
        computerAudioButton().click();
    }

    function doJoinMeeting() {
        joinMeetingButton().click();
    }

    waitForEntrypoint();
})();
<commit_msg>Move fetching of configuration to before the entrypoint<commit_after>// ==UserScript==
// @name         CargoShorts
// @namespace    http://github.com/Fryguy/cargo_shorts
// @version      0.2.1
// @description  Auto-click through BlueJeans screens
// @author       Jason Frey
// @match        https://bluejeans.com/*
// @run-at       document-end
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @connect      localhost
// ==/UserScript==

(function() {
    'use strict';

    function isInitialPage() {
        return !isSubsequentPage();
    }

    function isSubsequentPage() {
        return location.pathname == "/xd-local-storage-iframe/";
    }

    function guestLoginInput() {
        return window.top.document.querySelector(".guestView.panel > div:nth-child(2) > input");
    }

    function guestLoginButton() {
        return window.top.document.querySelector(".guestView.panel > button");
    }

    function computerAudioButton() {
        return window.top.document.querySelector(".primaryConnectionsDialog[style*=\"visible\"] .joinComputer");
    }

    function joinMeetingButton() {
        return window.top.document.querySelector("#modalContentMain > div.options.dynamicTooltipEl > div.decisionsHolder.connectionSwitching > div > div.enterHolder > button");
    }

    function waitForEntrypoint() {
        if (isInitialPage()) {
            if (guestLoginInput()) {
                doLogin();
            } else {
                setTimeout(waitForEntrypoint, 1000);
            }
        } else {
            if (computerAudioButton()) {
                doAudioOptions();
                setTimeout(waitForJoinMeetingButton, 1000);
            } else {
                setTimeout(waitForEntrypoint, 1000);
            }
        }
    }

    function waitForJoinMeetingButton() {
        if (joinMeetingButton()) {
            doJoinMeeting();
        } else {
            setTimeout(waitForJoinMeetingButton, 1000);
        }
    }

    function doLogin() {
        guestLoginInput().value = configuration().name;
        guestLoginButton().click();
    }

    function doAudioOptions() {
        computerAudioButton().click();
    }

    function doJoinMeeting() {
        joinMeetingButton().click();
    }

    function configuration() {
        return JSON.parse(GM_getValue("configuration"));
    }

    GM_xmlhttpRequest({
        method: "GET",
        headers: {
            Accept: "application/json",
        },
        url: "http://localhost:3000/configuration.json",
        onload: function(response) {
            GM_setValue("configuration", response.responseText);
            waitForEntrypoint();
        }
    });
})();
<|endoftext|>


<commit_before>import React from 'react'
import PropTypes from 'prop-types'

const svgStyles = {
  position: 'relative',
  width: '100%',
  height: '100%'
}

const SvgPlaceholder = ({styles}) => {
  return (

      <svg x="0" y="0" className={styles.svg} style={svgStyles}>
        <linearGradient id="loader_gradient" gradientUnits="userSpaceOnUse" x1="0%" y1="0%" x2="100%" y2="100%">
          <stop offset="0" style={{stopColor: '#ddd'}} stopOpacity="0.5" />
          <stop offset="0.5" style={{stopColor: '#eee'}} />
          <stop offset="1" style={{stopColor: '#ddd'}} stopOpacity="0.5" />
          <animate attributeName="x1" dur="700ms" from="-100" to="100%" repeatCount="indefinite" />
          <animate attributeName="y1" dur="700ms" from="-100" to="100%" repeatCount="indefinite" />
          <animate attributeName="x2" dur="700ms" from="0%" to="200%" repeatCount="indefinite" />
          <animate attributeName="y2" dur="700ms" from="0%" to="200%" repeatCount="indefinite" />
        </linearGradient>
        <g fill="url(#loader_gradient)" className={styles.placeholderG}>
          <rect className={styles.media} />
          <rect className={styles.date} width="80%" />
          <rect className={styles.title} width="80%" />
          <rect className={styles.subtitle} width="50%" />
          <rect className={styles.description_1} width="100%" />
          <rect className={styles.description_2} width="50%" />
        </g>
      </svg>

  )
}

SvgPlaceholder.propTypes = {
  styles: PropTypes.object
}

SvgPlaceholder.defaultProps = {
  styles: {}
}

export default SvgPlaceholder
<commit_msg>[components] Remove animation on SvgPlaceholder due to massive perf degradation<commit_after>import React from 'react'
import PropTypes from 'prop-types'

const svgStyles = {
  position: 'relative',
  width: '100%',
  height: '100%'
}

const SvgPlaceholder = ({styles}) => {
  return (

      <svg x="0" y="0" className={styles.svg} style={svgStyles}>
        <linearGradient id="loader_gradient" gradientUnits="userSpaceOnUse" x1="0%" y1="0%" x2="100%" y2="100%">
          <stop offset="0" style={{stopColor: '#ddd'}} stopOpacity="0.5" />
          <stop offset="0.5" style={{stopColor: '#eee'}} />
          <stop offset="1" style={{stopColor: '#ddd'}} stopOpacity="0.5" />
          {
          /*
            Animation disabled due to performance issues. Do not re-enable before verifying that it doesn't cause
            performance degradations having a *lot* of placeholders on the page /BN
          */
          }
          {/*<animate attributeName="x1" dur="700ms" from="-100" to="100%" repeatCount="indefinite" />*/}
          {/*<animate attributeName="y1" dur="700ms" from="-100" to="100%" repeatCount="indefinite" />*/}
          {/*<animate attributeName="x2" dur="700ms" from="0%" to="200%" repeatCount="indefinite" />*/}
          {/*<animate attributeName="y2" dur="700ms" from="0%" to="200%" repeatCount="indefinite" />*/}
        </linearGradient>
        <g fill="url(#loader_gradient)" className={styles.placeholderG}>
          <rect className={styles.media} />
          <rect className={styles.date} width="80%" />
          <rect className={styles.title} width="80%" />
          <rect className={styles.subtitle} width="50%" />
          <rect className={styles.description_1} width="100%" />
          <rect className={styles.description_2} width="50%" />
        </g>
      </svg>

  )
}

SvgPlaceholder.propTypes = {
  styles: PropTypes.object
}

SvgPlaceholder.defaultProps = {
  styles: {}
}

export default SvgPlaceholder
<|endoftext|>


<commit_before>/* eslint-disable no-console */
import React from 'react'
import {isPlainObject} from 'lodash'
import PropTypes from 'prop-types'
import sanityClient from 'part:@sanity/base/client'
import AnchorButton from 'part:@sanity/components/buttons/anchor'
import styles from './ProjectInfo.css'

function isUrl(url) {
  return /^https?:\/\//.test(`${url}`)
}

function getGraphQlUrl(projectId) {
  return `https://${projectId}.api.sanity.io/v1/graphql/test`
}

function getGroqUrl(projectId) {
  return `https://${projectId}.api.sanity.io/v1/groq/test`
}

function getManageUrl(projectId) {
  return `https://manage.sanity.io/projects/${projectId}`
}

class ProjectInfo extends React.Component {
  static propTypes = {
    data: PropTypes.array
  }
  static defaultProps = {
    data: []
  }

  state = {
    studioHost: null,
    graphqlApi: null
  }

  componentDidMount() {
    const {projectId} = sanityClient.config()
    // fetch project data
    sanityClient.projects
      .getById(projectId)
      .then(result => {
        const {studioHost} = result
        this.setState({studioHost: studioHost ? `https://${studioHost}.sanity.studio` : null})
      })
      .catch(error => {
        console.log('Error while looking for studioHost', error)
        this.setState({
          studioHost: {
            error: 'Something went wrong while looking up studioHost. See console.'
          }
        })
      })

    // ping assumed graphql endpoint
    sanityClient
      .request({
        method: 'HEAD',
        uri: `/graphql/${sanityClient.config().dataset}/default`
      })
      .then(response => {
        this.setState({graphqlApi: getGraphQlUrl(projectId)})
      })
      .catch(error => {
        if (error.statusCode === 404) {
          this.setState({graphqlApi: null})
        } else {
          console.log('Error while looking for graphqlApi', error)
          this.setState({
            graphqlApi: {
              error: 'Something went wrong while looking up graphqlApi. See console.'
            }
          })
        }
      })
  }

  assembleTableRows() {
    const {projectId, dataset} = sanityClient.config()
    const {graphqlApi, studioHost} = this.state
    const propsData = this.props.data

    let result = [
      {
        title: 'Sanity project',
        rows: [{title: 'Project ID', value: projectId}, {title: 'Dataset', value: dataset}]
      }
    ]

    // Handle any apps
    const apps = [studioHost ? {title: 'Studio', value: studioHost} : null]
      .concat(propsData.filter(item => item.category === 'apps'))
      .filter(Boolean)
    if (apps.length > 0) {
      result = result.concat([{title: 'Apps', rows: apps}])
    }

    // Handle APIs
    result = result.concat(
      [
        {
          title: 'APIs',
          rows: [
            {title: 'GROQ', value: getGroqUrl(projectId)},
            {title: 'GraphQL', value: graphqlApi || 'Not deployed'}
          ]
        }
      ],
      propsData.filter(item => item.category === 'apis')
    )

    // Handle whatever else there might be
    const otherStuff = {}
    propsData.forEach(item => {
      if (item.category !== 'apps' && item.category !== 'apis') {
        if (!otherStuff[item.category]) {
          otherStuff[item.category] = []
        }
        otherStuff[item.category].push(item)
      }
    })
    Object.keys(otherStuff).forEach(category => {
      result.push({title: category, rows: otherStuff[category]})
    })

    return result
  }

  render() {
    return (

Project info

          {this.assembleTableRows().map(item => {
            if (!item || !item.rows) {
              return null
            }

            return (

                    {item.title}

                {item.rows.map(row => {
                  return (

                      {row.title}
                      {isPlainObject(row.value) && (
                        {row.value.error}
                      )}
                      {!isPlainObject(row.value) && (

                          {isUrl(row.value) ? {row.value} : row.value}

                      )}

                  )
                })}

            )
          })}

          <AnchorButton
            href={getManageUrl(sanityClient.clientConfig.projectId)}
            bleed
            color="primary"
            kind="simple"
          >
            Manage project
          </AnchorButton>

    )
  }
}

export default ProjectInfo
<commit_msg>[dashboard] Fix graphQl url bug in the project-info widget<commit_after>/* eslint-disable react/forbid-prop-types, no-console */
import React from 'react'
import {isPlainObject} from 'lodash'
import PropTypes from 'prop-types'
import sanityClient from 'part:@sanity/base/client'
import AnchorButton from 'part:@sanity/components/buttons/anchor'
import styles from './ProjectInfo.css'

const {projectId, dataset} = sanityClient.config()

function isUrl(url) {
  return /^https?:\/\//.test(`${url}`)
}

function getGraphQlUrl() {
  return `https://${projectId}.api.sanity.io/v1/graphql/${dataset}/default`
}

function getGroqUrl() {
  return `https://${projectId}.api.sanity.io/v1/groq/${dataset}`
}

function getManageUrl() {
  return `https://manage.sanity.io/projects/${projectId}`
}

class ProjectInfo extends React.Component {
  static propTypes = {
    data: PropTypes.array
  }
  static defaultProps = {
    data: []
  }

  state = {
    studioHost: null,
    graphqlApi: null
  }

  componentDidMount() {
    // fetch project data
    sanityClient.projects
      .getById(projectId)
      .then(result => {
        const {studioHost} = result
        this.setState({studioHost: studioHost ? `https://${studioHost}.sanity.studio` : null})
      })
      .catch(error => {
        console.log('Error while looking for studioHost', error)
        this.setState({
          studioHost: {
            error: 'Something went wrong while looking up studioHost. See console.'
          }
        })
      })

    // ping assumed graphql endpoint
    sanityClient
      .request({
        method: 'HEAD',
        uri: `/graphql/${dataset}/default`
      })
      .then(response => {
        this.setState({graphqlApi: getGraphQlUrl()})
      })
      .catch(error => {
        if (error.statusCode === 404) {
          this.setState({graphqlApi: null})
        } else {
          console.log('Error while looking for graphqlApi', error)
          this.setState({
            graphqlApi: {
              error: 'Something went wrong while looking up graphqlApi. See console.'
            }
          })
        }
      })
  }

  assembleTableRows() {
    const {graphqlApi, studioHost} = this.state
    const propsData = this.props.data

    let result = [
      {
        title: 'Sanity project',
        rows: [{title: 'Project ID', value: projectId}, {title: 'Dataset', value: dataset}]
      }
    ]

    // Handle any apps
    const apps = [studioHost ? {title: 'Studio', value: studioHost} : null]
      .concat(propsData.filter(item => item.category === 'apps'))
      .filter(Boolean)
    if (apps.length > 0) {
      result = result.concat([{title: 'Apps', rows: apps}])
    }

    // Handle APIs
    result = result.concat(
      [
        {
          title: 'APIs',
          rows: [
            {title: 'GROQ', value: getGroqUrl()},
            {title: 'GraphQL', value: graphqlApi || 'Not deployed'}
          ]
        }
      ],
      propsData.filter(item => item.category === 'apis')
    )

    // Handle whatever else there might be
    const otherStuff = {}
    propsData.forEach(item => {
      if (item.category !== 'apps' && item.category !== 'apis') {
        if (!otherStuff[item.category]) {
          otherStuff[item.category] = []
        }
        otherStuff[item.category].push(item)
      }
    })
    Object.keys(otherStuff).forEach(category => {
      result.push({title: category, rows: otherStuff[category]})
    })

    return result
  }

  render() {
    return (

Project info

          {this.assembleTableRows().map(item => {
            if (!item || !item.rows) {
              return null
            }

            return (

                    {item.title}

                {item.rows.map(row => {
                  return (

                      {row.title}
                      {isPlainObject(row.value) && (
                        {row.value.error}
                      )}
                      {!isPlainObject(row.value) && (

                          {isUrl(row.value) ? {row.value} : row.value}

                      )}

                  )
                })}

            )
          })}

          <AnchorButton href={getManageUrl()} bleed color="primary" kind="simple">
            Manage project
          </AnchorButton>

    )
  }
}

export default ProjectInfo
<|endoftext|>


<commit_before>const setNewsletterPayload = attributes => ({
  name: "autoNewsletterPuff",
  attributes: {
    element: {
      value: "newsletter-puff",
      attributes: {
        label: "In your inbox",
        ...attributes
      }
    }
  },
  children: []
});

const newslettersBySection = [
  {
    section: "news",
    payload: setNewsletterPayload({
      code: "TNL-101",
      headline: "Best of Times",
      copy:
        "We’ll send you our top stories, across all sections, straight to your inbox. Simple as that.",
      imageUri:
        "https://www.thetimes.co.uk/imageserver/image/methode%2Ftimes%2Fprod%2Fweb%2Fbin%2F728c3e68-5311-4533-809a-b313a6503789.jpg?resize=800"
    })
  },
  {
    section: "comment",
    payload: setNewsletterPayload({
      code: "TNL-104",
      headline: "Comment and Opinion",
      copy:
        "Wit and wisdom from our award-winning stable of columnists and guest writers, including Caitlin Moran, Matthew Parris, Rod Liddle and Dominic Lawson.",
      imageUri:
        "https://www.thetimes.co.uk/imageserver/image/methode%2Ftimes%2Fprod%2Fweb%2Fbin%2Fb49851bd-b182-43fc-bd5d-1816bcda19fe.jpg?resize=800"
    })
  },
  {
    section: "business",
    payload: setNewsletterPayload({
      code: "TNL-103",
      headline: "Business briefing",
      copy:
        "In-depth analysis and comment on the latest financial and economic news from our award-winning Business teams.",
      imageUri:
        "https://www.thetimes.co.uk/imageserver/image/methode%2Ftimes%2Fprod%2Fweb%2Fbin%2F306637af-2b6f-48fc-b264-d661b2067818.jpg?resize=800"
    })
  },
  {
    section: "sport",
    payload: setNewsletterPayload({
      code: "TNL-112",
      headline: "Sport",
      copy:
        "Every Friday morning, Elgan Alderman looks ahead to the weekend, featuring exclusive interviews, agenda-setting comment and razor-sharp analysis.",
      imageUri:
        "https://www.thetimes.co.uk/imageserver/image/methode%2Ftimes%2Fprod%2Fweb%2Fbin%2F8920eef8-e084-47db-a1bf-00be3d72080e.jpg?resize=800"
    })
  },
  {
    section: "scotland",
    payload: setNewsletterPayload({
      code: "TNL-134",
      headline: "Editor’s Choice – Scotland",
      copy:
        "The biggest stories of the week from The Times and The Sunday Times Scotland, delivered directly to you every Saturday morning.",
      imageUri:
        "https://www.thetimes.co.uk/imageserver/image/methode%2Ftimes%2Fprod%2Fweb%2Fbin%2F5777acf9-363f-4aa3-8176-1ea09cdae7d6.jpg?resize=800"
    })
  }
];

const getNewsletterPuff = section => {
  const newsletter = newslettersBySection.find(
    item => item.section === section.toLowerCase()
  );
  return newsletter ? newsletter.payload : undefined;
};

const newsletterPuffExists = children =>
  children.find(
    item =>
      item.name === "interactive" &&
      item.attributes.element.value === "newsletter-puff"
  );

const insertPaywall = (paywall, insertBefore, newsletterPuff) => ({
  ...paywall,
  children: paywall.children.reduce(
    (acc, item, index) =>
      index === insertBefore ? [...acc, newsletterPuff, item] : [...acc, item],
    []
  )
});

const consecutiveParagraphs = children => {
  const index = children.findIndex(item => item.name !== "paragraph");
  return index === -1 ? children.length : index;
};

const checkParagraphs = (children, paywall) => {
  const paywallIndex = children.findIndex(item => item.name === "paywall");
  const paragraphs = consecutiveParagraphs(children);
  const paywallParagraphs = consecutiveParagraphs(paywall.children);

  if (paragraphs >= 5) {
    return true;
  }

  if (paywallIndex !== paragraphs) {
    return false;
  }

  return paragraphs + paywallParagraphs >= 5;
};

const insertNewsletterPuff = (section, isPreview) => children => {
  if (isPreview) return children;

  const newsletterPuff = getNewsletterPuff(section);
  if (!newsletterPuff) return children;

  const paywall = children.find(item => item.name === "paywall");
  if (!paywall) return children;

  if (
    newsletterPuffExists(children) ||
    newsletterPuffExists(paywall.children)
  ) {
    return children;
  }

  if (!checkParagraphs(children, paywall)) {
    return children;
  }

  const insertIndex = 3;

  return children.reduce((acc, item, index) => {
    if (index === insertIndex) return [...acc, newsletterPuff, item];

    if (item.name === "paywall")
      return [...acc, insertPaywall(item, insertIndex - index, newsletterPuff)];

    return [...acc, item];
  }, []);
};

export default insertNewsletterPuff;
<commit_msg>feat(TDP-861): Remove Comment and Sport Newsletters from Automated Newsletters Puff (#2821)<commit_after>const setNewsletterPayload = attributes => ({
  name: "autoNewsletterPuff",
  attributes: {
    element: {
      value: "newsletter-puff",
      attributes: {
        label: "In your inbox",
        ...attributes
      }
    }
  },
  children: []
});

const newslettersBySection = [
  {
    section: "news",
    payload: setNewsletterPayload({
      code: "TNL-101",
      headline: "Best of Times",
      copy:
        "We’ll send you our top stories, across all sections, straight to your inbox. Simple as that.",
      imageUri:
        "https://www.thetimes.co.uk/imageserver/image/methode%2Ftimes%2Fprod%2Fweb%2Fbin%2F728c3e68-5311-4533-809a-b313a6503789.jpg?resize=800"
    })
  },
  {
    section: "business",
    payload: setNewsletterPayload({
      code: "TNL-103",
      headline: "Business briefing",
      copy:
        "In-depth analysis and comment on the latest financial and economic news from our award-winning Business teams.",
      imageUri:
        "https://www.thetimes.co.uk/imageserver/image/methode%2Ftimes%2Fprod%2Fweb%2Fbin%2F306637af-2b6f-48fc-b264-d661b2067818.jpg?resize=800"
    })
  },
  {
    section: "scotland",
    payload: setNewsletterPayload({
      code: "TNL-134",
      headline: "Editor’s Choice – Scotland",
      copy:
        "The biggest stories of the week from The Times and The Sunday Times Scotland, delivered directly to you every Saturday morning.",
      imageUri:
        "https://www.thetimes.co.uk/imageserver/image/methode%2Ftimes%2Fprod%2Fweb%2Fbin%2F5777acf9-363f-4aa3-8176-1ea09cdae7d6.jpg?resize=800"
    })
  }
];

const getNewsletterPuff = section => {
  const newsletter = newslettersBySection.find(
    item => item.section === section.toLowerCase()
  );
  return newsletter ? newsletter.payload : undefined;
};

const newsletterPuffExists = children =>
  children.find(
    item =>
      item.name === "interactive" &&
      item.attributes.element.value === "newsletter-puff"
  );

const insertPaywall = (paywall, insertBefore, newsletterPuff) => ({
  ...paywall,
  children: paywall.children.reduce(
    (acc, item, index) =>
      index === insertBefore ? [...acc, newsletterPuff, item] : [...acc, item],
    []
  )
});

const consecutiveParagraphs = children => {
  const index = children.findIndex(item => item.name !== "paragraph");
  return index === -1 ? children.length : index;
};

const checkParagraphs = (children, paywall) => {
  const paywallIndex = children.findIndex(item => item.name === "paywall");
  const paragraphs = consecutiveParagraphs(children);
  const paywallParagraphs = consecutiveParagraphs(paywall.children);

  if (paragraphs >= 5) {
    return true;
  }

  if (paywallIndex !== paragraphs) {
    return false;
  }

  return paragraphs + paywallParagraphs >= 5;
};

const insertNewsletterPuff = (section, isPreview) => children => {
  if (isPreview) return children;

  const newsletterPuff = getNewsletterPuff(section);
  if (!newsletterPuff) return children;

  const paywall = children.find(item => item.name === "paywall");
  if (!paywall) return children;

  if (
    newsletterPuffExists(children) ||
    newsletterPuffExists(paywall.children)
  ) {
    return children;
  }

  if (!checkParagraphs(children, paywall)) {
    return children;
  }

  const insertIndex = 3;

  return children.reduce((acc, item, index) => {
    if (index === insertIndex) return [...acc, newsletterPuff, item];

    if (item.name === "paywall")
      return [...acc, insertPaywall(item, insertIndex - index, newsletterPuff)];

    return [...acc, item];
  }, []);
};

export default insertNewsletterPuff;
<|endoftext|>


<commit_before>var attr = Ember.attr;

module("Ember.EmbeddedHasManyArray - embedded objects saving");

test("derp", function() {
  var json = {
    id: 1,
    title: 'foo',
    comments: [
      {id: 1, text: 'uno'},
      {id: 2, text: 'dos'},
      {id: 3, text: 'tres'}
    ]
  };

  var Comment = Ember.Model.extend({
    id: attr(),
    text: attr()
  });

  var Article = Ember.Model.extend({
    title: attr(),

    comments: Ember.hasMany(Comment, { key: 'comments', embedded: true })
  });

  Comment.adapter = {

    createRecord: function(record) {
      return new Ember.RSVP.Promise(function(resolve, reject) {
        Ember.run.later(function() {
          record.load(4, {text: 'quattro'});
          record.didCreateRecord();
          resolve(record);
        }, 1);
      });
    },

    saveRecord: function(record) {
      return new Ember.RSVP.Promise(function(resolve, reject) {
        Ember.run.later(function() {
          record.didSaveRecord();
          resolve(record);
        }, 1);
      });
    }
  };

  var article = Article.create();
  Ember.run(article, article.load, json.id, json);

  var comments = article.get('comments');
  var newComment = Ember.run(comments, comments.create, {text: 'quattro'});

  equal(comments.get('length'), 4);
  ok(newComment instanceof Comment);
  deepEqual(Ember.run(comments, comments.mapProperty, 'text'), ['uno', 'dos', 'tres', 'quattro']);

  Ember.run(function() {
    stop();
    comments.save().then(function(record) {
      start();
      debugger;
      ok(!newComment.get('isDirty'), "New comment is not dirty");
      equal(newComment.get('id'), 4, "New comment has an ID");
    });
  });
});
<commit_msg>Remove debugger statement<commit_after>var attr = Ember.attr;

module("Ember.EmbeddedHasManyArray - embedded objects saving");

test("derp", function() {
  var json = {
    id: 1,
    title: 'foo',
    comments: [
      {id: 1, text: 'uno'},
      {id: 2, text: 'dos'},
      {id: 3, text: 'tres'}
    ]
  };

  var Comment = Ember.Model.extend({
    id: attr(),
    text: attr()
  });

  var Article = Ember.Model.extend({
    title: attr(),

    comments: Ember.hasMany(Comment, { key: 'comments', embedded: true })
  });

  Comment.adapter = {

    createRecord: function(record) {
      return new Ember.RSVP.Promise(function(resolve, reject) {
        Ember.run.later(function() {
          record.load(4, {text: 'quattro'});
          record.didCreateRecord();
          resolve(record);
        }, 1);
      });
    },

    saveRecord: function(record) {
      return new Ember.RSVP.Promise(function(resolve, reject) {
        Ember.run.later(function() {
          record.didSaveRecord();
          resolve(record);
        }, 1);
      });
    }
  };

  var article = Article.create();
  Ember.run(article, article.load, json.id, json);

  var comments = article.get('comments');
  var newComment = Ember.run(comments, comments.create, {text: 'quattro'});

  equal(comments.get('length'), 4);
  ok(newComment instanceof Comment);
  deepEqual(Ember.run(comments, comments.mapProperty, 'text'), ['uno', 'dos', 'tres', 'quattro']);

  Ember.run(function() {
    stop();
    comments.save().then(function(record) {
      start();
      ok(!newComment.get('isDirty'), "New comment is not dirty");
      equal(newComment.get('id'), 4, "New comment has an ID");
    });
  });
});
<|endoftext|>


<commit_before>// @flow
/**
 * Part of GDL gdl-frontend.
 * Copyright (C) 2018 GDL
 *
 * See LICENSE
 */
import React, { type Node } from 'react';
import Router from 'next/router';
import { FormattedMessage } from 'react-intl';
import { SwipeableDrawer, Typography } from '@material-ui/core';
import { Query } from 'react-apollo';
import gql from 'graphql-tag';

import type { languages_languages as Language } from '../../gqlTypes';
import LanguageList from '../LanguageList';
import { getBookLanguageCode } from '../../lib/storage';
import { GdlI18nConsumer } from '../GdlI18nProvider';

function linkProps(language, category) {
  return {
    route: category,
    params: { lang: language.code }
  };
}

const LANGUAGES_QUERY = gql`
  query languages {
    languages {
      code
      name
    }
  }
`;

type Props = {
  anchor?: 'left' | 'right',
  children: (data: { onClick: () => void, loading: boolean }) => Node,
  onSelectLanguage?: Language => void,
  enableParentSwipe?: () => void,
  disableParentSwipe?: () => void
};

type State = {
  showMenu: boolean,
  selectedLanguage: string
};

class SelectBookLanguage extends React.Component<Props, State> {
  state = {
    showMenu: false,
    selectedLanguage: getBookLanguageCode()
  };

  componentDidUpdate() {
    // Makes sure we always show the correct language as selected when the menu is opened
    // When React Hooks arrive this will be so much nicer
    const selectedLanguage = getBookLanguageCode();
    if (this.state.selectedLanguage !== selectedLanguage) {
      this.setState({
        selectedLanguage
      });
    }
  }

  handleSelectLanguage = (language: Language) => {
    this.handleCloseMenu();
    this.props.onSelectLanguage && this.props.onSelectLanguage(language);
    Router.push(`/${language.code}`);
  };

  handleShowMenu = () => {
    this.setState({ showMenu: true });
    this.props.disableParentSwipe && this.props.disableParentSwipe();
  };

  handleCloseMenu = () => {
    this.setState({ showMenu: false });
    this.props.enableParentSwipe && this.props.enableParentSwipe();
  };

  render() {
    const { children, anchor } = this.props;
    const { showMenu, selectedLanguage } = this.state;

    return (
      <Query query={LANGUAGES_QUERY} skip={!showMenu}>
        {({
          loading,
          error,
          data,
          networkStatus
        }: {
          loading: boolean,
          data: any,
          error: any,
          networkStatus: number
        }) => {
          const isLoading = Boolean(data) && !('languages' in data) && loading;

          return (
            <>
              {children({
                onClick: this.handleShowMenu,
                loading: isLoading
              })}
              <SwipeableDrawer
                disableDiscovery
                disableSwipeToOpen
                disableBackdropTransition
                onClose={this.handleCloseMenu}
                onOpen={() => {}}
                open={showMenu && !isLoading}
                anchor={anchor}
              >
                {error && (
                  <Typography
                    component="span"
                    color="error"
                    css={{ margin: '1rem' }}
                  >
                    <FormattedMessage
                      id="Error loading data"
                      defaultMessage="Error loading data."
                    />
                  </Typography>
                )}
                {!isLoading && data && (
                  <GdlI18nConsumer>
                    {value => (
                      <LanguageList
                        onSelectLanguage={language => {
                          this.handleSelectLanguage(language);
                          value && value.changeSiteLanguage(language);
                        }}
                        selectedLanguageCode={selectedLanguage}
                        linkProps={linkProps}
                        languages={data.languages}
                      />
                    )}
                  </GdlI18nConsumer>
                )}
              </SwipeableDrawer>
            </>
          );
        }}
      </Query>
    );
  }
}

export default SelectBookLanguage;
<commit_msg>Force route to be book<commit_after>// @flow
/**
 * Part of GDL gdl-frontend.
 * Copyright (C) 2018 GDL
 *
 * See LICENSE
 */
import React, { type Node } from 'react';
import Router from 'next/router';
import { FormattedMessage } from 'react-intl';
import { SwipeableDrawer, Typography } from '@material-ui/core';
import { Query } from 'react-apollo';
import gql from 'graphql-tag';

import type { languages_languages as Language } from '../../gqlTypes';
import LanguageList from '../LanguageList';
import { getBookLanguageCode } from '../../lib/storage';
import { GdlI18nConsumer } from '../GdlI18nProvider';

function linkProps(language, category) {
  return {
    route: 'books',
    params: { lang: language.code }
  };
}

const LANGUAGES_QUERY = gql`
  query languages {
    languages {
      code
      name
    }
  }
`;

type Props = {
  anchor?: 'left' | 'right',
  children: (data: { onClick: () => void, loading: boolean }) => Node,
  onSelectLanguage?: Language => void,
  enableParentSwipe?: () => void,
  disableParentSwipe?: () => void
};

type State = {
  showMenu: boolean,
  selectedLanguage: string
};

class SelectBookLanguage extends React.Component<Props, State> {
  state = {
    showMenu: false,
    selectedLanguage: getBookLanguageCode()
  };

  componentDidUpdate() {
    // Makes sure we always show the correct language as selected when the menu is opened
    // When React Hooks arrive this will be so much nicer
    const selectedLanguage = getBookLanguageCode();
    if (this.state.selectedLanguage !== selectedLanguage) {
      this.setState({
        selectedLanguage
      });
    }
  }

  handleSelectLanguage = (language: Language) => {
    this.handleCloseMenu();
    this.props.onSelectLanguage && this.props.onSelectLanguage(language);
  };

  handleShowMenu = () => {
    this.setState({ showMenu: true });
    this.props.disableParentSwipe && this.props.disableParentSwipe();
  };

  handleCloseMenu = () => {
    this.setState({ showMenu: false });
    this.props.enableParentSwipe && this.props.enableParentSwipe();
  };

  render() {
    const { children, anchor } = this.props;
    const { showMenu, selectedLanguage } = this.state;

    return (
      <Query query={LANGUAGES_QUERY} skip={!showMenu}>
        {({
          loading,
          error,
          data,
          networkStatus
        }: {
          loading: boolean,
          data: any,
          error: any,
          networkStatus: number
        }) => {
          const isLoading = Boolean(data) && !('languages' in data) && loading;

          return (
            <>
              {children({
                onClick: this.handleShowMenu,
                loading: isLoading
              })}
              <SwipeableDrawer
                disableDiscovery
                disableSwipeToOpen
                disableBackdropTransition
                onClose={this.handleCloseMenu}
                onOpen={() => {}}
                open={showMenu && !isLoading}
                anchor={anchor}
              >
                {error && (
                  <Typography
                    component="span"
                    color="error"
                    css={{ margin: '1rem' }}
                  >
                    <FormattedMessage
                      id="Error loading data"
                      defaultMessage="Error loading data."
                    />
                  </Typography>
                )}
                {!isLoading && data && (
                  <GdlI18nConsumer>
                    {value => (
                      <LanguageList
                        onSelectLanguage={language => {
                          this.handleSelectLanguage(language);
                          value && value.changeSiteLanguage(language);
                        }}
                        selectedLanguageCode={selectedLanguage}
                        linkProps={linkProps}
                        languages={data.languages}
                      />
                    )}
                  </GdlI18nConsumer>
                )}
              </SwipeableDrawer>
            </>
          );
        }}
      </Query>
    );
  }
}

export default SelectBookLanguage;
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import _ from 'lodash';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { Badge } from 'antd';
import { SvgIcon } from '../common';
import element from '../../common/element';
import editorStateMap from '../editor/editorStateMap';
import { stickTab } from '../home/redux/actions';

export class ProblemsView extends Component {
  static propTypes = {
    problems: PropTypes.object.isRequired,
    elementById: PropTypes.object.isRequired,
    actions: PropTypes.object.isRequired,
  };

  state = {
    closedFiles: {},
  };

  toggleCollapse = file => {
    this.setState({
      closedFiles: {
        ...this.state.closedFiles,
        [file]: !this.state.closedFiles[file],
      },
    });
  };

  handleMsgClick(file, msg) {
    _.merge(editorStateMap, {
      [file]: {
        viewState: {
          firstPosition: {
            column: msg.column,
            lineNumber: msg.line,
          },
        },
        cursorState: [
          {
            inSelectionMode: true,
            position: {
              column: msg.endColumn || msg.column,
              lineNumber: msg.endLine || msg.line,
            },
            selectionStart: {
              column: msg.column,
              lineNumber: msg.line,
            },
          },
        ],
      },
    });
    const res = element.show(file);
    if (!res) window.GLOBAL_EDITOR.restoreViewState(editorStateMap[file] || null);
  }
  handleMsgDblClick() {
    setTimeout(() => this.props.actions.stickTab(), 100);
  }

  renderErrorIcon(msg) {
    let type, color;
    if (msg.severity === 1) {
      type = 'warning';
      color = '#FFC107';
    } else {
      type = 'error';
      color = '#ef5350';
    }
    return <SvgIcon type={type} size={11} fill={color} />;
  }

  renderFileProblem(file, msgs) {
    const byId = id => this.props.elementById[id];
    let ele = byId(file);
    if (!ele) return null; // File may be deleted
    if (ele.owner) ele = byId(ele.owner);
    if (ele.target) ele = byId(ele.target);

    const isClosed = this.state.closedFiles[file];

    return (

 this.toggleCollapse(file)}>
          <SvgIcon
            type={isClosed ? 'anticon-caret-right' : 'anticon-caret-down'}
            size={8}
            fill="#aaa"
            className="error-switcher"
          />
          <SvgIcon type={ele.icon} size={12} fill={ele.iconColor} />
          {ele.name} {file}
          <Badge count={msgs.length} />

        {msgs.map(msg => (
          <dd
            key={`${msg.ruleId}-${msg.line}-${msg.column}`}
            onClick={() => this.handleMsgClick(file, msg)}
            onDoubleClick={() => this.handleMsgDblClick(file, msg)}
          >
            {this.renderErrorIcon(msg)}
            [eslint]

              {msg.message}

              ({msg.line}, {msg.column})

        ))}

    );
  }

  render() {
    const { problems } = this.props;
    return (

        {Object.keys(problems)
          .filter(k => !_.isEmpty(problems[k]))
          .map(k => this.renderFileProblem(k, problems[k]))}

    );
  }
}

/* istanbul ignore next */
function mapStateToProps(state) {
  return {
    problems: state.pluginDefault.problems,
    elementById: state.home.elementById,
  };
}

/* istanbul ignore next */
function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators({ stickTab }, dispatch),
  };
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(ProblemsView);
<commit_msg>Show rule id in msg.<commit_after>import React, { Component } from 'react';
import _ from 'lodash';
import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { Badge } from 'antd';
import { SvgIcon } from '../common';
import element from '../../common/element';
import editorStateMap from '../editor/editorStateMap';
import { stickTab } from '../home/redux/actions';

export class ProblemsView extends Component {
  static propTypes = {
    problems: PropTypes.object.isRequired,
    elementById: PropTypes.object.isRequired,
    actions: PropTypes.object.isRequired,
  };

  state = {
    closedFiles: {},
  };

  toggleCollapse = file => {
    this.setState({
      closedFiles: {
        ...this.state.closedFiles,
        [file]: !this.state.closedFiles[file],
      },
    });
  };

  handleMsgClick(file, msg) {
    _.merge(editorStateMap, {
      [file]: {
        viewState: {
          firstPosition: {
            column: msg.column,
            lineNumber: msg.line,
          },
        },
        cursorState: [
          {
            inSelectionMode: true,
            position: {
              column: msg.endColumn || msg.column,
              lineNumber: msg.endLine || msg.line,
            },
            selectionStart: {
              column: msg.column,
              lineNumber: msg.line,
            },
          },
        ],
      },
    });
    const res = element.show(file);
    if (!res) window.GLOBAL_EDITOR.restoreViewState(editorStateMap[file] || null);
  }
  handleMsgDblClick() {
    setTimeout(() => this.props.actions.stickTab(), 100);
  }

  renderErrorIcon(msg) {
    let type, color;
    if (msg.severity === 1) {
      type = 'warning';
      color = '#FFC107';
    } else {
      type = 'error';
      color = '#ef5350';
    }
    return <SvgIcon type={type} size={11} fill={color} />;
  }

  renderFileProblem(file, msgs) {
    const byId = id => this.props.elementById[id];
    let ele = byId(file);
    if (!ele) return null; // File may be deleted
    if (ele.owner) ele = byId(ele.owner);
    if (ele.target) ele = byId(ele.target);

    const isClosed = this.state.closedFiles[file];

    return (

 this.toggleCollapse(file)}>
          <SvgIcon
            type={isClosed ? 'anticon-caret-right' : 'anticon-caret-down'}
            size={8}
            fill="#aaa"
            className="error-switcher"
          />
          <SvgIcon type={ele.icon} size={12} fill={ele.iconColor} />
          {ele.name} {file}
          <Badge count={msgs.length} />

        {msgs.map(msg => (
          <dd
            key={`${msg.ruleId}-${msg.line}-${msg.column}`}
            onClick={() => this.handleMsgClick(file, msg)}
            onDoubleClick={() => this.handleMsgDblClick(file, msg)}
          >
            {this.renderErrorIcon(msg)}
            [eslint]

              {msg.message} ({msg.ruleId})

              ({msg.line}, {msg.column})

        ))}

    );
  }

  render() {
    const { problems } = this.props;
    return (

        {Object.keys(problems)
          .filter(k => !_.isEmpty(problems[k]))
          .map(k => this.renderFileProblem(k, problems[k]))}

    );
  }
}

/* istanbul ignore next */
function mapStateToProps(state) {
  return {
    problems: state.pluginDefault.problems,
    elementById: state.home.elementById,
  };
}

/* istanbul ignore next */
function mapDispatchToProps(dispatch) {
  return {
    actions: bindActionCreators({ stickTab }, dispatch),
  };
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(ProblemsView);
<|endoftext|>


<commit_before>var locastyle = locastyle || {};
locastyle.btnGroup = (function() {
  'use strict';

  function init() {
    checkBreakpoint();
    bindBreakpointUpdateOnChecker();
  }

  function unbind () {
    $(document).off("breakpoint-updated");
  }

  // adiciona o bind de breakpoint-updated e chama o checker quando o evento ocorre
  function bindBreakpointUpdateOnChecker() {
    unbind();

    $(document).on("breakpoint-updated", function () {
      checkBreakpoint();
    })
  }

  // verifica se o breakpoint é para mobile, se for faz a ação para agrupar
  function checkBreakpoint() {
    if(locastyle.breakpointClass == "ls-screen-sm" || locastyle.breakpointClass == "ls-screen-xs"){
      $(".ls-regroup").each(function (index, $element) {
        group($($element).find('a, button'));
      });
    }
  }

  // agrupa os botões em um dropdown
  function group($element){
    var list = $($element).wrap('
');
    $(".ls-regroup a[class*='ls-btn']").removeClass()
    $('.ls-regroup').html(locastyle.templates.dropdown(list));

    // chama os inits para os itens dentro do dropdown
    locastyle.init()
  }

  return {
    init: init
  };

}());
<commit_msg>call locastyle init after loop not inside each iteration to avoid rebuilding event binds many times<commit_after>var locastyle = locastyle || {};
locastyle.btnGroup = (function() {
  'use strict';

  function init() {
    checkBreakpoint();
    bindBreakpointUpdateOnChecker();
  }

  function unbind () {
    $(document).off("breakpoint-updated");
  }

  // adiciona o bind de breakpoint-updated e chama o checker quando o evento ocorre
  function bindBreakpointUpdateOnChecker() {
    unbind();

    $(document).on("breakpoint-updated", function () {
      checkBreakpoint();
    })
  }

  // verifica se o breakpoint é para mobile, se for faz a ação para agrupar
  function checkBreakpoint() {
    if(locastyle.breakpointClass == "ls-screen-sm" || locastyle.breakpointClass == "ls-screen-xs"){
      $(".ls-regroup").each(function (index, $element) {
        group($($element).find('a, button'));
      });

      // chama os inits para os itens dentro do dropdown
      locastyle.init();
    }
  }

  // agrupa os botões em um dropdown
  function group($element){
    var list = $($element).wrap('
');
    $(".ls-regroup a[class*='ls-btn']").removeClass()
    $('.ls-regroup').html(locastyle.templates.dropdown(list));
  }

  return {
    init: init
  };

}());
<|endoftext|>


<commit_before>"use strict";

/**
 * Context menu element.
 *
 * @class ContextMenu
 * @extends {Component}
 * @param {Component} parent Parent element.
 */
function ContextMenu(parent)
{
	Element.call(this, parent);

	var self = this;

	this.element.style.overflow = "visible";
	this.element.style.zIndex = "300";
	this.element.onmouseleave = function()
	{
		self.destroy();
	};

	this.offset = new THREE.Vector2(20, 10);

	/**
	 * Options in this menu.
	 *
	 * @attribute options
	 * @type {Array}
	 */
	this.options = [];
}

ContextMenu.prototype = Object.create(Element.prototype);

/**
 * Set the text of this context menu.
 *
 * @method setText
 * @param {String} text
 */
ContextMenu.prototype.setText = function(text)
{
	this.text.setText(text);
};

/**
 * Remove option from context menu.
 *
 * @method removeOption
 * @param {Number} index
 */
ContextMenu.prototype.removeOption = function(index)
{
	if(index >= 0 && index < this.options.length)
	{
		this.options[index].destroy();
		this.options.splice(index, 1);
	}
};

/**
 * Add new option to context menu
 *
 * @method addOption
 * @param {String} name of the option
 * @param {Function} callback Callback function
 */
ContextMenu.prototype.addOption = function(name, callback)
{
	var button = new ButtonMenu(this.element);
	button.element.style.zIndex = "10000";
	button.setText(name);
	button.setAlignment(Text.LEFT);
	button.position.x = 25;

	var self = this;
	button.setOnClick(function()
	{
		callback();
		self.destroy();
	});

	this.options.push(button);
};

/**
 * Add new menu to context menu
 *
 * @method addOption
 * @param {String} name of the option.
 * @return {DropdownMenu} The new menu created.
 */
ContextMenu.prototype.addMenu = function(name)
{
	var menu = new DropdownMenu(this.element);
	menu.setText(name);
	menu.setLocation(DropdownMenu.LEFT);
	menu.showArrow();
	menu.setAlignment(Text.LEFT);
	menu.setMargin(25);

	this.options.push(menu);

	return menu;
};

/**
 * Update all options in the menu.
 *
 * @method updateOptions
 */
ContextMenu.prototype.updateOptions = function()
{
	for(var i = 0; i < this.options.length; i++)
	{
		this.options[i].size.copy(this.size);
		this.options[i].position.set(0, this.size.y * i);
		this.options[i].updateInterface();
	}
};

ContextMenu.prototype.updateSize = function()
{
	this.element.style.width = this.size.x + "px";
	this.element.style.height = (this.size.y * this.options.length) + "px";

	this.updateOptions();
};

ContextMenu.prototype.updatePosition = function()
{
	this.element.style.top = (this.position.y - this.offset.y) + "px";
	this.element.style.left = (this.position.x - this.offset.x) + "px";

	//Check if its inside window
	var out = DOMUtils.checkBorder(this.element);
	if(out.x !== 0)
	{
		this.element.style.left = (this.position.x + this.offset.x - this.size.x) + "px";
	}
	if(out.y !== 0)
	{
		this.element.style.top = (this.position.y - this.offset.y - out.y) + "px";
	}
};
<commit_msg>Derp<commit_after>"use strict";

/**
 * Context menu element.
 *
 * @class ContextMenu
 * @extends {Component}
 * @param {Component} parent Parent element.
 */
function ContextMenu(parent)
{
	Element.call(this, parent);

	var self = this;

	this.element.style.overflow = "visible";
	this.element.style.zIndex = "300";
	this.element.onmouseleave = function()
	{
		self.destroy();
	};

	this.offset = new THREE.Vector2(20, 10);

	/**
	 * Options in this menu.
	 *
	 * @attribute options
	 * @type {Array}
	 */
	this.options = [];
}

ContextMenu.prototype = Object.create(Element.prototype);

/**
 * Set the text of this context menu.
 *
 * @method setText
 * @param {String} text
 */
ContextMenu.prototype.setText = function(text)
{
	this.text.setText(text);
};

/**
 * Remove option from context menu.
 *
 * @method removeOption
 * @param {Number} index
 */
ContextMenu.prototype.removeOption = function(index)
{
	if(index >= 0 && index < this.options.length)
	{
		this.options[index].destroy();
		this.options.splice(index, 1);
	}
};

/**
 * Add new option to context menu
 *
 * @method addOption
 * @param {String} name of the option
 * @param {Function} callback Callback function
 */
ContextMenu.prototype.addOption = function(name, callback)
{
	var button = new ButtonMenu(this.element);
	button.element.style.zIndex = "10000";
	button.setText(name);
	button.setAlignment(Text.LEFT);
	button.position.x = 25;

	var self = this;
	button.setOnClick(function()
	{
		callback();
		self.destroy();
	});

	this.options.push(button);
};

/**
 * Add new menu to context menu
 *
 * @method addOption
 * @param {String} name of the option.
 * @return {DropdownMenu} The new menu created.
 */
ContextMenu.prototype.addMenu = function(name)
{
	var menu = new DropdownMenu(this.element);
	menu.setText(name);
	menu.setLocation(DropdownMenu.LEFT);
	menu.showArrow();
	menu.setAlignment(Text.LEFT);
	menu.setMargin(25);

	this.options.push(menu);

	return menu;
};

/**
 * Update all options in the menu.
 *
 * @method updateOptions
 */
ContextMenu.prototype.updateOptions = function()
{
	for(var i = 0; i < this.options.length; i++)
	{
		this.options[i].size.copy(this.size);
		this.options[i].position.set(0, this.size.y * i);
		this.options[i].updateInterface();
	}
};

ContextMenu.prototype.updateSize = function()
{
	this.element.style.width = this.size.x + "px";
	this.element.style.height = (this.size.y * this.options.length) + "px";

	this.updateOptions();
};

ContextMenu.prototype.updatePosition = function()
{
	this.element.style.top = (this.position.y - this.offset.y) + "px";
	this.element.style.left = (this.position.x - this.offset.x) + "px";

	//Check if its inside window
	var out = DOMUtils.checkBorder(this.element);
	if(out.x !== 0)
	{
		this.element.style.left = (this.position.x + this.offset.x - this.size.x) + "px";
	}
	if(out.y !== 0)
	{
		this.element.style.top = (this.position.y - this.offset.y - out.y) + "px";
	}
};
<|endoftext|>