<commit_before>import React from 'react';
import purebem from 'purebem';
import moment from 'moment';

import isNull from 'lodash.isnull';
import filter from 'lodash.filter';
import pick from 'lodash.pick';
import omitBy from 'lodash.omitby';
import debounce from 'lodash.debounce';
import cloneDeep from 'lodash.clonedeep';
import assign from 'lodash.assign';

import firebaseRef from 'app/firebaseRef';

import {
    isDate,
    isEmpty
} from 'app/utils';

import ButtonSwitch from 'app/components/ButtonSwitch';
import ClickOutside from 'app/components/ClickOutside';
import ContentBox from 'app/components/ContentBox';
import InputField from 'app/components/InputField';
import Modal from 'app/components/Modal';
import ProjectMap from 'app/components/ProjectMap';
import ProjectSites from 'app/components/ProjectSites';
import ProjectSuccess from 'app/components/ProjectSuccess';

const ERROR_TITLE = 'Please fill in a title.';
const ERROR_DATE = 'A valid date follows the pattern YYYYMMDD.';
const ERROR_END = 'A project must start before it can end, silly.';

const initialState = {
    end: {},
    errors: {},
    isLocked: true,
    isPublic: true,
    isSubmitting: false,
    projectId: '',
    sites: [],
    showSuccess: false,
    start: {},
    title: {},
    toggleMapLock: ['Unlocked', 'Locked'],
    togglePrivacy: ['Private', 'Public']
};

const delayAction = debounce((action) => action(), 300);

const form = purebem.of('form');
const block = purebem.of('create-view');

const CreateView = React.createClass({

    componentWillMount() {
        this.setState(cloneDeep(initialState));
        const authData = firebaseRef.getAuth();
        const provider = authData[authData.provider];

        this.user = {
            id: authData.uid,
            name: provider.displayName
        };
    },

    hasErrors(errors) {
        errors = errors || this.state.errors;
        return Object.keys(omitBy(errors, isNull)).length;
    },

    getSites() {
        return filter(this.state.sites, (site) => site.name).map(site => pick(site, ['latlng', 'name']));
    },

    validateAll() {
        let errors = {};

        ['title', 'start', 'end'].map(name => {
            assign(errors, this.validateField(name, this.state[name].value));
        });

        return errors;
    },

    validateField(name, value) {
        const error = {};
        const { start, end } = this.state;

        switch (name) {
            case 'title':
                error[name] = isEmpty(value) ? ERROR_TITLE : null;
                break;
            default:
                error[name] = !isDate(value) ? ERROR_DATE : null;

                if (isDate(start.value) && isDate(end.value)) {
                    error.order = start.value >= end.value ? ERROR_END : null;
                }
        };

        return error;
    },

    handleChange(evt) {
        const { name, value } = evt.target;
        const { errors } = cloneDeep(this.state);
        this.setState({ [name]: { value } });

        delayAction(() => {
            this.setState({ errors: assign(errors, this.validateField(name, value)) });
        });
    },

    handleReset() {
        this.form.reset();
        this.handleSitesClear();
        this.setState(cloneDeep(initialState));
    },

    handleSubmit(evt) {
        evt.preventDefault();
        const errors = this.validateAll();

        if (this.hasErrors(errors)) {
            this.setState({ errors });
            return;
        }

        const { title, start, end, sites, isPublic } = this.state;
        const format = 'YYYYMMDD';

        this.project = {
            dateCreated: moment().unix(),
            dateEnd: moment(end.value, format).unix(),
            dateStart: moment(start.value, format).unix(),
            isPublic,
            ownerId: this.user.id,
            ownerName: this.user.name,
            title: title.value
        };

        // Add project
        this.projectRef = firebaseRef.child('projects').push(this.project, this.onComplete);
    },

    onComplete(error) {
        if (error) {
            console.log('Oh snap! An error occurred…');
            return;
        }

        // Get project ID
        const pid = this.projectRef.key();

        // Add sites
        this.getSites().map((site, index) => {
            const newSite = assign(cloneDeep(site), { ownerId: this.user.id, projectId: pid });
            const siteRef = firebaseRef.child('sites').push(newSite);
            const siteId = siteRef.key();

            // Update project/sites
            this.projectRef.child('sites').push(assign(site, { siteId }));
        });

        // Update user with ownership
        firebaseRef.child(`users/${this.user.id}/owner/${pid}`).set(true);

        // Set membership in memberships/project
        firebaseRef.child(`members/${pid}/active/${this.user.id}`).set(true);

        // Display Success state
        this.setState({ showSuccess: true, projectId: pid });
    },

    handleSwitch(name) {
        switch (name) {
            case 'privacy':
                this.setState({ isPublic: !this.state.isPublic });
                break;
            case 'maplock':
                this.setState({ isLocked: !this.state.isLocked });
                break;
        };
    },

    handleSiteAdd(marker) {
        const sites = [ ...this.state.sites, marker ];
        this.setState({ sites });
    },

    handleSiteChange(index) {
        return evt => {
            const { sites } = this.state;
            sites[index].name = evt.target.value;
            this.setState({ sites });
        }
    },

    handleSiteRemove(index) {
        let { sites } = this.state;
        sites[index].setMap(null);
        sites.splice(index, 1);
        this.setState({ sites });
    },

    handleSitesClear() {
        const sites = filter(this.state.sites, (item) => item.setMap(null));
        this.setState({ sites });
    },

    handleModalClose() {
        this.handleReset();
        window.scrollTo(0,0);
    },

    renderPrivacyInfo() {
        const info = this.state.isPublic
            ? (
Public projects aim at collaboration. Users can find your project and may request to join.
)
            : (
Private projects aim at privacy. Other users cannot find nor participate in private projects.
);

        return (

{ info }

        );
    },

    renderError() {
        if (!this.hasErrors()) {
            return null;
        }

        return (

Please correct any errors to proceed.

        );
    },

    renderForm() {
        const buttonClass = purebem.many(block('button', ['submit']), 'button-primary');
        const { title, start, end, errors, isSubmitting } = this.state;
        const hasErrors = this.hasErrors();

        return (
            <form className={ block('form') } onSubmit={ this.handleSubmit } ref={ (form) => this.form = form }>

                    Project title
                    <InputField
                        name="title"
                        error={ errors.title }
                        onBlur={ this.handleBlur }
                        onChange={ this.handleChange }
                        value={ title.value } />

                    Start date
                    <InputField
                        name="start"
                        error={ errors.start }
                        onBlur={ this.handleBlur }
                        onChange={ this.handleChange }
                        placeholder="yyyymmdd"
                        value={ start.value } />

                    End date
                    <InputField
                        name="end"
                        error={ errors.end || errors.order }
                        onBlur={ this.handleBlur }
                        onChange={ this.handleChange }
                        placeholder="yyyymmdd"
                        value={ end.value } />

                <ProjectSites
                    sites={ this.state.sites }
                    onChange={ (i) => this.handleSiteChange(i) }
                    onRemove={ (i) => this.handleSiteRemove(i) } />
                <ButtonSwitch
                    className={ block('switch', ['maplock']) }
                    isActive={ this.state.isLocked }
                    onClick={ () => this.handleSwitch('maplock') }
                    options={ this.state.toggleMapLock } />

Unlock the map to locate and mark your ringing sites. Geolocation data may be used for statistics.

                <ButtonSwitch
                    className={ block('switch', ['privacy']) }
                    isActive={ this.state.isPublic }
                    onClick={ () => this.handleSwitch('privacy') }
                    options={ this.state.togglePrivacy } />
                { this.renderPrivacyInfo() }
                { this.renderError() }

                    <button type="submit" className={ buttonClass } disabled={ hasErrors || isSubmitting }>Create Project</button>
                    <button type="button" className={ block('button', ['reset']) } onClick={ this.handleReset }>Reset</button>

            </form>
        );
    },

    renderSuccess() {
        if (!this.state.showSuccess) {
            return null;
        }

        return (
            <Modal>
                <ClickOutside onClick={ this.handleModalClose }>
                    <ProjectSuccess
                        projectId={ this.state.projectId }
                        onClose={ this.handleModalClose } />
                </ClickOutside>
            </Modal>
        );
    },

    render() {
        return (

                    <ProjectMap
                        onClick={ this.handleSiteAdd }
                        sites={ this.state.sites }
                        unlocked={ !this.state.isLocked } />
                    <ContentBox>
                        { this.renderForm() }
                    </ContentBox>

                { this.renderSuccess() }

        );
    }

});

export default CreateView;
<commit_msg>rethinking how data should be stored<commit_after>import React from 'react';
import purebem from 'purebem';
import moment from 'moment';

import isNull from 'lodash.isnull';
import filter from 'lodash.filter';
import pick from 'lodash.pick';
import omitBy from 'lodash.omitby';
import debounce from 'lodash.debounce';
import cloneDeep from 'lodash.clonedeep';
import assign from 'lodash.assign';

import firebaseRef from 'app/firebaseRef';

import {
    isDate,
    isEmpty
} from 'app/utils';

import ButtonSwitch from 'app/components/ButtonSwitch';
import ClickOutside from 'app/components/ClickOutside';
import ContentBox from 'app/components/ContentBox';
import InputField from 'app/components/InputField';
import Modal from 'app/components/Modal';
import ProjectMap from 'app/components/ProjectMap';
import ProjectSites from 'app/components/ProjectSites';
import ProjectSuccess from 'app/components/ProjectSuccess';

const ERROR_TITLE = 'Please fill in a title.';
const ERROR_DATE = 'A valid date follows the pattern YYYYMMDD.';
const ERROR_END = 'A project must start before it can end, silly.';

const initialState = {
    end: {},
    errors: {},
    isLocked: true,
    isPublic: true,
    isSubmitting: false,
    projectId: '',
    sites: [],
    showSuccess: false,
    start: {},
    title: {},
    toggleMapLock: ['Unlocked', 'Locked'],
    togglePrivacy: ['Private', 'Public']
};

const delayAction = debounce((action) => action(), 300);

const form = purebem.of('form');
const block = purebem.of('create-view');

