<commit_before>const _ = require('lodash');
const logging = require('@tryghost/logging');
const errors = require('@tryghost/errors');
const DomainEvents = require('@tryghost/domain-events');
const {SubscriptionCreatedEvent} = require('@tryghost/member-events');

module.exports = class WebhookController {
    /**
     * @param {object} deps
     * @param {import('./WebhookManager')} deps.webhookManager
     * @param {any} deps.deps.memberRepository
     */
    constructor(deps) {
        this.deps = deps;
        this.webhookManager = deps.webhookManager;
        this.api = deps.api;
        this.sendSignupEmail = deps.sendSignupEmail;
        this.handlers = {
            'customer.subscription.deleted': this.subscriptionEvent,
            'customer.subscription.updated': this.subscriptionEvent,
            'customer.subscription.created': this.subscriptionEvent,
            'invoice.payment_succeeded': this.invoiceEvent,
            'checkout.session.completed': this.checkoutSessionEvent
        };
    }

    async handle(req, res) {
        // if (!apiService.configured) {
        //     logging.error(`Stripe not configured, not handling webhook`);
        //     res.writeHead(400);
        //     return res.end();
        // }

        if (!req.body || !req.headers['stripe-signature']) {
            res.writeHead(400);
            return res.end();
        }
        let event;
        try {
            event = this.webhookManager.parseWebhook(req.body, req.headers['stripe-signature']);
        } catch (err) {
            logging.error(err);
            res.writeHead(401);
            return res.end();
        }

        logging.info(`Handling webhook ${event.type}`);
        try {
            await this.handleEvent(event);
            res.writeHead(200);
            res.end();
        } catch (err) {
            logging.error(`Error handling webhook ${event.type}`, err);
            res.writeHead(err.statusCode || 500);
            res.end();
        }
    }

    /**
     * @private
     */
    async handleEvent(event) {
        if (!this.handlers[event.type]) {
            return;
        }

        await this.handlers[event.type].call(this, event.data.object);
    }

    /**
     * @private
     */
    async subscriptionEvent(subscription) {
        const subscriptionPriceData = _.get(subscription, 'items.data');
        if (!subscriptionPriceData || subscriptionPriceData.length !== 1) {
            throw new errors.BadRequestError({
                message: 'Subscription should have exactly 1 price item'
            });
        }

        const member = await this.deps.memberRepository.get({
            customer_id: subscription.customer
        });

        if (member) {
            try {
                await this.deps.memberRepository.linkSubscription({
                    id: member.id,
                    subscription
                });
            } catch (err) {
                if (err.code !== 'ER_DUP_ENTRY' && err.code !== 'SQLITE_CONSTRAINT') {
                    throw err;
                }
                throw new errors.ConflictError({
                    err
                });
            }
        }
    }

    /**
     * @private
     */
    async invoiceEvent(invoice) {
        if (!invoice.subscription) {
            return;
        }
        const subscription = await this.api.getSubscription(invoice.subscription, {
            expand: ['default_payment_method']
        });

        const member = await this.deps.memberRepository.get({
            customer_id: subscription.customer
        });

        if (member) {
            if (invoice.paid && invoice.amount_paid !== 0) {
                await this.deps.eventRepository.registerPayment({
                    member_id: member.id,
                    currency: invoice.currency,
                    amount: invoice.amount_paid
                });
            }
        } else {
            // Subscription has more than one plan - meaning it is not one created by us - ignore.
            if (!subscription.plan) {
                return;
            }
            // Subscription is for a different product - ignore.
            const product = await this.deps.productRepository.get({
                stripe_product_id: subscription.plan.product
            });
            if (!product) {
                return;
            }

            // Could not find the member, which we need in order to insert an payment event.
            throw new errors.NotFoundError({
                message: `No member found for customer ${subscription.customer}`
            });
        }
    }

    /**
     * @private
     */
    async checkoutSessionEvent(session) {
        if (session.mode === 'setup') {
            const setupIntent = await this.api.getSetupIntent(session.setup_intent);
            const member = await this.deps.memberRepository.get({
                customer_id: setupIntent.metadata.customer_id
            });

            if (!member) {
                return;
            }

            await this.api.attachPaymentMethodToCustomer(
                setupIntent.metadata.customer_id,
                setupIntent.payment_method
            );

            if (setupIntent.metadata.subscription_id) {
                const updatedSubscription = await this.api.updateSubscriptionDefaultPaymentMethod(
                    setupIntent.metadata.subscription_id,
                    setupIntent.payment_method
                );
                try {
                    await this.deps.memberRepository.linkSubscription({
                        id: member.id,
                        subscription: updatedSubscription
                    });
                } catch (err) {
                    if (err.code !== 'ER_DUP_ENTRY' && err.code !== 'SQLITE_CONSTRAINT') {
                        throw err;
                    }
                    throw new errors.ConflictError({
                        err
                    });
                }
                return;
            }

            const subscriptions = await member.related('stripeSubscriptions').fetch();

            const activeSubscriptions = subscriptions.models.filter((subscription) => {
                return ['active', 'trialing', 'unpaid', 'past_due'].includes(subscription.get('status'));
            });

            for (const subscription of activeSubscriptions) {
                if (subscription.get('customer_id') === setupIntent.metadata.customer_id) {
                    const updatedSubscription = await this.api.updateSubscriptionDefaultPaymentMethod(
                        subscription.get('subscription_id'),
                        setupIntent.payment_method
                    );
                    try {
                        await this.deps.memberRepository.linkSubscription({
                            id: member.id,
                            subscription: updatedSubscription
                        });
                    } catch (err) {
                        if (err.code !== 'ER_DUP_ENTRY' && err.code !== 'SQLITE_CONSTRAINT') {
                            throw err;
                        }
                        throw new errors.ConflictError({
                            err
                        });
                    }
                }
            }
        }

        if (session.mode === 'subscription') {
            const customer = await this.api.getCustomer(session.customer, {
                expand: ['subscriptions.data.default_payment_method']
            });

            let member = await this.deps.memberRepository.get({
                email: customer.email
            });

            const checkoutType = _.get(session, 'metadata.checkoutType');

            if (!member) {
                const metadataName = _.get(session, 'metadata.name');
                const payerName = _.get(customer, 'subscriptions.data[0].default_payment_method.billing_details.name');
                const name = metadataName || payerName || null;
                member = await this.deps.memberRepository.create({email: customer.email, name});
            } else {
                const payerName = _.get(customer, 'subscriptions.data[0].default_payment_method.billing_details.name');

                if (payerName && !member.get('name')) {
                    await this.deps.memberRepository.update({name: payerName}, {id: member.get('id')});
                }
            }

            await this.deps.memberRepository.upsertCustomer({
                customer_id: customer.id,
                member_id: member.id,
                name: customer.name,
                email: customer.email
            });

            for (const subscription of customer.subscriptions.data) {
                try {
                    await this.deps.memberRepository.linkSubscription({
                        id: member.id,
                        subscription
                    });
                } catch (err) {
                    if (err.code !== 'ER_DUP_ENTRY' && err.code !== 'SQLITE_CONSTRAINT') {
                        throw err;
                    }
                    throw new errors.ConflictError({
                        err
                    });
                }
            }

            const subscription = await this.deps.memberRepository.getSubscriptionByStripeID(session.subscription);

            const event = SubscriptionCreatedEvent.create({
                memberId: member.id,
                subscriptionId: subscription.id,
                offerId: session.metadata.offer || null
            });

            DomainEvents.dispatch(event);

            if (checkoutType !== 'upgrade') {
                this.sendSignupEmail(customer.email);
            }
        }
    }
};
<commit_msg>Handled newsletter preference for paid members signup<commit_after>const _ = require('lodash');
const logging = require('@tryghost/logging');
const errors = require('@tryghost/errors');
const DomainEvents = require('@tryghost/domain-events');
const {SubscriptionCreatedEvent} = require('@tryghost/member-events');

