<commit_before>/* global angular */

(function () {

    angular
        .module('groups')
        .component('scientillaGroupForm', {
            templateUrl: 'partials/scientilla-group-form.html',
            controller: GroupFormController,
            controllerAs: 'vm',
            bindings: {
                group: "<",
                onFailure: "&",
                onSubmit: "&",
                checkAndClose: "&"
            }
        });

    GroupFormController.$inject = [
        'GroupsService',
        'Notification',
        'AuthService',
        '$scope',
        'groupTypes',
        'groupTypeLabels',
        'ValidateService',
        '$timeout',
        'EventsService'
    ];

    function GroupFormController(
        GroupsService,
        Notification,
        AuthService,
        $scope,
        groupTypes,
        groupTypeLabels,
        ValidateService,
        $timeout,
        EventsService
    ) {
        const vm = this;
        vm.getUsersQuery = getUsersQuery;
        vm.cancel = cancel;
        vm.isValid = isValid;
        vm.submit = submit;
        vm.validate = validate;
        vm.fieldValueHasChanged = fieldValueHasChanged;

        vm.errors = [];
        vm.errorText = '';

        let formValues = {};
        let originalGroupJson = '';
        let timeout;
        const watchers = [];

        const delay = 500;

        const requiredFields = [
            'name'
        ];

        const rules = [];

        const types = _.cloneDeep(groupTypes);
        delete types.PROJECT;

        vm.formStructure = {
            name: {
                inputType: 'text',
                label: 'Title',
                defaultValue: vm.group.name || '',
                disabled: isDisabled(vm.group),
                ngIf: isAdmin,
                required: true,
                type: 'field'
            },
            slug: {
                inputType: 'text',
                label: 'Slug',
                defaultValue: vm.group.slug,
                disabled: isDisabled(vm.group),
                ngIf: isAdmin,
                type: 'field'
            },
            shortname: {
                inputType: 'text',
                label: 'Short Name',
                defaultValue: vm.group.shortname,
                disabled: isDisabled(vm.group),
                type: 'field'
            },
            description: {
                inputType: 'text',
                label: 'Description',
                defaultValue: vm.group.description,
                type: 'field'
            },
            scopusId: {
                inputType: 'text',
                label: 'Scopus ID',
                defaultValue: vm.group.scopusId,
                type: 'field'
            },
            type: {
                inputType: 'select',
                label: 'Group Type',
                defaultValue: vm.group.type || groupTypes.OTHER,
                values: Object.keys(types).map(k => ({label: groupTypeLabels[k], value: types[k]})),
                disabled: isDisabled(vm.group),
                ngIf: isAdmin,
                type: 'field'
            },
            code: {
                inputType: 'text',
                label: 'CDR/CODE',
                defaultValue: vm.group.code,
                disabled: isDisabled(vm.group),
                type: 'field'
            },
            active: {
                inputType: 'select',
                label: 'Active',
                defaultValue: vm.group.active === undefined ? true : vm.group.active,
                values: [
                    {label: 'Yes', value: true},
                    {label: 'No', value: false}
                ],
                disabled: isDisabled(vm.group),
                ngIf: isAdmin,
                type: 'field'
            },
            onChange: function (values) {
                formValues = values;
                vm.formStructure.code.disabled = isDisabled(values);
                vm.formStructure.type.disabled = isDisabled(values);
                vm.formStructure.name.disabled = isDisabled(values);
                vm.formStructure.slug.disabled = isDisabled(values);
                vm.formStructure.shortname.disabled = isDisabled(values);
                vm.formStructure.active.disabled = isDisabled(values);
            }
        };

        vm.$onInit = function () {
            if (_.has(vm.group, 'type') && (
                vm.group.type === groupTypes.INSTITUTE ||
                vm.group.type === groupTypes.CENTER ||
                vm.group.type === groupTypes.RESEARCH_DOMAIN ||
                vm.group.type === groupTypes.RESEARCH_LINE ||
                vm.group.type === groupTypes.FACILITY ||
                vm.group.type === groupTypes.DIRECTORATE
            )) {
                const type = Object.keys(groupTypes).find(key => groupTypes[key] === vm.group.type);
                vm.formStructure.type.values = [{label: groupTypeLabels[type], value: groupTypes[type]}];
                vm.formStructure.type.defaultValue = vm.group.type;
            } else {
                const newGroupTypes = _.cloneDeep(groupTypes);
                delete newGroupTypes.INSTITUTE;
                delete newGroupTypes.CENTER;
                delete newGroupTypes.RESEARCH_DOMAIN;
                delete newGroupTypes.RESEARCH_LINE;
                delete newGroupTypes.FACILITY;
                delete newGroupTypes.DIRECTORATE;
                delete newGroupTypes.PROJECT;
                vm.formStructure.type.values = Object.keys(newGroupTypes).map(k => ({label: groupTypeLabels[k], value: newGroupTypes[k]}));
            }

            delete vm.group.members;
            delete vm.group.memberships;

            watchers.push($scope.$watch('vm.group.name', nameChanged));

            const originalGroup = angular.copy(vm.group);
            if (!originalGroup.slug)
                originalGroup.slug = calculateSlug(originalGroup);

            if (originalGroup.type === undefined)
                originalGroup.type = groupTypes.OTHER;

            if (originalGroup.active === undefined)
                originalGroup.active = true;

            originalGroupJson = angular.toJson(originalGroup);
        };

        vm.$onDestroy = function () {
            for (const watcher in watchers) {
                if (_.isFunction(watcher)) {
                    watcher();
                }
            }
        };

        function validate(field = false) {
            if (field && field.name) {
                vm.errors[field.name] = ValidateService.validate(formValues, field.name, requiredFields, rules);

                if (typeof vm.errors[field.name] === 'undefined') {
                    delete vm.errors[field.name];
                }
            } else {
                vm.errors = ValidateService.validate(formValues, false, requiredFields, rules);
            }

            if (Object.keys(vm.errors).length > 0) {
                vm.errorText = 'Please correct the errors on this form!';
            } else {
                vm.errorText = '';
            }
        }

        function fieldValueHasChanged(field = false) {
            if (!field || !_.has(field, 'name') || _.isEmpty(field.name)) {
                return;
            }

            $timeout.cancel(timeout);

            timeout = $timeout(function () {
                validate(field);
            }, delay);
        }

        function isValid() {
            return Object.keys(vm.errors).length === 0;
        }

        function nameChanged() {
            if (!vm.group)
                return;
            if (!vm.group.id) {
                vm.group.slug = calculateSlug(vm.group);
            }
        }

        function calculateSlug(group) {
            const name = group.name ? group.name : "";
            return name.toLowerCase().replace(/\s+/gi, '-');
        }

        function submit(group) {
            if (!group) return;

            validate();

            if (Object.keys(vm.errors).length > 0) {
                return;
            }

            updateGroupData();

            GroupsService.doSave(vm.group)
                .then(function () {
                    vm.errorText = '';
                    Notification.success("Group data saved");
                    EventsService.publish(EventsService.GROUP_UPDATED);
                    originalGroupJson = angular.copy(vm.group);
                    if (_.isFunction(vm.onSubmit()))
                        vm.onSubmit()(1);
                }, function (res) {
                    var errors = res.data.invalidAttributes;
                    vm.errors = {};

                    angular.forEach(errors, function (fields, fieldIndex) {
                        angular.forEach(fields, function (error, errorIndex) {
                            if (error.rule === 'required') {
                                error.message = 'This field is required.';
                                errors[fieldIndex][errorIndex] = error;
                            }
                        });

                        vm.errors[fieldIndex] = errors[fieldIndex];
                    });

                    vm.errorText = 'Please correct the errors on this form!';
                });
        }

        function getUsersQuery(searchText) {
            const qs = {where: {or: [
                {name: {contains: searchText}},
                {surname: {contains: searchText}},
                {displayName: {contains: searchText}},
                {displaySurname: {contains: searchText}},
            ]}};
            const model = 'users';
            return {model: model, qs: qs};
        }

        function cancel() {
            updateGroupData();
            vm.checkAndClose()(() => originalGroupJson === angular.toJson(vm.group));
        }

        function updateGroupData() {
            for (const key of Object.keys(vm.formStructure)) {
                vm.group[key] = formValues[key];
            }

            if (!vm.group.slug)
                vm.group.slug = calculateSlug(formValues);

        }

        function isAdmin() {
            return AuthService.user.isAdmin();
        }

        function isDisabled(group) {
            switch (group.type) {
                case types.INSTITUTE:
                case types.CENTER:
                case types.RESEARCH_DOMAIN:
                case types.RESEARCH_LINE:
                case types.FACILITY:
                case types.DIRECTORATE:
                    return true;
                default:
                    return false;
            }
        }
    }
})();
<commit_msg>Show group type project when editing<commit_after>/* global angular */