const CreateView = React.createClass({

    componentWillMount() {
        this.setState(cloneDeep(initialState));
        const authData = firebaseRef.getAuth();
        const provider = authData[authData.provider];

        this.user = {
            id: authData.uid,
            name: provider.displayName
        };
    },

    hasErrors(errors) {
        errors = errors || this.state.errors;
        return Object.keys(omitBy(errors, isNull)).length;
    },

    getSites() {
        return filter(this.state.sites, (site) => site.name).map(site => pick(site, ['latlng', 'name']));
    },

    validateAll() {
        let errors = {};

        ['title', 'start', 'end'].map(name => {
            assign(errors, this.validateField(name, this.state[name].value));
        });

        return errors;
    },

    validateField(name, value) {
        const error = {};
        const { start, end } = this.state;

        switch (name) {
            case 'title':
                error[name] = isEmpty(value) ? ERROR_TITLE : null;
                break;
            default:
                error[name] = !isDate(value) ? ERROR_DATE : null;

                if (isDate(start.value) && isDate(end.value)) {
                    error.order = start.value >= end.value ? ERROR_END : null;
                }
        };

        return error;
    },

    handleChange(evt) {
        const { name, value } = evt.target;
        const { errors } = cloneDeep(this.state);
        this.setState({ [name]: { value } });

        delayAction(() => {
            this.setState({ errors: assign(errors, this.validateField(name, value)) });
        });
    },

    handleReset() {
        this.form.reset();
        this.handleSitesClear();
        this.setState(cloneDeep(initialState));
    },

    handleSubmit(evt) {
        evt.preventDefault();
        const errors = this.validateAll();

        if (this.hasErrors(errors)) {
            this.setState({ errors });
            return;
        }

        const { title, start, end, sites, isPublic } = this.state;
        const format = 'YYYYMMDD';

        this.project = {
            date: {
                end: moment(end.value, format).unix(),
                start: moment(start.value, format).unix()
            },
            isPublic,
            owner: {
                id: this.user.id,
                name: this.user.name
            },
            title: title.value
        };

        // Add project
        this.projectRef = firebaseRef.child('projects').push(this.project, this.onComplete);
    },

    onComplete(error) {
        if (error) {
            console.log('Oh snap! An error occurred…');
            return;
        }

        // Get project ID
        const pid = this.projectRef.key();

        // Add sites
        this.getSites().map((site, index) => {
            // const newSite = assign(cloneDeep(site), { ownerId: this.user.id, projectId: pid });
            // const siteRef = firebaseRef.child('sites').push(newSite);
            // const siteId = siteRef.key();

            // Update project/sites
            this.projectRef.child('sites').push(site);
        });

        // Update user with ownership
        firebaseRef.child(`users/${this.user.id}/owner/`).push({
            id: pid,
            title: this.project.title
        });

        // Set membership in memberships/project
        firebaseRef.child(`members/${pid}/active/`).push({
            id: this.user.id,
            name: this.user.name,
            role: 'owner'
        });

        // Display Success state
        this.setState({ showSuccess: true, projectId: pid });
    },

    handleSwitch(name) {
        switch (name) {
            case 'privacy':
                this.setState({ isPublic: !this.state.isPublic });
                break;
            case 'maplock':
                this.setState({ isLocked: !this.state.isLocked });
                break;
        };
    },

    handleSiteAdd(marker) {
        const sites = [ ...this.state.sites, marker ];
        this.setState({ sites });
    },

    handleSiteChange(index) {
        return evt => {
            const { sites } = this.state;
            sites[index].name = evt.target.value;
            this.setState({ sites });
        }
    },

    handleSiteRemove(index) {
        let { sites } = this.state;
        sites[index].setMap(null);
        sites.splice(index, 1);
        this.setState({ sites });
    },

    handleSitesClear() {
        const sites = filter(this.state.sites, (item) => item.setMap(null));
        this.setState({ sites });
    },

    handleModalClose() {
        this.handleReset();
        window.scrollTo(0,0);
    },

    renderPrivacyInfo() {
        const info = this.state.isPublic
            ? (
Public projects aim at collaboration. Users can find your project and may request to join.
)
            : (
Private projects aim at privacy. Other users cannot find nor participate in private projects.
);

        return (

{ info }

        );
    },

    renderError() {
        if (!this.hasErrors()) {
            return null;
        }

        return (

Please correct any errors to proceed.

        );
    },

    renderForm() {
        const buttonClass = purebem.many(block('button', ['submit']), 'button-primary');
        const { title, start, end, errors, isSubmitting } = this.state;
        const hasErrors = this.hasErrors();

        return (
            <form className={ block('form') } onSubmit={ this.handleSubmit } ref={ (form) => this.form = form }>

                    Project title
                    <InputField
                        name="title"
                        error={ errors.title }
                        onBlur={ this.handleBlur }
                        onChange={ this.handleChange }
                        value={ title.value } />

                    Start date
                    <InputField
                        name="start"
                        error={ errors.start }
                        onBlur={ this.handleBlur }
                        onChange={ this.handleChange }
                        placeholder="yyyymmdd"
                        value={ start.value } />

                    End date
                    <InputField
                        name="end"
                        error={ errors.end || errors.order }
                        onBlur={ this.handleBlur }
                        onChange={ this.handleChange }
                        placeholder="yyyymmdd"
                        value={ end.value } />

                <ProjectSites
                    sites={ this.state.sites }
                    onChange={ (i) => this.handleSiteChange(i) }
                    onRemove={ (i) => this.handleSiteRemove(i) } />
                <ButtonSwitch
                    className={ block('switch', ['maplock']) }
                    isActive={ this.state.isLocked }
                    onClick={ () => this.handleSwitch('maplock') }
                    options={ this.state.toggleMapLock } />

Unlock the map to locate and mark your ringing sites. Geolocation data may be used for statistics.

                <ButtonSwitch
                    className={ block('switch', ['privacy']) }
                    isActive={ this.state.isPublic }
                    onClick={ () => this.handleSwitch('privacy') }
                    options={ this.state.togglePrivacy } />
                { this.renderPrivacyInfo() }
                { this.renderError() }

                    <button type="submit" className={ buttonClass } disabled={ hasErrors || isSubmitting }>Create Project</button>
                    <button type="button" className={ block('button', ['reset']) } onClick={ this.handleReset }>Reset</button>

            </form>
        );
    },

    renderSuccess() {
        if (!this.state.showSuccess) {
            return null;
        }

        return (
            <Modal>
                <ClickOutside onClick={ this.handleModalClose }>
                    <ProjectSuccess
                        projectId={ this.state.projectId }
                        onClose={ this.handleModalClose } />
                </ClickOutside>
            </Modal>
        );
    },

    render() {
        return (

                    <ProjectMap
                        onClick={ this.handleSiteAdd }
                        sites={ this.state.sites }
                        unlocked={ !this.state.isLocked } />
                    <ContentBox>
                        { this.renderForm() }
                    </ContentBox>

                { this.renderSuccess() }

        );
    }

});

export default CreateView;
<|endoftext|>


<commit_before>/* eslint-disable react/forbid-prop-types */
import React from 'react';
import PropTypes from 'prop-types';
import { TouchableOpacity, Text, StyleSheet, View, TouchableOpacityPropTypes } from 'react-native';

import { getAdjustedStyle } from './utilities';
/**
 * Simple component to be used in conjunction with HeaderRow component.
 *
 * Renders a title and if passed, a sorting icon.
 *
 * @prop {String}       title                 Text to display in the cell
 * @prop {String}       columnKey             The key for the column the cell heads.
 * @prop {Func}         onPressAction         Action for dispatching on press
 * @prop {Func}         dispatch              Dispatcher to backing reducer
 * @prop {ReactElement} SortAscComponent      Icon component for ascending sorting
 * @prop {ReactElement} SortDescComponent     Icon component for descending sorting
 * @prop {ReactElement} SortNeutralComponent  Icon component for neutral state, no sort.
 * @prop {Object}       containerStyle        Style object for the wrapping Touchable or View.
 * @prop {Object}       textStyle             Style object for the inner text component.
 */
const HeaderCell = React.memo(
  ({
    title,
    columnKey,
    sortDirection,
    SortAscComponent,
    SortDescComponent,
    SortNeutralComponent,
    onPressAction,
    dispatch,
    sortable,
    containerStyle,
    textStyle,
    width,
    isLastCell,
    ...otherProps
  }) => {
    const onPress = () => {
      dispatch(onPressAction(columnKey));
    };

    const Icon = () => {
      switch (sortDirection) {
        case 'ASC': {
          return SortAscComponent;
        }
        case 'DESC': {
          return SortDescComponent;
        }
        default: {
          return SortNeutralComponent;
        }
      }
    };

    const Container = onPressAction ? TouchableOpacity : View;

    const internalContainerStyle = getAdjustedStyle(containerStyle, width, isLastCell);

    const internalContainerStyle = getAdjustedStyle(containerStyle, width, isLastCell);

    return (
      <Container style={internalContainerStyle} onPress={onPress} {...otherProps}>
        <Text style={textStyle}>{title}</Text>
        {sortable && <Icon />}
      </Container>
    );
  }
);

const defaultStyles = StyleSheet.create({
  containerStyle: {},
  textStyle: {},
});

HeaderCell.propTypes = {
  ...TouchableOpacityPropTypes,
  title: PropTypes.string.isRequired,
  columnKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
  onPressAction: PropTypes.func,
  dispatch: PropTypes.func,
  sortDirection: PropTypes.oneOf(['ASC', 'DESC']),
  SortAscComponent: PropTypes.element,
  SortDescComponent: PropTypes.element,
  SortNeutralComponent: PropTypes.element,
  containerStyle: PropTypes.object,
  textStyle: PropTypes.object,
  width: PropTypes.number,
  sortable: PropTypes.bool,
};

HeaderCell.defaultProps = {
  dispatch: null,
  onPressAction: null,
  sortDirection: null,
  SortAscComponent: null,
  SortDescComponent: null,
  SortNeutralComponent: null,
  containerStyle: defaultStyles.containerStyle,
  textStyle: defaultStyles.textStyle,
  sortable: false,
  width: 0,
};

export default HeaderCell;
<commit_msg>Add fix for rebasing mistake<commit_after>/* eslint-disable react/forbid-prop-types */
import React from 'react';
import PropTypes from 'prop-types';
import { TouchableOpacity, Text, StyleSheet, View, TouchableOpacityPropTypes } from 'react-native';

import { getAdjustedStyle } from './utilities';
/**
 * Simple component to be used in conjunction with HeaderRow component.
 *
 * Renders a title and if passed, a sorting icon.
 *
 * @prop {String}       title                 Text to display in the cell
 * @prop {String}       columnKey             The key for the column the cell heads.
 * @prop {Func}         onPressAction         Action for dispatching on press
 * @prop {Func}         dispatch              Dispatcher to backing reducer
 * @prop {ReactElement} SortAscComponent      Icon component for ascending sorting
 * @prop {ReactElement} SortDescComponent     Icon component for descending sorting
 * @prop {ReactElement} SortNeutralComponent  Icon component for neutral state, no sort.
 * @prop {Object}       containerStyle        Style object for the wrapping Touchable or View.
 * @prop {Object}       textStyle             Style object for the inner text component.
 */
const HeaderCell = React.memo(
  ({
    title,
    columnKey,
    sortDirection,
    SortAscComponent,
    SortDescComponent,
    SortNeutralComponent,
    onPressAction,
    dispatch,
    sortable,
    containerStyle,
    textStyle,
    width,
    isLastCell,
    ...otherProps
  }) => {
    const onPress = () => {
      dispatch(onPressAction(columnKey));
    };

    const Icon = () => {
      switch (sortDirection) {
        case 'ASC': {
          return SortAscComponent;
        }
        case 'DESC': {
          return SortDescComponent;
        }
        default: {
          return SortNeutralComponent;
        }
      }
    };

    const Container = onPressAction ? TouchableOpacity : View;

    const internalContainerStyle = getAdjustedStyle(containerStyle, width, isLastCell);

    return (
      <Container style={internalContainerStyle} onPress={onPress} {...otherProps}>
        <Text style={textStyle}>{title}</Text>
        {sortable && <Icon />}
      </Container>
    );
  }
);

const defaultStyles = StyleSheet.create({
  containerStyle: {},
  textStyle: {},
});

HeaderCell.propTypes = {
  ...TouchableOpacityPropTypes,
  title: PropTypes.string.isRequired,
  columnKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
  onPressAction: PropTypes.func,
  dispatch: PropTypes.func,
  sortDirection: PropTypes.oneOf(['ASC', 'DESC']),
  SortAscComponent: PropTypes.element,
  SortDescComponent: PropTypes.element,
  SortNeutralComponent: PropTypes.element,
  containerStyle: PropTypes.object,
  textStyle: PropTypes.object,
  width: PropTypes.number,
  sortable: PropTypes.bool,
};