module.exports = class WebhookController {
    /**
     * @param {object} deps
     * @param {import('./WebhookManager')} deps.webhookManager
     * @param {any} deps.deps.memberRepository
     */
    constructor(deps) {
        this.deps = deps;
        this.webhookManager = deps.webhookManager;
        this.api = deps.api;
        this.sendSignupEmail = deps.sendSignupEmail;
        this.handlers = {
            'customer.subscription.deleted': this.subscriptionEvent,
            'customer.subscription.updated': this.subscriptionEvent,
            'customer.subscription.created': this.subscriptionEvent,
            'invoice.payment_succeeded': this.invoiceEvent,
            'checkout.session.completed': this.checkoutSessionEvent
        };
    }

    async handle(req, res) {
        // if (!apiService.configured) {
        //     logging.error(`Stripe not configured, not handling webhook`);
        //     res.writeHead(400);
        //     return res.end();
        // }

        if (!req.body || !req.headers['stripe-signature']) {
            res.writeHead(400);
            return res.end();
        }
        let event;
        try {
            event = this.webhookManager.parseWebhook(req.body, req.headers['stripe-signature']);
        } catch (err) {
            logging.error(err);
            res.writeHead(401);
            return res.end();
        }

        logging.info(`Handling webhook ${event.type}`);
        try {
            await this.handleEvent(event);
            res.writeHead(200);
            res.end();
        } catch (err) {
            logging.error(`Error handling webhook ${event.type}`, err);
            res.writeHead(err.statusCode || 500);
            res.end();
        }
    }

    /**
     * @private
     */
    async handleEvent(event) {
        if (!this.handlers[event.type]) {
            return;
        }

        await this.handlers[event.type].call(this, event.data.object);
    }

    /**
     * @private
     */
    async subscriptionEvent(subscription) {
        const subscriptionPriceData = _.get(subscription, 'items.data');
        if (!subscriptionPriceData || subscriptionPriceData.length !== 1) {
            throw new errors.BadRequestError({
                message: 'Subscription should have exactly 1 price item'
            });
        }

        const member = await this.deps.memberRepository.get({
            customer_id: subscription.customer
        });

        if (member) {
            try {
                await this.deps.memberRepository.linkSubscription({
                    id: member.id,
                    subscription
                });
            } catch (err) {
                if (err.code !== 'ER_DUP_ENTRY' && err.code !== 'SQLITE_CONSTRAINT') {
                    throw err;
                }
                throw new errors.ConflictError({
                    err
                });
            }
        }
    }

    /**
     * @private
     */
    async invoiceEvent(invoice) {
        if (!invoice.subscription) {
            return;
        }
        const subscription = await this.api.getSubscription(invoice.subscription, {
            expand: ['default_payment_method']
        });

        const member = await this.deps.memberRepository.get({
            customer_id: subscription.customer
        });

        if (member) {
            if (invoice.paid && invoice.amount_paid !== 0) {
                await this.deps.eventRepository.registerPayment({
                    member_id: member.id,
                    currency: invoice.currency,
                    amount: invoice.amount_paid
                });
            }
        } else {
            // Subscription has more than one plan - meaning it is not one created by us - ignore.
            if (!subscription.plan) {
                return;
            }
            // Subscription is for a different product - ignore.
            const product = await this.deps.productRepository.get({
                stripe_product_id: subscription.plan.product
            });
            if (!product) {
                return;
            }

            // Could not find the member, which we need in order to insert an payment event.
            throw new errors.NotFoundError({
                message: `No member found for customer ${subscription.customer}`
            });
        }
    }

    /**
     * @private
     */
    async checkoutSessionEvent(session) {
        if (session.mode === 'setup') {
            const setupIntent = await this.api.getSetupIntent(session.setup_intent);
            const member = await this.deps.memberRepository.get({
                customer_id: setupIntent.metadata.customer_id
            });

            if (!member) {
                return;
            }

            await this.api.attachPaymentMethodToCustomer(
                setupIntent.metadata.customer_id,
                setupIntent.payment_method
            );

            if (setupIntent.metadata.subscription_id) {
                const updatedSubscription = await this.api.updateSubscriptionDefaultPaymentMethod(
                    setupIntent.metadata.subscription_id,
                    setupIntent.payment_method
                );
                try {
                    await this.deps.memberRepository.linkSubscription({
                        id: member.id,
                        subscription: updatedSubscription
                    });
                } catch (err) {
                    if (err.code !== 'ER_DUP_ENTRY' && err.code !== 'SQLITE_CONSTRAINT') {
                        throw err;
                    }
                    throw new errors.ConflictError({
                        err
                    });
                }
                return;
            }

            const subscriptions = await member.related('stripeSubscriptions').fetch();

            const activeSubscriptions = subscriptions.models.filter((subscription) => {
                return ['active', 'trialing', 'unpaid', 'past_due'].includes(subscription.get('status'));
            });

            for (const subscription of activeSubscriptions) {
                if (subscription.get('customer_id') === setupIntent.metadata.customer_id) {
                    const updatedSubscription = await this.api.updateSubscriptionDefaultPaymentMethod(
                        subscription.get('subscription_id'),
                        setupIntent.payment_method
                    );
                    try {
                        await this.deps.memberRepository.linkSubscription({
                            id: member.id,
                            subscription: updatedSubscription
                        });
                    } catch (err) {
                        if (err.code !== 'ER_DUP_ENTRY' && err.code !== 'SQLITE_CONSTRAINT') {
                            throw err;
                        }
                        throw new errors.ConflictError({
                            err
                        });
                    }
                }
            }
        }

        if (session.mode === 'subscription') {
            const customer = await this.api.getCustomer(session.customer, {
                expand: ['subscriptions.data.default_payment_method']
            });

            let member = await this.deps.memberRepository.get({
                email: customer.email
            });

            const checkoutType = _.get(session, 'metadata.checkoutType');

            if (!member) {
                const metadataName = _.get(session, 'metadata.name');
                const metadataNewsletters = _.get(session, 'metadata.newsletters');
                const payerName = _.get(customer, 'subscriptions.data[0].default_payment_method.billing_details.name');
                const name = metadataName || payerName || null;
                const memberData = {email: customer.email, name};
                if (metadataNewsletters) {
                    try {
                        memberData.newsletters = JSON.parse(metadataNewsletters);
                    } catch (e) {
                        logging.error(`Ignoring invalid newsletters data - ${metadataNewsletters}.`);
                    }
                }
                member = await this.deps.memberRepository.create(memberData);
            } else {
                const payerName = _.get(customer, 'subscriptions.data[0].default_payment_method.billing_details.name');

                if (payerName && !member.get('name')) {
                    await this.deps.memberRepository.update({name: payerName}, {id: member.get('id')});
                }
            }

            await this.deps.memberRepository.upsertCustomer({
                customer_id: customer.id,
                member_id: member.id,
                name: customer.name,
                email: customer.email
            });

            for (const subscription of customer.subscriptions.data) {
                try {
                    await this.deps.memberRepository.linkSubscription({
                        id: member.id,
                        subscription
                    });
                } catch (err) {
                    if (err.code !== 'ER_DUP_ENTRY' && err.code !== 'SQLITE_CONSTRAINT') {
                        throw err;
                    }
                    throw new errors.ConflictError({
                        err
                    });
                }
            }

            const subscription = await this.deps.memberRepository.getSubscriptionByStripeID(session.subscription);

            const event = SubscriptionCreatedEvent.create({
                memberId: member.id,
                subscriptionId: subscription.id,
                offerId: session.metadata.offer || null
            });

            DomainEvents.dispatch(event);

            if (checkoutType !== 'upgrade') {
                this.sendSignupEmail(customer.email);
            }
        }
    }
};
<|endoftext|>


<commit_before>/**
 * Routes Configuration
 * (trails.config.routes)
 *
 * Configure how routes map to views and controllers.
 *
 * @see http://trailsjs.io/doc/config/routes.js
 */

'use strict';

module.exports = [
  /**
   * Frontend Routes
   */
    {
        method: 'GET',
        path: '/',
        handler: 'ViewController.home'
    },
    {
        method: 'GET',
        path: '/survey',
        handler: 'ViewController.survey'
    },
    {
        method: 'GET',
        path: '/category',
        handler: 'ViewController.category'
    },
    {
        method: 'GET',
        path: '/admin/{view*}',
        handler: 'ViewController.admin'
    },
    {
        method: 'GET',
        path: '/login',
        handler: 'ViewController.login'
    },
    {
        method: ['POST'],
        path: '/login',
        handler: 'AuthController.login'
    },
    {
        method: 'GET',
        path: '/logout',
        handler: 'AuthController.logout'
    },
    {
        method: 'GET',
        path: '/results',
        handler: 'ViewController.results'
    },
  /**
   * Backend Routes
   */
    {
        method: 'POST',
        path: '/api/v1/user',
        handler: 'UserController.create'
    },

    {
        method: 'POST',
        path: '/api/v1/user/edit/{id*}',
        handler: 'UserController.update'
    },

    {
        method: 'POST',
        path: '/api/v1/user/delete/{id*}',
        handler: 'UserController.delete'
    },
    {
        method: 'GET',
        path: '/api/v1/user',
        handler: 'UserController.getAll'
    },

    {
        method: 'GET',
        path: '/api/v1/user/{id*}',
        handler: 'UserController.getOne'
    },

    {
        method: [ 'GET' ],
        path: '/api/v1/default/info',
        handler: 'DefaultController.info'
    },
    {
        method: 'POST',
        path: '/api/v1/user',
        handler: 'UserController.create'
    },
    {
        method: ['GET'],
        path: '/api/v1/category',
        handler: 'CategoryController.getAll'
    },
    {
        method: ['GET'],
        path: '/api/v1/questions',
        handler: 'QuestionController.getAll'
    },
    {
        method: ['GET'],
        path: '/api/v1/default/info',
        handler: 'DefaultController.info'
    }
];
<commit_msg>Remove duplicate routes.<commit_after>/**
 * Routes Configuration
 * (trails.config.routes)
 *
 * Configure how routes map to views and controllers.
 *
 * @see http://trailsjs.io/doc/config/routes.js
 */

'use strict';

module.exports = [
  /**
   * Frontend Routes
   */
    {
        method: 'GET',
        path: '/',
        handler: 'ViewController.home'
    },
    {
        method: 'GET',
        path: '/survey',
        handler: 'ViewController.survey'
    },
    {
        method: 'GET',
        path: '/category',
        handler: 'ViewController.category'
    },
    {
        method: 'GET',
        path: '/admin/{view*}',
        handler: 'ViewController.admin'
    },
    {
        method: 'GET',
        path: '/login',
        handler: 'ViewController.login'
    },
    {
        method: ['POST'],
        path: '/login',
        handler: 'AuthController.login'
    },
    {
        method: 'GET',
        path: '/logout',
        handler: 'AuthController.logout'
    },
    {
        method: 'GET',
        path: '/results',
        handler: 'ViewController.results'
    },
  /**
   * Backend Routes
   */
    {
        method: 'POST',
        path: '/api/v1/user',
        handler: 'UserController.create'
    },

    {
        method: 'POST',
        path: '/api/v1/user/edit/{id*}',
        handler: 'UserController.update'
    },

    {
        method: 'POST',
        path: '/api/v1/user/delete/{id*}',
        handler: 'UserController.delete'
    },
    {
        method: 'GET',
        path: '/api/v1/user',
        handler: 'UserController.getAll'
    },

    {
        method: 'GET',
        path: '/api/v1/user/{id*}',
        handler: 'UserController.getOne'
    },

    {
        method: ['GET'],
        path: '/api/v1/category',
        handler: 'CategoryController.getAll'
    },
    {
        method: ['GET'],
        path: '/api/v1/questions',
        handler: 'QuestionController.getAll'
    },
    {
        method: ['GET'],
        path: '/api/v1/default/info',
        handler: 'DefaultController.info'
    }
];
<|endoftext|>


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

/**
 * Gets the user's OAuth 2.0 access token so that it can be passed to Picker.
 * This technique keeps Picker from needing to show its own authorization
 * dialog, but is only possible if the OAuth scope that Picker needs is
 * available in Apps Script. In this case, the function includes an unused call
 * to a DriveApp method to ensure that Apps Script requests access to all files
 * in the user's Drive.
 *
 * @return {string} The user's OAuth 2.0 access token.
 */
function getOAuthToken() {
  DriveApp.getRootFolder();
  return ScriptApp.getOAuthToken();
}

/**
 * Create a new sheet for each selected CSV file containing the imported data.
 * Also performs validation that selected files have the correct mime-type and
 * will not conflict with existing sheet names.
 *
 * @param {array} docs An array of JSON objects returned by Google Picker
 *     representing the selected files.
 * @return {string} A string representing the selected files for user display.
 */
function loadSelectedFiles(files) {
  var masteryTracker = new MasteryTracker();

  // Filter the incoming files to select only csv files.
  var validatedFiles = validateFiles(files, 'text/csv');

  // Storage for the imported sheet ids. The sheet ids will be persisted in
  // the user properties for later use in removing the imported sheets.
  var userProperties = PropertiesService.getUserProperties();
  var importedSheets = [];

  for (var i = 0; i < validatedFiles.valid.length; i++) {
    var file = validatedFiles.valid[i];

    // Incoming sheet cannot have same name as an existing sheet.
    // Append a number to the end of the new, duplicate sheet name
    // to make it unique.
    if (masteryTracker.hasSheet(file.name)) {
      file.sheetName = masteryTracker.getUniqueSheetName(file.name);
      validatedFiles.renamed.push(file);
    }
    var sheetId = importCsvFile(file);
    importedSheets.push(sheetId);
  }

  // Persist the imported sheet ids in user properties.
  userProperties.setProperty('IMPORTED_SHEETS', importedSheets.toString());

  // Generate the diplay of imported, renamed, and invalid files.
  importedFiles = displayPickerResults(validatedFiles);
  return importedFiles;
}

/**
 * Construct an HTML string for display showing the the selected files with
 * links to each file, the invalid files, and the files that were renamed.
 *
 * @param {object} files An object containing arrays of file objects for valid,
 *     invalid, and renamed files.
 * @return {string} An HTML-formatted string representing the selected files
 *     for user display.
 */