(function () {

    angular
        .module('groups')
        .component('scientillaGroupForm', {
            templateUrl: 'partials/scientilla-group-form.html',
            controller: GroupFormController,
            controllerAs: 'vm',
            bindings: {
                group: "<",
                onFailure: "&",
                onSubmit: "&",
                checkAndClose: "&"
            }
        });

    GroupFormController.$inject = [
        'GroupsService',
        'Notification',
        'AuthService',
        '$scope',
        'groupTypes',
        'groupTypeLabels',
        'ValidateService',
        '$timeout',
        'EventsService'
    ];

    function GroupFormController(
        GroupsService,
        Notification,
        AuthService,
        $scope,
        groupTypes,
        groupTypeLabels,
        ValidateService,
        $timeout,
        EventsService
    ) {
        const vm = this;
        vm.getUsersQuery = getUsersQuery;
        vm.cancel = cancel;
        vm.isValid = isValid;
        vm.submit = submit;
        vm.validate = validate;
        vm.fieldValueHasChanged = fieldValueHasChanged;

        vm.errors = [];
        vm.errorText = '';

        let formValues = {};
        let originalGroupJson = '';
        let timeout;
        const watchers = [];

        const delay = 500;

        const requiredFields = [
            'name'
        ];

        const rules = [];

        const types = _.cloneDeep(groupTypes);
        delete types.PROJECT;

        vm.formStructure = {
            name: {
                inputType: 'text',
                label: 'Title',
                defaultValue: vm.group.name || '',
                disabled: isDisabled(vm.group),
                ngIf: isAdmin,
                required: true,
                type: 'field'
            },
            slug: {
                inputType: 'text',
                label: 'Slug',
                defaultValue: vm.group.slug,
                disabled: isDisabled(vm.group),
                ngIf: isAdmin,
                type: 'field'
            },
            shortname: {
                inputType: 'text',
                label: 'Short Name',
                defaultValue: vm.group.shortname,
                disabled: isDisabled(vm.group),
                type: 'field'
            },
            description: {
                inputType: 'text',
                label: 'Description',
                defaultValue: vm.group.description,
                type: 'field'
            },
            scopusId: {
                inputType: 'text',
                label: 'Scopus ID',
                defaultValue: vm.group.scopusId,
                type: 'field'
            },
            type: {
                inputType: 'select',
                label: 'Group Type',
                defaultValue: vm.group.type || groupTypes.OTHER,
                values: Object.keys(types).map(k => ({label: groupTypeLabels[k], value: types[k]})),
                disabled: isDisabled(vm.group),
                ngIf: isAdmin,
                type: 'field'
            },
            code: {
                inputType: 'text',
                label: 'CDR/CODE',
                defaultValue: vm.group.code,
                disabled: isDisabled(vm.group),
                type: 'field'
            },
            active: {
                inputType: 'select',
                label: 'Active',
                defaultValue: vm.group.active === undefined ? true : vm.group.active,
                values: [
                    {label: 'Yes', value: true},
                    {label: 'No', value: false}
                ],
                disabled: isDisabled(vm.group),
                ngIf: isAdmin,
                type: 'field'
            },
            onChange: function (values) {
                formValues = values;
                vm.formStructure.code.disabled = isDisabled(values);
                vm.formStructure.type.disabled = isDisabled(values);
                vm.formStructure.name.disabled = isDisabled(values);
                vm.formStructure.slug.disabled = isDisabled(values);
                vm.formStructure.shortname.disabled = isDisabled(values);
                vm.formStructure.active.disabled = isDisabled(values);
            }
        };

        vm.$onInit = function () {
            if (_.has(vm.group, 'type') && (
                vm.group.type === groupTypes.INSTITUTE ||
                vm.group.type === groupTypes.CENTER ||
                vm.group.type === groupTypes.RESEARCH_DOMAIN ||
                vm.group.type === groupTypes.RESEARCH_LINE ||
                vm.group.type === groupTypes.FACILITY ||
                vm.group.type === groupTypes.DIRECTORATE ||
                vm.group.type === groupTypes.PROJECT
            )) {
                const type = Object.keys(groupTypes).find(key => groupTypes[key] === vm.group.type);
                vm.formStructure.type.values = [{label: groupTypeLabels[type], value: groupTypes[type]}];
                vm.formStructure.type.defaultValue = vm.group.type;
            } else {
                const newGroupTypes = _.cloneDeep(groupTypes);
                delete newGroupTypes.INSTITUTE;
                delete newGroupTypes.CENTER;
                delete newGroupTypes.RESEARCH_DOMAIN;
                delete newGroupTypes.RESEARCH_LINE;
                delete newGroupTypes.FACILITY;
                delete newGroupTypes.DIRECTORATE;
                delete newGroupTypes.PROJECT;
                vm.formStructure.type.values = Object.keys(newGroupTypes).map(k => ({label: groupTypeLabels[k], value: newGroupTypes[k]}));
            }

            delete vm.group.members;
            delete vm.group.memberships;

            watchers.push($scope.$watch('vm.group.name', nameChanged));

            const originalGroup = angular.copy(vm.group);
            if (!originalGroup.slug)
                originalGroup.slug = calculateSlug(originalGroup);

            if (originalGroup.type === undefined)
                originalGroup.type = groupTypes.OTHER;

            if (originalGroup.active === undefined)
                originalGroup.active = true;

            originalGroupJson = angular.toJson(originalGroup);
        };

        vm.$onDestroy = function () {
            for (const watcher in watchers) {
                if (_.isFunction(watcher)) {
                    watcher();
                }
            }
        };

        function validate(field = false) {
            if (field && field.name) {
                vm.errors[field.name] = ValidateService.validate(formValues, field.name, requiredFields, rules);

                if (typeof vm.errors[field.name] === 'undefined') {
                    delete vm.errors[field.name];
                }
            } else {
                vm.errors = ValidateService.validate(formValues, false, requiredFields, rules);
            }

            if (Object.keys(vm.errors).length > 0) {
                vm.errorText = 'Please correct the errors on this form!';
            } else {
                vm.errorText = '';
            }
        }

        function fieldValueHasChanged(field = false) {
            if (!field || !_.has(field, 'name') || _.isEmpty(field.name)) {
                return;
            }

            $timeout.cancel(timeout);

            timeout = $timeout(function () {
                validate(field);
            }, delay);
        }

        function isValid() {
            return Object.keys(vm.errors).length === 0;
        }

        function nameChanged() {
            if (!vm.group)
                return;
            if (!vm.group.id) {
                vm.group.slug = calculateSlug(vm.group);
            }
        }

        function calculateSlug(group) {
            const name = group.name ? group.name : "";
            return name.toLowerCase().replace(/\s+/gi, '-');
        }

        function submit(group) {
            if (!group) return;

            validate();

            if (Object.keys(vm.errors).length > 0) {
                return;
            }

            updateGroupData();

            GroupsService.doSave(vm.group)
                .then(function () {
                    vm.errorText = '';
                    Notification.success("Group data saved");
                    EventsService.publish(EventsService.GROUP_UPDATED);
                    originalGroupJson = angular.copy(vm.group);
                    if (_.isFunction(vm.onSubmit()))
                        vm.onSubmit()(1);
                }, function (res) {
                    var errors = res.data.invalidAttributes;
                    vm.errors = {};

                    angular.forEach(errors, function (fields, fieldIndex) {
                        angular.forEach(fields, function (error, errorIndex) {
                            if (error.rule === 'required') {
                                error.message = 'This field is required.';
                                errors[fieldIndex][errorIndex] = error;
                            }
                        });

                        vm.errors[fieldIndex] = errors[fieldIndex];
                    });

                    vm.errorText = 'Please correct the errors on this form!';
                });
        }

        function getUsersQuery(searchText) {
            const qs = {where: {or: [
                {name: {contains: searchText}},
                {surname: {contains: searchText}},
                {displayName: {contains: searchText}},
                {displaySurname: {contains: searchText}},
            ]}};
            const model = 'users';
            return {model: model, qs: qs};
        }

        function cancel() {
            updateGroupData();
            vm.checkAndClose()(() => originalGroupJson === angular.toJson(vm.group));
        }

        function updateGroupData() {
            for (const key of Object.keys(vm.formStructure)) {
                vm.group[key] = formValues[key];
            }

            if (!vm.group.slug)
                vm.group.slug = calculateSlug(formValues);

        }

        function isAdmin() {
            return AuthService.user.isAdmin();
        }

        function isDisabled(group) {
            switch (group.type) {
                case types.INSTITUTE:
                case types.CENTER:
                case types.RESEARCH_DOMAIN:
                case types.RESEARCH_LINE:
                case types.FACILITY:
                case types.DIRECTORATE:
                    return true;
                default:
                    return false;
            }
        }
    }
})();
<|endoftext|>


<commit_before>/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

import {dehydrate} from '../hydration';

import type {DehydratedData} from 'react-devtools-shared/src/devtools/views/Components/types';

export function cleanForBridge(
  data: Object | null,
  isPathWhitelisted: (path: Array<string | number>) => boolean,
  path?: Array<string | number> = [],
): DehydratedData | null {
  if (data !== null) {
    const cleanedPaths = [];
    const unserializablePaths = [];
    const cleanedData = dehydrate(
      data,
      cleanedPaths,
      unserializablePaths,
      path,
      isPathWhitelisted,
    );

    return {
      data: cleanedData,
      cleaned: cleanedPaths,
      unserializable: unserializablePaths,
    };
  } else {
    return null;
  }
}