HeaderCell.defaultProps = {
  dispatch: null,
  onPressAction: null,
  sortDirection: null,
  SortAscComponent: null,
  SortDescComponent: null,
  SortNeutralComponent: null,
  containerStyle: defaultStyles.containerStyle,
  textStyle: defaultStyles.textStyle,
  sortable: false,
  width: 0,
};

export default HeaderCell;
<|endoftext|>


<commit_before>(function(global) {
  // map tells the System loader where to look for things
  var map = {
    'app':                          'app', // 'dist',
    '@angular':                     'node_modules/@angular',
    'angular2-in-memory-web-api':   'node_modules/angular2-in-memory-web-api',
    'rxjs':                         'node_modules/rxjs',
    'ng2-select/ng2-select':        'node_modules/ng2-select/ng2-select',
    'angular2-modal':               'node_modules/angular2-modal',
    'angular2':                     'node_modules/@angular',
    'ng2-bootstrap':                'node_modules/ng2-bootstrap',
    'showdown':                     'node_modules/showdown',
    'jquery':                       'node_modules/jquery',
    'select2':                      'node_modules/select2',
    "@vaadin/angular2-polymer":     'node_modules/@vaadin/angular2-polymer'
  };
  // packages tells the System loader how to load when no filename and/or no extension
  var packages = {
    'app':                          { main: 'main.js',  defaultExtension: 'js' },
    'rxjs':                         { defaultExtension: 'js' },
    'angular2-in-memory-web-api':   { main: 'index.js', defaultExtension: 'js' },
    'node_modules/ng2-select':      { defaultExtension: 'js' },
    'node_modules/angular2-modal':  { defaultExtension: 'js' },
    'node_modules/ng2-bootstrap':   { defaultExtension: 'js' },
    'node_modules/showdown':        { main: './dist/showdown.js', defaultExtension: 'js' },
    'node_modules/jquery':          { main: './dist/jquery.js', defaultExtension: 'js' },
    'node_modules/select2':         { main: './dist/js/select2.full.js', defaultExtension: 'js' },
    '@vaadin/angular2-polymer':     { main: 'index.js', defaultExtension: 'js' }
  };
  var ngPackageNames = [
    'common',
    'compiler',
    'core',
    'forms',
    'http',
    'platform-browser',
    'platform-browser-dynamic',
    'router',
    'router-deprecated',
    'upgrade',
  ];
  // Individual files (~300 requests):
  function packIndex(pkgName) {
    packages['@angular/'+pkgName] = { main: 'index.js', defaultExtension: 'js' };
  }
  // Bundled (~40 requests):
  function packUmd(pkgName) {
    packages['@angular/'+pkgName] = { main: '/bundles/' + pkgName + '.umd.js', defaultExtension: 'js' };
  }
  // Most environments should use UMD; some (Karma) need the individual index files
  var setPackageConfig = System.packageWithIndex ? packIndex : packUmd;
  // Add package entries for angular packages
  ngPackageNames.forEach(setPackageConfig);
  var config = {
    map: map,
    packages: packages
  };
  System.config(config);
})(this);
<commit_msg>@vaadin/polymer fix<commit_after>(function(global) {
  // map tells the System loader where to look for things
  var map = {
    'app':                          'app', // 'dist',
    '@angular':                     'node_modules/@angular',
    'angular2-in-memory-web-api':   'node_modules/angular2-in-memory-web-api',
    'rxjs':                         'node_modules/rxjs',
    'ng2-select/ng2-select':        'node_modules/ng2-select/ng2-select',
    'angular2-modal':               'node_modules/angular2-modal',
    'angular2':                     'node_modules/@angular',
    'ng2-bootstrap':                'node_modules/ng2-bootstrap',
    'showdown':                     'node_modules/showdown',
    'jquery':                       'node_modules/jquery',
    'select2':                      'node_modules/select2',
    "@vaadin":                      'node_modules/@vaadin'
  };
  // packages tells the System loader how to load when no filename and/or no extension
  var packages = {
    'app':                          { main: 'main.js',  defaultExtension: 'js' },
    'rxjs':                         { defaultExtension: 'js' },
    'angular2-in-memory-web-api':   { main: 'index.js', defaultExtension: 'js' },
    'node_modules/ng2-select':      { defaultExtension: 'js' },
    'node_modules/angular2-modal':  { defaultExtension: 'js' },
    'node_modules/ng2-bootstrap':   { defaultExtension: 'js' },
    'node_modules/showdown':        { main: './dist/showdown.js', defaultExtension: 'js' },
    'node_modules/jquery':          { main: './dist/jquery.js', defaultExtension: 'js' },
    'node_modules/select2':         { main: './dist/js/select2.full.js', defaultExtension: 'js' },
    '@vaadin/angular2-polymer':     { main: 'index.js', defaultExtension: 'js' }
  };
  var ngPackageNames = [
    'common',
    'compiler',
    'core',
    'forms',
    'http',
    'platform-browser',
    'platform-browser-dynamic',
    'router',
    'router-deprecated',
    'upgrade',
  ];
  // Individual files (~300 requests):
  function packIndex(pkgName) {
    packages['@angular/'+pkgName] = { main: 'index.js', defaultExtension: 'js' };
  }
  // Bundled (~40 requests):
  function packUmd(pkgName) {
    packages['@angular/'+pkgName] = { main: '/bundles/' + pkgName + '.umd.js', defaultExtension: 'js' };
  }
  // Most environments should use UMD; some (Karma) need the individual index files
  var setPackageConfig = System.packageWithIndex ? packIndex : packUmd;
  // Add package entries for angular packages
  ngPackageNames.forEach(setPackageConfig);
  var config = {
    map: map,
    packages: packages
  };
  System.config(config);
})(this);
<|endoftext|>


<commit_before>/*global chrome, define, nodeRequire*/
define(function(require, exports, module) {
    plugin.consumes = ["command", "background"];
    plugin.provides = ["window"];
    return plugin;

    function plugin(options, imports, register) {
        var gui = nodeRequire("nw.gui");
        var opts = require("./lib/options");

        var command = imports.command;
        var background = imports.background;

        var win = gui.Window.get();

        background.registerWindow(opts.get("title"), opts.get("url"), win);

        var closeHandler = null;

        var api = {
            close: function(force) {
                if(force) {
                    win.close(true);
                } else {
                    closeHandler();
                }
            },
            setCloseHandler: function(handler) {
                closeHandler = handler;
                win.on("close", handler);
            },
            useNativeFrame: function() {
                return true;
            },
            // create: function(url, width, height) {
            //     width = width || 800;
            //     height = height || 600;
            //     var frame = true;
            //     var w = gui.Window.open(url, {
            //         position: 'center',
            //         width: width,
            //         height: height,
            //         frame: frame,
            //         toolbar: false,
            //         icon: "Icon.png"
            //     });
            //     return new Promise(function(resolve) {
            //         w.once("loaded", function() {
            //             w.focus();
            //             w.window.opener = window;
            //             resolve({
            //                 addCloseListener: function(listener) {
            //                     w.on("closed", function() {
            //                         listener();
            //                     });
            //                 },
            //                 window: w.window,
            //                 focus: function() {
            //                     w.focus();
            //                 }
            //             });
            //         });
            //     });
            // },
            fullScreen: function() {
                if (win.isFullscreen) {
                    win.leaveFullscreen();
                } else {
                    win.enterFullscreen();
                }
            },
            maximize: function() {
                win.maximize();
            },
            minimize: function() {
                win.minimize();
            },
            getBounds: function() {
                return {
                    width: win.width,
                    height: win.height,
                    top: win.y,
                    left: win.x
                };
            },
            setBounds: function(bounds) {
                win.width = bounds.width;
                win.height = bounds.height;
                win.y = bounds.top;
                win.x = bounds.left;
            },
            addResizeListener: function(listener) {
                win.on("resize", function() {
                    listener();
                });
                win.on("move", function() {
                    listener();
                });
            },
            focus: function() {
                win.focus();
            }
        };

        command.define("Development:Reload window", {
            doc: "Reload the current window.",
            exec: function() {
                win.reload();
            },
            readOnly: true
        });

        command.define("Development:Show DevTools", {
            doc: "Show the node-webkit developer tools.",
            exec: function() {
                win.showDevTools();
            },
            readOnly: true
        });

        register(null, {
            window: api
        });
    }
});
<commit_msg>Implemented suggestion by @lalomartins to remember window was maximized (standalone) #428<commit_after>/*global chrome, define, nodeRequire*/
define(function(require, exports, module) {
    plugin.consumes = ["command", "background"];
    plugin.provides = ["window"];
    return plugin;

    function plugin(options, imports, register) {
        var gui = nodeRequire("nw.gui");
        var opts = require("./lib/options");

        var command = imports.command;
        var background = imports.background;

        var win = gui.Window.get();

        background.registerWindow(opts.get("title"), opts.get("url"), win);

        var closeHandler = null;

        var isMaximized = false;

        var api = {
            close: function(force) {
                if(force) {
                    win.close(true);
                } else {
                    closeHandler();
                }
            },
            setCloseHandler: function(handler) {
                closeHandler = handler;
                win.on("close", handler);
            },
            useNativeFrame: function() {
                return true;
            },
            // create: function(url, width, height) {
            //     width = width || 800;
            //     height = height || 600;
            //     var frame = true;
            //     var w = gui.Window.open(url, {
            //         position: 'center',
            //         width: width,
            //         height: height,
            //         frame: frame,
            //         toolbar: false,
            //         icon: "Icon.png"
            //     });
            //     return new Promise(function(resolve) {
            //         w.once("loaded", function() {
            //             w.focus();
            //             w.window.opener = window;
            //             resolve({
            //                 addCloseListener: function(listener) {
            //                     w.on("closed", function() {
            //                         listener();
            //                     });
            //                 },
            //                 window: w.window,
            //                 focus: function() {
            //                     w.focus();
            //                 }
            //             });
            //         });
            //     });
            // },
            fullScreen: function() {
                if (win.isFullscreen) {
                    win.leaveFullscreen();
                } else {
                    win.enterFullscreen();
                }
            },
            maximize: function() {
                win.maximize();
            },
            minimize: function() {
                win.minimize();
            },
            getBounds: function() {
                return {
                    width: win.width,
                    height: win.height,
                    top: win.y,
                    left: win.x,
                    isMaximized: isMaximized
                };
            },
            setBounds: function(bounds) {
                win.width = bounds.width;
                win.height = bounds.height;
                win.y = bounds.top;
                win.x = bounds.left;
                if(bounds.isMaximized) {
                    win.maximize();
                }
            },
            addResizeListener: function(listener) {
                win.on("resize", function() {
                    isMaximized = false;
                    listener();
                });
                win.on("move", function() {
                    isMaximized = false;
                    listener();
                });
                win.on("maximize", function() {
                    // Give other events time to trigger (resize, move), then override
                    setTimeout(function() {
                        isMaximized = true;
                        listener();
                    }, 1000);
                });
            },
            focus: function() {
                win.focus();
            }
        };

        command.define("Development:Reload window", {
            doc: "Reload the current window.",
            exec: function() {
                win.reload();
            },
            readOnly: true
        });

        command.define("Development:Show DevTools", {
            doc: "Show the node-webkit developer tools.",
            exec: function() {
                win.showDevTools();
            },
            readOnly: true
        });

        register(null, {
            window: api
        });
    }
});
<|endoftext|>