function displayPickerResults(files) {
  var display = [];

  // Display valid and imported files.
  display.push(displayFiles_(
      files.valid,
      'The following files were successfully imported:',
      displayFileWithLink_));

  // Display renamed files.
  display.push(displayFiles_(
      files.renamed,
      'The following files were renamed because a sheet with that name ' +
          'already exists in this spreadsheet:',
      displayRenamedFile_));

  // Display invalid files.
  display.push(displayFiles_(
      files.invalid,
      'The following files are not valid csv files and were not imported:',
      displayFileWithLink_));

  // Display close button.
  display.push('
Close this window to continue.
');
  display.push('
' +
    '<input type="button" value="Close" class="btn" ' +
      'onclick="google.script.host.close()">' +
    '
');

  return display.join('');
}

/**
 * Returns an HTML-formatted string of files for display.
 *
 * @private
 * @param {array} files An array of JSON objects returned by Google Picker
 *     representing the selected files.
 * @param {string} message
 * @param {function} formatter A function that returns an HTML-formatted
 *     string of an individual file.
 * @returns An HTML-formatted string containing the file display.
 */
function displayFiles_(files, message, formatter) {
  var output = [];
  if (files.length > 0) {
    output.push(message);
    output.push('
');
    for (var i = 0; i < files.length; i++) {
      output.push(formatter(files[i]));
    }
    output.push('
');
  }
  return output.join('');
}

/**
 * Returns an HTML-formatted string of a single file for display.
 *
 * @private
 * @param {object} file The file object to be displayed.
 * @returns An HTML-formatted string for displaying a linked file name.
 */
function displayFileWithLink_(file) {
  return Utilities.formatString(
    '
Filename: %s
',
    file.url, file.name
  );
}

/**
 * Returns an HTML-formatted string of a single renamed file for display.
 *
 * @private
 * @param {object} file The file object to be displayed.
 * @returns An HTML-formatted string for displaying a linked file name with
 *     its new sheet name.
 */
function displayRenamedFile_(file) {
  return Utilities.formatString(
    '
File %s was renamed to %s in the current spreadsheet
',
    file.url, file.name, file.sheetName
  );
}<commit_msg>discontinue use of user properties<commit_after>// Copyright 2016 Joseph W. May. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/**
 * Gets the user's OAuth 2.0 access token so that it can be passed to Picker.
 * This technique keeps Picker from needing to show its own authorization
 * dialog, but is only possible if the OAuth scope that Picker needs is
 * available in Apps Script. In this case, the function includes an unused call
 * to a DriveApp method to ensure that Apps Script requests access to all files
 * in the user's Drive.
 *
 * @return {string} The user's OAuth 2.0 access token.
 */
function getOAuthToken() {
  DriveApp.getRootFolder();
  return ScriptApp.getOAuthToken();
}

/**
 * Create a new sheet for each selected CSV file containing the imported data.
 * Also performs validation that selected files have the correct mime-type and
 * will not conflict with existing sheet names.
 *
 * @param {array} docs An array of JSON objects returned by Google Picker
 *     representing the selected files.
 * @return {string} A string representing the selected files for user display.
 */
function loadSelectedFiles(files) {
  var masteryTracker = new MasteryTracker();

  // Filter the incoming files to select only csv files.
  var validatedFiles = validateFiles(files, 'text/csv');

  for (var i = 0; i < validatedFiles.valid.length; i++) {
    var file = validatedFiles.valid[i];

    // Incoming sheet cannot have same name as an existing sheet.
    // Append a number to the end of the new, duplicate sheet name
    // to make it unique.
    if (masteryTracker.hasSheet(file.name)) {
      file.sheetName = masteryTracker.getUniqueSheetName(file.name);
      validatedFiles.renamed.push(file);
    }
    var sheetId = importCsvFile(file);
  }

  // Generate the diplay of imported, renamed, and invalid files.
  importedFiles = displayPickerResults(validatedFiles);
  return importedFiles;
}

/**
 * Construct an HTML string for display showing the the selected files with
 * links to each file, the invalid files, and the files that were renamed.
 *
 * @param {object} files An object containing arrays of file objects for valid,
 *     invalid, and renamed files.
 * @return {string} An HTML-formatted string representing the selected files
 *     for user display.
 */
function displayPickerResults(files) {
  var display = [];

  // Display valid and imported files.
  display.push(displayFiles_(
      files.valid,
      'The following files were successfully imported:',
      displayFileWithLink_));

  // Display renamed files.
  display.push(displayFiles_(
      files.renamed,
      'The following files were renamed because a sheet with that name ' +
          'already exists in this spreadsheet:',
      displayRenamedFile_));

  // Display invalid files.
  display.push(displayFiles_(
      files.invalid,
      'The following files are not valid csv files and were not imported:',
      displayFileWithLink_));

  // Display close button.
  display.push('
Close this window to continue.
');
  display.push('
' +
    '<input type="button" value="Close" class="btn" ' +
      'onclick="google.script.host.close()">' +
    '
');

  return display.join('');
}

/**
 * Returns an HTML-formatted string of files for display.
 *
 * @private
 * @param {array} files An array of JSON objects returned by Google Picker
 *     representing the selected files.
 * @param {string} message
 * @param {function} formatter A function that returns an HTML-formatted
 *     string of an individual file.
 * @returns An HTML-formatted string containing the file display.
 */
function displayFiles_(files, message, formatter) {
  var output = [];
  if (files.length > 0) {
    output.push(message);
    output.push('
');
    for (var i = 0; i < files.length; i++) {
      output.push(formatter(files[i]));
    }
    output.push('
');
  }
  return output.join('');
}

/**
 * Returns an HTML-formatted string of a single file for display.
 *
 * @private
 * @param {object} file The file object to be displayed.
 * @returns An HTML-formatted string for displaying a linked file name.
 */
function displayFileWithLink_(file) {
  return Utilities.formatString(
    '
Filename: %s
',
    file.url, file.name
  );
}

/**
 * Returns an HTML-formatted string of a single renamed file for display.
 *
 * @private
 * @param {object} file The file object to be displayed.
 * @returns An HTML-formatted string for displaying a linked file name with
 *     its new sheet name.
 */
function displayRenamedFile_(file) {
  return Utilities.formatString(
    '
File %s was renamed to %s in the current spreadsheet
',
    file.url, file.name, file.sheetName
  );
}<|endoftext|>


<commit_before>
var mongoose = require('mongoose')
  , Article = mongoose.model('Article')
  , User = mongoose.model('User')
  , async = require('async')

module.exports = function (app, passport, auth) {

  // user routes
  var users = require('../app/controllers/users')
  app.get('/login', users.login)
  app.get('/signup', users.signup)
  app.get('/logout', users.logout)
  app.post('/users', users.create)
  app.post('/users/session', passport.authenticate('local', {failureRedirect: '/login', failureFlash: 'Invalid email or password.'}), users.session)
  app.get('/users/:userId', users.show)
  app.get('/auth/facebook', passport.authenticate('facebook', { scope: [ 'email', 'user_about_me'], failureRedirect: '/login' }), users.signin)
  app.get('/auth/facebook/callback', passport.authenticate('facebook', { failureRedirect: '/login' }), users.authCallback)
  app.get('/auth/github', passport.authenticate('github', { failureRedirect: '/login' }), users.signin)
  app.get('/auth/github/callback', passport.authenticate('github', { failureRedirect: '/login' }), users.authCallback)
  app.get('/auth/twitter', passport.authenticate('twitter', { failureRedirect: '/login' }), users.signin)
  app.get('/auth/twitter/callback', passport.authenticate('twitter', { failureRedirect: '/login' }), users.authCallback)
  app.get('/auth/google', passport.authenticate('google', { failureRedirect: '/login', scope: 'https://www.google.com/m8/feeds' }), users.signin)
  app.get('/auth/google/callback', passport.authenticate('google', { failureRedirect: '/login', scope: 'https://www.google.com/m8/feeds' }), users.authCallback)

  app.param('userId', users.user)

  // article routes
  var articles = require('../app/controllers/articles')
  app.get('/articles', articles.index)
  app.get('/articles/new', auth.requiresLogin, articles.new)
  app.post('/articles', auth.requiresLogin, articles.create)
  app.get('/articles/:id', articles.show)
  app.get('/articles/:id/edit', auth.requiresLogin, auth.article.hasAuthorization, articles.edit)
  app.put('/articles/:id', auth.requiresLogin, auth.article.hasAuthorization, articles.update)
  app.del('/articles/:id', auth.requiresLogin, auth.article.hasAuthorization, articles.destroy)

  app.param('id', articles.article)

  // home route
  app.get('/', articles.index)

  // comment routes
  var comments = require('../app/controllers/comments')
  app.post('/articles/:id/comments', auth.requiresLogin, comments.create)

  // tag routes
  var tags = require('../app/controllers/tags')
  app.get('/tags/:tag', tags.index)

}
<commit_msg>removed unused require from routes<commit_after>
var async = require('async')

module.exports = function (app, passport, auth) {

  // user routes
  var users = require('../app/controllers/users')
  app.get('/login', users.login)
  app.get('/signup', users.signup)
  app.get('/logout', users.logout)
  app.post('/users', users.create)
  app.post('/users/session', passport.authenticate('local', {failureRedirect: '/login', failureFlash: 'Invalid email or password.'}), users.session)
  app.get('/users/:userId', users.show)
  app.get('/auth/facebook', passport.authenticate('facebook', { scope: [ 'email', 'user_about_me'], failureRedirect: '/login' }), users.signin)
  app.get('/auth/facebook/callback', passport.authenticate('facebook', { failureRedirect: '/login' }), users.authCallback)
  app.get('/auth/github', passport.authenticate('github', { failureRedirect: '/login' }), users.signin)
  app.get('/auth/github/callback', passport.authenticate('github', { failureRedirect: '/login' }), users.authCallback)
  app.get('/auth/twitter', passport.authenticate('twitter', { failureRedirect: '/login' }), users.signin)
  app.get('/auth/twitter/callback', passport.authenticate('twitter', { failureRedirect: '/login' }), users.authCallback)
  app.get('/auth/google', passport.authenticate('google', { failureRedirect: '/login', scope: 'https://www.google.com/m8/feeds' }), users.signin)
  app.get('/auth/google/callback', passport.authenticate('google', { failureRedirect: '/login', scope: 'https://www.google.com/m8/feeds' }), users.authCallback)

  app.param('userId', users.user)

  // article routes
  var articles = require('../app/controllers/articles')
  app.get('/articles', articles.index)
  app.get('/articles/new', auth.requiresLogin, articles.new)
  app.post('/articles', auth.requiresLogin, articles.create)
  app.get('/articles/:id', articles.show)
  app.get('/articles/:id/edit', auth.requiresLogin, auth.article.hasAuthorization, articles.edit)
  app.put('/articles/:id', auth.requiresLogin, auth.article.hasAuthorization, articles.update)
  app.del('/articles/:id', auth.requiresLogin, auth.article.hasAuthorization, articles.destroy)

  app.param('id', articles.article)

  // home route
  app.get('/', articles.index)

  // comment routes
  var comments = require('../app/controllers/comments')
  app.post('/articles/:id/comments', auth.requiresLogin, comments.create)

  // tag routes
  var tags = require('../app/controllers/tags')
  app.get('/tags/:tag', tags.index)

}
<|endoftext|>


<commit_before>define(function (require) {

    return {

        /**
         * @public
         * @return {Array.<number|string|Date>}
         */
        getMin: function () {
            var option = this.option;
            return option.rangeStart != null ? option.rangeStart : option.min;
        },

        /**
         * @public
         * @return {Array.<number|string|Date>}
         */
        getMax: function () {
            var option = this.option;
            return option.rangeEnd != null ? option.rangeEnd : option.max;
        },

        /**
         * @public
         * @return {boolean}
         */
        getNeedCrossZero: function () {
            var option = this.option;
            return (option.rangeStart != null || option.rangeEnd != null)
                ? false : !option.scale;
        },

        /**
         * @public
         * @param {number} rangeStart
         * @param {number} rangeEnd
         */
        setRange: function (rangeStart, rangeEnd) {
            this.option.rangeStart = rangeStart;
            this.option.rangeEnd = rangeEnd;
        },

        /**
         * @public
         */
        resetRange: function () {
            // rangeStart and rangeEnd is readonly.
            this.option.rangeStart = this.option.rangeEnd = null;
        }
    };

});<commit_msg>[axis] Fix #4097 (enable min/max to be set as Date when using time axis)<commit_after>define(function (require) {

    return {

        /**
         * @public
         * @return {Array.<number|string|Date>}
         */
        getMin: function () {
            var option = this.option;
            var min = option.rangeStart != null ? option.rangeStart : option.min;
            // In case of axis.type === 'time', Date should be converted to timestamp.
            // In other cases, min/max should be a number or null/undefined.
            if (min != null) {
                min = +min;
            }
            return min;
        },

        /**
         * @public
         * @return {Array.<number|string|Date>}
         */
        getMax: function () {
            var option = this.option;
            var max = option.rangeEnd != null ? option.rangeEnd : option.max;
            // In case of axis.type === 'time', Date should be converted to timestamp.
            // In other cases, min/max should be a number or null/undefined.
            if (max != null) {
                max = +max;
            }
            return max;
        },

        /**
         * @public
         * @return {boolean}
         */
        getNeedCrossZero: function () {
            var option = this.option;
            return (option.rangeStart != null || option.rangeEnd != null)
                ? false : !option.scale;
        },

        /**
         * @public
         * @param {number} rangeStart
         * @param {number} rangeEnd
         */
        setRange: function (rangeStart, rangeEnd) {
            this.option.rangeStart = rangeStart;
            this.option.rangeEnd = rangeEnd;
        },

        /**
         * @public
         */
        resetRange: function () {
            // rangeStart and rangeEnd is readonly.
            this.option.rangeStart = this.option.rangeEnd = null;
        }
    };

});<|endoftext|>


<commit_before>/**
 * Copyright 2018-present Facebook.
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 * @format
 */
import Link from '../Link';
import type {DataInspectorSetValue} from './DataInspector.js';
import {PureComponent} from 'react';
import styled from '../../styled/index.js';
import {SketchPicker} from 'react-color';
import {Component, Fragment} from 'react';
import Popover from '../Popover.js';
import {colors} from '../colors.js';
import Input from '../Input.js';

const NullValue = styled('span')({
  color: 'rgb(128, 128, 128)',
});

const UndefinedValue = styled('span')({
  color: 'rgb(128, 128, 128)',
});

const StringValue = styled('span')({
  color: colors.cherryDark1,
});

const ColorValue = styled('span')({
  color: colors.blueGrey,
});

const SymbolValue = styled('span')({
  color: 'rgb(196, 26, 22)',
});

const NumberValue = styled('span')({
  color: colors.tealDark1,
});

const ColorBox = styled('span')(props => ({
  backgroundColor: props.color,
  boxShadow: 'inset 0 0 1px rgba(0, 0, 0, 1)',
  display: 'inline-block',
  height: 12,
  marginRight: 5,
  verticalAlign: 'middle',
  width: 12,
}));

const FunctionKeyword = styled('span')({
  color: 'rgb(170, 13, 145)',
  fontStyle: 'italic',
});

const FunctionName = styled('span')({
  fontStyle: 'italic',
});

const ColorPickerDescription = styled('div')({
  display: 'inline',
  position: 'relative',
});

type DataDescriptionProps = {|
  path?: Array<string>,
  type: string,
  value: any,
  setValue: ?DataInspectorSetValue,
|};

type DescriptionCommitOptions = {|
  value: any,
  keep: boolean,
  clear: boolean,
|};

class NumberTextEditor extends PureComponent<{
  commit: (opts: DescriptionCommitOptions) => void,
  type: string,
  value: any,
  origValue: any,
}> {
  onNumberTextInputChange = (e: SyntheticInputEvent<HTMLInputElement>) => {
    const val =
      this.props.type === 'number'
        ? parseFloat(e.target.value)
        : e.target.value;
    this.props.commit({
      clear: false,
      keep: true,
      value: val,
    });
  };

  onNumberTextInputKeyDown = (e: SyntheticKeyboardEvent<*>) => {
    if (e.key === 'Enter') {
      const val =
        this.props.type === 'number'
          ? parseFloat(this.props.value)
          : this.props.value;
      this.props.commit({clear: true, keep: true, value: val});
    } else if (e.key === 'Escape') {
      this.props.commit({
        clear: true,
        keep: false,
        value: this.props.origValue,
      });
    }
  };

  onNumberTextRef = (ref: ?HTMLElement) => {
    if (ref) {
      ref.focus();
    }
  };

  onNumberTextBlur = () => {
    this.props.commit({clear: true, keep: true, value: this.props.value});
  };

  render() {
    const extraProps: Object = {};
    if (this.props.type === 'number') {
      // render as a HTML number input
      extraProps.type = 'number';

      // step="any" allows any sort of float to be input, otherwise we're limited
      // to decimal
      extraProps.step = 'any';
    }

    return (
      <Input
        key="input"
        {...extraProps}
        compact={true}
        onChange={this.onNumberTextInputChange}
        onKeyDown={this.onNumberTextInputKeyDown}
        innerRef={this.onNumberTextRef}
        onBlur={this.onNumberTextBlur}
        value={this.props.value}
      />
    );
  }
}

type DataDescriptionState = {|
  editing: boolean,
  origValue: any,
  value: any,
|};

export default class DataDescription extends PureComponent<
  DataDescriptionProps,
  DataDescriptionState,
> {
  constructor(props: DataDescriptionProps, context: Object) {
    super(props, context);

    this.state = {
      editing: false,
      origValue: '',
      value: '',
    };
  }

  commit = (opts: DescriptionCommitOptions) => {
    const {path, setValue} = this.props;
    if (opts.keep && setValue && path) {
      const val = opts.value;
      this.setState({value: val});
      setValue(path, val);
    }

    if (opts.clear) {
      this.setState({
        editing: false,
        origValue: '',
        value: '',
      });
    }
  };

  _renderEditing() {
    const {type} = this.props;
    const {origValue, value} = this.state;

    if (
      type === 'string' ||
      type === 'text' ||
      type === 'number' ||
      type === 'enum'
    ) {
      return (
        <NumberTextEditor
          type={type}
          value={value}
          origValue={origValue}
          commit={this.commit}
        />
      );
    }

    if (type === 'color') {
      return <ColorEditor value={value} commit={this.commit} />;
    }

    return null;
  }

  _hasEditUI() {
    const {type} = this.props;
    return (
      type === 'string' ||
      type === 'text' ||
      type === 'number' ||
      type === 'enum' ||
      type === 'color'
    );
  }

  onEditStart = () => {
    this.setState({
      editing: this._hasEditUI(),
      origValue: this.props.value,
      value: this.props.value,
    });
  };

  render(): any {
    if (this.state.editing) {
      return this._renderEditing();
    } else {
      return (
        <DataDescriptionPreview
          type={this.props.type}
          value={this.props.value}
          editable={Boolean(this.props.setValue)}
          commit={this.commit}
          onEdit={this.onEditStart}
        />
      );
    }
  }
}

class ColorEditor extends Component<{
  value: any,
  commit: (opts: DescriptionCommitOptions) => void,
}> {
  onBlur = () => {
    this.props.commit({clear: true, keep: false, value: this.props.value});
  };

  onChange = ({
    hex,
    rgb: {a, b, g, r},
  }: {
    hex: string,
    rgb: {a: number, b: number, g: number, r: number},
  }) => {
    const prev = this.props.value;

    let val;
    if (typeof prev === 'string') {
      if (a === 1) {
        // hex is fine and has an implicit 100% alpha
        val = hex;
      } else {
        // turn into a css rgba value
        val = `rgba(${r}, ${g}, ${b}, ${a})`;
      }
    } else if (typeof prev === 'number') {
      // compute RRGGBBAA value
      val = (Math.round(a * 255) & 0xff) << 24;
      val |= (r & 0xff) << 16;
      val |= (g & 0xff) << 8;
      val |= b & 0xff;

      const prevClear = ((prev >> 24) & 0xff) === 0;
      const onlyAlphaChanged = (prev & 0x00ffffff) === (val & 0x00ffffff);

      if (!onlyAlphaChanged && prevClear) {
        val = 0xff000000 | (val & 0x00ffffff);
      }
    } else {
      return;
    }

    this.props.commit({clear: false, keep: true, value: val});
  };

  render() {
    const colorInfo = parseColor(this.props.value);
    if (!colorInfo) {
      return <Fragment />;
    }

    return (
      <ColorPickerDescription>
        <DataDescriptionPreview
          type="color"
          value={this.props.value}
          editable={false}
          commit={this.props.commit}
        />
        <Popover onDismiss={this.onBlur}>
          <SketchPicker
            color={colorInfo}
            presetColors={[
              colors.blue,
              colors.green,
              colors.red,
              colors.blueGrey,
              colors.slate,
              colors.aluminum,
              colors.seaFoam,
              colors.teal,
              colors.lime,
              colors.lemon,
              colors.orange,
              colors.tomato,
              colors.cherry,
              colors.pink,
              colors.grape,
            ]}
            onChange={this.onChange}
          />
        </Popover>
      </ColorPickerDescription>
    );
  }
}

class DataDescriptionPreview extends Component<{
  type: string,
  value: any,
  editable: boolean,
  commit: (opts: DescriptionCommitOptions) => void,
  onEdit?: () => void,
}> {
  onClick = () => {
    const {onEdit} = this.props;
    if (this.props.editable && onEdit) {
      onEdit();
    }
  };

  render() {
    const {type, value} = this.props;

    const description = (
      <DataDescriptionContainer
        type={type}
        value={value}
        editable={this.props.editable}
        commit={this.props.commit}
      />
    );

    // booleans are always editable so don't require the onEditStart handler
    if (type === 'boolean') {
      return description;
    }

    return (

        {description}

    );
  }
}

function parseColor(
  val: string | number,
): ?{|
  r: number,
  g: number,
  b: number,
  a: number,
|} {
  if (typeof val === 'number') {
    const a = ((val >> 24) & 0xff) / 255;
    const r = (val >> 16) & 0xff;
    const g = (val >> 8) & 0xff;
    const b = val & 0xff;
    return {a, b, g, r};
  }
  if (typeof val !== 'string') {
    return;
  }
  if (val[0] !== '#') {
    return;
  }

  // remove leading hash
  val = val.slice(1);

  // only allow RGB and ARGB hex values
  if (val.length !== 3 && val.length !== 6 && val.length !== 8) {
    return;
  }

  // split every 2 characters
  const parts = val.match(/.{1,2}/g);
  if (!parts) {
    return;
  }

  // get the alpha value
  let a = 1;

  // extract alpha if passed AARRGGBB
  if (val.length === 8) {
    a = parseInt(parts.shift(), 16) / 255;
  }

  const size = val.length;
  const [r, g, b] = parts.map(num => {
    if (size === 3) {
      return parseInt(num + num, 16);
    } else {
      return parseInt(num, 16);
    }
  });

  return {a, b, g, r};
}

class DataDescriptionContainer extends Component<{
  type: string,
  value: any,
  editable: boolean,
  commit: (opts: DescriptionCommitOptions) => void,
}> {
  onChangeCheckbox = (e: SyntheticInputEvent<HTMLInputElement>) => {
    this.props.commit({clear: true, keep: true, value: e.target.checked});
  };

  render(): any {
    const {type, editable, value: val} = this.props;

    switch (type) {
      case 'number':
        return <NumberValue>{Number(val)}</NumberValue>;

      case 'color': {
        const colorInfo = parseColor(val);
        if (typeof val === 'number' && val === 0) {
          return <UndefinedValue>(not set)</UndefinedValue>;
        } else if (colorInfo) {
          const {a, b, g, r} = colorInfo;
          return [
            <ColorBox key="color-box" color={`rgba(${r}, ${g}, ${b}, ${a})`} />,
            <ColorValue key="value">
              rgba({r}, {g}, {b}, {a === 1 ? '1' : a.toFixed(2)})
            </ColorValue>,
          ];
        } else {
          return Malformed color;
        }
      }

      case 'text':
      case 'string':
        if (val.startsWith('http://') || val.startsWith('https://')) {
          return <Link href={val}>{val}</Link>;
        } else {
          return <StringValue>"{String(val || '')}"</StringValue>;
        }

      case 'enum':
        return <StringValue>{String(val)}</StringValue>;

      case 'boolean':
        return editable ? (
          <input
            type="checkbox"
            checked={Boolean(val)}
            disabled={!editable}
            onChange={this.onChangeCheckbox}
          />
        ) : (
          <StringValue>{String(val)}</StringValue>
        );

      case 'undefined':
        return <UndefinedValue>undefined</UndefinedValue>;

      case 'date':
        if (Object.prototype.toString.call(val) === '[object Date]') {
          return {val.toString()};
        } else {
          return {val};
        }

      case 'null':
        return <NullValue>null</NullValue>;

      case 'array':
      case 'object':
        // no description necessary as we'll typically wrap it in [] or {} which already denotes the
        // type
        return null;

      case 'function':
        return (

            <FunctionKeyword>function</FunctionKeyword>
            <FunctionName> {val.name}()</FunctionName>

        );

      case 'symbol':
        return <SymbolValue>Symbol()</SymbolValue>;

      default:
        return Unknown type "{type}";
    }
  }
}
<commit_msg>Flipper sent messages while editing<commit_after>/**
 * Copyright 2018-present Facebook.
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 * @format
 */
import Link from '../Link';
import type {DataInspectorSetValue} from './DataInspector.js';
import {PureComponent} from 'react';
import styled from '../../styled/index.js';
import {SketchPicker} from 'react-color';
import {Component, Fragment} from 'react';
import Popover from '../Popover.js';
import {colors} from '../colors.js';
import Input from '../Input.js';

const NullValue = styled('span')({
  color: 'rgb(128, 128, 128)',
});

const UndefinedValue = styled('span')({
  color: 'rgb(128, 128, 128)',
});

const StringValue = styled('span')({
  color: colors.cherryDark1,
});

const ColorValue = styled('span')({
  color: colors.blueGrey,
});

const SymbolValue = styled('span')({
  color: 'rgb(196, 26, 22)',
});

const NumberValue = styled('span')({
  color: colors.tealDark1,
});

const ColorBox = styled('span')(props => ({
  backgroundColor: props.color,
  boxShadow: 'inset 0 0 1px rgba(0, 0, 0, 1)',
  display: 'inline-block',
  height: 12,
  marginRight: 5,
  verticalAlign: 'middle',
  width: 12,
}));

const FunctionKeyword = styled('span')({
  color: 'rgb(170, 13, 145)',
  fontStyle: 'italic',
});

const FunctionName = styled('span')({
  fontStyle: 'italic',
});

const ColorPickerDescription = styled('div')({
  display: 'inline',
  position: 'relative',
});

type DataDescriptionProps = {|
  path?: Array<string>,
  type: string,
  value: any,
  setValue: ?DataInspectorSetValue,
|};

type DescriptionCommitOptions = {|
  value: any,
  keep: boolean,
  clear: boolean,
  set: boolean,
|};

class NumberTextEditor extends PureComponent<{
  commit: (opts: DescriptionCommitOptions) => void,
  type: string,
  value: any,
  origValue: any,
}> {
  onNumberTextInputChange = (e: SyntheticInputEvent<HTMLInputElement>) => {
    const val =
      this.props.type === 'number'
        ? parseFloat(e.target.value)
        : e.target.value;
    this.props.commit({
      clear: false,
      keep: true,
      value: val,
      set: false,
    });
  };

  onNumberTextInputKeyDown = (e: SyntheticKeyboardEvent<*>) => {
    if (e.key === 'Enter') {
      const val =
        this.props.type === 'number'
          ? parseFloat(this.props.value)
          : this.props.value;
      this.props.commit({clear: true, keep: true, value: val, set: true});
    } else if (e.key === 'Escape') {
      this.props.commit({
        clear: true,
        keep: false,
        value: this.props.origValue,
        set: false,
      });
    }
  };

  onNumberTextRef = (ref: ?HTMLElement) => {
    if (ref) {
      ref.focus();
    }
  };

  onNumberTextBlur = () => {
    this.props.commit({
      clear: true,
      keep: true,
      value: this.props.value,
      set: true,
    });
  };

  render() {
    const extraProps: Object = {};
    if (this.props.type === 'number') {
      // render as a HTML number input
      extraProps.type = 'number';

      // step="any" allows any sort of float to be input, otherwise we're limited
      // to decimal
      extraProps.step = 'any';
    }

    return (
      <Input
        key="input"
        {...extraProps}
        compact={true}
        onChange={this.onNumberTextInputChange}
        onKeyDown={this.onNumberTextInputKeyDown}
        innerRef={this.onNumberTextRef}
        onBlur={this.onNumberTextBlur}
        value={this.props.value}
      />
    );
  }
}

type DataDescriptionState = {|
  editing: boolean,
  origValue: any,
  value: any,
|};

export default class DataDescription extends PureComponent<
  DataDescriptionProps,
  DataDescriptionState,
> {
  constructor(props: DataDescriptionProps, context: Object) {
    super(props, context);

    this.state = {
      editing: false,
      origValue: '',
      value: '',
    };
  }

  commit = (opts: DescriptionCommitOptions) => {
    const {path, setValue} = this.props;
    if (opts.keep && setValue && path) {
      const val = opts.value;
      this.setState({value: val});
      if (opts.set) {
        setValue(path, val);
      }
    }

    if (opts.clear) {
      this.setState({
        editing: false,
        origValue: '',
        value: '',
      });
    }
  };

  _renderEditing() {
    const {type} = this.props;
    const {origValue, value} = this.state;

    if (
      type === 'string' ||
      type === 'text' ||
      type === 'number' ||
      type === 'enum'
    ) {
      return (
        <NumberTextEditor
          type={type}
          value={value}
          origValue={origValue}
          commit={this.commit}
        />
      );
    }

    if (type === 'color') {
      return <ColorEditor value={value} commit={this.commit} />;
    }

    return null;
  }

  _hasEditUI() {
    const {type} = this.props;
    return (
      type === 'string' ||
      type === 'text' ||
      type === 'number' ||
      type === 'enum' ||
      type === 'color'
    );
  }

  onEditStart = () => {
    this.setState({
      editing: this._hasEditUI(),
      origValue: this.props.value,
      value: this.props.value,
    });
  };

  render(): any {
    if (this.state.editing) {
      return this._renderEditing();
    } else {
      return (
        <DataDescriptionPreview
          type={this.props.type}
          value={this.props.value}
          editable={Boolean(this.props.setValue)}
          commit={this.commit}
          onEdit={this.onEditStart}
        />
      );
    }
  }
}

class ColorEditor extends Component<{
  value: any,
  commit: (opts: DescriptionCommitOptions) => void,
}> {
  onBlur = () => {
    this.props.commit({
      clear: true,
      keep: false,
      value: this.props.value,
      set: true,
    });
  };

  onChange = ({
    hex,
    rgb: {a, b, g, r},
  }: {
    hex: string,
    rgb: {a: number, b: number, g: number, r: number},
  }) => {
    const prev = this.props.value;

    let val;
    if (typeof prev === 'string') {
      if (a === 1) {
        // hex is fine and has an implicit 100% alpha
        val = hex;
      } else {
        // turn into a css rgba value
        val = `rgba(${r}, ${g}, ${b}, ${a})`;
      }
    } else if (typeof prev === 'number') {
      // compute RRGGBBAA value
      val = (Math.round(a * 255) & 0xff) << 24;
      val |= (r & 0xff) << 16;
      val |= (g & 0xff) << 8;
      val |= b & 0xff;

      const prevClear = ((prev >> 24) & 0xff) === 0;
      const onlyAlphaChanged = (prev & 0x00ffffff) === (val & 0x00ffffff);

      if (!onlyAlphaChanged && prevClear) {
        val = 0xff000000 | (val & 0x00ffffff);
      }
    } else {
      return;
    }

    this.props.commit({clear: false, keep: true, value: val, set: true});
  };

  render() {
    const colorInfo = parseColor(this.props.value);
    if (!colorInfo) {
      return <Fragment />;
    }

    return (
      <ColorPickerDescription>
        <DataDescriptionPreview
          type="color"
          value={this.props.value}
          editable={false}
          commit={this.props.commit}
        />
        <Popover onDismiss={this.onBlur}>
          <SketchPicker
            color={colorInfo}
            presetColors={[
              colors.blue,
              colors.green,
              colors.red,
              colors.blueGrey,
              colors.slate,
              colors.aluminum,
              colors.seaFoam,
              colors.teal,
              colors.lime,
              colors.lemon,
              colors.orange,
              colors.tomato,
              colors.cherry,
              colors.pink,
              colors.grape,
            ]}
            onChange={this.onChange}
          />
        </Popover>
      </ColorPickerDescription>
    );
  }
}

class DataDescriptionPreview extends Component<{
  type: string,
  value: any,
  editable: boolean,
  commit: (opts: DescriptionCommitOptions) => void,
  onEdit?: () => void,
}> {
  onClick = () => {
    const {onEdit} = this.props;
    if (this.props.editable && onEdit) {
      onEdit();
    }
  };

  render() {
    const {type, value} = this.props;

    const description = (
      <DataDescriptionContainer
        type={type}
        value={value}
        editable={this.props.editable}
        commit={this.props.commit}
      />
    );

    // booleans are always editable so don't require the onEditStart handler
    if (type === 'boolean') {
      return description;
    }

    return (

        {description}

    );
  }
}

function parseColor(
  val: string | number,
): ?{|
  r: number,
  g: number,
  b: number,
  a: number,
|} {
  if (typeof val === 'number') {
    const a = ((val >> 24) & 0xff) / 255;
    const r = (val >> 16) & 0xff;
    const g = (val >> 8) & 0xff;
    const b = val & 0xff;
    return {a, b, g, r};
  }
  if (typeof val !== 'string') {
    return;
  }
  if (val[0] !== '#') {
    return;
  }

  // remove leading hash
  val = val.slice(1);

  // only allow RGB and ARGB hex values
  if (val.length !== 3 && val.length !== 6 && val.length !== 8) {
    return;
  }

  // split every 2 characters
  const parts = val.match(/.{1,2}/g);
  if (!parts) {
    return;
  }

  // get the alpha value
  let a = 1;

  // extract alpha if passed AARRGGBB
  if (val.length === 8) {
    a = parseInt(parts.shift(), 16) / 255;
  }

  const size = val.length;
  const [r, g, b] = parts.map(num => {
    if (size === 3) {
      return parseInt(num + num, 16);
    } else {
      return parseInt(num, 16);
    }
  });

  return {a, b, g, r};
}

class DataDescriptionContainer extends Component<{
  type: string,
  value: any,
  editable: boolean,
  commit: (opts: DescriptionCommitOptions) => void,
}> {
  onChangeCheckbox = (e: SyntheticInputEvent<HTMLInputElement>) => {
    this.props.commit({
      clear: true,
      keep: true,
      value: e.target.checked,
      set: true,
    });
  };

  render(): any {
    const {type, editable, value: val} = this.props;

    switch (type) {
      case 'number':
        return <NumberValue>{Number(val)}</NumberValue>;

      case 'color': {
        const colorInfo = parseColor(val);
        if (typeof val === 'number' && val === 0) {
          return <UndefinedValue>(not set)</UndefinedValue>;
        } else if (colorInfo) {
          const {a, b, g, r} = colorInfo;
          return [
            <ColorBox key="color-box" color={`rgba(${r}, ${g}, ${b}, ${a})`} />,
            <ColorValue key="value">
              rgba({r}, {g}, {b}, {a === 1 ? '1' : a.toFixed(2)})
            </ColorValue>,
          ];
        } else {
          return Malformed color;
        }
      }

      case 'text':
      case 'string':
        if (val.startsWith('http://') || val.startsWith('https://')) {
          return <Link href={val}>{val}</Link>;
        } else {
          return <StringValue>"{String(val || '')}"</StringValue>;
        }

      case 'enum':
        return <StringValue>{String(val)}</StringValue>;

      case 'boolean':
        return editable ? (
          <input
            type="checkbox"
            checked={Boolean(val)}
            disabled={!editable}
            onChange={this.onChangeCheckbox}
          />
        ) : (
          <StringValue>{String(val)}</StringValue>
        );

      case 'undefined':
        return <UndefinedValue>undefined</UndefinedValue>;

      case 'date':
        if (Object.prototype.toString.call(val) === '[object Date]') {
          return {val.toString()};
        } else {
          return {val};
        }

      case 'null':
        return <NullValue>null</NullValue>;

      case 'array':
      case 'object':
        // no description necessary as we'll typically wrap it in [] or {} which already denotes the
        // type
        return null;

      case 'function':
        return (

            <FunctionKeyword>function</FunctionKeyword>
            <FunctionName> {val.name}()</FunctionName>

        );

      case 'symbol':
        return <SymbolValue>Symbol()</SymbolValue>;

      default:
        return Unknown type "{type}";
    }
  }
}
<|endoftext|>


<commit_before>/** @jsx jsx */
import { jsx } from "theme-ui"
import { Component } from "react"
import { graphql } from "gatsby"
import { Helmet } from "react-helmet"

import Button from "../components/button"
import Layout from "../components/layout"
import Container from "../components/container"
import FooterLinks from "../components/shared/footer-links"
import LegendTable from "../components/features/legend-table"
import FeaturesFooter from "../components/features/features-footer"
import SimpleEvaluationTable from "../components/features/simple-evaluation-table"

const FeaturesHeader = () => (

      Features Overview

      There are many ways to build a website. If you’re considering Gatsby, you
      may also be looking at some alternatives:

        JAMstack frameworks such as
        {` `}

          Jekyll

        {`, `}

          Next.js

        , and
        {` `}

          Nuxt.js

        {` `}
        let you put text or markdown in a specific directory such as
        pages/ in a version-controlled codebase. They then build a
        specific kind of site, usually a blog, as HTML files from the content
        you’ve added. These files can be cached and served from a CDN.

Coming from the JAMstack world?

        <Button to="/features/jamstack" secondary>
          Compare Gatsby vs JAMstack
        </Button>

        Traditional content management systems (CMSs) like
        {` `}

          WordPress

        {` `}
        and
        {` `}

          Drupal

        {` `}
        give you an online text editor to create content. You customize the look
        and feel by choosing themes and plugins or by writing custom PHP or
        JavaScript code. Content is saved in a database, which is retrieved and
        sent to users when they visit the website. Depending on your
        requirements you can self-host your website or use an official hosting
        provider.

Coming from the CMS world?

        <Button to="/features/cms" secondary>
          Compare Gatsby vs CMS
        </Button>

      The chart below details Gatsby’s capabilities in comparison with a
      representative from each category. Click on any row to see a more detailed
      explanation on that feature and our rating for each system.

    <h6
      id="legend"
      sx={{
        fontWeight: `body`,
        letterSpacing: `tracked`,
        textTransform: `uppercase`,
      }}
    >
      Legend

    <LegendTable />

)

class FeaturesPage extends Component {
  render() {
    return (
      <Layout location={this.props.location} enableScrollSync={true}>
        <Helmet>
          <title>Features</title>
          <meta
            name="description"
            content="Learn how specific features like performance and support for modern technologies make Gatsby worth using."
          />
        </Helmet>
        <Container>
          <main id={`reach-skip-nav`}>
            <FeaturesHeader />
            <SimpleEvaluationTable
              title="Feature Comparison"
              headers={[
                { display: `Category`, nodeFieldProperty: `Category` },
                { display: `Gatsby`, nodeFieldProperty: `Gatsby` },
                {
                  display: `JAMstack frameworks`,
                  nodeFieldProperty: `Jamstack`,
                },
                { display: `Traditional CMS`, nodeFieldProperty: `Cms` },
              ]}
              data={this.props.data.allGatsbyFeaturesSpecsCsv.edges}
            />
            <FeaturesFooter />

          <FooterLinks />
        </Container>
      </Layout>
    )
  }
}

export default FeaturesPage

export const pageQuery = graphql`
  query {
    allGatsbyFeaturesSpecsCsv {
      edges {
        node {
          Category
          Gatsby
          Jamstack
          Cms
          Description
        }
      }
    }
  }
`
<commit_msg>fix(www): Add slashes to links in features overview (#21318)<commit_after>/** @jsx jsx */
import { jsx } from "theme-ui"
import { Component } from "react"
import { graphql } from "gatsby"
import { Helmet } from "react-helmet"

import Button from "../components/button"
import Layout from "../components/layout"
import Container from "../components/container"
import FooterLinks from "../components/shared/footer-links"
import LegendTable from "../components/features/legend-table"
import FeaturesFooter from "../components/features/features-footer"
import SimpleEvaluationTable from "../components/features/simple-evaluation-table"

const FeaturesHeader = () => (

      Features Overview

      There are many ways to build a website. If you’re considering Gatsby, you
      may also be looking at some alternatives:

        JAMstack frameworks such as
        {` `}

          Jekyll

        {`, `}

          Next.js

        , and
        {` `}

          Nuxt.js

        {` `}
        let you put text or markdown in a specific directory such as
        pages/ in a version-controlled codebase. They then build a
        specific kind of site, usually a blog, as HTML files from the content
        you’ve added. These files can be cached and served from a CDN.

Coming from the JAMstack world?

        <Button to="/features/jamstack/" secondary>
          Compare Gatsby vs JAMstack
        </Button>

        Traditional content management systems (CMSs) like
        {` `}

          WordPress

        {` `}
        and
        {` `}

          Drupal

        {` `}
        give you an online text editor to create content. You customize the look
        and feel by choosing themes and plugins or by writing custom PHP or
        JavaScript code. Content is saved in a database, which is retrieved and
        sent to users when they visit the website. Depending on your
        requirements you can self-host your website or use an official hosting
        provider.

Coming from the CMS world?

        <Button to="/features/cms/" secondary>
          Compare Gatsby vs CMS
        </Button>

      The chart below details Gatsby’s capabilities in comparison with a
      representative from each category. Click on any row to see a more detailed
      explanation on that feature and our rating for each system.

    <h6
      id="legend"
      sx={{
        fontWeight: `body`,
        letterSpacing: `tracked`,
        textTransform: `uppercase`,
      }}
    >
      Legend

    <LegendTable />

)

class FeaturesPage extends Component {
  render() {
    return (
      <Layout location={this.props.location} enableScrollSync={true}>
        <Helmet>
          <title>Features</title>
          <meta
            name="description"
            content="Learn how specific features like performance and support for modern technologies make Gatsby worth using."
          />
        </Helmet>
        <Container>
          <main id={`reach-skip-nav`}>
            <FeaturesHeader />
            <SimpleEvaluationTable
              title="Feature Comparison"
              headers={[
                { display: `Category`, nodeFieldProperty: `Category` },
                { display: `Gatsby`, nodeFieldProperty: `Gatsby` },
                {
                  display: `JAMstack frameworks`,
                  nodeFieldProperty: `Jamstack`,
                },
                { display: `Traditional CMS`, nodeFieldProperty: `Cms` },
              ]}
              data={this.props.data.allGatsbyFeaturesSpecsCsv.edges}
            />
            <FeaturesFooter />

          <FooterLinks />
        </Container>
      </Layout>
    )
  }
}

export default FeaturesPage

export const pageQuery = graphql`
  query {
    allGatsbyFeaturesSpecsCsv {
      edges {
        node {
          Category
          Gatsby
          Jamstack
          Cms
          Description
        }
      }
    }
  }
`
<|endoftext|>


<commit_before>Elm.Native.Slider = {};
Elm.Native.Slider.make = function(elm) {

    elm.Native = elm.Native || {};
    elm.Native.Slider = elm.Native.Slider || {};
    if (elm.Native.Slider.values) return elm.Native.Slider.values;

    var newNode = ElmRuntime.use(ElmRuntime.Render.Utils).newElement;
    var newElement = Elm.Graphics.Element.make(elm).newElement;

    function renderSlider(model) {
        var node = newNode('input');
        node.type = 'range';

        node.min = model.styling.min;
        node.max = model.styling.max;
        node.step = model.styling.step;
        node.value = model.styling.value;

        if (!model.styling.horizontal) {
            node.orient = "vertical"; // FF
            node.style.webkitAppearance = "slider-vertical"; // webkit
            node.style.writingMode = "bt-lr"; // ie
        }

        if (model.styling.disabled) {
            node.disabled = true;
        }

        node.style.display = 'block';
        node.style.pointerEvents = 'auto';
        node.elm_handler = model.handler;
        node.addEventListener('input', notifySlider);
        node.addEventListener('change', notifySlider);
        function notifySlider() {
            node.elm_handler(node.value)();
        }
        return node;
    }

    function updateSlider(node, oldModel, newModel) {
        if (newModel.styling.disabled) {
            node.disabled = true;
        } else {
            node.disabled = false;
        }
        node.elm_handler = newModel.handler;
        node.min = newModel.styling.min;
        node.max = newModel.styling.max;
        node.step = newModel.styling.step;
        node.value = newModel.styling.value;
    }

    function slider(handler, styling) {
        var width = styling.length;
        var height = 24;
        if (!styling.horizontal) {
            var temp = width;
            width = height;
            height = temp;
        }
        return A3(newElement, width, height, {
            ctor: 'Custom',
            type: 'Slider',
            render: renderSlider,
            update: updateSlider,
            model: { handler:handler, styling:styling }
        });
    }

    return elm.Native.Slider.values = {
        slider:F3(slider)
    };
}
<commit_msg>Fix imports for Slider implementation<commit_after>Elm.Native.Slider = {};
Elm.Native.Slider.make = function(localRuntime) {

    localRuntime.Native = localRuntime.Native || {};
    localRuntime.Native.Slider = localRuntime.Native.Slider || {};
    if (localRuntime.Native.Slider.values) {
        return localRuntime.Native.Slider.values;
    }

    var Element = Elm.Graphics.Element.make(localRuntime);
    var NativeElement = Elm.Native.Graphics.Element.make(localRuntime);

    function renderSlider(model) {
        var node = NativeElement.createNode('input');
        node.type = 'range';

        node.min = model.styling.min;
        node.max = model.styling.max;
        node.step = model.styling.step;
        node.value = model.styling.value;

        if (!model.styling.horizontal) {
            node.orient = "vertical"; // FF
            node.style.webkitAppearance = "slider-vertical"; // webkit
            node.style.writingMode = "bt-lr"; // ie
        }

        if (model.styling.disabled) {
            node.disabled = true;
        }

        node.style.display = 'block';
        node.style.pointerEvents = 'auto';
        node.elm_handler = model.handler;
        node.addEventListener('input', notifySlider);
        node.addEventListener('change', notifySlider);
        function notifySlider() {
            node.elm_handler(node.value)();
        }
        return node;
    }

    function updateSlider(node, oldModel, newModel) {
        if (newModel.styling.disabled) {
            node.disabled = true;
        } else {
            node.disabled = false;
        }
        node.elm_handler = newModel.handler;
        node.min = newModel.styling.min;
        node.max = newModel.styling.max;
        node.step = newModel.styling.step;
        node.value = newModel.styling.value;
    }

    function slider(handler, styling) {
        var width = styling.length;
        var height = 24;
        if (!styling.horizontal) {
            var temp = width;
            width = height;
            height = temp;
        }
        return A3(Element.newElement, width, height, {
            ctor: 'Custom',
            type: 'Slider',
            render: renderSlider,
            update: updateSlider,
            model: { handler:handler, styling:styling }
        });
    }

    return localRuntime.Native.Slider.values = {
        slider: F2(slider)
    };
}
<|endoftext|>


<commit_before><commit_msg>Add auto-track-event to analytics bundle<commit_after><|endoftext|>


<commit_before>import * as CONST from '../constants/actionTypes';

export function changeTurn(turn) {
  return { type: CONST.CHANGE_TURN, turn };
}

export function holdPiece(piece) {
  return { type: CONST.HOLD_PIECE, piece };
}

export function releasePiece(piece) {
  return { type: CONST.RELEASE_PIECE, piece };
}

export function enhanceMovablePoint(board, piece) {
  return { type: CONST.ENHANCE_MOVABLE_POINT, board, piece };
}

export function movePiece(board, piece) {
  return { type: CONST.MOVE_PIECE, board, piece };
}

export function addBlackPieceStand(piece) {
  return { type: CONST.ADD_BLACK_PIECE_STAND, piece };
}

export function addWhitePieceStand(piece) {
  return { type: CONST.ADD_WHITE_PIECE_STAND, piece };
}

export function showPromoteModal() {
  return { type: CONST.SHOW_PROMOTE_MODAL };
}

export function hidePromoteModal() {
  return { type: CONST.HIDE_PROMOTE_MODAL };
}

export function promotePiece(piece) {
  return { type: CONST.PROMOTE_PIECE, piece };
}
<commit_msg>`showPromoteModal` action receives piece for promoting the piece.<commit_after>import * as CONST from '../constants/actionTypes';

export function changeTurn(turn) {
  return { type: CONST.CHANGE_TURN, turn };
}

export function holdPiece(piece) {
  return { type: CONST.HOLD_PIECE, piece };
}

export function releasePiece(piece) {
  return { type: CONST.RELEASE_PIECE, piece };
}

export function enhanceMovablePoint(board, piece) {
  return { type: CONST.ENHANCE_MOVABLE_POINT, board, piece };
}

export function movePiece(board, piece) {
  return { type: CONST.MOVE_PIECE, board, piece };
}

export function addBlackPieceStand(piece) {
  return { type: CONST.ADD_BLACK_PIECE_STAND, piece };
}

export function addWhitePieceStand(piece) {
  return { type: CONST.ADD_WHITE_PIECE_STAND, piece };
}

export function showPromoteModal(piece) {
  return { type: CONST.SHOW_PROMOTE_MODAL, piece };
}

export function hidePromoteModal() {
  return { type: CONST.HIDE_PROMOTE_MODAL };
}

export function promotePiece(piece) {
  return { type: CONST.PROMOTE_PIECE, piece };
}
<|endoftext|>


<commit_before>// Given a set of geographies and a dataset,
// draws a D3 choropleth map

// the template including this should set the following vars:
// var geodata = {{ map_data }},
//     tabledata = {{ table_data }},
//     percentify = '{{ percentify }}',
//     parent_fips_code = '{{ parent_fips_code }}';
//
// var parent = {{ parent_shape }},
//     children = {{ child_shapes }};

var mapDiv = d3.select("#data-map")
    .attr("class", "svg-map");

var width = mapDiv[0][0].offsetWidth;
var height = width*.67;

mapDiv.style("height", height);

var svgMap = mapDiv.append("svg")
    .attr("width", width)
    .attr("height", height)
    .style("float", "left")
    .style("position", "relative");

var label = mapDiv.append("div")
    .attr("class", "label")
    .style("opacity", 1e-6);

var labelTitle = "";

var columns = d3.entries(tabledata['columns']);

// add dropdown
d3.select("#map-select").append("select")
    .attr("id", "map-select-select")
    .on("change", changeMap)
.selectAll("option").data(columns).enter().append("option")
    .attr("value", function(d){ return d['key']; })
    .attr("disabled", function(d){ return (d['key'].indexOf('.') >= 0) ? true : null; })
    .text(function(d){
            var indents = new Array(d['value']['indent']);
            return indents.join("\xA0\xA0") + d['value']['name'];
        })

// make the map, defaulting to first column of data
makeMap(geodata[columns[0]['key']]);
window.labelTitle = columns[0]['value']['full_name'];

// rebuild map with new data on select menu change
function changeMap() {
    var dataIndex = this.options[this.selectedIndex].value;
    window.labelTitle = tabledata['columns'][dataIndex]['full_name'];
    makeMap(geodata[dataIndex]);
}

function makeMap(geodata) {
    if (!!percentify) {
        var values = d3.values(geodata).map(function(d) {return d['percentage'];});
    } else {
        var values = d3.values(geodata).map(function(d) {return d['value'];});
    }
    var quantize = d3.scale.quantile()
        .domain([d3.min(values), d3.max(values)])
        .range(d3.range(5).map(function(i) { return "q" + i; }));

    var labelData = quantize.quantiles().slice(0);
    labelData.unshift(d3.min(values));
    labelData.push(d3.max(values));
    var legendLabels = d3.select("#map-legend")
        .selectAll("span")
        .data(labelData)
        .text(function(d){
            if (typeof(d) != 'undefined') {
                if (!!percentify) {
                    return roundNumber(d) + '%'
                } else {
                    return numberWithCommas(d)
                }
            }
        });

    // zoom to the parent
    var projection = d3.geo.albersUsa().precision(.3);

    if (!!parent_fips_code && parent_fips_code != '02' && parent_fips_code != '15') {
        projection = d3.geo.mercator().precision(1000);
    }

    var path = d3.geo.path()
        .projection(projection);

    if (!!parent) {
        // zoom to bounding box of parent
        projection
            .scale(1)
            .translate([0, 0]);

        var b = path.bounds(parent),
            s = .95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height),
            t = [(width - s * (b[1][0] + b[0][0])) / 2, (height - s * (b[1][1] + b[0][1])) / 2];

        projection
            .scale(s)
            .translate(t);
    } else {
        // standard projection to show whole U.S.
        projection
            .scale(1000)
            .translate([width / 2, height / 2]);
    }

    // outline the parent geography
    svgMap.append("path")
        .datum(parent)
        .attr("d", path)
        .attr("class", "outline");

    // add the quantiled child geographies
    svgMap.append("g")
            .attr("class", "geographies")
        .selectAll("path")
            .data(children)
        .enter().append("path")
            .attr("d", path)
            .attr("id", function(d) { return "poly-" + d['id']; })
            .attr("class", function(d) {
                if (!!geodata[d['id']]) {
                    if (!!percentify) {
                        return quantize(geodata[d['id']]['percentage']);
                    } else {
                        return quantize(geodata[d['id']]['value']);
                    }
                }
            })
            .on("mouseover", function(d) { return mouseover(d) })
            .on("mousemove", mousemove)
            .on("mouseout", mouseout);

    function mouseover(d) {
        if (!!geodata[d['id']]) {
            var centroid = path.centroid(d),
                x = centroid[0],
                y = centroid[1];

            label
                .html(makeLabel(d))
                .transition()
                .duration(200)
                .style("width", "200px")
                .style("opacity", 1);
                //.style("left", x + "px")
                //.style("top", y + "px");
        }
    }

    function mousemove() {
        label
            .style("left", (d3.mouse(this)[0] - 100) + "px")
            .style("bottom", height-(d3.mouse(this)[1] - 12) + "px");
    }

    function mouseout() {
        label.transition()
            .duration(200)
            .style("opacity", 1e-6);
    }

    function makeLabel(d) {
        if (!!geodata[d['id']]) {
            var label = "" + labelTitle + "";
            label += "" + geodata[d['id']]['name'] + "";
            if (!!geodata[d['id']]['percentage']) {
                label += "" + geodata[d['id']]['percentage'] + "%";
                if (!!geodata[d['id']]['value']) {
                    label += " (" + numberWithCommas(geodata[d['id']]['value']) + ")";
                }
                label += "";
            }
            else if (!!geodata[d['id']]['value']) {
                label += "" + numberWithCommas(geodata[d['id']]['value']) + "";
            }
        }
        return label
    }
    //svg.selectAll(".label")
        //    .data(geographies.features)
        //.enter().append("text")
        //    .attr("id", function(d) { return "label-" + d['id']; })
        //    .attr("class", "label")
        //    .attr("transform", function(d) { return "translate(" + path.centroid(d) + ")"; })
        //    .text(function(d) { return d['properties']['name'] + geodata[d['id']]; });
}<commit_msg>fix mouseover label with floated svg<commit_after>// Given a set of geographies and a dataset,
// draws a D3 choropleth map

// the template including this should set the following vars:
// var geodata = {{ map_data }},
//     tabledata = {{ table_data }},
//     percentify = '{{ percentify }}',
//     parent_fips_code = '{{ parent_fips_code }}';
//
// var parent = {{ parent_shape }},
//     children = {{ child_shapes }};

var mapDiv = d3.select("#data-map")
    .attr("class", "svg-map");

var width = mapDiv[0][0].offsetWidth;
var height = width*.67;

var svgMap = mapDiv.append("svg")
    .attr("width", width)
    .attr("height", height)
    .style("float", "left")
    .style("position", "relative");

var label = mapDiv.append("div")
    .attr("class", "label")
    .style("opacity", 1e-6);

var labelTitle = "";

var columns = d3.entries(tabledata['columns']);

// add dropdown
d3.select("#map-select").append("select")
    .attr("id", "map-select-select")
    .on("change", changeMap)
.selectAll("option").data(columns).enter().append("option")
    .attr("value", function(d){ return d['key']; })
    .attr("disabled", function(d){ return (d['key'].indexOf('.') >= 0) ? true : null; })
    .text(function(d){
            var indents = new Array(d['value']['indent']);
            return indents.join("\xA0\xA0") + d['value']['name'];
        })

// make the map, defaulting to first column of data
makeMap(geodata[columns[0]['key']]);
window.labelTitle = columns[0]['value']['full_name'];

// rebuild map with new data on select menu change
function changeMap() {
    var dataIndex = this.options[this.selectedIndex].value;
    window.labelTitle = tabledata['columns'][dataIndex]['full_name'];
    makeMap(geodata[dataIndex]);
}

function makeMap(geodata) {
    if (!!percentify) {
        var values = d3.values(geodata).map(function(d) {return d['percentage'];});
    } else {
        var values = d3.values(geodata).map(function(d) {return d['value'];});
    }
    var quantize = d3.scale.quantile()
        .domain([d3.min(values), d3.max(values)])
        .range(d3.range(5).map(function(i) { return "q" + i; }));

    var labelData = quantize.quantiles().slice(0);
    labelData.unshift(d3.min(values));
    labelData.push(d3.max(values));
    var legendLabels = d3.select("#map-legend")
        .selectAll("span")
        .data(labelData)
        .text(function(d){
            if (typeof(d) != 'undefined') {
                if (!!percentify) {
                    return roundNumber(d) + '%'
                } else {
                    return numberWithCommas(d)
                }
            }
        });

    // zoom to the parent
    var projection = d3.geo.albersUsa().precision(.3);

    if (!!parent_fips_code && parent_fips_code != '02' && parent_fips_code != '15') {
        projection = d3.geo.mercator().precision(1000);
    }

    var path = d3.geo.path()
        .projection(projection);

    if (!!parent) {
        // zoom to bounding box of parent
        projection
            .scale(1)
            .translate([0, 0]);

        var b = path.bounds(parent),
            s = .95 / Math.max((b[1][0] - b[0][0]) / width, (b[1][1] - b[0][1]) / height),
            t = [(width - s * (b[1][0] + b[0][0])) / 2, (height - s * (b[1][1] + b[0][1])) / 2];

        projection
            .scale(s)
            .translate(t);
    } else {
        // standard projection to show whole U.S.
        projection
            .scale(1000)
            .translate([width / 2, height / 2]);
    }

    // outline the parent geography
    svgMap.append("path")
        .datum(parent)
        .attr("d", path)
        .attr("class", "outline");

    // add the quantiled child geographies
    svgMap.append("g")
            .attr("class", "geographies")
        .selectAll("path")
            .data(children)
        .enter().append("path")
            .attr("d", path)
            .attr("id", function(d) { return "poly-" + d['id']; })
            .attr("class", function(d) {
                if (!!geodata[d['id']]) {
                    if (!!percentify) {
                        return quantize(geodata[d['id']]['percentage']);
                    } else {
                        return quantize(geodata[d['id']]['value']);
                    }
                }
            })
            .on("mouseover", function(d) { return mouseover(d) })
            .on("mousemove", mousemove)
            .on("mouseout", mouseout);

    function mouseover(d) {
        if (!!geodata[d['id']]) {
            var centroid = path.centroid(d),
                x = centroid[0],
                y = centroid[1];

            label
                .html(makeLabel(d))
                .transition()
                .duration(200)
                .style("width", "200px")
                .style("opacity", 1);
                //.style("left", x + "px")
                //.style("top", y + "px");
        }
    }

    function mousemove() {
        label
            .style("left", (d3.mouse(this)[0] - 100) + "px")
            .style("top", (d3.mouse(this)[1] - 65) + "px");
    }

    function mouseout() {
        label.transition()
            .duration(200)
            .style("opacity", 1e-6);
    }

    function makeLabel(d) {
        if (!!geodata[d['id']]) {
            var label = "" + labelTitle + "";
            label += "" + geodata[d['id']]['name'] + "";
            if (!!geodata[d['id']]['percentage']) {
                label += "" + geodata[d['id']]['percentage'] + "%";
                if (!!geodata[d['id']]['value']) {
                    label += " (" + numberWithCommas(geodata[d['id']]['value']) + ")";
                }
                label += "";
            }
            else if (!!geodata[d['id']]['value']) {
                label += "" + numberWithCommas(geodata[d['id']]['value']) + "";
            }
        }
        return label
    }
    //svg.selectAll(".label")
        //    .data(geographies.features)
        //.enter().append("text")
        //    .attr("id", function(d) { return "label-" + d['id']; })
        //    .attr("class", "label")
        //    .attr("transform", function(d) { return "translate(" + path.centroid(d) + ")"; })
        //    .text(function(d) { return d['properties']['name'] + geodata[d['id']]; });
}<|endoftext|>


<commit_before>import StringManipulator from '../stringManipulation';

import exceptions from '../exceptions';

import constants from '../constants';

import DateTime from '../generated-data/dateTime';

const alasql = require('alasql');

const stringManipulator = new StringManipulator();

const dateTime = new DateTime();

function getFormattedCurrentDateTime() {
  return stringManipulator.stringConcat('"', dateTime.getCurrentDateTime(), '"');
}

export default function sqlCommands() {
  this.linkDatabaseToIndexedDB = function linkDatabaseToIndexedDB() {
    const databaseRequestStr = "CREATE INDEXEDDB DATABASE IF NOT EXISTS lectureNote; " +
                               "ATTACH INDEXEDDB DATABASE lectureNote; " +
                               "USE lectureNote;";
    alasql.promise([databaseRequestStr])
      .then().catch(sqlError => sqlError);
  };

  this.getFullTableData = function getFullTableData(tableName) {
    // ensure working in browsers that support Promise
    if (typeof Promise === 'function') {
      return new Promise((resolve, reject) => {
        alasql.promise([stringManipulator.stringConcat('SELECT * FROM ', tableName)])
          .then(data => resolve(data))
          .catch(sqlError => sqlError);
      });
    } else {
      throw new exceptions.PromiseFunctionNotDefined();
    }
  };

  this.createTable = function createTable(sqlStatement) {
    alasql.promise([stringManipulator.stringConcat('CREATE TABLE IF NOT EXISTS ', sqlStatement)])
      .then().catch(sqlError => sqlError);
  };
  };

  this.deleteTable = function deleteTable(tableName) {
    alasql(stringManipulator.stringConcat('DROP TABLE ', tableName), callback());
  };

  this.cleanTable = function cleanTable(tableName) {
    alasql(stringManipulator.stringConcat('DELETE * FROM ', tableName), callback());
  };
}
<commit_msg>Add insertContent implementation to sqlCommands.js<commit_after>import StringManipulator from '../stringManipulation';

import exceptions from '../exceptions';

import constants from '../constants';

import DateTime from '../generated-data/dateTime';

const alasql = require('alasql');

const stringManipulator = new StringManipulator();

const dateTime = new DateTime();

function getFormattedCurrentDateTime() {
  return stringManipulator.stringConcat('"', dateTime.getCurrentDateTime(), '"');
}

export default function sqlCommands() {
  this.linkDatabaseToIndexedDB = function linkDatabaseToIndexedDB() {
    const databaseRequestStr = "CREATE INDEXEDDB DATABASE IF NOT EXISTS lectureNote; " +
                               "ATTACH INDEXEDDB DATABASE lectureNote; " +
                               "USE lectureNote;";
    alasql.promise([databaseRequestStr])
      .then().catch(sqlError => sqlError);
  };

  this.getFullTableData = function getFullTableData(tableName) {
    // ensure working in browsers that support Promise
    if (typeof Promise === 'function') {
      return new Promise((resolve, reject) => {
        alasql.promise([stringManipulator.stringConcat('SELECT * FROM ', tableName)])
          .then(data => resolve(data))
          .catch(sqlError => sqlError);
      });
    } else {
      throw new exceptions.PromiseFunctionNotDefined();
    }
  };

  this.createTable = function createTable(sqlStatement) {
    alasql.promise([stringManipulator.stringConcat('CREATE TABLE IF NOT EXISTS ', sqlStatement)])
      .then().catch(sqlError => sqlError);
  };

  this.insertContent = function insertContent(tableName, ...content) {
    if (typeof Promise === 'function') {
      return new Promise((resolve, reject) => {
        // concatenate all the values to insert together
        const valuesToInsertIntoTable = content.join(', ');
        alasql.promise([stringManipulator.stringConcat('INSERT INTO ', tableName,
                                                       ' VALUES (', valuesToInsertIntoTable, ')')])
        .then(() => true)
        .catch(sqlError => sqlError);
      });
    } else {
      throw new exceptions.PromiseFunctionNotDefined();
    }
  };
  };

  this.deleteTable = function deleteTable(tableName) {
    alasql(stringManipulator.stringConcat('DROP TABLE ', tableName), callback());
  };

  this.cleanTable = function cleanTable(tableName) {
    alasql(stringManipulator.stringConcat('DELETE * FROM ', tableName), callback());
  };
}
<|endoftext|>