export function copyWithSet(
  obj: Object | Array<any>,
  path: Array<string | number>,
  value: any,
  index: number = 0,
): Object | Array<any> {
  console.log('[utils] copyWithSet()', obj, path, index, value);
  if (index >= path.length) {
    return value;
  }
  const key = path[index];
  const updated = Array.isArray(obj) ? obj.slice() : {...obj};
  // $FlowFixMe number or string is fine here
  updated[key] = copyWithSet(obj[key], path, value, index + 1);
  return updated;
}
<commit_msg>Remove console.log from copyWithSet (#16716)<commit_after>/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

import {dehydrate} from '../hydration';

import type {DehydratedData} from 'react-devtools-shared/src/devtools/views/Components/types';

export function cleanForBridge(
  data: Object | null,
  isPathWhitelisted: (path: Array<string | number>) => boolean,
  path?: Array<string | number> = [],
): DehydratedData | null {
  if (data !== null) {
    const cleanedPaths = [];
    const unserializablePaths = [];
    const cleanedData = dehydrate(
      data,
      cleanedPaths,
      unserializablePaths,
      path,
      isPathWhitelisted,
    );

    return {
      data: cleanedData,
      cleaned: cleanedPaths,
      unserializable: unserializablePaths,
    };
  } else {
    return null;
  }
}

export function copyWithSet(
  obj: Object | Array<any>,
  path: Array<string | number>,
  value: any,
  index: number = 0,
): Object | Array<any> {
  if (index >= path.length) {
    return value;
  }
  const key = path[index];
  const updated = Array.isArray(obj) ? obj.slice() : {...obj};
  // $FlowFixMe number or string is fine here
  updated[key] = copyWithSet(obj[key], path, value, index + 1);
  return updated;
}
<|endoftext|>


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

/**
 * External dependencies
 */
import isEqual from 'lodash/isEqual';

/**
 * Internal dependencies
 */
import { SPECIAL_WIDGET_STATES } from './constants';

function stateAndRowMatch( stateA, stateB, rowA, rowB ) {
	return rowA === rowB && isEqual( stateA, stateB );
}

/**
 * Determines whether all the widgets in a given area should be combined.
 *
 * The given widgets in an area can all be combined if they are
 * all from the same module and they are all in the same special
 * state (see #3225).
 *
 * @since n.e.x.t
 *
 * @param {Array.<Object>} widgets      List of widgets.
 * @param {Object}         widgetStates Map of widget slug and their
 *                                             state (either an object with
 *                                             `Component` and `metadata`, or
 *                                             `null`).
 * @return {boolean} 			Whether all the widgets should be combined.
 */
function shouldCombineAllWidgets( widgets, widgetStates ) {
	const states = {};

	for ( let i = 0; i < widgets.length; i++ ) {
		const widget = widgets[ i ];
		const widgetState = widgetStates?.[ widget.slug ];

		const state = widgetState?.Component?.name;
		const module = widgetState?.metadata?.moduleSlug;
		const isSpecialState = SPECIAL_WIDGET_STATES.includes( state );

		if ( ! state || ! module || ! isSpecialState ) {
			return false;
		}

		if ( states[ module ] ) {
			if ( states[ module ].includes( state ) === false ) {
				// We have a widget from a given module in a different state from the others.
				return false;
			}

			states[ module ].push( state );
		} else {
			states[ module ] = [ state ];
		}
	}

	if ( Object.keys( states ).length > 1 ) {
		// We have multiple modules, so the widgets can't be combined.
		return false;
	}

	// const isMultipleModules = Object.keys( states ).length > 1;
	return true;
}

/*
 * Combines consecutive widgets with similar states within the same row.
 *
 * @since 1.25.0
 *
 * @param {Array.<Object>} widgets             List of widgets.
 * @param {Object}         widgetStates        Map of widget slug and their
 *                                             state (either an object with
 *                                             `Component` and `metadata`, or
 *                                             `null`).
 * @param {Object}         layout              Layout arguments from
 *                                             	`getWidgetLayout()`.
 * @param {Array.<number>} layout.columnWidths List of column widths for each
 *                                             widget.
 * @param {Array.<number>} layout.rowIndexes   List of row indexes for each
 *                                             widget.
 * @return {Object} Object with `overrideComponents` property, both of which are a
 * 					list with one item for each widget. Every `overrideComponents`
 * 					entry is either an object with `Component` and `metadata`, or
 * 					`null` (similar to the `widgetStates` parameter).
 */
export function combineWidgets( widgets, widgetStates, {
	columnWidths,
	rowIndexes,
} ) {
	const overrideComponents = [];
	const gridColumnWidths = [ ...columnWidths ];

	let currentState = null;
	let currentRowIndex = -1;
	let columnWidthsBuffer = [];

	if ( shouldCombineAllWidgets( widgets, widgetStates ) ) {
		// All the widgets have the same state, so we should only render one and hide the rest.
		const hiddenRows = Array.from( { length: widgets.length - 1 } ).fill( 0 );
		// Since all the components are the same, we can just pick the first.
		const overrideComponent = widgetStates[ widgets[ 0 ].slug ];

		return {
			overrideComponents: [ overrideComponent ],
			gridColumnWidths: [
				12, // Full width row.
				...hiddenRows,
			],
		};
	}

	widgets.forEach( ( widget, i ) => {
		overrideComponents.push( null );

		currentState = widgetStates[ widget.slug ];
		currentRowIndex = rowIndexes[ i ];

		// If the current widget has a special state...
		if ( currentState ) {
			if ( stateAndRowMatch( currentState, widgetStates[ widgets[ i + 1 ]?.slug ], currentRowIndex, rowIndexes[ i + 1 ] ) ) {
				// If the current widget state and row index match the next
				// state and row index, hide the widget entirely. Only the last
				// similar instance will be rendered in this case.
				columnWidthsBuffer.push( columnWidths[ i ] );
				// Mark this column as width = 0, so we can hide it
				gridColumnWidths[ i ] = 0;
			} else if ( columnWidthsBuffer.length > 0 ) {
				// If the state and row index do not match the next ones and
				// there are already similar instances (from previous
				// iterations), this is the last similar instance, so the
				// combined version will need to be displayed instead of the
				// widget. The combined version will use the common Component
				// and pass all common metadata as props.
				columnWidthsBuffer.push( columnWidths[ i ] );

				// Get total (desktop) column width. For tablet and phone,
				// the component should span the full width as by definition
				// it is at least a "half" widget wide (which has that behavior).
				const combinedColumnWidth = columnWidthsBuffer.reduce( ( sum, columnWidth ) => sum + columnWidth, 0 );

				overrideComponents[ i ] = currentState;

				// This final column should have the combined width
				gridColumnWidths[ i ] = combinedColumnWidth;

				// Reset the columnWidthsBuffer variable.
				columnWidthsBuffer = [];
			}
		}
	} );

	return {
		gridColumnWidths,
		overrideComponents,
	};
}
<commit_msg>Simplify states map assignment.<commit_after>/**
 * Widgets combination utilities.
 *
 * Site Kit by Google, Copyright 2021 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import isEqual from 'lodash/isEqual';

/**
 * Internal dependencies
 */
import { SPECIAL_WIDGET_STATES } from './constants';

function stateAndRowMatch( stateA, stateB, rowA, rowB ) {
	return rowA === rowB && isEqual( stateA, stateB );
}

/**
 * Determines whether all the widgets in a given area should be combined.
 *
 * The given widgets in an area can all be combined if they are
 * all from the same module and they are all in the same special
 * state (see #3225).
 *
 * @since n.e.x.t
 *
 * @param {Array.<Object>} widgets      List of widgets.
 * @param {Object}         widgetStates Map of widget slug and their
 *                                             state (either an object with
 *                                             `Component` and `metadata`, or
 *                                             `null`).
 * @return {boolean} 			Whether all the widgets should be combined.
 */
function shouldCombineAllWidgets( widgets, widgetStates ) {
	const states = {};

	for ( let i = 0; i < widgets.length; i++ ) {
		const widget = widgets[ i ];
		const widgetState = widgetStates?.[ widget.slug ];

		const state = widgetState?.Component?.name;
		const module = widgetState?.metadata?.moduleSlug;
		const isSpecialState = SPECIAL_WIDGET_STATES.includes( state );

		if ( ! state || ! module || ! isSpecialState ) {
			return false;
		}

		if ( states[ module ] ) {
			if ( states[ module ] !== state ) {
				// We have a widget from a given module in a different state from the others.
				return false;
			}
		} else {
			states[ module ] = state;
		}
	}

	if ( Object.keys( states ).length > 1 ) {
		// We have multiple modules, so the widgets can't be combined.
		return false;
	}

	return true;
}

/*
 * Combines consecutive widgets with similar states within the same row.
 *
 * @since 1.25.0
 *
 * @param {Array.<Object>} widgets             List of widgets.
 * @param {Object}         widgetStates        Map of widget slug and their
 *                                             state (either an object with
 *                                             `Component` and `metadata`, or
 *                                             `null`).
 * @param {Object}         layout              Layout arguments from
 *                                             	`getWidgetLayout()`.
 * @param {Array.<number>} layout.columnWidths List of column widths for each
 *                                             widget.
 * @param {Array.<number>} layout.rowIndexes   List of row indexes for each
 *                                             widget.
 * @return {Object} Object with `overrideComponents` property, both of which are a
 * 					list with one item for each widget. Every `overrideComponents`
 * 					entry is either an object with `Component` and `metadata`, or
 * 					`null` (similar to the `widgetStates` parameter).
 */
export function combineWidgets( widgets, widgetStates, {
	columnWidths,
	rowIndexes,
} ) {
	const overrideComponents = [];
	const gridColumnWidths = [ ...columnWidths ];

	let currentState = null;
	let currentRowIndex = -1;
	let columnWidthsBuffer = [];

	if ( shouldCombineAllWidgets( widgets, widgetStates ) ) {
		// All the widgets have the same state, so we should only render one and hide the rest.
		const hiddenRows = Array.from( { length: widgets.length - 1 } ).fill( 0 );
		// Since all the components are the same, we can just pick the first.
		const overrideComponent = widgetStates[ widgets[ 0 ].slug ];

		return {
			overrideComponents: [ overrideComponent ],
			gridColumnWidths: [
				12, // Full width row.
				...hiddenRows,
			],
		};
	}

	widgets.forEach( ( widget, i ) => {
		overrideComponents.push( null );

		currentState = widgetStates[ widget.slug ];
		currentRowIndex = rowIndexes[ i ];

		// If the current widget has a special state...
		if ( currentState ) {
			if ( stateAndRowMatch( currentState, widgetStates[ widgets[ i + 1 ]?.slug ], currentRowIndex, rowIndexes[ i + 1 ] ) ) {
				// If the current widget state and row index match the next
				// state and row index, hide the widget entirely. Only the last
				// similar instance will be rendered in this case.
				columnWidthsBuffer.push( columnWidths[ i ] );
				// Mark this column as width = 0, so we can hide it
				gridColumnWidths[ i ] = 0;
			} else if ( columnWidthsBuffer.length > 0 ) {
				// If the state and row index do not match the next ones and
				// there are already similar instances (from previous
				// iterations), this is the last similar instance, so the
				// combined version will need to be displayed instead of the
				// widget. The combined version will use the common Component
				// and pass all common metadata as props.
				columnWidthsBuffer.push( columnWidths[ i ] );

				// Get total (desktop) column width. For tablet and phone,
				// the component should span the full width as by definition
				// it is at least a "half" widget wide (which has that behavior).
				const combinedColumnWidth = columnWidthsBuffer.reduce( ( sum, columnWidth ) => sum + columnWidth, 0 );

				overrideComponents[ i ] = currentState;

				// This final column should have the combined width
				gridColumnWidths[ i ] = combinedColumnWidth;

				// Reset the columnWidthsBuffer variable.
				columnWidthsBuffer = [];
			}
		}
	} );

	return {
		gridColumnWidths,
		overrideComponents,
	};
}
<|endoftext|>


<commit_before>export default function metaForField(content, fieldName) {
  if (!content) { return; }

  try {
    return content.constructor.metaForProperty(fieldName);
  } catch (err) {
    return;
  }

}

<commit_msg>Fix field editors not appearing for field names that contain dashes<commit_after>import { camelize } from "@ember/string";

export default function metaForField(content, fieldName) {
  if (!content) { return; }

  fieldName = camelize(fieldName);
  try {
    return content.constructor.metaForProperty(fieldName);
  } catch (err) {
    return;
  }

}

<|endoftext|>


<commit_before>'use strict'
const db      = require('../neo4j_driver')
const helpers = require('../utils').helpers
const Promise = require('bluebird')
const _       = require('lodash')
const co      = require('co')

const operatorMap = {
  'regex': ' =~ ',
  'eq': ' = ',
  'lt': ' < ',
  'le': ' <= ',
  'gt': ' > ',
  'ge': ' >= ',
  'ne': ' <> '
}

class searchService {

  static getGraph (dbAlias) {
    return new Promise ((resolve, reject) => {
      let graph = {}
      try {
        graph = require('../graphs/' + dbAlias + '.json')
        return resolve(graph)
      }
      catch (err) {
        return reject({status: 'BAD_REQUEST',response: {invalid_dbAlias: dbAlias}})
      }
    })
  }

  static getNodeProperties (node, graph) {
    return graph[node].properties
  }

  static getRelatedNodes (node, graph) {
    return graph[node].relatedNodes
  }

  static buildReturn (node, graph, reqQuery) {
    if (reqQuery.return) {
      return reqQuery.return
    }
    else {
      return 'ID(' + node + ')'
    }
  }

  static transfPseudoStr(value) {
    if (value.constructor !== Array) {
      value = [value]
    }
    value = _.map(value, item => {
      if (item.constructor === String && item.indexOf('"') === -1){
        return '"' + item + '"'
      }
      else{
        return item
      }
    })
    return value.length === 1? value[0]:value
  }

  static buildFilter (label, property, filterObj) {
    const objectCheck = filterObj.constructor === Object
    const filter = objectCheck ? Object.keys(filterObj)[0] : 'eq'
    let value
    let nodeProperty = label + '.' + property
    if (filter !== 'like' && filter !== 'regex') {
      value = objectCheck ? this.transfPseudoStr(filterObj[filter]) : this.transfPseudoStr(filterObj)
    }
    else {
      value = objectCheck ? filterObj[filter] : filterObj
    }
    if (property === 'ID'){
      nodeProperty =  'ID(' + label + ')'
    }

    switch (filter) {
      case 'in':
        return nodeProperty + ' IN [' + value + ']'
      case 'out':
        return 'NOT ' + nodeProperty + ' IN [' + value + ']'
      case 'containsAny':
        return 'ANY (x IN ' + '[' + value + ']' + ' WHERE x in ' + nodeProperty + ')'
      case 'containsAll':
        return 'All (x IN ' + '[' + value + ']' + ' WHERE x in ' + nodeProperty + ')'
      case 'excludesAny':
        return 'NOT ANY (x IN ' + '[' + value + ']' + ' WHERE x in ' + nodeProperty + ')'
      case 'excludesAll':
        return 'NOT All (x IN ' + '[' + value + ']' + ' WHERE x in ' + nodeProperty + ')'
      case 'like':
        return 'TOSTRING(' + nodeProperty + ')' + operatorMap.regex + '"(?i).*' + value + '.*"'
      case 'regex':
        return 'TOSTRING(' + nodeProperty + ')' + operatorMap.regex + '"' + value + '"'
      default:
        return nodeProperty + operatorMap[filter] + value
    }
  }

  static buildWhere (filters, propertiesList, label, where) {
    if (propertiesList.length>0) {
      _.each(propertiesList, property => {
        if (where.length > 1) {
          where.push('and')
        }
        where.push(this.buildFilter(label, property, filters[label + '.' + property]))
      })
      return where
    }
    else {
      return []
    }
  }

  static buildWith (currentNode, customReturn, withArr, node) {
    if (withArr.indexOf(node) === -1) {
      withArr.push(node)
    }
    if (withArr.indexOf(currentNode) === -1 && customReturn && customReturn.indexOf(currentNode) !== -1) {
      withArr.push(currentNode)
    }
    return withArr
  }

  static buildOptionals (reqQuery, node, graph) {
    return new Promise ((resolve, reject) => {
      let q         = []
      q.push(node)
      let optionals = []
      let tree      = {}
      tree[node]    = {leafOf: ''}
      let withArr     = []
      while (q.length > 0) {
        const currentNode = q[0]
        let relatedNodes  = this.getRelatedNodes(currentNode, graph)
        _.each(relatedNodes, relatedNode =>{
          if (!tree.hasOwnProperty(relatedNode)) {
            q.push(relatedNode)
            tree[relatedNode] = {leafOf: currentNode}
          }
        })
        let baseOptional = ''
        const father     = tree[tree[currentNode].leafOf]
        if (father && father.initialQuery) {
          baseOptional                   = father.initialQuery + '-[]-(' + currentNode + ':' + currentNode + ')'
          tree[currentNode].initialQuery = baseOptional
        }
        else {
          baseOptional                   = 'MATCH (' + currentNode + ':' + currentNode + ')'
          tree[currentNode].initialQuery = baseOptional
        }
        let where                 = ['WHERE']
        const properties          = _.map(Object.keys(reqQuery.filters), property => {
          return property.replace(currentNode+'.','')
        })

        let currentNodeProperties = this.getNodeProperties(currentNode, graph)
        currentNodeProperties     = _.intersection(currentNodeProperties, properties)
        where = this.buildWhere(reqQuery.filters, currentNodeProperties, currentNode, where)

        if (where.length > 1 || (reqQuery.return && reqQuery.return.indexOf(currentNode) !== -1 && currentNode !== node)) {
          let qWith = ['WITH']
          withArr   = this.buildWith(currentNode, reqQuery.return, withArr, node)
          qWith.push(withArr.slice(0).join(', '))
          if (where.length < 1) {baseOptional = baseOptional.replace('MATCH', 'OPTIONAL MATCH')}
          const optionalQuery = [baseOptional, where.join(' '), qWith.join(' ')]
          optionals = optionals.concat(optionalQuery.join(' '))
        }

        q = helpers.dequeue(q)
      }
      return resolve(optionals.join(' '))
    })
  }

  static search (reqQuery) {
    return co(function*() {
      let processStart = process.hrtime()
      const graph        = yield this.getGraph(reqQuery.dbAlias)
      const node         = reqQuery.node
      const skip         = parseInt(reqQuery.offset) * parseInt(reqQuery.limit) - parseInt(reqQuery.limit) || 0
      const limit        = parseInt(reqQuery.limit) || 10
      const orderBy      = reqQuery.orderBy ? 'ORDER BY ' + node + '.' + reqQuery.orderBy + ' ' + reqQuery.direction : ''
      let builtOptionals = ''
      let basicQuery     = []

      if (Object.keys(reqQuery).length > 5 && !_.isEmpty(reqQuery.filters)) {
        builtOptionals = yield this.buildOptionals(reqQuery, node, graph)
      }
      if (builtOptionals) {
        basicQuery.push('')
      }
      else if (!builtOptionals) {
        basicQuery.push('MATCH (' + node + ':' + node + ') WITH ' + node + ' ')
      }

      basicQuery.push(builtOptionals)
      let query      = basicQuery.slice(0)
      query.push(orderBy + ' RETURN DISTINCT ' + this.buildReturn(node,graph,reqQuery) + ' SKIP ' + skip + ' LIMIT ' + limit)
      query          = query.join(' ')
      let countQuery = basicQuery.slice(0)
      countQuery.push('RETURN COUNT(DISTINCT ID(' + node + '))')
      countQuery     = countQuery.join(' ')

      const dbUrl    = reqQuery.dbUrl.replace(/\/$/,'')
      let queryResults
      let queryStart         = process.hrtime()
      try {
        queryResults = yield db.query([{statement: query}, {statement: countQuery}], dbUrl)
        let queryStop          = process.hrtime(queryStart)
        console.log(`[ DBQuery ] => ${reqQuery.node} <= (hr): %ds %dms`, queryStop[0], queryStop[1]/1000000)
      }
      catch (err){
        return err
      }

      let result = {count: queryResults ? queryResults.pop() : 0, results: queryResults || []}
      if (reqQuery.debug) result.query = query
      let processStop  = process.hrtime(processStart)
      console.log(`[ Process Total ] => ${reqQuery.node} <= (hr): %ds %dms`, processStop[0], processStop[1]/1000000)
      return result
    }.bind(this))
  }

}

module.exports = searchService
<commit_msg>added 'has' boolean filter<commit_after>'use strict'
const db      = require('../neo4j_driver')
const helpers = require('../utils').helpers
const Promise = require('bluebird')
const _       = require('lodash')
const co      = require('co')

const operatorMap = {
  'regex': ' =~ ',
  'eq': ' = ',
  'lt': ' < ',
  'le': ' <= ',
  'gt': ' > ',
  'ge': ' >= ',
  'ne': ' <> '
}

class searchService {

  static getGraph (dbAlias) {
    return new Promise ((resolve, reject) => {
      let graph = {}
      try {
        graph = require('../graphs/' + dbAlias + '.json')
        return resolve(graph)
      }
      catch (err) {
        return reject({status: 'BAD_REQUEST',response: {invalid_dbAlias: dbAlias}})
      }
    })
  }

  static getNodeProperties (node, graph) {
    return graph[node].properties
  }

  static getRelatedNodes (node, graph) {
    return graph[node].relatedNodes
  }

  static buildReturn (node, graph, reqQuery) {
    if (reqQuery.return) {
      return reqQuery.return
    }
    else {
      return 'ID(' + node + ')'
    }
  }

  static transfPseudoStr(value) {
    if (value.constructor !== Array) {
      value = [value]
    }
    value = _.map(value, item => {
      if (item.constructor === String && item.indexOf('"') === -1){
        return '"' + item + '"'
      }
      else{
        return item
      }
    })
    return value.length === 1? value[0]:value
  }

  static buildFilter (label, property, filterObj) {
    const objectCheck = filterObj.constructor === Object
    const filter = objectCheck ? Object.keys(filterObj)[0] : 'eq'
    let value = objectCheck ? filterObj[filter] : filterObj
    let nodeProperty = label + '.' + property
    if (filter !== 'like' && filter !== 'regex' && value.constructor !== Boolean) {
      value = this.transfPseudoStr(value)
    }
    if (property === 'ID'){
      nodeProperty =  'ID(' + label + ')'
    }

    switch (filter) {
      case 'in':
        return nodeProperty + ' IN [' + value + ']'
      case 'out':
        return 'NOT ' + nodeProperty + ' IN [' + value + ']'
      case 'has':
        return 'has(' + nodeProperty + ') = ' + value
      case 'containsAny':
        return 'ANY (x IN ' + '[' + value + ']' + ' WHERE x in ' + nodeProperty + ')'
      case 'containsAll':
        return 'All (x IN ' + '[' + value + ']' + ' WHERE x in ' + nodeProperty + ')'
      case 'excludesAny':
        return 'NOT ANY (x IN ' + '[' + value + ']' + ' WHERE x in ' + nodeProperty + ')'
      case 'excludesAll':
        return 'NOT All (x IN ' + '[' + value + ']' + ' WHERE x in ' + nodeProperty + ')'
      case 'like':
        return 'TOSTRING(' + nodeProperty + ')' + operatorMap.regex + '"(?i).*' + value + '.*"'
      case 'regex':
        return 'TOSTRING(' + nodeProperty + ')' + operatorMap.regex + '"' + value + '"'
      default:
        return nodeProperty + operatorMap[filter] + value
    }
  }

  static buildWhere (filters, propertiesList, label, where) {
    if (propertiesList.length>0) {
      _.each(propertiesList, property => {
        if (where.length > 1) {
          where.push('and')
        }
        where.push(this.buildFilter(label, property, filters[label + '.' + property]))
      })
      return where
    }
    else {
      return []
    }
  }

  static buildWith (currentNode, customReturn, withArr, node) {
    if (withArr.indexOf(node) === -1) {
      withArr.push(node)
    }
    if (withArr.indexOf(currentNode) === -1 && customReturn && customReturn.indexOf(currentNode) !== -1) {
      withArr.push(currentNode)
    }
    return withArr
  }

  static buildOptionals (reqQuery, node, graph) {
    return new Promise ((resolve, reject) => {
      let q         = []
      q.push(node)
      let optionals = []
      let tree      = {}
      tree[node]    = {leafOf: ''}
      let withArr     = []
      while (q.length > 0) {
        const currentNode = q[0]
        let relatedNodes  = this.getRelatedNodes(currentNode, graph)
        _.each(relatedNodes, relatedNode =>{
          if (!tree.hasOwnProperty(relatedNode)) {
            q.push(relatedNode)
            tree[relatedNode] = {leafOf: currentNode}
          }
        })
        let baseOptional = ''
        const father     = tree[tree[currentNode].leafOf]
        if (father && father.initialQuery) {
          baseOptional                   = father.initialQuery + '-[]-(' + currentNode + ':' + currentNode + ')'
          tree[currentNode].initialQuery = baseOptional
        }
        else {
          baseOptional                   = 'MATCH (' + currentNode + ':' + currentNode + ')'
          tree[currentNode].initialQuery = baseOptional
        }
        let where                 = ['WHERE']
        const properties          = _.map(Object.keys(reqQuery.filters), property => {
          return property.replace(currentNode+'.','')
        })

        let currentNodeProperties = this.getNodeProperties(currentNode, graph)
        currentNodeProperties     = _.intersection(currentNodeProperties, properties)
        where = this.buildWhere(reqQuery.filters, currentNodeProperties, currentNode, where)

        if (where.length > 1 || (reqQuery.return && reqQuery.return.indexOf(currentNode) !== -1 && currentNode !== node)) {
          let qWith = ['WITH']
          withArr   = this.buildWith(currentNode, reqQuery.return, withArr, node)
          qWith.push(withArr.slice(0).join(', '))
          if (where.length < 1) {baseOptional = baseOptional.replace('MATCH', 'OPTIONAL MATCH')}
          const optionalQuery = [baseOptional, where.join(' '), qWith.join(' ')]
          optionals = optionals.concat(optionalQuery.join(' '))
        }

        q = helpers.dequeue(q)
      }
      return resolve(optionals.join(' '))
    })
  }

  static search (reqQuery) {
    return co(function*() {
      let processStart = process.hrtime()
      const graph        = yield this.getGraph(reqQuery.dbAlias)
      const node         = reqQuery.node
      const skip         = parseInt(reqQuery.offset) * parseInt(reqQuery.limit) - parseInt(reqQuery.limit) || 0
      const limit        = parseInt(reqQuery.limit) || 10
      const orderBy      = reqQuery.orderBy ? 'ORDER BY ' + node + '.' + reqQuery.orderBy + ' ' + reqQuery.direction : ''
      let builtOptionals = ''
      let basicQuery     = []

      if (Object.keys(reqQuery).length > 5 && !_.isEmpty(reqQuery.filters)) {
        builtOptionals = yield this.buildOptionals(reqQuery, node, graph)
      }
      if (builtOptionals) {
        basicQuery.push('')
      }
      else if (!builtOptionals) {
        basicQuery.push('MATCH (' + node + ':' + node + ') WITH ' + node + ' ')
      }

      basicQuery.push(builtOptionals)
      let query      = basicQuery.slice(0)
      query.push(orderBy + ' RETURN DISTINCT ' + this.buildReturn(node,graph,reqQuery) + ' SKIP ' + skip + ' LIMIT ' + limit)
      query          = query.join(' ')
      let countQuery = basicQuery.slice(0)
      countQuery.push('RETURN COUNT(DISTINCT ID(' + node + '))')
      countQuery     = countQuery.join(' ')

      const dbUrl    = reqQuery.dbUrl.replace(/\/$/,'')
      let queryResults
      let queryStart         = process.hrtime()
      try {
        queryResults = yield db.query([{statement: query}, {statement: countQuery}], dbUrl)
        let queryStop          = process.hrtime(queryStart)
        console.log(`[ DBQuery ] => ${reqQuery.node} <= (hr): %ds %dms`, queryStop[0], queryStop[1]/1000000)
      }
      catch (err){
        return err
      }

      let result = {count: queryResults ? queryResults.pop() : 0, results: queryResults || []}
      if (reqQuery.debug) result.query = query
      let processStop  = process.hrtime(processStart)
      console.log(`[ Process Total ] => ${reqQuery.node} <= (hr): %ds %dms`, processStop[0], processStop[1]/1000000)
      return result
    }.bind(this))
  }

}

module.exports = searchService
<|endoftext|>


<commit_before>/**
 * WordPress dependencies
 */
import { compose } from '@wordpress/compose';
import { Draggable, DropZone } from '@wordpress/components';
import { Fragment, Component } from '@wordpress/element';
import { withDispatch, withSelect } from '@wordpress/data';

/**
 * Internal dependencies
 */
import BlockPreview from './block-preview';

/**
 * Parses drag & drop events to ensure the event contains valid transfer data.
 *
 * @param {Object} event
 * @return {Object} Parsed event data.
 */
const parseDropEvent = ( event ) => {
	let result = {
		srcRootClientId: null,
		srcClientId: null,
		srcIndex: null,
		type: null,
	};

	if ( ! event.dataTransfer ) {
		return result;
	}

	try {
		result = Object.assign( result, JSON.parse( event.dataTransfer.getData( 'text' ) ) );
	} catch ( err ) {
		return result;
	}

	return result;
};

/**
 * A single item within the list of pages to be reordered.
 *
 * Re-uses existing Draggable and DropZone provided by WordPress
 * in order to not re-invent the wheel.
 */
class ReordererItem extends Component {
	constructor() {
		super( ...arguments );

		this.state = {
			isDragging: false,
		};

		this.onDrop = this.onDrop.bind( this );
	}

	getInsertIndex( position ) {
		const { index } = this.props;

		if ( index !== undefined ) {
			return position.x === 'right' ? index + 1 : index;
		}
	}

	onDrop( event, position ) {
		const { page: { clientId }, movePageToPosition } = this.props;
		const { srcClientId, type } = parseDropEvent( event );

		const isBlockDropType = ( dropType ) => dropType === 'block';
		const isSameBlock = ( src, dst ) => src === dst;

		if ( ! isBlockDropType( type ) || isSameBlock( srcClientId, clientId ) ) {
			return;
		}

		const insertIndex = this.getInsertIndex( position );
		movePageToPosition( srcClientId, insertIndex );
	}

	render() {
		const { page, index } = this.props;
		const { clientId } = page;
		const pageElementId = `reorder-page-${ clientId }`;
		const transferData = {
			type: 'block',
			srcIndex: index,
			srcClientId: clientId,
		};

		return (

				<Draggable
					elementId={ pageElementId }
					transferData={ transferData }
					onDragStart={ () => this.setState( { isDragging: true } ) }
					onDragEnd={ () => this.setState( { isDragging: false } ) }
				>
					{
						( { onDraggableStart, onDraggableEnd } ) => (
							<Fragment>
								<DropZone
									className={ this.state.isDragging ? 'is-dragging-page' : undefined }
									onDrop={ this.onDrop }
								/>

										<BlockPreview { ...page } />

							</Fragment>
						)
					}
				</Draggable>

		);
	}
}

const applyWithSelect = withSelect( ( select, { page: { clientId } } ) => {
	const { getBlockIndex } = select( 'amp/story' );

	return {
		index: getBlockIndex( clientId ),
	};
} );

const applyWithDispatch = withDispatch( ( dispatch ) => {
	const { movePageToPosition } = dispatch( 'amp/story' );

	return {
		movePageToPosition,
	};
} );

export default compose(
	applyWithSelect,
	applyWithDispatch,
)( ReordererItem );
<commit_msg>Fix reorder index calculation<commit_after>/**
 * WordPress dependencies
 */
import { compose } from '@wordpress/compose';
import { Draggable, DropZone } from '@wordpress/components';
import { Fragment, Component } from '@wordpress/element';
import { withDispatch, withSelect } from '@wordpress/data';

/**
 * Internal dependencies
 */
import BlockPreview from './block-preview';

/**
 * Parses drag & drop events to ensure the event contains valid transfer data.
 *
 * @param {Object} event
 * @return {Object} Parsed event data.
 */
const parseDropEvent = ( event ) => {
	let result = {
		srcRootClientId: null,
		srcClientId: null,
		srcIndex: null,
		type: null,
	};

	if ( ! event.dataTransfer ) {
		return result;
	}

	try {
		result = Object.assign( result, JSON.parse( event.dataTransfer.getData( 'text' ) ) );
	} catch ( err ) {
		return result;
	}

	return result;
};

/**
 * A single item within the list of pages to be reordered.
 *
 * Re-uses existing Draggable and DropZone provided by WordPress
 * in order to not re-invent the wheel.
 */
class ReordererItem extends Component {
	constructor() {
		super( ...arguments );

		this.state = {
			isDragging: false,
		};

		this.onDrop = this.onDrop.bind( this );
	}

	getInsertIndex( position ) {
		const { index } = this.props;

		if ( index !== undefined ) {
			return position.x === 'right' ? index + 1 : index;
		}
	}

	onDrop( event, position ) {
		const { page: { clientId }, movePageToPosition, index } = this.props;
		const { srcClientId, srcIndex, type } = parseDropEvent( event );

		const isBlockDropType = ( dropType ) => dropType === 'block';
		const isSameBlock = ( src, dst ) => src === dst;

		if ( ! isBlockDropType( type ) || isSameBlock( srcClientId, clientId ) ) {
			return;
		}

		const positionIndex = this.getInsertIndex( position );
		const insertIndex = srcIndex < index ? positionIndex - 1 : positionIndex;
		movePageToPosition( srcClientId, insertIndex );
	}

	render() {
		const { page, index } = this.props;
		const { clientId } = page;
		const pageElementId = `reorder-page-${ clientId }`;
		const transferData = {
			type: 'block',
			srcIndex: index,
			srcClientId: clientId,
		};

		return (

				<Draggable
					elementId={ pageElementId }
					transferData={ transferData }
					onDragStart={ () => this.setState( { isDragging: true } ) }
					onDragEnd={ () => this.setState( { isDragging: false } ) }
				>
					{
						( { onDraggableStart, onDraggableEnd } ) => (
							<Fragment>
								<DropZone
									className={ this.state.isDragging ? 'is-dragging-page' : undefined }
									onDrop={ this.onDrop }
								/>

										<BlockPreview { ...page } />

							</Fragment>
						)
					}
				</Draggable>

		);
	}
}

const applyWithSelect = withSelect( ( select, { page: { clientId } } ) => {
	const { getBlockIndex } = select( 'amp/story' );

	return {
		index: getBlockIndex( clientId ),
	};
} );

const applyWithDispatch = withDispatch( ( dispatch ) => {
	const { movePageToPosition } = dispatch( 'amp/story' );

	return {
		movePageToPosition,
	};
} );

export default compose(
	applyWithSelect,
	applyWithDispatch,
)( ReordererItem );
<|endoftext|>


<commit_before>'use strict';
var LevelUtil = require('../../src/levels').LevelUtil,
  Skills = require('../../src/skills').Skills,
  CommandUtil = require('../../src/command_util').CommandUtil,
  util = require('util');

exports.listeners = {

  // Function wrappers needed to access "this" (Player obj)
  regen: function(l10n) {
    return function(bonus) {
      bonus = bonus || 1;
      const self = this;
      const regenInterval = 2000;

      self.prompt();

      const regen = setInterval(() => {
        const health = self.getAttribute('health');
        let regenerated = Math.floor(Math.random() * self.getAttribute('stamina') + bonus);

        regenerated = Math.min(self.getAttribute('max_health'), health + regenerated);
        util.log(self.getName() + ' has regenerated up to ' + regenerated + ' health.');

        self.setAttribute('health', regenerated);
        if (regenerated === self.getAttribute('max_health')) {
          clearInterval(regen);
        }
      }, regenInterval);
    }
  },

  experience: function(l10n) {
    return function(experience) {

      const maxLevel = 60;
      if (this.getAttribute('level') >= maxLevel) {
        return;
      }

      util.log(this.getName() + ' has gained ' + experience + ' XP.');
      this.sayL10n(l10n, 'EXPGAIN', experience);

      const tnl = LevelUtil.expToLevel(this.getAttribute('level')) - this.getAttribute('experience');

      if (experience >= tnl) {
        return this.emit('level');
      }

      this.setAttribute('experience', this.getAttribute('experience') + experience);
    }
  },

  level: function(l10n) {
    return function() {
      const name = this.getName();
      const newLevel = this.getAttribute('level') + 1;
      const healthGain = Math.ceil(this.getAttribute('max_health') * 1.10);

      let mutationPoints = this.getAttribute('mutagens');

      if (newLevel % 2 === 0) {
        mutationPoints++;
        this.setAttribute('mutagens', mutationPoints);
      }

      util.log(name + ' is now level ' + newLevel);

      this.sayL10n(l10n, 'LEVELUP');
      this.sayL10n(l10n, 'MUTAGEN_GAIN');
      this.setAttribute('level', newLevel);
      this.setAttribute('experience', 0);
      this.setAttribute('attrPoints', (this.getAttribute('attrPoints') || 0) + 1);

      // Do whatever you want to do here when a player levels up...
      this.setAttribute('max_health', healthGain);
      this.setAttribute('health', this.getAttribute('max_health'));
      util.log(name + ' now has ' + healthGain + ' max health.');

      // Add points for skills
      const skillGain = LevelUtil.getTrainingTime(newLevel);
      const newTrainingTime = this.getTraining('time') + skillGain;
      util.log(name + ' can train x', newTrainingTime);
      this.setTraining('time', newTrainingTime);
    }
  },

  //TODO: Permadeath, add it.
  die: function(l10n) {
    return function() {
      // they died, move then back to the start... you can do whatever you want instead of this
      this.setLocation(1);
      this.emit('regen');
      util.log(this.getName() + ' died.');
      this.setAttribute('experience', this.getAttribute('experience') - Math.ceil((this.getAttribute('experience') * 0.10)));
    }
  },

  changeTime: function(l10n) {
    return function (wasDaytime, rooms) {
      const playerIsOutside = rooms.getAt(this.getLocation()).biome === 'outdoors';

      if (playerIsOutside) {
        if (wasDaytime) {
          this.sayL10n(l10n, "SUN_SETTING");
          setTimeout(() => this.sayL10n(l10n, "SUN_SET"), 5000);

        } else {
          this.sayL10n(l10n, "SUN_RISING");
          setTimeout(() => this.sayL10n(l10n, "SUN_UP"), 5000);
        }

      } else if (wasDaytime) {
        this.sayL10n(l10n, "SUN_SETTING_INDOORS");
      } else {
        this.sayL10n(l10n, "SUN_UP_INDOORS");
      }

    }
  }
};
<commit_msg>Refactoring and trying to remove prompts.<commit_after>'use strict';
var LevelUtil = require('../../src/levels').LevelUtil,
  Skills = require('../../src/skills').Skills,
  CommandUtil = require('../../src/command_util').CommandUtil,
  util = require('util');

exports.listeners = {

  //// Function wrappers needed to access "this" (Player obj)
  //TODO: Do regenHealth, regenSanity, and regenActions
  regen: function(l10n) {
    return function(bonus) {
      bonus = bonus || 1;
      const self = this;
      const regenInterval = 2000;

      const regen = setInterval(() => {
        const health = self.getAttribute('health');
        let regenerated = Math.floor(Math.random() * self.getAttribute('stamina') + bonus);

        regenerated = Math.min(self.getAttribute('max_health'), health + regenerated);
        util.log(self.getName() + ' has regenerated up to ' + regenerated + ' health.');

        self.setAttribute('health', regenerated);
        if (regenerated === self.getAttribute('max_health')) {
          clearInterval(regen);
        }
      }, regenInterval);
    }
  },

  experience: function(l10n) {
    return function(experience) {

      const maxLevel = 60;
      if (this.getAttribute('level') >= maxLevel) {
        return;
      }

      util.log(this.getName() + ' has gained ' + experience + ' XP.');
      this.sayL10n(l10n, 'EXPGAIN', experience);

      const tnl = LevelUtil.expToLevel(this.getAttribute('level')) - this.getAttribute('experience');

      if (experience >= tnl) {
        return this.emit('level');
      }

      this.setAttribute('experience', this.getAttribute('experience') + experience);
    }
  },

  level: function(l10n) {
    return function() {
      const name = this.getName();
      const newLevel = this.getAttribute('level') + 1;
      const healthGain = Math.ceil(this.getAttribute('max_health') * 1.10);
      const gainedMutation = newLevel % 2 === 0;

      let mutationPoints = this.getAttribute('mutagens');

      util.log(name + ' is now level ' + newLevel);

      this.sayL10n(l10n, 'LEVELUP');

      if (gainedMut) {
        this.sayL10n(l10n, 'MUTAGEN_GAIN');
        mutationPoints++;
        this.setAttribute('mutagens', mutationPoints);
      }

      this.setAttribute('level', newLevel);
      this.setAttribute('experience', 0);
      this.setAttribute('attrPoints', (this.getAttribute('attrPoints') || 0) + 1);

      // Do whatever you want to do here when a player levels up...
      this.setAttribute('max_health', healthGain);
      this.setAttribute('health', this.getAttribute('max_health'));
      util.log(name + ' now has ' + healthGain + ' max health.');

      // Add points for skills
      const skillGain = LevelUtil.getTrainingTime(newLevel);
      const newTrainingTime = this.getTraining('time') + skillGain;
      util.log(name + ' can train x', newTrainingTime);
      this.setTraining('time', newTrainingTime);
    }
  },

  //TODO: Permadeath, add it.
  die: function(l10n) {
    return function() {
      // they died, move then back to the start... you can do whatever you want instead of this
      this.setLocation(1);
      this.emit('regen');
      util.log(this.getName() + ' died.');
      this.setAttribute('experience', this.getAttribute('experience') - Math.ceil((this.getAttribute('experience') * 0.10)));
    }
  },

  changeTime: function(l10n) {
    return function (wasDaytime, rooms) {
      const playerIsOutside = rooms.getAt(this.getLocation()).biome === 'outdoors';

      if (playerIsOutside) {
        if (wasDaytime) {
          this.sayL10n(l10n, "SUN_SETTING");
          setTimeout(() => this.sayL10n(l10n, "SUN_SET"), 5000);

        } else {
          this.sayL10n(l10n, "SUN_RISING");
          setTimeout(() => this.sayL10n(l10n, "SUN_UP"), 5000);
        }

      } else if (wasDaytime) {
        this.sayL10n(l10n, "SUN_SETTING_INDOORS");
      } else {
        this.sayL10n(l10n, "SUN_UP_INDOORS");
      }

    }
  }
};
<|endoftext|>


<commit_before>
(function () {
    'use strict';

    /* ngInject */
    function Directions ($http, $q, $timeout, Config, MapControl, TurnAmenities) {

        var directionsUrl = Config.routing.hostname + '/otp/routers/default/plan';
        var currentRouteSummary = {
            distanceMeters: 0,
            timeMinutes: 0,
            turns: 0
        };

        var module = {
            get: get,
            getFlagIconName: getFlagIconName,
            getTurnIconName: getTurnIconName,
            getRouteSummary: getRouteSummary,
            isAudited: isAudited,
            isTurn: isTurn
        };

        return module;

        /**
         * Make a routing request from origin to destination with options
         *
         * @param origin {array} [lon, lat]
         * @param destination {array} [lon, lat]
         * @param options {object}
         * // TODO: Document options object
         */
        function get(origin, destination, options) {
            var dfd = $q.defer();
            if (!(_.isArray(origin) && origin.length >= 2)) {
                dfd.reject({ msg: 'Invalid origin parameter' });
                return dfd.promise;
            }
            if (!(_.isArray(destination) && destination.length >= 2)) {
                dfd.reject({ msg: 'Invalid destination parameter' });
                return dfd.promise;
            }
            var now = new Date();
            // This is the minimum param list (+ toPlace,fromPlace) to make a OTP plan API call
            var defaults = {
                mode: 'WALK',
                time: now.getHours() + ':' + now.getMinutes(),
                date: defaultDate(),
                arriveBy: false,
                wheelchair: false,
                showIntermediateStops: false
            };
            var params = angular.extend({}, defaults, options);
            // Swap, OTP request uses [lat,lon]
            params.fromPlace = [origin[1], origin[0]].join(',');
            params.toPlace = [destination[1], destination[0]].join(',');

            $http.get(directionsUrl, {
                params: params
            }).then(function (response) {
                if (response.data.error) {
                    //  OTP returns an array indicating which fields are outside the graph bounds.
                    var missingData = response.data.error.missing;
                    if (missingData) {
                        if (missingData.length === 2) {
                            dfd.reject({
                                msg: 'Both the requested origin and destination are outside the planner bounds.'
                            });
                        } else if (missingData[0] === 'from') {
                            dfd.reject({
                                msg: 'The requested origin point is outside the planner bounds.'
                            });
                        }  else if (missingData[0] === 'to') {
                            dfd.reject({
                                msg: 'The requested destination point is outside the planner bounds.'
                            });
                        } else {
                            dfd.reject(response.data.error);
                        }
                    } else {
                        dfd.reject(response.data.error);
                    }
                } else {
                    var geojson = transformOtpToGeoJson(response.data);
                    TurnAmenities.attach(geojson);
                    dfd.resolve(geojson);
                }
            });
            return dfd.promise;
        }

        function getFlagIconName(flagType, value) {
            // TODO: Write tests once actual icons exist
            if (!value) {
                return '';
            }

            switch (flagType) {
                case 'benches':
                    return 'glyphicon-flash';
                case 'hazards':
                    return 'glyphicon-warning-sign';
                case 'bathrooms':
                    return 'glyphicon-trash';
                default:
                    return '';
            }
        }

        function getTurnIconName(turnType) {
            // TODO: Write tests once actual icons exist
            switch (turnType) {
                case 'DEPART':
                    return 'glyphicon-flag';
                case 'CONTINUE':
                    return 'glyphicon-arrow-up';
                // Temporarily fall through to similar cases for left/right
                case 'LEFT':
                case 'SLIGHTLY_LEFT':
                case 'HARD_LEFT':
                case 'UTURN_LEFT':
                    return 'glyphicon-arrow-left';
                case 'RIGHT':
                case 'SLIGHTLY_RIGHT':
                case 'HARD_RIGHT':
                case 'UTURN_RIGHT':
                    return 'glyphicon-arrow-right';
                case 'CIRCLE_CLOCKWISE':
                case 'CIRCLE_COUNTERCLOCKWISE':
                    return 'glyphicon-repeat';
                case 'ELEVATOR':
                    return 'glyphicon-cloud-upload';
                default:
                    return 'glyphicon-remove-circle';
            }
        }

        /**
         * Get a summary for the last requested route via Directions.get,
         *     including time, distance and # of turns
         *
         * @param  {object} geojson     Geojson returned from Directions.get
         * @param  {float} walkSpeed    Walk speed in m/s, used to calculate time, optional, default 1
         * @return {[type]}             [description]
         */
        function getRouteSummary() {
            return currentRouteSummary;
        }

        function isTurn(turnType) {
            var turnTypes = [
                'LEFT', 'SLIGHTLY_LEFT', 'HARD_LEFT', 'UTURN_LEFT',
                'RIGHT', 'SLIGHTLY_RIGHT', 'HARD_RIGHT', 'UTURN_RIGHT',
                'CIRCLE_CLOCKWISE', 'CIRCLE_COUNTERCLOCKWISE'
            ];
            return turnTypes.indexOf(turnType) !== -1;
        }

        /**
         * Checks the geojson response to verify that each feature was audited
         * @param  {geojson}  geojson Geojson object returned from Directions.get
         * @return {Boolean}  true if each feature has a valid lastModified property, otherwise false
         */
        function isAudited(geojson) {
            var lineStrings = _.filter(geojson.features, function (feature) {
                return feature.geometry.type === 'LineString';
            });
            var numLinestrings = lineStrings.length;
            var auditedLineStrings = 0;
            angular.forEach(lineStrings, function (lineString) {
                if (lineString.properties.lastModified) {
                    auditedLineStrings++;
                }
            });
            return auditedLineStrings === numLinestrings;
        }

        function defaultDate() {
            var now = new Date();
            var day = now.getDay();
            day = day < 10 ? '0' + day : day;
            var month = now.getMonth() + 1;
            month = month < 10 ? '0' + month : month;
            var year = now.getYear() + 1900;
            return month + '-' + day + '-' + year;
        }

        /**
         * Transform OTP response to our GeoJson stub
         * @param  {Object} otpResponse
         * @return {geojson}    NIH geojson expected response
         */
        function transformOtpToGeoJson(otpResponse) {

            var itineraries = otpResponse.plan.itineraries;
            var itinerary = itineraries[0];
            var lineStrings = [];
            currentRouteSummary.distanceMeters = itinerary.walkDistance;
            currentRouteSummary.timeMinutes = itinerary.duration / 60;
            var turns = 0;

            // Foreach leg in legs
            angular.forEach(itinerary.legs, function (leg) {
                _.each(leg.steps, function (step) {
                    console.log(step);
                    var stepPoints = L.PolylineUtil.decode(step.stepGeometry.points);
                    var invertedPoints = _.map(stepPoints, function(pt) {
                        return MapControl.pointToLngLat(pt);
                    });
                    lineStrings.push(turf.lineString(invertedPoints, propertiesFromStep(step)));
                });
                /*
                // get steps as points
                var steps = _.map(leg.steps, function (step) {
                    return stepToPoint(step);
                });
                var numSteps = steps.length;

                // Get legGeometry as feature collection of points
                var legPoints = L.PolylineUtil.decode(leg.legGeometry.points);
                var stepCollection = turf.featurecollection(_.map(legPoints, function (point) {
                    return turf.point([point[1], point[0]]);
                }));

                // Loop each of the legGeometry points, and add them to the previous step until we hit
                //      a legGeometry point that is nearest to the next step. At that point, save off
                //      the existing line points to the first step LineString and attach properties to it.
                var currentStep = 1;
                var stepLinePoints = [MapControl.pointToLngLat(stepCollection.features[0])];
                var numFeatures = stepCollection.features.length;
                for (var i = 0; i < numFeatures; i++) {
                    var feature = stepCollection.features[i];
                    var lngLatPoint = MapControl.pointToLngLat(feature);
                    if (currentStep < numSteps && feature === turf.nearest(steps[currentStep], stepCollection) ||
                        i === numFeatures - 1) {
                        var lastStepPoint = steps[currentStep - 1];
                        var lastStepProperties = propertiesFromStep(lastStepPoint);
                        if (isTurn(lastStepProperties.directions.turn)) {
                            turns++;
                        }
                        stepLinePoints.push(lngLatPoint);
                        lineStrings.push(turf.linestring(stepLinePoints, lastStepProperties));
                        stepLinePoints = [];
                        currentStep++;
                    }
                    stepLinePoints.push(lngLatPoint);
                }
                */
            });
            currentRouteSummary.turns = turns;
            return turf.featurecollection(lineStrings);

            /**
             * Transform OTP step object properties to NIH LineString properties
             * @param  {object} step OTP step object
             * @return {object}     NIH properties object
             */
            function propertiesFromStep(step) {
                var distance = step.properties.distance;
                var lastModified = (step.properties.lastAudited || 0) / 1000;
                var turn = step.properties.relativeDirection;
                var direction = step.properties.absoluteDirection;
                var street = step.properties.streetName;
                var text = turnText(turn, street, direction);
                var flags = angular.extend({}, step.properties);
                var keys = ['distance', 'relativeDirection', 'absoluteDirection',
                            'streetName', 'lon', 'lat'];
                angular.forEach(keys, function (key) {
                    delete flags[key];
                });
                var properties = {
                    directions: {
                       distanceMeters: distance,
                       turn: turn,
                       text: text
                    },
                    flags: flags,
                    lastModified: lastModified
                };
                return properties;
            }

            /**
             * Transform OTP step object to GeoJson Point, with properties
             * @param  {object} step
             * @return {Point}
             */
            function stepToPoint(step) {
                var lat = step.lat;
                var lon = step.lon;
                var properties = angular.extend({}, step);
                delete properties.lat;
                delete properties.lon;
                return turf.point([lon, lat], properties);
            }

            function turnText(turn, street, direction) {
                var turnTextString = '';
                var turnLower = turn.toLowerCase();
                var turnSplit = turnLower.replace('_', ' ');
                if (turn === 'DEPART') {
                    turnTextString = 'Head ' + direction.toLowerCase() + ' on ' + street;
                } else if (turn === 'CONTINUE') {
                    turnTextString = 'Continue on to ' + street;
                } else if (turn === 'ELEVATOR') {
                    turnTextString = 'Take the elevator to ' + street;
                } else if (turn.indexOf('UTURN') !== -1) {
                    turnTextString = 'Take a U-turn on to ' + street;
                } else if (turn.indexOf('LEFT') !== -1 || turn.indexOf('RIGHT') !== -1) {
                    turnTextString = 'Turn ' + turnSplit + ' on to ' + street;
                } else if (turn.indexOf('CIRCLE') !== -1) {
                    turnTextString = 'Enter the traffic circle, then exit on to ' + street;
                }
                return turnTextString;
            }
        }
    }

    angular.module('nih.routing')
    .factory('Directions', Directions);

})();
<commit_msg>Use individual step geometries<commit_after>
(function () {
    'use strict';

    /* ngInject */
    function Directions ($http, $q, $timeout, Config, MapControl, TurnAmenities) {

        var directionsUrl = Config.routing.hostname + '/otp/routers/default/plan';
        var currentRouteSummary = {
            distanceMeters: 0,
            timeMinutes: 0,
            turns: 0
        };

        var module = {
            get: get,
            getFlagIconName: getFlagIconName,
            getTurnIconName: getTurnIconName,
            getRouteSummary: getRouteSummary,
            isAudited: isAudited,
            isTurn: isTurn
        };

        return module;

        /**
         * Make a routing request from origin to destination with options
         *
         * @param origin {array} [lon, lat]
         * @param destination {array} [lon, lat]
         * @param options {object}
         * // TODO: Document options object
         */
        function get(origin, destination, options) {
            var dfd = $q.defer();
            if (!(_.isArray(origin) && origin.length >= 2)) {
                dfd.reject({ msg: 'Invalid origin parameter' });
                return dfd.promise;
            }
            if (!(_.isArray(destination) && destination.length >= 2)) {
                dfd.reject({ msg: 'Invalid destination parameter' });
                return dfd.promise;
            }
            var now = new Date();
            // This is the minimum param list (+ toPlace,fromPlace) to make a OTP plan API call
            var defaults = {
                mode: 'WALK',
                time: now.getHours() + ':' + now.getMinutes(),
                date: defaultDate(),
                arriveBy: false,
                wheelchair: false,
                showIntermediateStops: false
            };
            var params = angular.extend({}, defaults, options);
            // Swap, OTP request uses [lat,lon]
            params.fromPlace = [origin[1], origin[0]].join(',');
            params.toPlace = [destination[1], destination[0]].join(',');

            $http.get(directionsUrl, {
                params: params
            }).then(function (response) {
                if (response.data.error) {
                    //  OTP returns an array indicating which fields are outside the graph bounds.
                    var missingData = response.data.error.missing;
                    if (missingData) {
                        if (missingData.length === 2) {
                            dfd.reject({
                                msg: 'Both the requested origin and destination are outside the planner bounds.'
                            });
                        } else if (missingData[0] === 'from') {
                            dfd.reject({
                                msg: 'The requested origin point is outside the planner bounds.'
                            });
                        }  else if (missingData[0] === 'to') {
                            dfd.reject({
                                msg: 'The requested destination point is outside the planner bounds.'
                            });
                        } else {
                            dfd.reject(response.data.error);
                        }
                    } else {
                        dfd.reject(response.data.error);
                    }
                } else {
                    var geojson = transformOtpToGeoJson(response.data);
                    TurnAmenities.attach(geojson);
                    dfd.resolve(geojson);
                }
            });
            return dfd.promise;
        }

        function getFlagIconName(flagType, value) {
            // TODO: Write tests once actual icons exist
            if (!value) {
                return '';
            }

            switch (flagType) {
                case 'benches':
                    return 'glyphicon-flash';
                case 'hazards':
                    return 'glyphicon-warning-sign';
                case 'bathrooms':
                    return 'glyphicon-trash';
                default:
                    return '';
            }
        }

        function getTurnIconName(turnType) {
            // TODO: Write tests once actual icons exist
            switch (turnType) {
                case 'DEPART':
                    return 'glyphicon-flag';
                case 'CONTINUE':
                    return 'glyphicon-arrow-up';
                // Temporarily fall through to similar cases for left/right
                case 'LEFT':
                case 'SLIGHTLY_LEFT':
                case 'HARD_LEFT':
                case 'UTURN_LEFT':
                    return 'glyphicon-arrow-left';
                case 'RIGHT':
                case 'SLIGHTLY_RIGHT':
                case 'HARD_RIGHT':
                case 'UTURN_RIGHT':
                    return 'glyphicon-arrow-right';
                case 'CIRCLE_CLOCKWISE':
                case 'CIRCLE_COUNTERCLOCKWISE':
                    return 'glyphicon-repeat';
                case 'ELEVATOR':
                    return 'glyphicon-cloud-upload';
                default:
                    return 'glyphicon-remove-circle';
            }
        }

        /**
         * Get a summary for the last requested route via Directions.get,
         *     including time, distance and # of turns
         *
         * @param  {object} geojson     Geojson returned from Directions.get
         * @param  {float} walkSpeed    Walk speed in m/s, used to calculate time, optional, default 1
         * @return {[type]}             [description]
         */
        function getRouteSummary() {
            return currentRouteSummary;
        }

        function isTurn(turnType) {
            var turnTypes = [
                'LEFT', 'SLIGHTLY_LEFT', 'HARD_LEFT', 'UTURN_LEFT',
                'RIGHT', 'SLIGHTLY_RIGHT', 'HARD_RIGHT', 'UTURN_RIGHT',
                'CIRCLE_CLOCKWISE', 'CIRCLE_COUNTERCLOCKWISE'
            ];
            return turnTypes.indexOf(turnType) !== -1;
        }

        /**
         * Checks the geojson response to verify that each feature was audited
         * @param  {geojson}  geojson Geojson object returned from Directions.get
         * @return {Boolean}  true if each feature has a valid lastModified property, otherwise false
         */
        function isAudited(geojson) {
            var lineStrings = _.filter(geojson.features, function (feature) {
                return feature.geometry.type === 'LineString';
            });
            var numLinestrings = lineStrings.length;
            var auditedLineStrings = 0;
            angular.forEach(lineStrings, function (lineString) {
                if (lineString.properties.lastModified) {
                    auditedLineStrings++;
                }
            });
            return auditedLineStrings === numLinestrings;
        }

        function defaultDate() {
            var now = new Date();
            var day = now.getDay();
            day = day < 10 ? '0' + day : day;
            var month = now.getMonth() + 1;
            month = month < 10 ? '0' + month : month;
            var year = now.getYear() + 1900;
            return month + '-' + day + '-' + year;
        }

        /**
         * Transform OTP response to our GeoJson stub
         * @param  {Object} otpResponse
         * @return {geojson}    NIH geojson expected response
         */
        function transformOtpToGeoJson(otpResponse) {

            var itineraries = otpResponse.plan.itineraries;
            var itinerary = itineraries[0];
            var lineStrings = [];
            currentRouteSummary.distanceMeters = itinerary.walkDistance;
            currentRouteSummary.timeMinutes = itinerary.duration / 60;
            currentRouteSummary.turns = 0; // increment turn count when fetching properties

            angular.forEach(itinerary.legs, function (leg) {
                _.each(leg.steps, function (step) {
                    var stepPoints = L.PolylineUtil.decode(step.stepGeometry.points);
                    var invertedPoints = _.map(stepPoints, function(pt) {
                        return [pt[1], [pt[0]]];
                    });
                    lineStrings.push(turf.linestring(invertedPoints, propertiesFromStep(step)));
                });
            });
            return turf.featurecollection(lineStrings);

            /**
             * Transform OTP step object to NIH LineString properties.
             * Increments route summary turn count if turn found.
             *
             * @param  {object} step OTP step object
             * @return {object}     NIH properties object
             */
            function propertiesFromStep(step) {
                var distance = step.distance;
                var lastModified = (step.lastAudited || 0) / 1000;
                var turn = step.relativeDirection;
                var direction = step.absoluteDirection;
                var street = step.streetName;
                var text = turnText(turn, street, direction);
                var flags = angular.extend({}, step);
                var keys = ['distance', 'relativeDirection', 'absoluteDirection',
                            'streetName', 'lon', 'lat'];
                angular.forEach(keys, function (key) {
                    delete flags[key];
                });
                var properties = {
                    directions: {
                       distanceMeters: distance,
                       turn: turn,
                       text: text
                    },
                    flags: flags,
                    lastModified: lastModified
                };
                if (isTurn(turn)) {
                    currentRouteSummary.turns++;
                }
                return properties;
            }

            /**
             * Transform OTP step object to GeoJson Point, with properties
             * @param  {object} step
             * @return {Point}
             */
            function stepToPoint(step) {
                var lat = step.lat;
                var lon = step.lon;
                var properties = angular.extend({}, step);
                delete properties.lat;
                delete properties.lon;
                return turf.point([lon, lat], properties);
            }

            function turnText(turn, street, direction) {
                var turnTextString = '';
                var turnLower = turn.toLowerCase();
                var turnSplit = turnLower.replace('_', ' ');
                if (turn === 'DEPART') {
                    turnTextString = 'Head ' + direction.toLowerCase() + ' on ' + street;
                } else if (turn === 'CONTINUE') {
                    turnTextString = 'Continue on to ' + street;
                } else if (turn === 'ELEVATOR') {
                    turnTextString = 'Take the elevator to ' + street;
                } else if (turn.indexOf('UTURN') !== -1) {
                    turnTextString = 'Take a U-turn on to ' + street;
                } else if (turn.indexOf('LEFT') !== -1 || turn.indexOf('RIGHT') !== -1) {
                    turnTextString = 'Turn ' + turnSplit + ' on to ' + street;
                } else if (turn.indexOf('CIRCLE') !== -1) {
                    turnTextString = 'Enter the traffic circle, then exit on to ' + street;
                }
                return turnTextString;
            }
        }
    }

    angular.module('nih.routing')
    .factory('Directions', Directions);

})();
<|endoftext|>


<commit_before>// @flow
import axios from 'axios'
import { api } from 'neon-js'
import { createActions } from 'spunky'

import { getDefaultTokens } from '../core/nep5'

export const NEO_ID =
  'c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b'
export const GAS_ID =
  '602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7'

type Props = {
  net: string,
  address: string,
  shouldIncrementPagination: boolean
}

function parseAbstractData(data, currentUserAddress, tokens) {
  const parsedIconType = abstract => {
    if (
      abstract.address_to === currentUserAddress &&
      abstract.address_from !== 'claim'
    )
      return 'RECEIVE'
    if (abstract.address_from === 'claim') return 'CLAIM'
    return 'SEND'
  }

  const parsedAsset = abstract => {
    const token = tokens.find(token => token.scriptHash === abstract.asset)
    if (token) return token
    if (abstract.asset === NEO_ID) {
      return {
        symbol: 'NEO'
      }
    }
    if (abstract.asset === GAS_ID) {
      return {
        symbol: 'GAS'
      }
    }
    return {}
  }

  const parsedTo = abstract => {
    if (abstract.address_to === 'fees') return 'NETWORK FEES'
    if (abstract.address_to === 'mint') return 'MINT TOKENS'
    return abstract.address_to
  }

  const parsedFrom = abstract => {
    if (abstract.address_from === 'mint') return 'MINT TOKENS'
    return abstract.address_from
  }

  return data.map(abstract => {
    const asset = parsedAsset(abstract)
    const iconType = parsedIconType(abstract)
    const summary = {
      to: parsedTo(abstract),
      isNetworkFee: abstract.address_to === 'fees',
      from: parsedFrom(abstract),
      txid: abstract.txid,
      time: abstract.time,
      amount: abstract.amount,
      asset,
      label: iconType === 'CLAIM' ? 'Gas Claim' : asset.symbol,
      iconType,
      id: `_${Math.random()
        .toString(36)
        .substr(2, 9)}`
    }

    return summary
  })
}

export const ID = 'transactionHistory'

// TODO: Refactor to use immutable data types!
// hold entries in memory for infinite scroll
let entries = []
let page = 1
export default createActions(
  ID,
  ({
    net,
    address,
    shouldIncrementPagination = false
  }: Props = {}) => async () => {
    // If refresh action dispatched reset pagination
    // to grab the most recent abstracts
    if (!shouldIncrementPagination) {
      page = 1
    }

    // $FlowFixMe
    const tokens = await getDefaultTokens()
    const endpoint = api.neoscan.getAPIEndpoint(net)
    const { data } = await axios.get(
      `${endpoint}/v1/get_address_abstracts/${address}/${page}`
    )
    totalPages = data.total_pages

    const parsedEntries = parseAbstractData(data.entries, address, tokens)
    page += 1
    if (shouldIncrementPagination) {
      if (page === 1) entries = []
      entries.push(...parsedEntries)
      return entries
    }
    entries = [...parsedEntries]
    return entries
  }
)
<commit_msg>refactor: remove unused code in transactionHistoryActions.js<commit_after>// @flow
import axios from 'axios'
import { api } from 'neon-js'
import { createActions } from 'spunky'

import { getDefaultTokens } from '../core/nep5'

export const NEO_ID =
  'c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b'
export const GAS_ID =
  '602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7'

type Props = {
  net: string,
  address: string,
  shouldIncrementPagination: boolean
}

function parseAbstractData(data, currentUserAddress, tokens) {
  const parsedIconType = abstract => {
    if (
      abstract.address_to === currentUserAddress &&
      abstract.address_from !== 'claim'
    )
      return 'RECEIVE'
    if (abstract.address_from === 'claim') return 'CLAIM'
    return 'SEND'
  }

  const parsedAsset = abstract => {
    const token = tokens.find(token => token.scriptHash === abstract.asset)
    if (token) return token
    if (abstract.asset === NEO_ID) {
      return {
        symbol: 'NEO'
      }
    }
    if (abstract.asset === GAS_ID) {
      return {
        symbol: 'GAS'
      }
    }
    return {}
  }

  const parsedTo = abstract => {
    if (abstract.address_to === 'fees') return 'NETWORK FEES'
    if (abstract.address_to === 'mint') return 'MINT TOKENS'
    return abstract.address_to
  }

  const parsedFrom = abstract => {
    if (abstract.address_from === 'mint') return 'MINT TOKENS'
    return abstract.address_from
  }

  return data.map(abstract => {
    const asset = parsedAsset(abstract)
    const iconType = parsedIconType(abstract)
    const summary = {
      to: parsedTo(abstract),
      isNetworkFee: abstract.address_to === 'fees',
      from: parsedFrom(abstract),
      txid: abstract.txid,
      time: abstract.time,
      amount: abstract.amount,
      asset,
      label: iconType === 'CLAIM' ? 'Gas Claim' : asset.symbol,
      iconType,
      id: `_${Math.random()
        .toString(36)
        .substr(2, 9)}`
    }

    return summary
  })
}

export const ID = 'transactionHistory'

// TODO: Refactor to use immutable data types!
// hold entries in memory for infinite scroll
let entries = []
let page = 1
export default createActions(
  ID,
  ({
    net,
    address,
    shouldIncrementPagination = false
  }: Props = {}) => async () => {
    // If refresh action dispatched reset pagination
    // to grab the most recent abstracts
    if (!shouldIncrementPagination) {
      page = 1
    }

    // $FlowFixMe
    const tokens = await getDefaultTokens()
    const endpoint = api.neoscan.getAPIEndpoint(net)
    const { data } = await axios.get(
      `${endpoint}/v1/get_address_abstracts/${address}/${page}`
    )

    const parsedEntries = parseAbstractData(data.entries, address, tokens)
    page += 1
    if (shouldIncrementPagination) {
      if (page === 1) entries = []
      entries.push(...parsedEntries)
      return entries
    }
    entries = [...parsedEntries]
    return entries
  }
)
<|endoftext|>