<commit_before>import { Laptop, LaptopCarousel } from './laptops';

// Subclasses of NavigationAnimationBase
import HomeAnim from '../home/nav-shrink';
import AboutAnim from '../about/nav-shrink';
import WorkAnim from '../work/nav-shrink';

import { ease } from '../helpers';

const os = ease.outSin;

/** Coordinates navigation */
export class Navigation {

  constructor(carousel, ...pages) {
    this.carousel = carousel;
    this.pages = Array.from(pages);

    this.shown = false;
    this.navTransitionDuration = 300;

    // Navigation bar

    Navigation.navBarUpdate();
    window.addEventListener('hashchange', Navigation.navBarUpdate);
    // Navigate when top bar clicked
    Array.from(document.getElementById('navigation').getElementsByTagName('a')).forEach((e) => {
      e.addEventListener('click', () => this.navigateTo(e.getAttribute('href').substring(1)));
    });
    // Configure navigation trigger clicked
    document.getElementById('navigation-trigger').addEventListener('click', () => this.toggle());

    // Set up initial state of transition

    this.transitionUpdate(0);
    // Jump to either open or closed on resize
    window.addEventListener('resize', () => this.transitionUpdate(this.shown)); // this.shown is 1 if shown, 0 if not

    // Set up laptops

    // Convert all elements with the class 'laptop3d' into laptops
    const laptopsContainer = document.getElementsByClassName('laptops-container')[0];
    const laptopElems = [...laptopsContainer.getElementsByClassName('laptop3d')];
    const laptops = laptopElems.map((l, i) => new Laptop(l, i - 1));
    // Create laptop carousel
    this.carousel = new LaptopCarousel(laptopsContainer, laptops);

    // Key bindings

    this.keyBind();
  }

  keyBind() {
    this.carousel.keyBind();
  }

  /** Get the current page hash without the # */
  static get hash() {
    return window.location.hash.startsWith('#')
      ? window.location.hash.substring(1)
      : window.location.hash;
  }

  /** Switch to a new page */
  navigateTo(hash) {
    return new Promise(async (resolve) => {
      if (hash !== Navigation.hash) {
        await this.open();
        await this.carousel.getLaptopWithHash(hash).centerSelf();
        await this.close();
        resolve();
      } else resolve();
    });
  }

  /** Move to a specific point in the shrink animation */
  transitionUpdate(progress) {
    return new Promise((resolve) => {
      // Update navigation bar opacity
      document.getElementById('navigation').style.opacity = (progress * 0.75) + 0.25;
      // Update navigation trigger opacity in the opposite direction
      document.getElementById('navigation-trigger').style.opacity = Math.max(0.25, 1 - (progress * 0.75));
      // Page shrinking into laptop
      requestAnimationFrame(() => {
        this.pages[this.carousel.position + 1].update(progress);
        resolve();
      });
    });
  }

  /** Show navigation */
  open() {
    return new Promise((resolve) => {
      // Only needs to happen if it's not already open
      if (!this.shown) {
        // Set up each animation frame for its correct time
        for (let i = 0; i < 1; i += (1 / 60)) {
          setTimeout(
            () => this.transitionUpdate(os(i)),       // Update to proper progress
            i * this.navTransitionDuration  // at proper time
          );
        }
        // When we're finished
        setTimeout(
          async () => { await this.transitionUpdate(1); resolve(); },
          this.navTransitionDuration
        );
        this.shown = true;
      // If it's already open, resolve immediately
      } else {
        resolve();
      }
    });
  }

  /** Hide navigation */
  close() {
    return new Promise((resolve) => {
      // Only needs to happen if it's not already open
      if (this.shown) {
        // Set up each animation frame for its correct time
        for (let i = 0; i < 1; i += (1 / 60)) {
          setTimeout(
            () => this.transitionUpdate(1 - os(i)),   // Update to proper progress
            i * this.navTransitionDuration  // at proper time
          );
        }
        // When we're finished
        setTimeout(
          async () => { await this.transitionUpdate(0); resolve(); },
          this.navTransitionDuration
        );
        this.shown = false;
      // If it's already open, resolve immediately
      } else {
        resolve();
      }
    });
  }

  /** Toggle navigation */
  toggle() {
    if (this.shown) return this.close();
    return this.open();
  }

  /** Update the top nav bar to reflect the currently active page */
  static navBarUpdate() {
    const linkToHere = document.getElementById('navigation').querySelector(`a[href='#${Navigation.hash}']`);
    // un-highlight all the links
    document.getElementById('navigation').querySelectorAll('a').forEach(a => a.classList.remove('selected'));
    // re-highlight the right one
    linkToHere.classList.add('selected');
  }

}

export default function init() {
  window.nav = window.navigation = new Navigation(
    window.carousel,
    new AboutAnim(),
    new HomeAnim(),
    new WorkAnim()
  );
}
<commit_msg>Add keybindings to navigation<commit_after>import { Laptop, LaptopCarousel } from './laptops';

// Subclasses of NavigationAnimationBase
import HomeAnim from '../home/nav-shrink';
import AboutAnim from '../about/nav-shrink';
import WorkAnim from '../work/nav-shrink';

import { ease } from '../helpers';

const os = ease.outSin;

/** Coordinates navigation */
export class Navigation {

  constructor(carousel, ...pages) {
    this.carousel = carousel;
    this.pages = Array.from(pages);

    this.shown = false;
    this.navTransitionDuration = 300;

    // Navigation bar

    Navigation.navBarUpdate();
    window.addEventListener('hashchange', Navigation.navBarUpdate);
    // Navigate when top bar clicked
    Array.from(document.getElementById('navigation').getElementsByTagName('a')).forEach((e) => {
      e.addEventListener('click', () => this.navigateTo(e.getAttribute('href').substring(1)));
    });
    // Configure navigation trigger clicked
    document.getElementById('navigation-trigger').addEventListener('click', () => this.toggle());

    // Set up initial state of transition

    this.transitionUpdate(0);
    // Jump to either open or closed on resize
    window.addEventListener('resize', () => this.transitionUpdate(this.shown)); // this.shown is 1 if shown, 0 if not

    // Set up laptops

    // Convert all elements with the class 'laptop3d' into laptops
    const laptopsContainer = document.getElementsByClassName('laptops-container')[0];
    const laptopElems = [...laptopsContainer.getElementsByClassName('laptop3d')];
    const laptops = laptopElems.map((l, i) => new Laptop(l, i - 1));
    // Create laptop carousel
    this.carousel = new LaptopCarousel(laptopsContainer, laptops);

    // Key bindings

    this.keyBind();
  }

  keyBind() {
    // LaptopCarousel keybindings (left/right)
    this.carousel.keyBind();
    // Others
    window.addEventListener('keydown', (e) => {
      // Open navigation when down is pressed
      if (e.key === 'ArrowDown') this.open();
      // Close navigation when escape, up, or enter is pressed
      if (e.key === 'Escape' || e.key === 'ArrowUp' || e.key === 'Enter') this.close();
      // Toggle navigation when space is pressed
      if (e.key === ' ') this.toggle();
    });
  }

  /** Get the current page hash without the # */
  static get hash() {
    return window.location.hash.startsWith('#')
      ? window.location.hash.substring(1)
      : window.location.hash;
  }

  /** Switch to a new page */
  navigateTo(hash) {
    return new Promise(async (resolve) => {
      if (hash !== Navigation.hash) {
        await this.open();
        await this.carousel.getLaptopWithHash(hash).centerSelf();
        await this.close();
        resolve();
      } else resolve();
    });
  }

  /** Move to a specific point in the shrink animation */
  transitionUpdate(progress) {
    return new Promise((resolve) => {
      // Update navigation bar opacity
      document.getElementById('navigation').style.opacity = (progress * 0.75) + 0.25;
      // Update navigation trigger opacity in the opposite direction
      document.getElementById('navigation-trigger').style.opacity = Math.max(0.25, 1 - (progress * 0.75));
      // Page shrinking into laptop
      requestAnimationFrame(() => {
        this.pages[this.carousel.position + 1].update(progress);
        resolve();
      });
    });
  }

  /** Show navigation */
  open() {
    return new Promise((resolve) => {
      // Only needs to happen if it's not already open
      if (!this.shown) {
        // Set up each animation frame for its correct time
        for (let i = 0; i < 1; i += (1 / 60)) {
          setTimeout(
            () => this.transitionUpdate(os(i)),       // Update to proper progress
            i * this.navTransitionDuration  // at proper time
          );
        }
        // When we're finished
        setTimeout(
          async () => { await this.transitionUpdate(1); resolve(); },
          this.navTransitionDuration
        );
        this.shown = true;
      // If it's already open, resolve immediately
      } else {
        resolve();
      }
    });
  }

  /** Hide navigation */
  close() {
    return new Promise((resolve) => {
      // Only needs to happen if it's not already open
      if (this.shown) {
        // Set up each animation frame for its correct time
        for (let i = 0; i < 1; i += (1 / 60)) {
          setTimeout(
            () => this.transitionUpdate(1 - os(i)),   // Update to proper progress
            i * this.navTransitionDuration  // at proper time
          );
        }
        // When we're finished
        setTimeout(
          async () => { await this.transitionUpdate(0); resolve(); },
          this.navTransitionDuration
        );
        this.shown = false;
      // If it's already open, resolve immediately
      } else {
        resolve();
      }
    });
  }

  /** Toggle navigation */
  toggle() {
    if (this.shown) return this.close();
    return this.open();
  }

  /** Update the top nav bar to reflect the currently active page */
  static navBarUpdate() {
    const linkToHere = document.getElementById('navigation').querySelector(`a[href='#${Navigation.hash}']`);
    // un-highlight all the links
    document.getElementById('navigation').querySelectorAll('a').forEach(a => a.classList.remove('selected'));
    // re-highlight the right one
    linkToHere.classList.add('selected');
  }

}

export default function init() {
  window.nav = window.navigation = new Navigation(
    window.carousel,
    new AboutAnim(),
    new HomeAnim(),
    new WorkAnim()
  );
}
<|endoftext|>


<commit_before>import {
  pageReducer as reducer,
  metaReducer,
  controlFlowReducer
} from '../lib/reducers'

describe('reducers', () => {
  describe('meta reducer', () => {
    describe('BREEZY_HISTORY_CHANGE', () => {
      it('sets the currentUrl', () => {
        const prevState = {foo: 'bar'}
        const action = {
          type: '@@breezy/HISTORY_CHANGE',
          payload: {
            url: '/some_url'
          }
        }
        const nextState = metaReducer(prevState, action)

        expect(nextState).toEqual({
          foo: 'bar',
          currentUrl: '/some_url'
        })
      })
    })

    describe('BREEZY_SET_BASE_URL', () => {
      it('sets the base URL', () => {
        const prevState = {foo: 'bar'}
        const action = {
          type: '@@breezy/SET_BASE_URL',
          payload: {
            baseUrl: '/some_url'
          }
        }
        const nextState = metaReducer(prevState, action)

        expect(nextState).toEqual({
          foo: 'bar',
          baseUrl: '/some_url'
        })
      })
    })

    describe('BREEZY_SAVE_RESPONSE', () => {
      it('saves the response csrfToken', () => {
        const prevState = {foo: 'bar'}
        const action = {
          type: '@@breezy/SAVE_RESPONSE',
          payload: {
            page: {csrfToken: 'some_token'}
          }
        }
        const nextState = metaReducer(prevState, action)

        expect(nextState).toEqual({
          foo: 'bar',
          csrfToken: 'some_token'
        })
      })
    })

    describe('BREEZY_SET_CSRF_TOKEN', () => {
      it('sets the initial CSRF token', () => {
        const prevState = {foo: 'bar'}
        const action = {
          type: '@@breezy/SET_CSRF_TOKEN',
          payload: {
            csrfToken: 'some_token'
          }
        }
        const nextState = metaReducer(prevState, action)

        expect(nextState).toEqual({
          foo: 'bar',
          csrfToken: 'some_token'
        })
      })
    })
  })

  describe('page reducer', () => {
    describe('BREEZY_HANDLE_GRAFT', () => {
      it('takes a grafting response and grafts it', () => {
        const prevState = {
          '/foo': {
            data: {
              header: {
                cart: {
                  total: 30
                }
              }
            },
            csrfToken: 'token',
            assets: ['application-123.js'],
            fragments: {}
          }
        }

        const graftResponse = {
          data: { total: 100},
          action: 'graft',
          path: 'header.cart',
          title: 'foobar',
          csrfToken: 'token',
          assets: ['application-123.js']
        }

        const nextState = reducer(prevState, {
          type: '@@breezy/HANDLE_GRAFT',
          payload: {
            pageKey: '/foo',
            node: graftResponse.data,
            pathToNode: graftResponse.path,
          }
        })

        expect(nextState).not.toBe(prevState)
        expect(nextState).toEqual({
          '/foo': {
            data: {
              header: {
                cart: {
                  total: 100
                }
              }
            },
            csrfToken: 'token',
            assets: ['application-123.js'],
            fragments: {}
          }
        })
      })

      it('takes any new fragments and merges them', () => {
        const prevState = {
          '/foo': {
            data: {
              header: {
                cart: {
                  total: 30
                }
              }
            },
            csrfToken: 'token',
            assets: ['application-123.js'],
            fragments: {
              info: ['header.cart']
            }
          }
        }

        const graftResponse = {
          data: { total: 100},
          action: 'graft',
          path: 'header.cart',
          title: 'foobar',
          csrfToken: 'token',
          fragments: {
            info: ['header.cart.cat', 'header.cart.cat'],
            footer: ['footer']
          },
          assets: ['application-123.js']
        }

        const nextState = reducer(prevState, {
          type: '@@breezy/HANDLE_GRAFT',
          payload: {
            pageKey: '/foo',
            node: graftResponse.data,
            pathToNode: graftResponse.path,
            fragments: graftResponse.fragments
          }
        })

        expect(nextState).toEqual({
          '/foo': {
            data: {
              header: {
                cart: {
                  total: 100
                }
              }
            },
            csrfToken: 'token',
            assets: ['application-123.js'],
            fragments: {
              info: ['header.cart.cat', 'header.cart'],
              footer: ['footer']
            }
          }
        })
      })

      fit('throws cant find page if the page does not exist for grafting', () => {
        const prevState = {
        }

        const graftResponse = {
          data: { total: 100},
          action: 'graft',
          path: 'header.cart',
          title: 'foobar',
          csrfToken: 'token',
          fragments: {
            info: ['header.cart.cat', 'header.cart.cat'],
            footer: ['footer']
          },
          assets: ['application-123.js']
        }

        expect(() => {
          const nextState = reducer(prevState, {
            type: '@@breezy/HANDLE_GRAFT',
            payload: {
              pageKey: '/foo',
              node: graftResponse.data,
              pathToNode: graftResponse.path,
              fragments: graftResponse.fragments
            }
          })
        }).toThrow(new Error("Breezy was looking for /foo in your state, but could not find it in your mapping. Did you forget to pass in a valid pageKey to this.props.remote or this.props.visit?"))
      })
    })

    describe('BREEZY_SAVE_RESPONSE', () => {
      it('saves page', () => {
        const prevState = {}
        const nextState = reducer(prevState, {
          type: '@@breezy/SAVE_RESPONSE',
          payload: {
            pageKey: '/foo',
            page: {
              data: {},
              csrfToken: 'token',
              assets: ['application-123.js']
            }
          }
        })

        expect(nextState['/foo']).toEqual(jasmine.objectContaining({
          data: {},
          csrfToken: 'token',
          cachedAt: jasmine.any(Number),
          assets: [ 'application-123.js' ],
          pageKey: '/foo',
          fragments: {}
        }))
      })
    })

    describe('BREEZY_UPDATE_ALL_JOINTS', () => {
      it('updates all fragments using the selected page as reference', () => {
        const prevState = {
          '/foo': {
            data: {
              header: {
                cart: {
                  total: 30
                }
              }
            },
            csrfToken: 'token',
            assets: ['application-123.js'],
            fragments: {
              info: ['header.cart']
            }
          },
          '/bar': {
            data: {
              profile: {
                header: {
                  cart: {
                    total: 10
                  }
                }
              }
            },
            csrfToken: 'token',
            assets: ['application-123.js'],
            fragments: {
              info: ['profile.header.cart']
            }
          }
        }

        const nextState = reducer(prevState, {
          type: '@@breezy/UPDATE_ALL_JOINTS',
          payload: {
            pageKey: '/bar',
          }
        })

        const nextStateCartTotal = nextState['/foo'].data.header.cart.total;
        expect(nextStateCartTotal).toEqual(10)
      })
    })

    describe('MATCH_JOINTS_IN_PAGE', () => {
      it('updates all fragments in a page using the selected fragment as reference', () => {
        const prevState = {
          '/foo': {
            data: {
              header: {
                cart: {
                  status: 'new'
                }
              },
              body : {
                menu: {
                  sideCart: {
                    status: 'old'
                  }
                },
                miniMenu: {
                  sideCart: {
                    status: 'old'
                  }
                }
              }
            },
            csrfToken: 'token',
            assets: ['application-123.js'],
            fragments: {
              info: [
                'header.cart',
                'body.menu.sideCart',
                'body.miniMenu.sideCart'
              ]
            }
          }
        }

        const nextState = reducer(prevState, {
          type: '@@breezy/MATCH_JOINTS_IN_PAGE',
          payload: {
            pageKey: '/foo',
            lastFragmentName: 'info',
            lastFragmentPath: 'header.cart'
          }
        })

        const page = nextState['/foo'].data
        expect(page.header.cart.status).toEqual('new')
        expect(page.body.menu.sideCart.status).toEqual('new')
        expect(page.body.miniMenu.sideCart.status).toEqual('new')

        expect(page.body.menu.sideCart).not.toBe(page.body.miniMenu.sideCart.status)
      })
    })
  })
})
<commit_msg>remove fit on reducers spec left in by accident<commit_after>import {
  pageReducer as reducer,
  metaReducer,
  controlFlowReducer
} from '../lib/reducers'

describe('reducers', () => {
  describe('meta reducer', () => {
    describe('BREEZY_HISTORY_CHANGE', () => {
      it('sets the currentUrl', () => {
        const prevState = {foo: 'bar'}
        const action = {
          type: '@@breezy/HISTORY_CHANGE',
          payload: {
            url: '/some_url'
          }
        }
        const nextState = metaReducer(prevState, action)

        expect(nextState).toEqual({
          foo: 'bar',
          currentUrl: '/some_url'
        })
      })
    })

    describe('BREEZY_SET_BASE_URL', () => {
      it('sets the base URL', () => {
        const prevState = {foo: 'bar'}
        const action = {
          type: '@@breezy/SET_BASE_URL',
          payload: {
            baseUrl: '/some_url'
          }
        }
        const nextState = metaReducer(prevState, action)

        expect(nextState).toEqual({
          foo: 'bar',
          baseUrl: '/some_url'
        })
      })
    })

    describe('BREEZY_SAVE_RESPONSE', () => {
      it('saves the response csrfToken', () => {
        const prevState = {foo: 'bar'}
        const action = {
          type: '@@breezy/SAVE_RESPONSE',
          payload: {
            page: {csrfToken: 'some_token'}
          }
        }
        const nextState = metaReducer(prevState, action)

        expect(nextState).toEqual({
          foo: 'bar',
          csrfToken: 'some_token'
        })
      })
    })

    describe('BREEZY_SET_CSRF_TOKEN', () => {
      it('sets the initial CSRF token', () => {
        const prevState = {foo: 'bar'}
        const action = {
          type: '@@breezy/SET_CSRF_TOKEN',
          payload: {
            csrfToken: 'some_token'
          }
        }
        const nextState = metaReducer(prevState, action)

        expect(nextState).toEqual({
          foo: 'bar',
          csrfToken: 'some_token'
        })
      })
    })
  })

  describe('page reducer', () => {
    describe('BREEZY_HANDLE_GRAFT', () => {
      it('takes a grafting response and grafts it', () => {
        const prevState = {
          '/foo': {
            data: {
              header: {
                cart: {
                  total: 30
                }
              }
            },
            csrfToken: 'token',
            assets: ['application-123.js'],
            fragments: {}
          }
        }

        const graftResponse = {
          data: { total: 100},
          action: 'graft',
          path: 'header.cart',
          title: 'foobar',
          csrfToken: 'token',
          assets: ['application-123.js']
        }

        const nextState = reducer(prevState, {
          type: '@@breezy/HANDLE_GRAFT',
          payload: {
            pageKey: '/foo',
            node: graftResponse.data,
            pathToNode: graftResponse.path,
          }
        })

        expect(nextState).not.toBe(prevState)
        expect(nextState).toEqual({
          '/foo': {
            data: {
              header: {
                cart: {
                  total: 100
                }
              }
            },
            csrfToken: 'token',
            assets: ['application-123.js'],
            fragments: {}
          }
        })
      })

      it('takes any new fragments and merges them', () => {
        const prevState = {
          '/foo': {
            data: {
              header: {
                cart: {
                  total: 30
                }
              }
            },
            csrfToken: 'token',
            assets: ['application-123.js'],
            fragments: {
              info: ['header.cart']
            }
          }
        }

        const graftResponse = {
          data: { total: 100},
          action: 'graft',
          path: 'header.cart',
          title: 'foobar',
          csrfToken: 'token',
          fragments: {
            info: ['header.cart.cat', 'header.cart.cat'],
            footer: ['footer']
          },
          assets: ['application-123.js']
        }

        const nextState = reducer(prevState, {
          type: '@@breezy/HANDLE_GRAFT',
          payload: {
            pageKey: '/foo',
            node: graftResponse.data,
            pathToNode: graftResponse.path,
            fragments: graftResponse.fragments
          }
        })

        expect(nextState).toEqual({
          '/foo': {
            data: {
              header: {
                cart: {
                  total: 100
                }
              }
            },
            csrfToken: 'token',
            assets: ['application-123.js'],
            fragments: {
              info: ['header.cart.cat', 'header.cart'],
              footer: ['footer']
            }
          }
        })
      })

      it('throws cant find page if the page does not exist for grafting', () => {
        const prevState = {
        }

        const graftResponse = {
          data: { total: 100},
          action: 'graft',
          path: 'header.cart',
          title: 'foobar',
          csrfToken: 'token',
          fragments: {
            info: ['header.cart.cat', 'header.cart.cat'],
            footer: ['footer']
          },
          assets: ['application-123.js']
        }

        expect(() => {
          const nextState = reducer(prevState, {
            type: '@@breezy/HANDLE_GRAFT',
            payload: {
              pageKey: '/foo',
              node: graftResponse.data,
              pathToNode: graftResponse.path,
              fragments: graftResponse.fragments
            }
          })
        }).toThrow(new Error("Breezy was looking for /foo in your state, but could not find it in your mapping. Did you forget to pass in a valid pageKey to this.props.remote or this.props.visit?"))
      })
    })

    describe('BREEZY_SAVE_RESPONSE', () => {
      it('saves page', () => {
        const prevState = {}
        const nextState = reducer(prevState, {
          type: '@@breezy/SAVE_RESPONSE',
          payload: {
            pageKey: '/foo',
            page: {
              data: {},
              csrfToken: 'token',
              assets: ['application-123.js']
            }
          }
        })

        expect(nextState['/foo']).toEqual(jasmine.objectContaining({
          data: {},
          csrfToken: 'token',
          cachedAt: jasmine.any(Number),
          assets: [ 'application-123.js' ],
          pageKey: '/foo',
          fragments: {}
        }))
      })
    })

    describe('BREEZY_UPDATE_ALL_JOINTS', () => {
      it('updates all fragments using the selected page as reference', () => {
        const prevState = {
          '/foo': {
            data: {
              header: {
                cart: {
                  total: 30
                }
              }
            },
            csrfToken: 'token',
            assets: ['application-123.js'],
            fragments: {
              info: ['header.cart']
            }
          },
          '/bar': {
            data: {
              profile: {
                header: {
                  cart: {
                    total: 10
                  }
                }
              }
            },
            csrfToken: 'token',
            assets: ['application-123.js'],
            fragments: {
              info: ['profile.header.cart']
            }
          }
        }

        const nextState = reducer(prevState, {
          type: '@@breezy/UPDATE_ALL_JOINTS',
          payload: {
            pageKey: '/bar',
          }
        })

        const nextStateCartTotal = nextState['/foo'].data.header.cart.total;
        expect(nextStateCartTotal).toEqual(10)
      })
    })

    describe('MATCH_JOINTS_IN_PAGE', () => {
      it('updates all fragments in a page using the selected fragment as reference', () => {
        const prevState = {
          '/foo': {
            data: {
              header: {
                cart: {
                  status: 'new'
                }
              },
              body : {
                menu: {
                  sideCart: {
                    status: 'old'
                  }
                },
                miniMenu: {
                  sideCart: {
                    status: 'old'
                  }
                }
              }
            },
            csrfToken: 'token',
            assets: ['application-123.js'],
            fragments: {
              info: [
                'header.cart',
                'body.menu.sideCart',
                'body.miniMenu.sideCart'
              ]
            }
          }
        }

        const nextState = reducer(prevState, {
          type: '@@breezy/MATCH_JOINTS_IN_PAGE',
          payload: {
            pageKey: '/foo',
            lastFragmentName: 'info',
            lastFragmentPath: 'header.cart'
          }
        })

        const page = nextState['/foo'].data
        expect(page.header.cart.status).toEqual('new')
        expect(page.body.menu.sideCart.status).toEqual('new')
        expect(page.body.miniMenu.sideCart.status).toEqual('new')

        expect(page.body.menu.sideCart).not.toBe(page.body.miniMenu.sideCart.status)
      })
    })
  })
})
<|endoftext|>


<commit_before>import $ from 'jquery'
import '../jquery-extensions'

import Garnish from 'garnish'
import Craft from 'craft'

import NS from '../namespace'

import BlockType from './BlockType'
import Group from './Group'
import Block from './Block'
import Buttons from './Buttons'

import renderTemplate from './templates/input.twig'
import '../twig-extensions'
import './styles/input.scss'

const _defaults = {
	namespace: [],
	blockTypes: [],
	groups: [],
	blocks: [],
	inputId: null,
	maxBlocks: 0
}

export default Garnish.Base.extend({

	_templateNs: [],

	init(settings = {})
	{
		settings = Object.assign({}, _defaults, settings)

		this._templateNs = NS.parse(settings.namespace)
		this._blockTypes = []
		this._groups = []
		this._blocks = []
		this._maxBlocks = settings.maxBlocks

		NS.enter(this._templateNs)

		this.$container = $('#' + settings.inputId).append(renderTemplate({
			blockTypes: settings.blockTypes
		}))

		NS.leave()

		for(let btInfo of settings.blockTypes)
		{
			let blockType = new BlockType(btInfo)

			this._blockTypes.push(blockType)
			this._blockTypes[blockType.getHandle()] = blockType
		}

		for(let gInfo of settings.groups)
		{
			let group = new Group(gInfo)

			this._groups.push(group)
		}

		const $neo = this.$container.find('[data-neo]')
		this.$blocksContainer = $neo.filter('[data-neo="container.blocks"]')
		this.$buttonsContainer = $neo.filter('[data-neo="container.buttons"]')

		this._buttons = new Buttons({
			blockTypes: this.getBlockTypes(true),
			groups: this.getGroups(),
			maxBlocks: this.getMaxBlocks()
		})

		this.$buttonsContainer.append(this._buttons.$container)
		this._buttons.on('newBlock', e => this['@newBlock'](e))
		this._buttons.initUi()

		this._blockSort = new Garnish.DragSort(null, {
			container: this.$blocksContainer,
			handle: '[data-neo-b="button.move"]',
			axis: 'y',
			filter: () =>
			{
				// Only return all the selected items if the target item is selected
				if(this._blockSort.$targetItem.hasClass('is-selected'))
				{
					// Also only use selected items that are on the same level as the target one
					const $parent = this._blockSort.$targetItem.parent()
					return this._blockSelect.getSelectedItems().filter((i, el) => $(el).parent().is($parent))
				}
				else
				{
					return this._blockSort.$targetItem
				}
			},
			collapseDraggees: true,
			magnetStrength: 4,
			helperLagBase: 1.5,
			helperOpacity: 0.9,
			onSortChange: () => this._updateBlockOrder()
		})

		this._blockSelect = new Garnish.Select(this.$blocksContainer, null, {
			multi: true,
			vertical: true,
			handle: '[data-neo-b="select"], [data-neo-b="button.toggler"]',
			checkboxMode: true,
			selectedClass: 'is-selected sel'
		});

		for(let bInfo of settings.blocks)
		{
			let blockType = this._blockTypes[bInfo.blockType]

			bInfo.namespace = [...this._templateNs, bInfo.id]
			bInfo.blockType = new BlockType({
				id: blockType.getId(),
				fieldLayoutId: blockType.getFieldLayoutId(),
				name: blockType.getName(),
				handle: blockType.getHandle(),
				maxBlocks: blockType.getMaxBlocks(),
				childBlocks: blockType.getChildBlocks(),
				topLevel: blockType.getTopLevel(),
				tabs: bInfo.tabs
			})
			bInfo.buttons = new Buttons({
				items: blockType.getChildBlockItems(this.getItems()),
				maxBlocks: this.getMaxBlocks()
			})

			if(isNaN(parseInt(bInfo.id)))
			{
				bInfo.id = Block.getNewId()
			}

			let block = new Block(bInfo)
			this.addBlock(block, -1, bInfo.level|0, false)
		}
	},

	addBlock(block, index = -1, level = 0, animate = null)
	{
		const blockCount = this._blocks.length
		index = (index >= 0 ? Math.max(0, Math.min(index, blockCount)) : blockCount)
		animate = (typeof animate === 'boolean' ? animate : true)

		const prevBlock = index > 0 ? this._blocks[index - 1] : false
		const nextBlock = index < blockCount ? this._blocks[index] : false

		if(!prevBlock)
		{
			this.$blocksContainer.prepend(block.$container)
		}
		else
		{
			const minLevel = nextBlock ? nextBlock.getLevel() : 0
			const maxLevel = prevBlock.getLevel() + (prevBlock.getBlockType().isParent() ? 1 : 0)

			level = Math.max(minLevel, Math.min(level, maxLevel))

			const prevBlockOnLevel = this._findPrevBlockOnLevel(index, level)

			if(prevBlockOnLevel)
			{
				prevBlockOnLevel.$container.after(block.$container)
			}
			else
			{
				prevBlock.$blocksContainer.prepend(block.$container)
			}
		}

		block.setLevel(level)

		this._blocks.push(block)
		this._blockSort.addItems(block.$container)
		this._blockSelect.addItems(block.$container)

		block.initUi()
		block.on('destroy.input',         e => this._blockBatch(block, b => this.removeBlock(b)))
		block.on('toggleEnabled.input',   e => this._blockBatch(block, b => b.toggleEnabled(e.enabled)))
		block.on('toggleExpansion.input', e => this._blockBatch(block, b => b.toggleExpansion(e.expanded)))
		block.on('newBlock.input',        e =>
		{
			const nextBlock = this.getBlockByElement(block.$container.next())
			const index = (nextBlock ? this._blocks.indexOf(nextBlock) : -1)
			this['@newBlock'](Object.assign(e, {index: index}))
		})
		block.on('addBlockAbove.input',   e => this['@addBlockAbove'](e))

		this._destroyTempButtons()
		this._updateButtons()
		this._updateBlockOrder()

		if(animate)
		{
			block.$container
				.css({
					opacity: 0,
					marginBottom: -(block.$container.outerHeight())
				})
				.velocity({
					opacity: 1,
					marginBottom: 10
				}, 'fast', e => Garnish.requestAnimationFrame(() => Garnish.scrollContainerToElement(block.$container)))
		}

		this.trigger('addBlock', {
			block: block,
			index: index
		})
	},

	removeBlock(block, animate = null, _delayAnimate = null)
	{
		animate = (typeof animate === 'boolean' ? animate : true)
		_delayAnimate = (typeof _delayAnimate === 'boolean' ? _delayAnimate : false)

		const childBlocks = this._findChildBlocks(this._blocks.indexOf(block))
		for(let childBlock of childBlocks)
		{
			this.removeBlock(childBlock, true, true)
		}

		block.off('.input')

		this._blocks = this._blocks.filter(b => b !== block)
		this._blockSort.removeItems(block.$container)
		this._blockSelect.removeItems(block.$container)

		this._destroyTempButtons()
		this._updateButtons()

		if(animate)
		{
			block.$container
				.css({
					opacity: 1,
					marginBottom: 10
				})
				.velocity({
					opacity: 0,
					marginBottom: _delayAnimate ? 10 : -(block.$container.outerHeight())
				}, 'fast', e => block.$container.remove())
		}
		else
		{
			block.$container.remove()
		}

		this.trigger('removeBlock', {
			block: block
		})
	},

	getBlockByElement($block)
	{
		return this._blocks.find(block => block.$container.is($block))
	},

	getBlocks()
	{
		return Array.from(this._blocks)
	},

	getBlockTypes(topLevelOnly)
	{
		topLevelOnly = (typeof topLevelOnly === 'boolean' ? topLevelOnly : false)

		return topLevelOnly ?
			this._blockTypes.filter(bt => bt.getTopLevel()) :
			Array.from(this._blockTypes)
	},

	getGroups()
	{
		return Array.from(this._groups)
	},

	getItems()
	{
		return [...this.getBlockTypes(), ...this.getGroups()].sort((a, b) => a.getSortOrder() - b.getSortOrder())
	},

	getMaxBlocks()
	{
		return this._maxBlocks
	},

	getSelectedBlocks()
	{
		const $selectedBlocks = this._blockSelect.getSelectedItems()
		return this._blocks.filter(block => block.$container.closest($selectedBlocks).length > 0)
	},

	_updateBlockOrder()
	{
		const blocks = []

		this._blockSort.$items.each((index, element) =>
		{
			const block = this.getBlockByElement(element)
			blocks.push(block)
		})

		this._blocks = blocks
	},

	_updateButtons()
	{
		const blocks = this.getBlocks()
		this._buttons.updateButtonStates(blocks)

		if(this._tempButtons)
		{
			this._tempButtons.updateButtonStates(blocks)
		}

		for(let block of blocks)
		{
			let buttons = block.getButtons()
			if(buttons)
			{
				buttons.updateButtonStates(blocks)
			}
		}
	},

	_blockBatch(block, callback)
	{
		const blocks = block.isSelected() ? this.getSelectedBlocks() : [block]

		for(let b of blocks)
		{
			callback(b)
		}
	},

	_destroyTempButtons(animate = null)
	{
		animate = (typeof animate === 'boolean' ? animate : true)

		if(this._tempButtons)
		{
			const buttons = this._tempButtons
			buttons.off('newBlock')

			if(animate)
			{
				buttons.$container
					.css({
						opacity: 1,
						marginBottom: 10
					})
					.velocity({
						opacity: 0,
						marginBottom: -(buttons.$container.outerHeight())
					}, 'fast', e => buttons.$container.remove())
			}
			else
			{
				buttons.$container.remove()
			}

			this._tempButtons = null
		}
	},

	_findPrevBlockOnLevel(index, level)
	{
		const blocks = this._blocks

		let block = blocks[--index]
		let lowestLevel = Number.MAX_VALUE

		while(block)
		{
			let blockLevel = block.getLevel()

			if(blockLevel < lowestLevel)
			{
				if(blockLevel === level)
				{
					return block
				}

				lowestLevel = blockLevel
			}

			block = this._blocks[--index]
		}

		return false
	},

	_findChildBlocks(index)
	{
		const blocks = this._blocks
		const block = blocks[index]
		const childBlocks = []

		if(block)
		{
			const level = block.getLevel()

			let currentBlock = blocks[++index]
			while(currentBlock && currentBlock.getLevel() > level)
			{
				if(currentBlock.getLevel() === level + 1)
				{
					childBlocks.push(currentBlock)
				}

				currentBlock = blocks[++index]
			}
		}

		return childBlocks
	},

	'@newBlock'(e)
	{
		const blockId = Block.getNewId()
		const block = new Block({
			namespace: [...this._templateNs, blockId],
			blockType: e.blockType,
			id: blockId,
			buttons: new Buttons({
				items: e.blockType.getChildBlockItems(this.getItems()),
				maxBlocks: this.getMaxBlocks()
			})
		})

		this.addBlock(block, e.index, e.level)
	},

	'@addBlockAbove'(e)
	{
		this._destroyTempButtons()

		const block = e.block
		const buttons = new Buttons({
			blockTypes: this.getBlockTypes(true),
			groups: this.getGroups(),
			maxBlocks: this.getMaxBlocks(),
			blocks: this.getBlocks()
		})

		block.$container.before(buttons.$container)

		buttons.on('newBlock', e =>
		{
			this['@newBlock']({
				blockType: e.blockType,
				index: this._blocks.indexOf(block),
				level: block.getLevel()
			})
		})

		buttons.initUi()

		if(e.animate !== false)
		{
			buttons.$container
				.css({
					opacity: 0,
					marginBottom: -(buttons.$container.outerHeight())
				})
				.velocity({
					opacity: 1,
					marginBottom: 10
				}, 'fast', e => Garnish.requestAnimationFrame(() => Garnish.scrollContainerToElement(buttons.$container)))
		}

		this._tempButtons = buttons
	}
})
<commit_msg>Add block above now works for child blocks<commit_after>import $ from 'jquery'
import '../jquery-extensions'

import Garnish from 'garnish'
import Craft from 'craft'

import NS from '../namespace'

import BlockType from './BlockType'
import Group from './Group'
import Block from './Block'
import Buttons from './Buttons'

import renderTemplate from './templates/input.twig'
import '../twig-extensions'
import './styles/input.scss'

const _defaults = {
	namespace: [],
	blockTypes: [],
	groups: [],
	blocks: [],
	inputId: null,
	maxBlocks: 0
}

export default Garnish.Base.extend({

	_templateNs: [],

	init(settings = {})
	{
		settings = Object.assign({}, _defaults, settings)

		this._templateNs = NS.parse(settings.namespace)
		this._blockTypes = []
		this._groups = []
		this._blocks = []
		this._maxBlocks = settings.maxBlocks

		NS.enter(this._templateNs)

		this.$container = $('#' + settings.inputId).append(renderTemplate({
			blockTypes: settings.blockTypes
		}))

		NS.leave()

		for(let btInfo of settings.blockTypes)
		{
			let blockType = new BlockType(btInfo)

			this._blockTypes.push(blockType)
			this._blockTypes[blockType.getHandle()] = blockType
		}

		for(let gInfo of settings.groups)
		{
			let group = new Group(gInfo)

			this._groups.push(group)
		}

		const $neo = this.$container.find('[data-neo]')
		this.$blocksContainer = $neo.filter('[data-neo="container.blocks"]')
		this.$buttonsContainer = $neo.filter('[data-neo="container.buttons"]')

		this._buttons = new Buttons({
			blockTypes: this.getBlockTypes(true),
			groups: this.getGroups(),
			maxBlocks: this.getMaxBlocks()
		})

		this.$buttonsContainer.append(this._buttons.$container)
		this._buttons.on('newBlock', e => this['@newBlock'](e))
		this._buttons.initUi()

		this._blockSort = new Garnish.DragSort(null, {
			container: this.$blocksContainer,
			handle: '[data-neo-b="button.move"]',
			axis: 'y',
			filter: () =>
			{
				// Only return all the selected items if the target item is selected
				if(this._blockSort.$targetItem.hasClass('is-selected'))
				{
					// Also only use selected items that are on the same level as the target one
					const $parent = this._blockSort.$targetItem.parent()
					return this._blockSelect.getSelectedItems().filter((i, el) => $(el).parent().is($parent))
				}
				else
				{
					return this._blockSort.$targetItem
				}
			},
			collapseDraggees: true,
			magnetStrength: 4,
			helperLagBase: 1.5,
			helperOpacity: 0.9,
			onSortChange: () => this._updateBlockOrder()
		})

		this._blockSelect = new Garnish.Select(this.$blocksContainer, null, {
			multi: true,
			vertical: true,
			handle: '[data-neo-b="select"], [data-neo-b="button.toggler"]',
			checkboxMode: true,
			selectedClass: 'is-selected sel'
		});

		for(let bInfo of settings.blocks)
		{
			let blockType = this._blockTypes[bInfo.blockType]

			bInfo.namespace = [...this._templateNs, bInfo.id]
			bInfo.blockType = new BlockType({
				id: blockType.getId(),
				fieldLayoutId: blockType.getFieldLayoutId(),
				name: blockType.getName(),
				handle: blockType.getHandle(),
				maxBlocks: blockType.getMaxBlocks(),
				childBlocks: blockType.getChildBlocks(),
				topLevel: blockType.getTopLevel(),
				tabs: bInfo.tabs
			})
			bInfo.buttons = new Buttons({
				items: blockType.getChildBlockItems(this.getItems()),
				maxBlocks: this.getMaxBlocks()
			})

			if(isNaN(parseInt(bInfo.id)))
			{
				bInfo.id = Block.getNewId()
			}

			let block = new Block(bInfo)
			this.addBlock(block, -1, bInfo.level|0, false)
		}
	},

	addBlock(block, index = -1, level = 0, animate = null)
	{
		const blockCount = this._blocks.length
		index = (index >= 0 ? Math.max(0, Math.min(index, blockCount)) : blockCount)
		animate = (typeof animate === 'boolean' ? animate : true)

		const prevBlock = index > 0 ? this._blocks[index - 1] : false
		const nextBlock = index < blockCount ? this._blocks[index] : false

		if(!prevBlock)
		{
			this.$blocksContainer.prepend(block.$container)
		}
		else
		{
			const minLevel = nextBlock ? nextBlock.getLevel() : 0
			const maxLevel = prevBlock.getLevel() + (prevBlock.getBlockType().isParent() ? 1 : 0)

			level = Math.max(minLevel, Math.min(level, maxLevel))

			const prevBlockOnLevel = this._findPrevBlockOnLevel(index, level)

			if(prevBlockOnLevel)
			{
				prevBlockOnLevel.$container.after(block.$container)
			}
			else
			{
				prevBlock.$blocksContainer.prepend(block.$container)
			}
		}

		block.setLevel(level)

		this._blocks.push(block)
		this._blockSort.addItems(block.$container)
		this._blockSelect.addItems(block.$container)

		block.initUi()
		block.on('destroy.input',         e => this._blockBatch(block, b => this.removeBlock(b)))
		block.on('toggleEnabled.input',   e => this._blockBatch(block, b => b.toggleEnabled(e.enabled)))
		block.on('toggleExpansion.input', e => this._blockBatch(block, b => b.toggleExpansion(e.expanded)))
		block.on('newBlock.input',        e =>
		{
			const nextBlock = this.getBlockByElement(block.$container.next())
			const index = (nextBlock ? this._blocks.indexOf(nextBlock) : -1)
			this['@newBlock'](Object.assign(e, {index: index}))
		})
		block.on('addBlockAbove.input',   e => this['@addBlockAbove'](e))

		this._destroyTempButtons()
		this._updateButtons()
		this._updateBlockOrder()

		if(animate)
		{
			block.$container
				.css({
					opacity: 0,
					marginBottom: -(block.$container.outerHeight())
				})
				.velocity({
					opacity: 1,
					marginBottom: 10
				}, 'fast', e => Garnish.requestAnimationFrame(() => Garnish.scrollContainerToElement(block.$container)))
		}

		this.trigger('addBlock', {
			block: block,
			index: index
		})
	},

	removeBlock(block, animate = null, _delayAnimate = null)
	{
		animate = (typeof animate === 'boolean' ? animate : true)
		_delayAnimate = (typeof _delayAnimate === 'boolean' ? _delayAnimate : false)

		const childBlocks = this._findChildBlocks(this._blocks.indexOf(block))
		for(let childBlock of childBlocks)
		{
			this.removeBlock(childBlock, true, true)
		}

		block.off('.input')

		this._blocks = this._blocks.filter(b => b !== block)
		this._blockSort.removeItems(block.$container)
		this._blockSelect.removeItems(block.$container)

		this._destroyTempButtons()
		this._updateButtons()

		if(animate)
		{
			block.$container
				.css({
					opacity: 1,
					marginBottom: 10
				})
				.velocity({
					opacity: 0,
					marginBottom: _delayAnimate ? 10 : -(block.$container.outerHeight())
				}, 'fast', e => block.$container.remove())
		}
		else
		{
			block.$container.remove()
		}

		this.trigger('removeBlock', {
			block: block
		})
	},

	getBlockByElement($block)
	{
		return this._blocks.find(block => block.$container.is($block))
	},

	getBlocks()
	{
		return Array.from(this._blocks)
	},

	getBlockTypes(topLevelOnly)
	{
		topLevelOnly = (typeof topLevelOnly === 'boolean' ? topLevelOnly : false)

		return topLevelOnly ?
			this._blockTypes.filter(bt => bt.getTopLevel()) :
			Array.from(this._blockTypes)
	},

	getGroups()
	{
		return Array.from(this._groups)
	},

	getItems()
	{
		return [...this.getBlockTypes(), ...this.getGroups()].sort((a, b) => a.getSortOrder() - b.getSortOrder())
	},

	getMaxBlocks()
	{
		return this._maxBlocks
	},

	getSelectedBlocks()
	{
		const $selectedBlocks = this._blockSelect.getSelectedItems()
		return this._blocks.filter(block => block.$container.closest($selectedBlocks).length > 0)
	},

	_updateBlockOrder()
	{
		const blocks = []

		this._blockSort.$items.each((index, element) =>
		{
			const block = this.getBlockByElement(element)
			blocks.push(block)
		})

		this._blocks = blocks
	},

	_updateButtons()
	{
		const blocks = this.getBlocks()
		this._buttons.updateButtonStates(blocks)

		if(this._tempButtons)
		{
			this._tempButtons.updateButtonStates(blocks)
		}

		for(let block of blocks)
		{
			let buttons = block.getButtons()
			if(buttons)
			{
				buttons.updateButtonStates(blocks)
			}
		}
	},

	_blockBatch(block, callback)
	{
		const blocks = block.isSelected() ? this.getSelectedBlocks() : [block]

		for(let b of blocks)
		{
			callback(b)
		}
	},

	_destroyTempButtons(animate = null)
	{
		animate = (typeof animate === 'boolean' ? animate : true)

		if(this._tempButtons)
		{
			const buttons = this._tempButtons
			buttons.off('newBlock')

			if(animate)
			{
				buttons.$container
					.css({
						opacity: 1,
						marginBottom: 10
					})
					.velocity({
						opacity: 0,
						marginBottom: -(buttons.$container.outerHeight())
					}, 'fast', e => buttons.$container.remove())
			}
			else
			{
				buttons.$container.remove()
			}

			this._tempButtons = null
		}
	},

	_findPrevBlockOnLevel(index, level)
	{
		const blocks = this._blocks

		let block = blocks[--index]
		let lowestLevel = Number.MAX_VALUE

		while(block)
		{
			let blockLevel = block.getLevel()

			if(blockLevel < lowestLevel)
			{
				if(blockLevel === level)
				{
					return block
				}

				lowestLevel = blockLevel
			}

			block = this._blocks[--index]
		}

		return false
	},

	_findChildBlocks(index)
	{
		const blocks = this._blocks
		const block = blocks[index]
		const childBlocks = []

		if(block)
		{
			const level = block.getLevel()

			let currentBlock = blocks[++index]
			while(currentBlock && currentBlock.getLevel() > level)
			{
				if(currentBlock.getLevel() === level + 1)
				{
					childBlocks.push(currentBlock)
				}

				currentBlock = blocks[++index]
			}
		}

		return childBlocks
	},

	_findParentBlock(index)
	{
		const blocks = this._blocks
		const block = blocks[index]

		if(block)
		{
			const level = block.getLevel()

			if(level > 0)
			{
				let i = index
				let currentBlock = block

				while(currentBlock && currentBlock.getLevel() >= level)
				{
					currentBlock = blocks[--i]
				}

				return currentBlock
			}
		}

		return null
	},

	'@newBlock'(e)
	{
		const blockId = Block.getNewId()
		const block = new Block({
			namespace: [...this._templateNs, blockId],
			blockType: e.blockType,
			id: blockId,
			buttons: new Buttons({
				items: e.blockType.getChildBlockItems(this.getItems()),
				maxBlocks: this.getMaxBlocks()
			})
		})

		this.addBlock(block, e.index, e.level)
	},

	'@addBlockAbove'(e)
	{
		this._destroyTempButtons()

		const block = e.block
		const index = this._blocks.indexOf(block)
		const parent = this._findParentBlock(index)
		let buttons

		if(parent)
		{
			const parentType = parent.getBlockType()
			buttons = new Buttons({
				items: parentType.getChildBlockItems(this.getItems()),
				maxBlocks: this.getMaxBlocks(),
				blocks: this.getBlocks()
			})
		}
		else
		{
			buttons = new Buttons({
				blockTypes: this.getBlockTypes(true),
				groups: this.getGroups(),
				maxBlocks: this.getMaxBlocks(),
				blocks: this.getBlocks()
			})
		}

		block.$container.before(buttons.$container)

		buttons.on('newBlock', e =>
		{
			this['@newBlock']({
				blockType: e.blockType,
				index: this._blocks.indexOf(block),
				level: block.getLevel()
			})
		})

		buttons.initUi()

		if(e.animate !== false)
		{
			buttons.$container
				.css({
					opacity: 0,
					marginBottom: -(buttons.$container.outerHeight())
				})
				.velocity({
					opacity: 1,
					marginBottom: 10
				}, 'fast', e => Garnish.requestAnimationFrame(() => Garnish.scrollContainerToElement(buttons.$container)))
		}

		this._tempButtons = buttons
	}
})
<|endoftext|>


<commit_before>(function (global) {
    // map tells the System loader where to look for things
    var map = {
        'app': 'app', // 'dist',
        '@angular': 'node_modules/@angular',
        'angular-in-memory-web-api': 'node_modules/angular-in-memory-web-api',
        'rxjs': 'node_modules/rxjs',
        'primeng': 'node_modules/primeng'
    };
    var packages = {
        'app': { main: 'main.js', defaultExtension: 'js' },
        'rxjs': { defaultExtension: 'js' },
        'angular-in-memory-web-api': { main: 'index.js', defaultExtension: 'js' },
        'primeng': {defaultExtension: 'js'}
    };
    var ngPackageNames = [
      'common',
      'compiler',
      'core',
      'forms',
      'http',
      'platform-browser',
      'platform-browser-dynamic',
      'router',
      'router-deprecated',
      'upgrade',
    ];
    // Individual files (~300 requests):
    function packIndex(pkgName) {
        packages['@angular/' + pkgName] = { main: 'index.js', defaultExtension: 'js' };
    }
    // Bundled (~40 requests):
    function packUmd(pkgName) {
        packages['@angular/' + pkgName] = { main: '/bundles/' + pkgName + '.umd.js', defaultExtension: 'js' };
    }
    // Most environments should use UMD; some (Karma) need the individual index files
    var setPackageConfig = System.packageWithIndex ? packIndex : packUmd;
    // Add package entries for angular packages
    ngPackageNames.forEach(setPackageConfig);
    var config = {
        map: map,
        packages: packages
    };
    System.config(config);
})(this);
<commit_msg>Updated System Js<commit_after>/**
 * System configuration for Angular samples
 * Adjust as necessary for your application needs.
 */
(function(global) {
    System.config({
        paths: {
            // paths serve as alias
            'npm:': 'node_modules/'
        },
        // map tells the System loader where to look for things
        map: {
            // our app is within the app folder
            app: 'app',
            // angular bundles
            '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
            '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
            '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
            '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
            '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
            '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
            '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
            '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
        'primeng': 'node_modules/primeng',
            // other libraries
            'rxjs': 'npm:rxjs',
            'angular-in-memory-web-api': 'npm:angular-in-memory-web-api',

        },
        // packages tells the System loader how to load when no filename and/or no extension
        packages: {
            'app': {
                main: './main.js',
                defaultExtension: 'js'
            },
            'test': {
                defaultExtension: 'js',
                format: 'register'
            },
            rxjs: {
                defaultExtension: 'js'
            },
            'angular-in-memory-web-api': {
                main: './index.js',
                defaultExtension: 'js'
            },
			'primeng': {defaultExtension: 'js'}

        }
    });
})(this);   <|endoftext|>


<commit_before>import { CONSTANT_TAG, DirtyableTag } from '@glimmer/reference';
import { meta as metaFor } from './meta';
import require from 'require';

let hasViews = () => false;

export function setHasViews(fn) {
  hasViews = fn;
}

function makeTag() {
  return DirtyableTag.create();
}

export function tagForProperty(object, propertyKey, _meta) {
  if (typeof object !== 'object' || object === null) { return CONSTANT_TAG; }

  let meta = _meta === undefined ? metaFor(object) : _meta;
  if (meta.isProxy()) {
    return tagFor(object, meta);
  }

  let tags = meta.writableTags();
  let tag = tags[propertyKey];
  if (tag) { return tag; }

  return tags[propertyKey] = makeTag();
}

export function tagFor(object, _meta) {
  if (typeof object === 'object' && object !== null) {
    let meta = _meta === undefined ? metaFor(object) : _meta;
    return meta.writableTag(makeTag);
  } else {
    return CONSTANT_TAG;
  }
}

export function markObjectAsDirty(meta, propertyKey) {
  let objectTag = meta.readableTag();

  if (objectTag !== undefined) {
    if (meta.isProxy()) {
      objectTag.inner.first.inner.dirty();
    } else {
      objectTag.inner.dirty();
    }
  }

  let tags = meta.readableTags();
  let propertyTag = tags !== undefined ? tags[propertyKey] : undefined;

  if (propertyTag !== undefined) {
    propertyTag.inner.dirty();
  }

  if (objectTag !== undefined || propertyTag !== undefined) {
    ensureRunloop();
  }
}

let backburner;
function ensureRunloop() {
  if (backburner === undefined) {
    // TODO why does this need to be lazy
    backburner = require('ember-metal').run.backburner;
  }

  if (hasViews()) {
    backburner.ensureInstance();
  }
}
<commit_msg>Remove require() for backburner instance<commit_after>import { CONSTANT_TAG, DirtyableTag } from '@glimmer/reference';
import { meta as metaFor } from './meta';
import run from './run_loop';

let hasViews = () => false;

export function setHasViews(fn) {
  hasViews = fn;
}

function makeTag() {
  return DirtyableTag.create();
}

export function tagForProperty(object, propertyKey, _meta) {
  if (typeof object !== 'object' || object === null) { return CONSTANT_TAG; }

  let meta = _meta === undefined ? metaFor(object) : _meta;
  if (meta.isProxy()) {
    return tagFor(object, meta);
  }

  let tags = meta.writableTags();
  let tag = tags[propertyKey];
  if (tag) { return tag; }

  return tags[propertyKey] = makeTag();
}

export function tagFor(object, _meta) {
  if (typeof object === 'object' && object !== null) {
    let meta = _meta === undefined ? metaFor(object) : _meta;
    return meta.writableTag(makeTag);
  } else {
    return CONSTANT_TAG;
  }
}

export function markObjectAsDirty(meta, propertyKey) {
  let objectTag = meta.readableTag();

  if (objectTag !== undefined) {
    if (meta.isProxy()) {
      objectTag.inner.first.inner.dirty();
    } else {
      objectTag.inner.dirty();
    }
  }

  let tags = meta.readableTags();
  let propertyTag = tags !== undefined ? tags[propertyKey] : undefined;

  if (propertyTag !== undefined) {
    propertyTag.inner.dirty();
  }

  if (objectTag !== undefined || propertyTag !== undefined) {
    ensureRunloop();
  }
}

let backburner;
function ensureRunloop() {
  if (backburner === undefined) {
    backburner = run.backburner;
  }

  if (hasViews()) {
    backburner.ensureInstance();
  }
}
<|endoftext|>