<commit_before>/* vim: ts=2 sw=2
 * jQuery dragscrollable Plugin
 * version: 1.0 (25-Jun-2009)
 * Copyright (c) 2009 Miquel Herrera
 *
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 *
 */
/*
 * Modified by Kaj Magnus Lindberg.
 * Those changes indicated with "[Debiki]".
 * Dual licensed under the above licenses: MIT, GPL.
 */

;(function($){ // secure $ jQuery alias

/**
 * Adds the ability to manage elements scroll by dragging
 * one or more of its descendant elements. Options parameter
 * allow to specifically select which inner elements will
 * respond to the drag events.
 *
 * options properties:
 * ------------------------------------------------------------------------
 *  dragSelector         | jquery selector to apply to each wrapped element
 *                       | to find which will be the dragging elements.
 *                       | Defaults to '>:first' which is the first child of
 *                       | scrollable element
 * ------------------------------------------------------------------------
 *  [Debiki]
 *  scrollable           | Only scroll if the mouse-down event happened on
 *                       | something scrollable, i.e. on somethin in
 *                       | $(event.target).filter(scrollable).
 *                       | Defaults to any element, i.e. '*'.
 * ------------------------------------------------------------------------
 *  preventDefault       | Prevents the event to propagate further effectivey
 *                       | dissabling other default actions. Defaults to true
 * ------------------------------------------------------------------------
 *
 *  usage examples:
 *
 *  To add the scroll by drag to the element id=viewport when dragging its
 *  first child accepting any propagated events
 *	$('#viewport').debiki_dragscrollable();
 *
 */
// [Debiki] Renamed from dragscrollable to debiki_dragscrollable,
// so as to not clash with unmodified versions of this plugin.
$.fn.debiki_dragscrollable = function( options ){

	var settings = $.extend(
		{
			dragSelector:'>:first',
			scrollable: '*',
            preventDefault: true
		},options || {});

	var dragscroll= {
		mouseDownHandler : function(event) {
			// Only left button drag-scrolls.
			if (event.which !=1 )
				return false;

			// [Debiki]
			// Only scroll if the mouse-down event was on something scrollable.
			if (! $(event.target).filter(settings.scrollable).length)
				return true;

			// Initial coordinates will be the last when dragging
			event.data.lastCoord = {left: event.clientX, top: event.clientY};

			// [Debiki] Remember start coordinates
			event.data.startCoord = {left: event.clientX, top: event.clientY};

			$.event.add( document, "mouseup",
						 dragscroll.mouseUpHandler, event.data );
			$.event.add( document, "mousemove",
						 dragscroll.mouseMoveHandler, event.data );
			if (event.data.preventDefault) {
                event.preventDefault();
                return false;
            }
		},
		mouseMoveHandler : function(event) { // User is dragging
			// How much did the mouse move?
			var delta = {left: (event.clientX - event.data.lastCoord.left),
						 top: (event.clientY - event.data.lastCoord.top)};

			// [Debiki]
			// Find movement since drag start.
			var deltaAccum = {
					left: Math.abs(event.clientX - event.data.startCoord.left),
					top: Math.abs(event.clientY - event.data.startCoord.top)};
			// If moved alot, move faster. Then can easily move viewport
			// large distances, and still retain high precision when
			// moving small distances. (The calculations below are just
			// heuristics that works well on my computer.)
			var mul;
			if (deltaAccum.left > 9){
				mul = Math.log((deltaAccum.left - 9) / 3);
				if (mul > 1) delta.left *= mul;
			}
			if (deltaAccum.top > 5){
				mul = Math.log((deltaAccum.top - 5) / 2);
				if (mul > 1) delta.top *= mul;
			}

			// Set the scroll position relative to what ever the scroll is now
			//--------------------------------
			// This won't work in Opera, debiki-core run:
			//   event.data.scrollable.scrollLeft(
			//			event.data.scrollable.scrollLeft() - delta.left);
			//event.data.scrollable.scrollTop(
			//			event.data.scrollable.scrollTop() - delta.top);
			// with this initialization:
			//   jQuery(window).load(function(){
			//     var selectors =
			//       '.dw-thread, .dw-cmts, .dw-cmt, .dw-cmt-wrap, .dw-cmt-bdy, '+
			//       '.dw-cmt-acts';
			//     $('body').debiki_dragscrollable({ scrollable: selectors });
			//   });
			// but works with debiki-jspwiki-forum, with this init:
			//  dragRoot = $('body');
			//  scrollSelectors = '#content, #page, .leftmenu, .userbox, '+
			//      '.dw-thread, .dw-cmts, .dw-cmt, .dw-cmt-wrap, .dw-cmt-acts';
			//  $(scrollSelectors).addClass('dw-dragscrollable'); //--> cursor: move
			//  dragRoot.debiki_dragscrollable({ scrollable: scrollSelectors });
			//
			//--------------------------------
			// This works in all browsers, but... it's a hack!
			// debiki_dragscrollable wasn't applied to `window'!
			window.scrollTo($(window).scrollLeft() - delta.left,
					$(window).scrollTop() - delta.top);
			//--------------------------------

			// Save where the cursor is
			event.data.lastCoord={left: event.clientX, top: event.clientY}
			if (event.data.preventDefault) {
                event.preventDefault();
                return false;
            }

		},
		mouseUpHandler : function(event) { // Stop scrolling
			$.event.remove( document, "mousemove", dragscroll.mouseMoveHandler);
			$.event.remove( document, "mouseup", dragscroll.mouseUpHandler);
			if (event.data.preventDefault) {
                event.preventDefault();
                return false;
            }
		}
	}

	// set up the initial events
	this.each(function() {
		// closure object data for each scrollable element
		var data = {scrollable : $(this),
					acceptPropagatedEvent : settings.acceptPropagatedEvent,
                    preventDefault : settings.preventDefault }
		// Set mouse initiating event on the desired descendant
		$(this).find(settings.dragSelector).
					bind('mousedown', data, dragscroll.mouseDownHandler)
					.filter(settings.scrollable)
					.addClass('dw-dragscrollable'); // sets cursor:move.

	});
}; //end plugin debiki_dragscrollable

})( jQuery ); // confine scope
<commit_msg>Make scrolldrag work better with Opera: slow down.<commit_after>/* vim: ts=2 sw=2
 * jQuery dragscrollable Plugin
 * version: 1.0 (25-Jun-2009)
 * Copyright (c) 2009 Miquel Herrera
 *
 * Dual licensed under the MIT and GPL licenses:
 *   http://www.opensource.org/licenses/mit-license.php
 *   http://www.gnu.org/licenses/gpl.html
 *
 */
/*
 * Modified by Kaj Magnus Lindberg.
 * Those changes indicated with "[Debiki]".
 * Dual licensed under the above licenses: MIT, GPL.
 */

;(function($){ // secure $ jQuery alias

/**
 * Adds the ability to manage elements scroll by dragging
 * one or more of its descendant elements. Options parameter
 * allow to specifically select which inner elements will
 * respond to the drag events.
 *
 * options properties:
 * ------------------------------------------------------------------------
 *  dragSelector         | jquery selector to apply to each wrapped element
 *                       | to find which will be the dragging elements.
 *                       | Defaults to '>:first' which is the first child of
 *                       | scrollable element
 * ------------------------------------------------------------------------
 *  [Debiki]
 *  scrollable           | Only scroll if the mouse-down event happened on
 *                       | something scrollable, i.e. on somethin in
 *                       | $(event.target).filter(scrollable).
 *                       | Defaults to any element, i.e. '*'.
 * ------------------------------------------------------------------------
 *  preventDefault       | Prevents the event to propagate further effectivey
 *                       | dissabling other default actions. Defaults to true
 * ------------------------------------------------------------------------
 *
 *  usage examples:
 *
 *  To add the scroll by drag to the element id=viewport when dragging its
 *  first child accepting any propagated events
 *	$('#viewport').debiki_dragscrollable();
 *
 */
// [Debiki] Renamed from dragscrollable to debiki_dragscrollable,
// so as to not clash with unmodified versions of this plugin.
$.fn.debiki_dragscrollable = function( options ){

	var settings = $.extend(
		{
			dragSelector:'>:first',
			scrollable: '*',
            preventDefault: true
		},options || {});

	var dragscroll= {
		mouseDownHandler : function(event) {
			// Only left button drag-scrolls.
			if (event.which !=1 )
				return false;

			// [Debiki]
			// Only scroll if the mouse-down event was on something scrollable.
			if (! $(event.target).filter(settings.scrollable).length)
				return true;

			// Initial coordinates will be the last when dragging
			event.data.lastCoord = {left: event.clientX, top: event.clientY};

			// [Debiki] Remember start coordinates
			event.data.startCoord = {left: event.clientX, top: event.clientY};

			$.event.add( document, "mouseup",
						 dragscroll.mouseUpHandler, event.data );
			$.event.add( document, "mousemove",
						 dragscroll.mouseMoveHandler, event.data );
			if (event.data.preventDefault) {
                event.preventDefault();
                return false;
            }
		},
		mouseMoveHandler : function(event) { // User is dragging
			// How much did the mouse move?
			var delta = {left: (event.clientX - event.data.lastCoord.left),
						 top: (event.clientY - event.data.lastCoord.top)};

			// var deltaOrg = ' orig: '+ delta.left +', '+ delta.top; // debug

			// [Debiki]
			// Find movement since drag start.
			var deltaAccum = {
					left: Math.abs(event.clientX - event.data.startCoord.left),
					top: Math.abs(event.clientY - event.data.startCoord.top)};
			// If moved alot, move faster. Then can easily move viewport
			// large distances, and still retain high precision when
			// moving small distances. (The calculations below are just
			// heuristics that works well on my computer.)
			// Don't move too fast for Opera though: it re-renders the screen
			// slowly (unbearably slowly if there're lots of SVG arrows!) and
			// the mouse deltas would becom terribly huge, e.g. 1000px,
			// and then the viewport jumps randomly.
			var mul;
			if (deltaAccum.left > 9){
				mul = Math.log((deltaAccum.left - 9) / 3);
				if (mul > 1.7 && $.browser.opera) mul = 1.7;  // see comment above
				if (mul > 1) delta.left *= mul;
			}
			if (deltaAccum.top > 5){
				mul = Math.log((deltaAccum.top - 5) / 2);
				if (mul > 1.3 && $.browser.opera) mul = 1.3;
				if (mul > 1) delta.top *= mul;
			}

			/*
			console.log(
				' clnt: '+ event.clientX +', '+ event.clientY +
				' data: '+ event.data.startCoord.left +', '+ event.data.startCoord.top +
				deltaOrg +
				' accm: '+ deltaAccum.left +', '+ deltaAccum.top +
				' rslt: '+ delta.left +', '+ delta.top);
			*/

			// Set the scroll position relative to what ever the scroll is now
			//--------------------------------
			// This won't work in Opera, debiki-core run:
			//   event.data.scrollable.scrollLeft(
			//			event.data.scrollable.scrollLeft() - delta.left);
			//event.data.scrollable.scrollTop(
			//			event.data.scrollable.scrollTop() - delta.top);
			// with this initialization:
			//   jQuery(window).load(function(){
			//     var selectors =
			//       '.dw-thread, .dw-cmts, .dw-cmt, .dw-cmt-wrap, .dw-cmt-bdy, '+
			//       '.dw-cmt-acts';
			//     $('body').debiki_dragscrollable({ scrollable: selectors });
			//   });
			// but works with debiki-jspwiki-forum, with this init:
			//  dragRoot = $('body');
			//  scrollSelectors = '#content, #page, .leftmenu, .userbox, '+
			//      '.dw-thread, .dw-cmts, .dw-cmt, .dw-cmt-wrap, .dw-cmt-acts';
			//  $(scrollSelectors).addClass('dw-dragscrollable'); //--> cursor: move
			//  dragRoot.debiki_dragscrollable({ scrollable: scrollSelectors });
			//
			//--------------------------------
			// This works in all browsers, but... it's a hack!
			// debiki_dragscrollable wasn't applied to `window'!
			window.scrollTo($(window).scrollLeft() - delta.left,
					$(window).scrollTop() - delta.top);
			//--------------------------------

			// Save where the cursor is
			event.data.lastCoord={left: event.clientX, top: event.clientY}
			if (event.data.preventDefault) {
                event.preventDefault();
                return false;
            }

		},
		mouseUpHandler : function(event) { // Stop scrolling
			$.event.remove( document, "mousemove", dragscroll.mouseMoveHandler);
			$.event.remove( document, "mouseup", dragscroll.mouseUpHandler);
			if (event.data.preventDefault) {
                event.preventDefault();
                return false;
            }
		}
	}

	// set up the initial events
	this.each(function() {
		// closure object data for each scrollable element
		var data = {scrollable : $(this),
					acceptPropagatedEvent : settings.acceptPropagatedEvent,
                    preventDefault : settings.preventDefault }
		// Set mouse initiating event on the desired descendant
		$(this).find(settings.dragSelector).
					bind('mousedown', data, dragscroll.mouseDownHandler)
					.filter(settings.scrollable)
					.addClass('dw-dragscrollable'); // sets cursor:move.

	});
}; //end plugin debiki_dragscrollable

})( jQuery ); // confine scope
<|endoftext|>


<commit_before>import {div, span, label, a} from '@cycle/dom'
import Menu from '../widgets/Menu'
import checkbox from '../widgets/Checkbox'
import {transformInputs} from './helpers'

import {absSizeFromBBox} from '../../utils/formatters'
import {pluck, filter} from 'ramda'

const icon = `<svg viewBox="0 0 24 21" preserveAspectRatio="xMidYMid meet" class='icon'
version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <!-- Generator: Sketch 3.8.3 (29802) - http://www.bohemiancoding.com/sketch -->
    <title>scale</title>
    <desc>Created with Sketch.</desc>
    <defs></defs>
    <g id="icons" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
        <path d="M21.251,0 L5.149,0 L7.833,6.999 L1.701,7 L3.401,11.667 L0,21 L2.465,21 L13.606,21 L23.935,21 L18.569,6.999 L21.251,0 Z M13.241,20 L10.204,11.667 L11.904,7 L8.903,7 L8.903,6.999 L8.765,6.641 L6.603,1 L19.797,1 L17.635,6.641 L17.497,6.999 L17.635,7.356 L22.481,20 L13.241,20 L13.241,20 Z" id="scale" fill="#000000"></path>
    </g>
</svg>`

export function renderScaleUi (state) {
  const {settings, activeTool, selections, entities} = state
  const toggled = activeTool === 'scale'
  const disabled = selections.instIds.length === 0

  const snapDefaults = 0.1 // snap scaling snaps to tens of percentages
  const transformStep = settings.snapScaling ? snapDefaults : 0.1
  const precision = 2
  const min = 0.01

  // match entities by id from the selections list
  const idMatch = entity => selections.instIds.indexOf(entity.meta.id) > -1
  const transforms = pluck('transforms')(filter(idMatch, entities))
  const bounds = pluck('bounds')(filter(idMatch, entities))

  const avgPcent = pluck('sca')(transforms)
    .reduce(function (acc, cur) {
      return !acc ? cur : [acc[0] + cur[0], acc[1] + cur[1], acc[2] + cur[2]].map(x => x * 0.5)
    }, undefined)

  const avg = pluck('size')(bounds)
    .reduce(function (acc, cur) {
      return !acc ? cur : [acc[0] + cur[0], acc[1] + cur[1], acc[2] + cur[2]].map(x => x * 0.5)
    }, undefined)

  const valuePercents = (avgPcent || [0, 0, 0]).map(x => x * 100)
  const values = (avg || [0, 0, 0]).map((x, index) => x * valuePercents[index] / 100)
//
  const subTools = span('.scalingSubTools .twoColumns', [
    div('.transformsGroup',
      transformInputs({fieldName: 'sca', unit: '', showPercents: true, step: transformStep, values, valuePercents, precision, min,
        disabled: false, extraKlasses: ['absScaling'] })),
    div('.optionsGroup', [
      label('.menuContent', [
        checkbox({id: 'snapScaling', className: 'snapScaling', checked: settings.snapScaling}),
        'snap scaling'
      ])
    ]),
    div('.defaultsGroup', [
      label('.resetScaling', [
        a('.textLink', 'Reset Scaling')
      ])
    ])
  ])

  return Menu({toggled, disabled, icon, wrapperKlass: 'scaleMenu', klass: 'toScaleMode', tooltip: 'scale', tooltipPos: 'bottom', content: subTools})
}

export function view (state$) {
  return state$.map(renderScaleUi)
}
<commit_msg>fix(entityInfos): added missing class for resetScale button<commit_after>import {div, span, label, a} from '@cycle/dom'
import Menu from '../widgets/Menu'
import checkbox from '../widgets/Checkbox'
import {transformInputs} from './helpers'

import {absSizeFromBBox} from '../../utils/formatters'
import {pluck, filter} from 'ramda'

const icon = `<svg viewBox="0 0 24 21" preserveAspectRatio="xMidYMid meet" class='icon'
version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <!-- Generator: Sketch 3.8.3 (29802) - http://www.bohemiancoding.com/sketch -->
    <title>scale</title>
    <desc>Created with Sketch.</desc>
    <defs></defs>
    <g id="icons" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
        <path d="M21.251,0 L5.149,0 L7.833,6.999 L1.701,7 L3.401,11.667 L0,21 L2.465,21 L13.606,21 L23.935,21 L18.569,6.999 L21.251,0 Z M13.241,20 L10.204,11.667 L11.904,7 L8.903,7 L8.903,6.999 L8.765,6.641 L6.603,1 L19.797,1 L17.635,6.641 L17.497,6.999 L17.635,7.356 L22.481,20 L13.241,20 L13.241,20 Z" id="scale" fill="#000000"></path>
    </g>
</svg>`

export function renderScaleUi (state) {
  const {settings, activeTool, selections, entities} = state
  const toggled = activeTool === 'scale'
  const disabled = selections.instIds.length === 0

  const snapDefaults = 0.1 // snap scaling snaps to tens of percentages
  const transformStep = settings.snapScaling ? snapDefaults : 0.1
  const precision = 2
  const min = 0.01

  // match entities by id from the selections list
  const idMatch = entity => selections.instIds.indexOf(entity.meta.id) > -1
  const transforms = pluck('transforms')(filter(idMatch, entities))
  const bounds = pluck('bounds')(filter(idMatch, entities))

  const avgPcent = pluck('sca')(transforms)
    .reduce(function (acc, cur) {
      return !acc ? cur : [acc[0] + cur[0], acc[1] + cur[1], acc[2] + cur[2]].map(x => x * 0.5)
    }, undefined)

  const avg = pluck('size')(bounds)
    .reduce(function (acc, cur) {
      return !acc ? cur : [acc[0] + cur[0], acc[1] + cur[1], acc[2] + cur[2]].map(x => x * 0.5)
    }, undefined)

  const valuePercents = (avgPcent || [0, 0, 0]).map(x => x * 100)
  const values = (avg || [0, 0, 0]).map((x, index) => x * valuePercents[index] / 100)
//
  const subTools = span('.scalingSubTools .twoColumns', [
    div('.transformsGroup',
      transformInputs({fieldName: 'sca', unit: '', showPercents: true, step: transformStep, values, valuePercents, precision, min,
        disabled: false, extraKlasses: ['absScaling'] })),
    div('.optionsGroup', [
      label('.menuContent', [
        checkbox({id: 'snapScaling', className: 'snapScaling', checked: settings.snapScaling}),
        'snap scaling'
      ])
    ]),
    div('.defaultsGroup', [
      label('.resetScaling', [
        a('.textLink.resetScaling', 'Reset Scaling')
      ])
    ])
  ])

  return Menu({toggled, disabled, icon, wrapperKlass: 'scaleMenu', klass: 'toScaleMode', tooltip: 'scale', tooltipPos: 'bottom', content: subTools})
}

export function view (state$) {
  return state$.map(renderScaleUi)
}
<|endoftext|>


<commit_before>import findIndex from 'lodash/array/findIndex'
import clone from 'lodash/lang/clone'
import reject from 'lodash/collection/reject'

import {
	LOG_MESSAGE,
	LOG_ERROR,
	START_PROGRESS,
	INCREMENT_PROGRESS,
	REMOVE_NOTIFICATION,
} from '../constants/notifications'

const initialState = []

function checkForDuplicateIds(state, {payload}) {
	if (findIndex(state, {id: payload.id}) >= 0) {
		console.error(`cannot add a second notification with an existing id "${payload.id}"`)
		return true
	}
	return false
}

export default function reducer(state = initialState, action) {
	const {type, payload} = action

	switch (type) {
		case LOG_MESSAGE: {
			if (checkForDuplicateIds(state, action)) {
				return state
			}

			return [...state, {
				id: payload.id,
				message: payload.message,
				type: 'message',
			}]
		}

		case LOG_ERROR: {
			if (checkForDuplicateIds(state, action)) {
				return state
			}

			return [...state, {
				id: payload.id,
				message: payload.error.message,
				type: 'error',
			}]
		}

		case START_PROGRESS: {
			if (checkForDuplicateIds(state, action)) {
				return state
			}

			return [...state, {
				id: payload.id,
				message: payload.message,
				value: payload.value,
				max: payload.max,
				showButton: payload.showButton,
				type: 'progress',
			}]
		}

		case INCREMENT_PROGRESS: {
			// make a copy of the previous item
			const itemIndex = findIndex(state, {id: payload.id})
			const progress = clone(state[itemIndex])
			progress.value += payload.by
			progress.value = progress.value <= progress.max
				? progress.value
				: progress.max

			return [
				...state.slice(0, itemIndex),
				progress,
				...state.slice(itemIndex + 1),
			]
		}

		case REMOVE_NOTIFICATION: {
			return reject(state, {id: payload.id})
		}

		default: {
			return state
		}
	}
}
<commit_msg>tweak wording<commit_after>import findIndex from 'lodash/array/findIndex'
import clone from 'lodash/lang/clone'
import reject from 'lodash/collection/reject'

import {
	LOG_MESSAGE,
	LOG_ERROR,
	START_PROGRESS,
	INCREMENT_PROGRESS,
	REMOVE_NOTIFICATION,
} from '../constants/notifications'

const initialState = []

function checkForDuplicateIds(state, {payload}) {
	if (findIndex(state, {id: payload.id}) >= 0) {
		console.error(`cannot add a second notification with the existing id "${payload.id}"`)
		return true
	}
	return false
}

export default function reducer(state = initialState, action) {
	const {type, payload} = action

	switch (type) {
		case LOG_MESSAGE: {
			if (checkForDuplicateIds(state, action)) {
				return state
			}

			return [...state, {
				id: payload.id,
				message: payload.message,
				type: 'message',
			}]
		}

		case LOG_ERROR: {
			if (checkForDuplicateIds(state, action)) {
				return state
			}

			return [...state, {
				id: payload.id,
				message: payload.error.message,
				type: 'error',
			}]
		}

		case START_PROGRESS: {
			if (checkForDuplicateIds(state, action)) {
				return state
			}

			return [...state, {
				id: payload.id,
				message: payload.message,
				value: payload.value,
				max: payload.max,
				showButton: payload.showButton,
				type: 'progress',
			}]
		}

		case INCREMENT_PROGRESS: {
			// make a copy of the previous item
			const itemIndex = findIndex(state, {id: payload.id})
			const progress = clone(state[itemIndex])
			progress.value += payload.by
			progress.value = progress.value <= progress.max
				? progress.value
				: progress.max

			return [
				...state.slice(0, itemIndex),
				progress,
				...state.slice(itemIndex + 1),
			]
		}

		case REMOVE_NOTIFICATION: {
			return reject(state, {id: payload.id})
		}

		default: {
			return state
		}
	}
}
<|endoftext|>


<commit_before>// Extend Ember.Route to add support for sensible
// document.title integration.
Ember.Route.reopen({

  // `titleToken` can either be a static string or a function
  // that accepts a model object and returns a string (or array
  // of strings if there are multiple tokens).
  titleToken: null,

  // `title` can either be a static string or a function
  // that accepts an array of tokens and returns a string
  // that will be the document title. The `collectTitleTokens` action
  // stops bubbling once a route is encountered that has a `title`
  // defined.
  title: null,

  // Provided by Ember
  _actions: {
    collectTitleTokens: function(tokens) {
      var titleToken = this.get('titleToken');
      if (typeof titleToken === 'function') {
        titleToken = titleToken(this.currentModel);
      }

      if (Ember.isArray(titleToken)) {
        tokens.unshift.apply(this, titleToken);
      } else if (titleToken) {
        tokens.unshift(titleToken);
      }

      // If `title` exists, it signals the end of the
      // token-collection, and the title is decided right here.
      var title = this.get('title');
      if (title) {
        var finalTitle;
        if (typeof title === 'function') {
          finalTitle = title(tokens);
        } else {
          // Tokens aren't even considered... a string
          // title just sledgehammer overwrites any children tokens.
          finalTitle = title;
        }

        // Stubbable fn that sets document.title
        this.router.setTitle(finalTitle);
      } else {
        // Continue bubbling.
        return true;
      }
    }
  }
});

Ember.Router.reopen({
  updateTitle: function() {
    this.send('collectTitleTokens', []);
  }.on('didTransition'),

  setTitle: function(title) {
    if (Ember.testing) {
      this._title = title;
    } else {
      window.document.title = title;
    }
  }
});
<commit_msg>use Ember.get for case when prototype extension is not enabled<commit_after>// Extend Ember.Route to add support for sensible
// document.title integration.
Ember.Route.reopen({

  // `titleToken` can either be a static string or a function
  // that accepts a model object and returns a string (or array
  // of strings if there are multiple tokens).
  titleToken: null,

  // `title` can either be a static string or a function
  // that accepts an array of tokens and returns a string
  // that will be the document title. The `collectTitleTokens` action
  // stops bubbling once a route is encountered that has a `title`
  // defined.
  title: null,

  // Provided by Ember
  _actions: {
    collectTitleTokens: function(tokens) {
      var titleToken = Ember.get(this, 'titleToken');
      if (typeof titleToken === 'function') {
        titleToken = titleToken(Ember.get(this, 'currentModel'));
      }

      if (Ember.isArray(titleToken)) {
        tokens.unshift.apply(this, titleToken);
      } else if (titleToken) {
        tokens.unshift(titleToken);
      }

      // If `title` exists, it signals the end of the
      // token-collection, and the title is decided right here.
      var title = Ember.get(this, 'title');
      if (title) {
        var finalTitle;
        if (typeof title === 'function') {
          finalTitle = title(tokens);
        } else {
          // Tokens aren't even considered... a string
          // title just sledgehammer overwrites any children tokens.
          finalTitle = title;
        }

        // Stubbable fn that sets document.title
        this.router.setTitle(finalTitle);
      } else {
        // Continue bubbling.
        return true;
      }
    }
  }
});

Ember.Router.reopen({
  updateTitle: function() {
    this.send('collectTitleTokens', []);
  }.on('didTransition'),

  setTitle: function(title) {
    if (Ember.testing) {
      this._title = title;
    } else {
      window.document.title = title;
    }
  }
});
<|endoftext|>


<commit_before>/*
 * Waltz - Enterprise Architecture
 * Copyright (C) 2016  Khartec Ltd.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
import _ from 'lodash';
import {initialiseData} from '../common';

const initialState = {
    tabs: [],
    visibility: {
        tab: null
    },
};

function prepareTabs(categories = [], measurables = [], counts = []) {
    const countsById = _.keyBy(counts, 'id');

    const measurablesByCategory = _.chain(measurables)
        .map(m => {
            const directCount = (countsById[m.id] || { count: 0 }).count;
            return Object.assign({}, m, { directCount })
        })
        .groupBy('categoryId')
        .value();

    const tabs = _.map(categories, c => {
        return {
            category: c,
            measurables: measurablesByCategory[c.id]
        };
    });

    return _.sortBy(
        tabs,
        g => g.category.name);
}

function findFirstNonEmptyTab(tabs = []) {
    const tab = _.find(tabs, t => (t.measurables || []).length > 0);
    return _.get(tab || tabs[0], 'category.id');
}

function mkMeasurableKey(categoryId, externalId) {
    return categoryId + "_" + externalId;
}

function controller($q,
                    $state,
                    $stateParams,
                    measurableStore,
                    measurableCategoryStore,
                    measurableRatingStore,
                    staticPanelStore,
                    svgStore) {

    const vm = initialiseData(this, initialState);

    const measurablePromise = measurableStore
        .findAll();

    const measurableCategoryPromise = measurableCategoryStore
        .findAll()
        .then(cs => vm.categories = cs);

    const countPromise = measurableRatingStore
        .countByMeasurable();

    $q.all([measurablePromise, measurableCategoryPromise, countPromise])
        .then(([measurables = [], categories = [], counts = []]) => {
            vm.tabs = prepareTabs(categories, measurables, counts);
            vm.measurablesByCategoryAndExternalId = _.keyBy(measurables, m => mkMeasurableKey(m.categoryId, m.externalId));
            vm.visibility.tab = $stateParams.category || findFirstNonEmptyTab(vm.tabs);
        });

    measurableCategoryPromise
        .then((cs) => staticPanelStore.findByGroups(_.map(cs, c => `HOME.MEASURABLE.${c.id}`)))
        .then(panels => vm.panelsByCategory = _.groupBy(panels, d => d.group.replace('HOME.MEASURABLE.', '')));

    measurableCategoryPromise
        .then((cs) => svgStore.findByGroups(_.map(cs, c => `NAVAID.MEASURABLE.${c.id}`)))
        .then(diagrams => vm.diagramsByCategory = _.groupBy(diagrams, d => d.group.replace('NAVAID.MEASURABLE.', '')));

    measurableCategoryPromise
        .then(cs => vm.categoriesById = _.keyBy(cs, 'id'));

    vm.blockProcessor = b => {
        const extId = b.value;
        const measurable = vm.measurablesByCategoryAndExternalId[mkMeasurableKey(vm.visibility.tab, extId)];
        if (measurable) {
            b.block.onclick = () => $state.go('main.measurable.view', { id: measurable.id });
            angular.element(b.block).addClass('clickable');
        } else {
            console.log(`MeasurableList: Could not find measurable with external id: ${extId}`, b)
        }
    };

}

controller.$inject = [
    '$q',
    '$state',
    '$stateParams',
    'MeasurableStore',
    'MeasurableCategoryStore',
    'MeasurableRatingStore',
    'StaticPanelStore',
    'SvgDiagramStore'
];

export default {
    template: require('./measurable-list.html'),
    controller,
    controllerAs: 'ctrl'
};
<commit_msg>Measurable - Nav Aid for function misdirects user - review feedback<commit_after>/*
 * Waltz - Enterprise Architecture
 * Copyright (C) 2016  Khartec Ltd.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
import _ from 'lodash';
import {initialiseData} from '../common';
import {nest} from "d3-collection";

const initialState = {
    tabs: [],
    visibility: {
        tab: null
    },
};

function prepareTabs(categories = [], measurables = [], counts = []) {
    const countsById = _.keyBy(counts, 'id');

    const measurablesByCategory = _.chain(measurables)
        .map(m => {
            const directCount = (countsById[m.id] || { count: 0 }).count;
            return Object.assign({}, m, { directCount })
        })
        .groupBy('categoryId')
        .value();

    const tabs = _.map(categories, c => {
        return {
            category: c,
            measurables: measurablesByCategory[c.id]
        };
    });

    return _.sortBy(
        tabs,
        g => g.category.name);
}

function findFirstNonEmptyTab(tabs = []) {
    const tab = _.find(tabs, t => (t.measurables || []).length > 0);
    return _.get(tab || tabs[0], 'category.id');
}

function controller($q,
                    $state,
                    $stateParams,
                    measurableStore,
                    measurableCategoryStore,
                    measurableRatingStore,
                    staticPanelStore,
                    svgStore) {

    const vm = initialiseData(this, initialState);

    const measurablePromise = measurableStore
        .findAll();

    const measurableCategoryPromise = measurableCategoryStore
        .findAll()
        .then(cs => vm.categories = cs);

    const countPromise = measurableRatingStore
        .countByMeasurable();

    $q.all([measurablePromise, measurableCategoryPromise, countPromise])
        .then(([measurables = [], categories = [], counts = []]) => {
            vm.tabs = prepareTabs(categories, measurables, counts);
            vm.measurablesByCategoryAndExternalId = nest()
                .key(d => d.categoryId)
                .key(d => d.externalId)
                .rollup(vs => vs[0] || undefined)
                .object(measurables);
            vm.visibility.tab = $stateParams.category || findFirstNonEmptyTab(vm.tabs);
        });

    measurableCategoryPromise
        .then((cs) => staticPanelStore.findByGroups(_.map(cs, c => `HOME.MEASURABLE.${c.id}`)))
        .then(panels => vm.panelsByCategory = _.groupBy(panels, d => d.group.replace('HOME.MEASURABLE.', '')));

    measurableCategoryPromise
        .then((cs) => svgStore.findByGroups(_.map(cs, c => `NAVAID.MEASURABLE.${c.id}`)))
        .then(diagrams => vm.diagramsByCategory = _.groupBy(diagrams, d => d.group.replace('NAVAID.MEASURABLE.', '')));

    measurableCategoryPromise
        .then(cs => vm.categoriesById = _.keyBy(cs, 'id'));

    vm.blockProcessor = b => {
        const extId = b.value;
        const measurable = vm.measurablesByCategoryAndExternalId[vm.visibility.tab][extId];
        if (measurable) {
            b.block.onclick = () => $state.go('main.measurable.view', { id: measurable.id });
            angular.element(b.block).addClass('clickable');
        } else {
            console.log(`MeasurableList: Could not find measurable with external id: ${extId}`, b)
        }
    };

}

controller.$inject = [
    '$q',
    '$state',
    '$stateParams',
    'MeasurableStore',
    'MeasurableCategoryStore',
    'MeasurableRatingStore',
    'StaticPanelStore',
    'SvgDiagramStore'
];

export default {
    template: require('./measurable-list.html'),
    controller,
    controllerAs: 'ctrl'
};
<|endoftext|>


<commit_before>var setOnshape = P(Element, function(_, super_) {
	_.lineNumber = true;

	_.postInsertHandler = function() {
		super_.postInsertHandler.call(this);
		connectOnshape(false).insertAfter(this).show(0).focus(L);
		window.setTimeout(function(_this) { return function() { _this.remove(0); } }(this));
		return this;
	}
  _.toString = function() {
  	return '{onshape_set}{{' + this.argumentList().join('}{') + '}}';
  }
});

var setOnshapeVariable = P(MathOutput, function(_, super_) {
	_.klass = ['connectOnshape'];
	_.part_name = "";
	_.part_id = "";
	_.var_name = "";
	_.var_id = "";
	_.needsEvaluation = false;
	_.savedProperties = ['part_name','part_id','var_name','var_id'];

	_.init = function(part_name, part_id, var_name, var_id) {
		this.part_name = part_name;
		this.part_id = part_id;
		this.var_name = var_name;
		this.var_id = var_id;
		super_.init.call(this);
	}
	_.innerHtml = function() {
	 	return '

' + focusableHTML('CodeBlock', 'Onshape')
	 	+ '

 ()'
	  + '≡' + focusableHTML('MathQuill',  'to_store') + '  Updating Onshape

'
	 	+ answerSpan();
	}
	_.postInsertHandler = function() {
		this.varField = registerFocusable(MathQuill, this, 'to_store', { ghost: 'value', handlers: {
			enter: this.enterPressed(this),
			blur: this.submissionHandler(this)
		}});
		this.focusableItems = [[registerFocusable(CodeBlock, this, 'Onshape', { }),this.varField]];
		super_.postInsertHandler.call(this);
		this.jQ.find('.var_name').html(this.var_name);
		this.jQ.find('.part_name').html(this.part_name);
		return this;
	}
  _.toString = function() {
  	return '{setOnshapeVariable}{{' + this.argumentList().join('}{') + '}}';
  }
	_.submissionHandler = function(_this) {
		return function(mathField) {
			if(_this.needsEvaluation) {
				errors = [];
				if(_this.varField.empty())
					errors.push('Please enter a value to store.');
				if(errors.length) {
					if(_this.outputMathBox) {
						_this.worksheet.save();
						_this.outputMathBox.clear();
						_this.setError(errors.join('
'));
					}
				} else {
					if(_this.jQ) _this.jQ.removeClass('warn error');
					_this.commands = _this.genCommand(_this.varField.text());
					_this.commands.push({command: "evalf(" + _this.varField.text() + ")", nomarkup: true});
					_this.evaluate();
					_this.needsEvaluation = false;
				}
			}
		}
	}
	_.enterPressed = function(_this) {
		return function(item) {
			_this.submissionHandler(_this)();
			if(_this[R] && (_this[R] instanceof math) && _this[R].empty())
				_this[R].focus(L);
			else
				math().setImplicit().insertAfter(_this).show().focus(0);
		};
	}
	// Callback for focusable items notifying that this element has been changed
	_.changed = function(el) {
		this.needsEvaluation = true;
	}

	_.genCommand = function(to_compute) {
		var out = super_.genCommand.call(this, to_compute);
		out.push({command: "mksa_base(evalf(" + to_compute + "))", nomarkup: true});
		out.push({command: "default_units(evalf(" + to_compute + "))", nomarkup: true});
		return out;
	}
	// Hijack the evaluation chain and set the variable in Onshape
	_.evaluationFinished = function(result) {
		if(result[1].success && result[2].success) {
			if((result[1].returned.trim() == "1_m") || (result[1].returned.trim() == "1")) {
				var to_store = result[2].returned.trim().replace(/_/g,' ');
				if(!to_store.match(/^\-? *[0-9]*(\.[0-9]*)? *[a-z]*$/i) && !to_store.match(/^\-? *[0-9]*(\.[0-9]*)? *(e|E) *(\-|\+)? *[0-9]*(\.[0-9]*)? *[a-z]*$/i))
					result[0] = {success: false, returned: 'Invalid Value (' + to_store.replace(/_/g,'') + '):  Answer must be numeric.'};
				else {
					this.jQ.find('.to_store').show();
					window.ajaxRequest("/onshape/set_variable", {hash_string: this.worksheet.hash_string, eid: this.part_id, fid: this.var_id, name: this.var_name, value: to_store}, function(_this) { return function(response) {
						_this.jQ.find('.to_store').hide();
						if(!response.true_success) {
							if(response.message.indexOf("400")>=0)
								_this.setError('Invalid Value: ' + to_store + '.  Answer must be numeric, and only units of length are allowed.  Ensure variable still exists in Onshape.')
							else
							_this.setError(response.message);
						}
					}}(this), function(_this) { return function(response) {
						_this.jQ.find('.to_store').hide();
						_this.setError(response.message)
					}});
				}
			} else
				result[0] = {success: false, returned: 'Invalid Units ' + result[1].returned.replace(/_/g,' ').replace(/^1/,'') + ':  Only units of length are allowed.'};
		}
	}
});<commit_msg>Fix onshape bug<commit_after>var setOnshape = P(Element, function(_, super_) {
	_.lineNumber = true;

	_.postInsertHandler = function() {
		super_.postInsertHandler.call(this);
		connectOnshape(false).insertAfter(this).show(0).focus(L);
		window.setTimeout(function(_this) { return function() { _this.remove(0); } }(this));
		return this;
	}
  _.toString = function() {
  	return '{onshape_set}{{' + this.argumentList().join('}{') + '}}';
  }
});

var setOnshapeVariable = P(MathOutput, function(_, super_) {
	_.klass = ['connectOnshape'];
	_.part_name = "";
	_.part_id = "";
	_.var_name = "";
	_.var_id = "";
	_.needsEvaluation = false;
	_.savedProperties = ['part_name','part_id','var_name','var_id'];

	_.init = function(part_name, part_id, var_name, var_id) {
		this.part_name = part_name;
		this.part_id = part_id;
		this.var_name = var_name;
		this.var_id = var_id;
		super_.init.call(this);
	}
	_.innerHtml = function() {
	 	return '

' + focusableHTML('CodeBlock', 'Onshape')
	 	+ '

 ()'
	  + '≡' + focusableHTML('MathQuill',  'to_store') + '  Updating Onshape

'
	 	+ answerSpan();
	}
	_.postInsertHandler = function() {
		this.varField = registerFocusable(MathQuill, this, 'to_store', { ghost: 'value', handlers: {
			enter: this.enterPressed(this),
			blur: this.submissionHandler(this)
		}});
		this.focusableItems = [[registerFocusable(CodeBlock, this, 'Onshape', { }),this.varField]];
		super_.postInsertHandler.call(this);
		this.jQ.find('.var_name').html(this.var_name);
		this.jQ.find('.part_name').html(this.part_name);
		return this;
	}
  _.toString = function() {
  	return '{setOnshapeVariable}{{' + this.argumentList().join('}{') + '}}';
  }
	_.submissionHandler = function(_this) {
		return function(mathField) {
			if(_this.needsEvaluation) {
				errors = [];
				if(_this.varField.empty())
					errors.push('Please enter a value to store.');
				if(errors.length) {
					if(_this.outputMathBox) {
						_this.worksheet.save();
						_this.outputMathBox.clear();
						_this.setError(errors.join('
'));
					}
				} else {
					if(_this.jQ) _this.jQ.removeClass('warn error');
					_this.commands = _this.genCommand(_this.varField.text());
					_this.evaluate();
					_this.needsEvaluation = false;
				}
			}
		}
	}
	_.enterPressed = function(_this) {
		return function(item) {
			_this.submissionHandler(_this)();
			if(_this[R] && (_this[R] instanceof math) && _this[R].empty())
				_this[R].focus(L);
			else
				math().setImplicit().insertAfter(_this).show().focus(0);
		};
	}
	// Callback for focusable items notifying that this element has been changed
	_.changed = function(el) {
		this.needsEvaluation = true;
	}

	_.genCommand = function(to_compute) {
		var out = super_.genCommand.call(this, to_compute);
		out.push({command: "mksa_base(evalf(" + to_compute + "))", nomarkup: true});
		out.push({command: "default_units(evalf(" + to_compute + "))", nomarkup: true});
		return out;
	}
	// Hijack the evaluation chain and set the variable in Onshape
	_.evaluationFinished = function(result) {
		if(result[1].success && result[2].success) {
			if((result[1].returned.trim() == "1_m") || (result[1].returned.trim() == "1")) {
				var to_store = result[2].returned.trim().replace(/_/g,' ');
				if(!to_store.match(/^\-? *[0-9]*(\.[0-9]*)? *[a-z]*$/i) && !to_store.match(/^\-? *[0-9]*(\.[0-9]*)? *(e|E) *(\-|\+)? *[0-9]*(\.[0-9]*)? *[a-z]*$/i))
					result[0] = {success: false, returned: 'Invalid Value (' + to_store.replace(/_/g,'') + '):  Answer must be numeric.'};
				else {
					this.jQ.find('.to_store').show();
					window.ajaxRequest("/onshape/set_variable", {hash_string: this.worksheet.hash_string, eid: this.part_id, fid: this.var_id, name: this.var_name, value: to_store}, function(_this) { return function(response) {
						_this.jQ.find('.to_store').hide();
						if(!response.true_success) {
							if(response.message.indexOf("400")>=0)
								_this.setError('Invalid Value: ' + to_store + '.  Answer must be numeric, and only units of length are allowed.  Ensure variable still exists in Onshape.')
							else
							_this.setError(response.message);
						}
					}}(this), function(_this) { return function(response) {
						_this.jQ.find('.to_store').hide();
						_this.setError(response.message)
					}});
				}
			} else
				result[0] = {success: false, returned: 'Invalid Units ' + result[1].returned.replace(/_/g,' ').replace(/^1/,'') + ':  Only units of length are allowed.'};
		}
		super_.evaluationFinished.call(this, result);
		return true;
	}
});<|endoftext|>


<commit_before>// board is double array
// snake is a list w/head and tail

snake = [[1, 0], [0, 0]]; // x, y
dir = 'right';
var board = [];     // top left is 0, 0, bottom right is height-1, width-1
var food = [-1, -1];
var refreshRate = 500; // How quickly the snake moves. (ms)
var intervalId;

// initialize board and snake, and start snake movement
function initSnake() {
  if (intervalId !== null) {
    return;
  }
  board.length = width; // board[x][y]
  for (var i = 0; i < width; i++) {
    board[i] = [];
    board[i].length = height;
  }
  intervalId = window.setInterval(aiLevel0, refreshRate);
  aiLevel0();
  updateBoard();
}

function speedUp() {
  refreshRate /= 1.5; // 50% faster
  window.clearInterval(intervalId);
  intervalId = window.setInterval(aiLevel0, refreshRate);
}

function aiLevel0() {
  moveHead(dir);
  if (dir == 'right' || dir == 'left') {
    if (snake[0][0] == food[0]) {   // is the snake above or below the food?
      if (snake[0][1] < food[1]) {
        dir = 'down';
      } else {
        dir = 'up';
      }
    } else if ((snake[0][0] == width - 1) && dir == 'right') { // is the snake at the right wall
  		dir = 'down';
  	} else if ((snake[0][0] === 0) && dir == 'left') {  // is the snake at the left wall?
  		dir = 'up';
  	}
  } else if (dir == 'up' || dir == 'down') {
    if (snake[0][1] == food[1]) {   // is the snake to the right or left of the food?
      if (snake[0][0] < food[0]) {
        dir = 'right';
      } else {
        dir = 'left';
      }
    } else if ((snake[0][1] === 0) && dir == 'up') { // is the snake at the top wall
  		dir = 'right';
    } else if (snake[0][1] == height - 1 && dir == 'down') {  // is the snake at the bottom wall?
  		dir = 'left';
  	} // else dir is unchanged
  }
}

function moveHead(dir) {
  // move snake's head in given direction
  // 'right', 'left', 'up', and 'down'
  var newHead = snake[0];
  if (dir == 'up') {  // add new head above current one
    newHead[1]--;  // changes y-coordinate by -1 (going UP)
  } else if (dir == 'down') {
    newHead[1]++;
  } else if (dir == 'right') {
    newHead[0]++;
  } else if (dir == 'left') {
    newHead[0]--;
  }
//  if (newHead[0] < 0 || newHead[0] >= width || newHead[1] < 0 || newHead[1] >= height) {

//  }
  snake.unshift(newHead); // add newHead to the beginning of the list
  console.log(snake);
  updateBoard();
  if (snake[0] == food) { // if snake's head is on the food
    food = [-1, -1];
  } else {
    delTail();
  }
}

function delTail() {  // simple function to remove the tail
  console.log(snake.pop());
  updateBoard();
}

function placeFood(x, y) {
  initSnake();
  for (var s=0; s<snake.length; s++) {
		if (x == snake[s][0] && y == snake[s][1]) {
      console.log("Invalid food placement: Food collides with snake");
			return;
		}
	}
	if (food != [-1, -1]) { // already food
		console.log("Invalid food placement: There is already food");
	} else if (x >= width || y >= height || x < 0 || y < 0) { // off of board
		console.log("Invalid food placement: Food is off board");
	} else {
		food = [x, y];
		console.log("Food placed at "+x+" "+y);
    updateBoard();
	}
}
<commit_msg>Debug more!<commit_after>// board is double array
// snake is a list w/head and tail

snake = [[1, 0], [0, 0]]; // x, y
dir = 'right';
var board = [];     // top left is 0, 0, bottom right is height-1, width-1
var food = [-1, -1];
var refreshRate = 500; // How quickly the snake moves. (ms)
var intervalId;

// initialize board and snake, and start snake movement
function initSnake() {
  if (intervalId !== null) {
    return;
  }
  board.length = width; // board[x][y]
  for (var i = 0; i < width; i++) {
    board[i] = [];
    board[i].length = height;
  }
  intervalId = window.setInterval(aiLevel0, refreshRate);
  aiLevel0();
  updateBoard();
}

function speedUp() {
  refreshRate /= 1.5; // 50% faster
  window.clearInterval(intervalId);
  intervalId = window.setInterval(aiLevel0, refreshRate);
}

function aiLevel0() {
  moveHead(dir);
  if (dir == 'right' || dir == 'left') {
    if (snake[0][0] == food[0]) {   // is the snake above or below the food?
      if (snake[0][1] < food[1]) {
        dir = 'down';
      } else {
        dir = 'up';
      }
    } else if ((snake[0][0] == width - 1) && dir == 'right') { // is the snake at the right wall
  		dir = 'down';
  	} else if ((snake[0][0] === 0) && dir == 'left') {  // is the snake at the left wall?
  		dir = 'up';
  	}
  } else if (dir == 'up' || dir == 'down') {
    if (snake[0][1] == food[1]) {   // is the snake to the right or left of the food?
      if (snake[0][0] < food[0]) {
        dir = 'right';
      } else {
        dir = 'left';
      }
    } else if ((snake[0][1] === 0) && dir == 'up') { // is the snake at the top wall
  		dir = 'right';
    } else if (snake[0][1] == height - 1 && dir == 'down') {  // is the snake at the bottom wall?
  		dir = 'left';
  	} // else dir is unchanged
  }
}

function moveHead(dir) {
  // move snake's head in given direction
  // 'right', 'left', 'up', and 'down'
  var newHead = snake[0];
  if (dir == 'up') {  // add new head above current one
    newHead[1]--;  // changes y-coordinate by -1 (going UP)
  } else if (dir == 'down') {
    newHead[1]++;
  } else if (dir == 'right') {
    newHead[0]++;
  } else if (dir == 'left') {
    newHead[0]--;
  }
//  if (newHead[0] < 0 || newHead[0] >= width || newHead[1] < 0 || newHead[1] >= height) {

//  }
  snake.unshift(newHead); // add newHead to the beginning of the list
  console.log(snake);
  updateBoard();
  if (snake[0] == food) { // if snake's head is on the food
    food = [-1, -1];
  } else {
    delTail();
  }
}

function delTail() {  // simple function to remove the tail
  console.log(snake.pop());
  updateBoard();
}

function placeFood(x, y) {
  initSnake();
  console.log(food);
  for (var s=0; s<snake.length; s++) {
		if (x == snake[s][0] && y == snake[s][1]) {
      console.log("Invalid food placement: Food collides with snake");
			return;
		}
	}
	if (food != [-1, -1]) { // already food
		console.log("Invalid food placement: There is already food");
	} else if (x >= width || y >= height || x < 0 || y < 0) { // off of board
		console.log("Invalid food placement: Food is off board");
	} else {
		food = [x, y];
		console.log("Food placed at "+x+" "+y);
    updateBoard();
	}
}
<|endoftext|>


<commit_before>// Variables
var snake = [[2, 0], [1, 0], [0, 0]]; // An array of [x, y] pairs. The head is at [0], tail is at [-1].
// At level 0, the snake has 3 elements. Each level, it gains 1 starting element.
var board; // top left is 0, 0, bottom right is height-1, width-1
var food = [-10, -10];  // if there is no food, use this to avoid null pointers
// Settings
var refreshRate = 250; // How quickly the snake moves. (ms)
var intervalId;
var gameIsStopped = true;
var level = 0;
var maxLevel = 0; // Starting max level
// high scores (both time- and food-based) for each level
//var timeHighScores = [];
//var pelletHighScores = [0, 0, 0, 0, 0];
//var currentTimeScore = 0;
// HTML elements
var refreshButton = document.getElementsByClassName("refresh")[0];
var upButton = document.getElementsByClassName("upButton")[0];
var levelGauge = document.getElementsByClassName("level")[0];
var downButton = document.getElementsByClassName("downButton")[0];
var score = document.getElementsByClassName("score")[0];
var time = document.getElementsByClassName("time")[0];
var startTime = [];
var stopTime = [];
// score.innerHTML = (snake.length - level - 2);

if (document.cookie !== null) {
  cookies = document.cookie.split(';');
  for (var c=0; c<cookies.length; c++) {
    var cookie = cookies[c].split('=');
    if (cookie[0] == 'speed') {
      var speed = parseInt(cookie[1]);
      var speedSlider = document.getElementById('speedSlider');
      speedSlider.value = speed;
      showValue(speedSlider);
    }
  }
}

// Clears board & stops AI
function stopGame() {
  if (level == 4) {
    showVictory();
  }
  startTime = [];
  /*if (pelletHighScores[level] == 0 || ((snake.length - snake.level) < pelletHighScores[level])) {
    console.log(snake.length);
    console.log(pelletHighScores[level]);
    document.getElementsByClassName("highScore0")[0].innerHTML = pelletHighScores[0];
    document.getElementsByClassName("highScore1")[1].innerHTML = pelletHighScores[1];
    document.getElementsByClassName("highScore2")[2].innerHTML = pelletHighScores[2];
    document.getElementsByClassName("highScore3")[3].innerHTML = pelletHighScores[3];
    document.getElementsByClassName("highScore4")[4].innerHTML = pelletHighScores[4];
  }*/
  stopTime = new Date();
  console.log("Round ended! Advancing AI to level", level);
  maxLevel++;
  upLevel();
  refreshButton.disabled = true;
  console.log("Game stopped");
  resetGame();
}

function resetGame() {
  followLeftEdge = false;
  moves = [];
  console.log("Resetting Game");
  window.clearInterval(intervalId);
  stopTime = [];
  startTime = [];
  upButton.disabled = (level == maxLevel || level == 4);
  downButton.disabled = (level === 0);
  gameIsStopped = true;
  board = [];
  board.length = width; // board[x][y]
  for (var i = 0; i < width; i++) {
    board[i] = [];
    board[i].length = height;
  }
  food = [-10, -10];
  snake = [];
  for (var i=0; i<level+3; i++) {
    snake.unshift([i, 0]);
  }
  dir = 'right';
  updateBoard();
}

// Snake collided with self, start next AI level
function startGame() {
  stopTime = [];
  if (!gameIsStopped) {
    return;
  }
  startTime[0] = new Date().getMinutes();
  startTime[1] = new Date().getSeconds();
  refreshButton.disabled = false;
  upButton.disabled = true;
  downButton.disabled = true;
  // document.cookie = 'speed='+refreshRate+'; maxLevel='+maxLevel;
  gameIsStopped = false;
  console.log("Game started");
  // Date.getMinutes() does not work
  //console.log(startTime);
  //console.log("startTime is defined");
  intervalId = window.setInterval(ai, refreshRate, level); // calls ai(level) at refreshRate
  ai(level);
}

function changeSpeed(rate) {
  refreshRate = rate; // set to new value
  window.clearInterval(intervalId);
  if (!gameIsStopped) { // ensures only one interval going on at once
    intervalId = window.setInterval(ai, refreshRate, level); // calls ai(level) at refreshRate
    ai(level);
  }
}

function debugSnake() {
  for (var s=0; s<snake.length; s++) {
    console.log(snake[s][0], snake[s][1]);
  }
}

// String is of form x_y to allow compatibility with Board.js
function inSnakeTwice(string) {
  var count = 0;
  for (var s=0; s<snake.length; s++) {
    if (string == snake[s][0]+'_'+snake[s][1]) {
      count++;
    }
  }
  return count==2;
}

// String is of form x_y to allow compatibility with Board.js
function inSnake(string) {
  for (var s=0; s<snake.length; s++) {
    if (string == snake[s][0]+'_'+snake[s][1]) {
      return true;
    }
  }
  return false;
}

// String is of form x_y to allow compatibility with Board.js
function getSnakeIndex(string) {
  for (var s=0; s<snake.length; s++) {
    if (string == snake[s][0]+'_'+snake[s][1]) {
      return s;
    }
  }
  return -1;  // not in snake
}

function moveHead(dir) {
  //pelletHighScores[level] = snake.length - snake.level - 2;
  // move snake's head in given direction
  // 'right', 'left', 'up', and 'down'
  var newHead = snake[0].slice(0); // Clone so we don't move the head by accident.
  if (dir == 'right') {
    if (newHead[0] == width - 1) {
      throw 'cannot move right from ' + newHead[0] + ', ' + newHead[1];
    } else if (inSnake((newHead[0]+1)+'_'+newHead[1])) {
      window.clearInterval(intervalId);
      window.setTimeout(stopGame, 20*refreshRate);
    }
    newHead[0]++;
  } else if (dir == 'left') {
    if (newHead[0] === 0) {
      throw 'cannot move left from ' + newHead[0] + ', ' + newHead[1];
    } else if (inSnake((newHead[0]-1)+'_'+newHead[1])) {
      window.clearInterval(intervalId);
      window.setTimeout(stopGame, 20*refreshRate);
    }
    newHead[0]--;
  } else if (dir == 'up') {  // add new head above current one
    if (newHead[1] === 0) {
      throw 'cannot move up from ' + newHead[0] + ', ' + newHead[1];
    } else if (inSnake(newHead[0]+'_'+(newHead[1]-1))) {
      window.clearInterval(intervalId);
      window.setTimeout(stopGame, 20*refreshRate);
    }
    newHead[1]--;  // changes y-coordinate by -1 (going UP)
  } else if (dir == 'down') {
    if (newHead[1] == height - 1) {
      throw 'cannot move down from ' + newHead[0] + ', ' + newHead[1];
    } else if (inSnake(newHead[0]+'_'+(newHead[1]+1))) {
      window.clearInterval(intervalId);
      window.setTimeout(stopGame, 20*refreshRate);
    }
    newHead[1]++;
  }
  snake.unshift(newHead); // add newHead to the beginning of the list
  if (snake[0][0] == food[0] && snake[0][1] == food[1]) { // if snake's head is on the food
  food = [-10, -10];
} else {
  snake.pop(); // Remove the tail
}
updateBoard();
}

function placeFood(x, y) {
  startGame();
  if (inSnake(x+'_'+y)) {
    console.log('Invalid food placement: Food collides with snake');
  } else if (x >= width || y >= height || x < 0 || y < 0) { // off of board
    console.log('Invalid food placement: Food is off board');
  } else {
    food = [x, y];
    updateBoard();
  }
}
<commit_msg>fixed showVictory() I think<commit_after>// Variables
var snake = [[2, 0], [1, 0], [0, 0]]; // An array of [x, y] pairs. The head is at [0], tail is at [-1].
// At level 0, the snake has 3 elements. Each level, it gains 1 starting element.
var board; // top left is 0, 0, bottom right is height-1, width-1
var food = [-10, -10];  // if there is no food, use this to avoid null pointers
// Settings
var refreshRate = 250; // How quickly the snake moves. (ms)
var intervalId;
var gameIsStopped = true;
var level = 0;
var maxLevel = 0; // Starting max level
// high scores (both time- and food-based) for each level
//var timeHighScores = [];
//var pelletHighScores = [0, 0, 0, 0, 0];
//var currentTimeScore = 0;
// HTML elements
var refreshButton = document.getElementsByClassName("refresh")[0];
var upButton = document.getElementsByClassName("upButton")[0];
var levelGauge = document.getElementsByClassName("level")[0];
var downButton = document.getElementsByClassName("downButton")[0];
var score = document.getElementsByClassName("score")[0];
var time = document.getElementsByClassName("time")[0];
var startTime = [];
var stopTime = [];
// score.innerHTML = (snake.length - level - 2);

if (document.cookie !== null) {
  cookies = document.cookie.split(';');
  for (var c=0; c<cookies.length; c++) {
    var cookie = cookies[c].split('=');
    if (cookie[0] == 'speed') {
      var speed = parseInt(cookie[1]);
      var speedSlider = document.getElementById('speedSlider');
      speedSlider.value = speed;
      showValue(speedSlider);
    }
  }
}

// Clears board & stops AI
function stopGame() {
  startTime = [];
  /*if (pelletHighScores[level] == 0 || ((snake.length - snake.level) < pelletHighScores[level])) {
    console.log(snake.length);
    console.log(pelletHighScores[level]);
    document.getElementsByClassName("highScore0")[0].innerHTML = pelletHighScores[0];
    document.getElementsByClassName("highScore1")[1].innerHTML = pelletHighScores[1];
    document.getElementsByClassName("highScore2")[2].innerHTML = pelletHighScores[2];
    document.getElementsByClassName("highScore3")[3].innerHTML = pelletHighScores[3];
    document.getElementsByClassName("highScore4")[4].innerHTML = pelletHighScores[4];
  }*/
  stopTime = new Date();
  console.log("Round ended! Advancing AI to level", level);
  maxLevel++;
  upLevel();
  if (level == 5) {
    showVictory();
  }
  refreshButton.disabled = true;
  console.log("Game stopped");
  resetGame();
}

function resetGame() {
  followLeftEdge = false;
  moves = [];
  console.log("Resetting Game");
  window.clearInterval(intervalId);
  stopTime = [];
  startTime = [];
  upButton.disabled = (level == maxLevel || level == 4);
  downButton.disabled = (level === 0);
  gameIsStopped = true;
  board = [];
  board.length = width; // board[x][y]
  for (var i = 0; i < width; i++) {
    board[i] = [];
    board[i].length = height;
  }
  food = [-10, -10];
  snake = [];
  for (var i=0; i<level+3; i++) {
    snake.unshift([i, 0]);
  }
  dir = 'right';
  updateBoard();
}

// Snake collided with self, start next AI level
function startGame() {
  stopTime = [];
  if (!gameIsStopped) {
    return;
  }
  startTime[0] = new Date().getMinutes();
  startTime[1] = new Date().getSeconds();
  refreshButton.disabled = false;
  upButton.disabled = true;
  downButton.disabled = true;
  // document.cookie = 'speed='+refreshRate+'; maxLevel='+maxLevel;
  gameIsStopped = false;
  console.log("Game started");
  // Date.getMinutes() does not work
  //console.log(startTime);
  //console.log("startTime is defined");
  intervalId = window.setInterval(ai, refreshRate, level); // calls ai(level) at refreshRate
  ai(level);
}

function changeSpeed(rate) {
  refreshRate = rate; // set to new value
  window.clearInterval(intervalId);
  if (!gameIsStopped) { // ensures only one interval going on at once
    intervalId = window.setInterval(ai, refreshRate, level); // calls ai(level) at refreshRate
    ai(level);
  }
}

function debugSnake() {
  for (var s=0; s<snake.length; s++) {
    console.log(snake[s][0], snake[s][1]);
  }
}

// String is of form x_y to allow compatibility with Board.js
function inSnakeTwice(string) {
  var count = 0;
  for (var s=0; s<snake.length; s++) {
    if (string == snake[s][0]+'_'+snake[s][1]) {
      count++;
    }
  }
  return count==2;
}

// String is of form x_y to allow compatibility with Board.js
function inSnake(string) {
  for (var s=0; s<snake.length; s++) {
    if (string == snake[s][0]+'_'+snake[s][1]) {
      return true;
    }
  }
  return false;
}

// String is of form x_y to allow compatibility with Board.js
function getSnakeIndex(string) {
  for (var s=0; s<snake.length; s++) {
    if (string == snake[s][0]+'_'+snake[s][1]) {
      return s;
    }
  }
  return -1;  // not in snake
}

function moveHead(dir) {
  //pelletHighScores[level] = snake.length - snake.level - 2;
  // move snake's head in given direction
  // 'right', 'left', 'up', and 'down'
  var newHead = snake[0].slice(0); // Clone so we don't move the head by accident.
  if (dir == 'right') {
    if (newHead[0] == width - 1) {
      throw 'cannot move right from ' + newHead[0] + ', ' + newHead[1];
    } else if (inSnake((newHead[0]+1)+'_'+newHead[1])) {
      window.clearInterval(intervalId);
      window.setTimeout(stopGame, 20*refreshRate);
    }
    newHead[0]++;
  } else if (dir == 'left') {
    if (newHead[0] === 0) {
      throw 'cannot move left from ' + newHead[0] + ', ' + newHead[1];
    } else if (inSnake((newHead[0]-1)+'_'+newHead[1])) {
      window.clearInterval(intervalId);
      window.setTimeout(stopGame, 20*refreshRate);
    }
    newHead[0]--;
  } else if (dir == 'up') {  // add new head above current one
    if (newHead[1] === 0) {
      throw 'cannot move up from ' + newHead[0] + ', ' + newHead[1];
    } else if (inSnake(newHead[0]+'_'+(newHead[1]-1))) {
      window.clearInterval(intervalId);
      window.setTimeout(stopGame, 20*refreshRate);
    }
    newHead[1]--;  // changes y-coordinate by -1 (going UP)
  } else if (dir == 'down') {
    if (newHead[1] == height - 1) {
      throw 'cannot move down from ' + newHead[0] + ', ' + newHead[1];
    } else if (inSnake(newHead[0]+'_'+(newHead[1]+1))) {
      window.clearInterval(intervalId);
      window.setTimeout(stopGame, 20*refreshRate);
    }
    newHead[1]++;
  }
  snake.unshift(newHead); // add newHead to the beginning of the list
  if (snake[0][0] == food[0] && snake[0][1] == food[1]) { // if snake's head is on the food
  food = [-10, -10];
} else {
  snake.pop(); // Remove the tail
}
updateBoard();
}

function placeFood(x, y) {
  startGame();
  if (inSnake(x+'_'+y)) {
    console.log('Invalid food placement: Food collides with snake');
  } else if (x >= width || y >= height || x < 0 || y < 0) { // off of board
    console.log('Invalid food placement: Food is off board');
  } else {
    food = [x, y];
    updateBoard();
  }
}
<|endoftext|>


<commit_before>/** @module delite/Store */
define(["dcl/dcl", "dojo/when", "./Invalidating"], function (dcl, when, Invalidating) {

	var isStoreInvalidated = function (props) {
		return props.store || props.query;
	};

	var setStoreValidate = function (props) {
		props.store = props.query = false;
	};

	/**
	 * @summary
	 * Mixin for widgets for store management that creates widget render items from store items after
	 * querying the store. The receiving class must extend delite/Stateful and dojo/Evented or
	 * delite/Widget.
	 * @description
	 * Classes extending this mixin automatically create render items that are consumable by the widget
	 * from store items after querying the store. This happens each time the widget store, query or
	 * queryOptions properties are set. If that store is Observable it will be observed and render items
	 * will be automatically updated, added or deleted from the items property based on store notifications.
	 * @mixin module:delite/Store
	 * @augments {module:delite/Invalidating}
	 */
	return dcl(Invalidating, {
		/**
		 * The store that contains the items to display.
		 * @member {dstore/Store}
		 * @default null
		 */
		store: null,

		/**
		 * A query filter to apply to the store.
		 * @member {Object}
		 * @default {}
		 */
		query: {},

		/**
		 * A function that processes the store/collection and returns a new collection (to sort it,
		 * range it etc...). This processing is applied before potentially tracking the store for modifications
		 * (if Observable).
		 * Changing this function on the instance will not automatically refresh the class.
		 * @member {Function}
		 * @default identity function
		 */
		preProcessStore: function (store) { return store; },

		/**
		 * A function that processes the store/collection and returns a new collection (to sort it,
		 * range it etc...).
		 * This processing is applied after potentially tracking the store for modifications (if Observable).
		 * This allows for example to be notified of modifications that occurred outside of the range.
		 * Changing this function on the instance will not automatically refresh the class.
		 * @member {Function}
		 * @default identity function
		 */
		postProcessStore: function (store) { return store; },

		/**
		 * The render items corresponding to the store items for this widget. This is filled from the store and
		 * is not supposed to be modified directly. Initially null.
		 * @member {Object[]}
		 * @default null
		 */
		renderItems: null,

		preCreate: function () {
			// we want to be able to wait for potentially several of those properties to be set before
			// actually firing the store request
			this.addInvalidatingProperties({
					"store": "invalidateProperty",
					"query": "invalidateProperty"
				}
			);
		},

		/**
		 * Creates a store item based from the widget internal item.
		 * @param {Object} renderItem The render item
		 * @returns {Object}
		 */
		renderItemToItem: function (/*Object*/ renderItem) {
			return renderItem;
		},

		/**
		 * Returns the widget internal item for a given store item. By default it returns the store
		 * item itself.
		 * @param {Object} item The store item
		 * @returns {Object}
		 * @protected
		 */
		itemToRenderItem: function (item) {
			return item;
		},

		/**
		 * @summary
		 * This method is called once the query has been executed to initial the renderItems array
		 * with the list of initial render items.
		 * @description
		 * This method sets the renderItems property to the render items array passed as parameter. Once
		 * done, it fires a 'query-success' event.
		 * @param {Object[]} renderItems The array of initial render items to be set in the renderItems property.
		 * @protected
		 */
		initItems: function (renderItems) {
			this.renderItems = renderItems;
			this.emit("query-success", { renderItems: renderItems, cancelable: false, bubbles: true });
		},

		/**
		 * If the store parameters are invalidated, query the store, create the render items and call initItems()
		 * when ready. If an error occurs a 'query-error' event will be fired.
		 * @param props
		 * @protected
		 */
		refreshProperties: function (props) {
			if (isStoreInvalidated(props)) {
				setStoreValidate(props);
				this.queryStoreAndInitItems(this.preProcessStore, this.postProcessStore);
			}
		},

		/**
		 * @summary
		 * Query the store, create the render items and call initItems() when ready. If an error occurs
		 * a 'query-error' event will be fired.
		 * @description
		 * This method is not supposed to be called by application developer.
		 * It will be called automatically when modifying the store related properties or by the subclass
		 * if needed.
		 * @param preProcessStore A function that processes the store/collection and returns a new collection
		 * (to sort it, range it etc...), applied before tracking.
		 * @param postProcessStore A function that processes the store/collection and returns a new collection
		 * (to sort it, range it etc...), applied after tracking.
		 * @returns {Promise} If store to be processed is not null a promise that will be resolved when the loading
		 * process will be finished.
		 * @protected
		 */
		queryStoreAndInitItems: function (preProcessStore, postProcessStore) {
			this._untrack();
			if (this.store != null) {
				var collection = preProcessStore.call(this, this.store.filter(this.query));
				if (collection.track) {
					// user asked us to observe the store
					var tracked = this._tracked = collection.track();
					tracked.on("add", this._itemAdded.bind(this));
					tracked.on("update", this._itemUpdated.bind(this));
					tracked.on("remove", this._itemRemoved.bind(this));
				}
				collection = postProcessStore.call(this, collection);
				// if we have a mapping function between store item and some intermediary items use it
				return when(collection.map(function (item) {
					return this.itemToRenderItem(item);
				}, this)).then(this.initItems.bind(this), this._queryError.bind(this));
			} else {
				this.initItems([]);
			}
		},

		_queryError: function (error) {
			this.emit("query-error", { error: error, cancelable: false, bubbles: true });
		},

		_untrack: function () {
			if (this._tracked) {
				this._tracked.tracking.remove();
				this._tracked = null;
			}
		},

		destroy: function () {
			this._untrack();
		},

		/**
		 * This method is called when an item is removed from an observable store. The default
		 * implementation actually removes a renderItem from the renderItems array. This can be redefined but
		 * must not be called directly.
		 * @param {number} index The index of the render item to remove.
		 * @param {Object[]} renderItems The array of render items to remove the render item from.
		 * @protected
		 */
		itemRemoved: function (index, renderItems) {
			renderItems.splice(index, 1);
		},

		/**
		 * This method is called when an item is added in an observable store. The default
		 * implementation actually adds the renderItem to the renderItems array. This can be redefined but
		 * must not be called directly.
		 * @param {number} index The index where to add the render item.
		 * @param {Object} renderItem The render item to be added.
		 * @param {Object[]} renderItems The array of render items to add the render item to.
		 * @protected
		 */
		itemAdded: function (index, renderItem, renderItems) {
			renderItems.splice(index, 0, renderItem);
		},

		/**
		 * This method is called when an item is updated in an observable store. The default
		 * implementation actually updates the renderItem in the renderItems array. This can be redefined but
		 * must not be called directly.
		 * @param {number} index The index of the render item to update.
		 * @param {Object} renderItem The render item data the render item must be updated with.
		 * @param {Object[]} renderItems The array of render items to render item to be updated is part of.
		 * @protected
		 */
		itemUpdated: function (index, renderItem, renderItems) {
			// we want to keep the same item object and mixin new values into old object
			dcl.mix(renderItems[index], renderItem);
		},

		/**
		 * This method is called when an item is moved in an observable store. The default
		 * implementation actually moves the renderItem in the renderItems array. This can be redefined but
		 * must not be called directly.
		 * @param {number} previousIndex The previous index of the render item.
		 * @param {number} newIndex The new index of the render item.
		 * @param {Object} renderItem The render item to be moved.
		 * @param {Object[]} renderItems The array of render items to render item to be moved is part of.
		 * @protected
		 */
		itemMoved: function (previousIndex, newIndex, renderItem, renderItems) {
			// we want to keep the same item object and mixin new values into old object
			this.itemRemoved(previousIndex, renderItems);
			this.itemAdded(newIndex, renderItem, renderItems);
		},

		/**
		 * When the store is observed and an item is removed in the store this method is called to remove the
		 * corresponding render item. This can be redefined but must not be called directly.
		 * @param {Event} event The "remove" dstore/Observable event.
		 * @private
		 */
		_itemRemoved: function (event) {
			// summary:
			//
			// event: Event
			//
			// tags:
			//		protected
			if (event.previousIndex !== undefined) {
				this.itemRemoved(event.previousIndex, this.renderItems);
				this.renderItems = this.renderItems;
			}
			// if no previousIndex the items is removed outside of the range we monitor so we don't care
		},

		/**
		 * When the store is observed and an item is updated in the store this method is called to update the
		 * corresponding render item. This can be redefined but must not be called directly.
		 * @param {Event} event The "update" dstore/Observable event
		 * @private
		 */
		_itemUpdated: function (event) {
			if (event.index === undefined) {
				// this is actually a remove
				this.itemRemoved(event.previousIndex, this.renderItems);
			} else if (event.previousIndex === undefined) {
				// this is actually a add
				this.itemAdded(event.index, this.itemToRenderItem(event.target), this.renderItems);
			} else if (event.index !== event.previousIndex) {
				// this is a move
				this.itemMoved(event.previousIndex, event.index, this.itemToRenderItem(event.target), this.renderItems);
			} else {
				// we want to keep the same item object and mixin new values into old object
				this.itemUpdated(event.index, this.itemToRenderItem(event.target), this.renderItems);
			}
			// set back the modified items property
			this.renderItems = this.renderItems;
		},

		/**
		 * When the store is observed and an item is added in the store this method is called to add the
		 * corresponding render item. This can be redefined but must not be called directly.
		 * @param {Event} event The "add" dstore/Observable event.
		 * @private
		 */
		_itemAdded: function (event) {
			if (event.index !== undefined) {
				this.itemAdded(event.index, this.itemToRenderItem(event.target), this.renderItems);
				// set back the modified items property
				this.renderItems = this.renderItems;
			}
			// if no index the item is added outside of the range we monitor so we don't care
		}
	});
});
<commit_msg>Add fetch() callback from queryStoreAndInitItems(). Intended for classes like Pageable that want to process the collection bit-by-bit (one range() at a time) rather than processing the whole thing at once.<commit_after>/** @module delite/Store */
define(["dcl/dcl", "dojo/when", "./Invalidating"], function (dcl, when, Invalidating) {

	var isStoreInvalidated = function (props) {
		return props.store || props.query;
	};

	var setStoreValidate = function (props) {
		props.store = props.query = false;
	};

	/**
	 * @summary
	 * Mixin for widgets for store management that creates widget render items from store items after
	 * querying the store. The receiving class must extend delite/Stateful and dojo/Evented or
	 * delite/Widget.
	 * @description
	 * Classes extending this mixin automatically create render items that are consumable by the widget
	 * from store items after querying the store. This happens each time the widget store, query or
	 * queryOptions properties are set. If that store is Observable it will be observed and render items
	 * will be automatically updated, added or deleted from the items property based on store notifications.
	 * @mixin module:delite/Store
	 * @augments {module:delite/Invalidating}
	 */
	return dcl(Invalidating, {
		/**
		 * The store that contains the items to display.
		 * @member {dstore/Store}
		 * @default null
		 */
		store: null,

		/**
		 * A query filter to apply to the store.
		 * @member {Object}
		 * @default {}
		 */
		query: {},

		/**
		 * A function that processes the store/collection and returns a new collection (to sort it,
		 * range it etc...). This processing is applied before potentially tracking the store for modifications
		 * (if Observable).
		 * Changing this function on the instance will not automatically refresh the class.
		 * @member {Function}
		 * @default identity function
		 */
		preProcessStore: function (store) { return store; },

		/**
		 * A function that processes the store/collection and returns a new collection (to sort it,
		 * range it etc...).
		 * This processing is applied after potentially tracking the store for modifications (if Observable).
		 * This allows for example to be notified of modifications that occurred outside of the range.
		 * Changing this function on the instance will not automatically refresh the class.
		 * @member {Function}
		 * @default identity function
		 */
		postProcessStore: function (store) { return store; },

		/**
		 * The render items corresponding to the store items for this widget. This is filled from the store and
		 * is not supposed to be modified directly. Initially null.
		 * @member {Object[]}
		 * @default null
		 */
		renderItems: null,

		preCreate: function () {
			// we want to be able to wait for potentially several of those properties to be set before
			// actually firing the store request
			this.addInvalidatingProperties({
					"store": "invalidateProperty",
					"query": "invalidateProperty"
				}
			);
		},

		/**
		 * Creates a store item based from the widget internal item.
		 * @param {Object} renderItem The render item
		 * @returns {Object}
		 */
		renderItemToItem: function (/*Object*/ renderItem) {
			return renderItem;
		},

		/**
		 * Returns the widget internal item for a given store item. By default it returns the store
		 * item itself.
		 * @param {Object} item The store item
		 * @returns {Object}
		 * @protected
		 */
		itemToRenderItem: function (item) {
			return item;
		},

		/**
		 * @summary
		 * This method is called once the query has been executed to initial the renderItems array
		 * with the list of initial render items.
		 * @description
		 * This method sets the renderItems property to the render items array passed as parameter. Once
		 * done, it fires a 'query-success' event.
		 * @param {Object[]} renderItems The array of initial render items to be set in the renderItems property.
		 * @protected
		 */
		initItems: function (renderItems) {
			this.renderItems = renderItems;
			this.emit("query-success", { renderItems: renderItems, cancelable: false, bubbles: true });
		},

		/**
		 * If the store parameters are invalidated, query the store, create the render items and call initItems()
		 * when ready. If an error occurs a 'query-error' event will be fired.
		 * @param props
		 * @protected
		 */
		refreshProperties: function (props) {
			if (isStoreInvalidated(props)) {
				setStoreValidate(props);
				this.queryStoreAndInitItems(this.preProcessStore, this.postProcessStore);
			}
		},

		/**
		 * @summary
		 * Query the store, create the render items and call initItems() when ready. If an error occurs
		 * a 'query-error' event will be fired.
		 * @description
		 * This method is not supposed to be called by application developer.
		 * It will be called automatically when modifying the store related properties or by the subclass
		 * if needed.
		 * @param preProcessStore A function that processes the store/collection and returns a new collection
		 * (to sort it, range it etc...), applied before tracking.
		 * @param postProcessStore A function that processes the store/collection and returns a new collection
		 * (to sort it, range it etc...), applied after tracking.
		 * @returns {Promise} If store to be processed is not null a promise that will be resolved when the loading
		 * process will be finished.
		 * @protected
		 */
		queryStoreAndInitItems: function (preProcessStore, postProcessStore) {
			this._untrack();
			if (this.store != null) {
				var collection = preProcessStore.call(this, this.store.filter(this.query));
				if (collection.track) {
					// user asked us to observe the store
					var tracked = this._tracked = collection.track();
					tracked.on("add", this._itemAdded.bind(this));
					tracked.on("update", this._itemUpdated.bind(this));
					tracked.on("remove", this._itemRemoved.bind(this));
				}
				collection = postProcessStore.call(this, collection);
				return this.fetch(collection);
			} else {
				this.initItems([]);
			}
		},

		fetch: function (collection) {
			// summary:
			//			Called to process the items returned after querying the store

			return when(collection.map(function (item) {
				// if we have a mapping function between store item and some intermediary items use it
				return this.itemToRenderItem(item);
			}, this)).then(this.initItems.bind(this), this._queryError.bind(this));
		},

		_queryError: function (error) {
			this.emit("query-error", { error: error, cancelable: false, bubbles: true });
		},

		_untrack: function () {
			if (this._tracked) {
				this._tracked.tracking.remove();
				this._tracked = null;
			}
		},

		destroy: function () {
			this._untrack();
		},

		/**
		 * This method is called when an item is removed from an observable store. The default
		 * implementation actually removes a renderItem from the renderItems array. This can be redefined but
		 * must not be called directly.
		 * @param {number} index The index of the render item to remove.
		 * @param {Object[]} renderItems The array of render items to remove the render item from.
		 * @protected
		 */
		itemRemoved: function (index, renderItems) {
			renderItems.splice(index, 1);
		},

		/**
		 * This method is called when an item is added in an observable store. The default
		 * implementation actually adds the renderItem to the renderItems array. This can be redefined but
		 * must not be called directly.
		 * @param {number} index The index where to add the render item.
		 * @param {Object} renderItem The render item to be added.
		 * @param {Object[]} renderItems The array of render items to add the render item to.
		 * @protected
		 */
		itemAdded: function (index, renderItem, renderItems) {
			renderItems.splice(index, 0, renderItem);
		},

		/**
		 * This method is called when an item is updated in an observable store. The default
		 * implementation actually updates the renderItem in the renderItems array. This can be redefined but
		 * must not be called directly.
		 * @param {number} index The index of the render item to update.
		 * @param {Object} renderItem The render item data the render item must be updated with.
		 * @param {Object[]} renderItems The array of render items to render item to be updated is part of.
		 * @protected
		 */
		itemUpdated: function (index, renderItem, renderItems) {
			// we want to keep the same item object and mixin new values into old object
			dcl.mix(renderItems[index], renderItem);
		},

		/**
		 * This method is called when an item is moved in an observable store. The default
		 * implementation actually moves the renderItem in the renderItems array. This can be redefined but
		 * must not be called directly.
		 * @param {number} previousIndex The previous index of the render item.
		 * @param {number} newIndex The new index of the render item.
		 * @param {Object} renderItem The render item to be moved.
		 * @param {Object[]} renderItems The array of render items to render item to be moved is part of.
		 * @protected
		 */
		itemMoved: function (previousIndex, newIndex, renderItem, renderItems) {
			// we want to keep the same item object and mixin new values into old object
			this.itemRemoved(previousIndex, renderItems);
			this.itemAdded(newIndex, renderItem, renderItems);
		},

		/**
		 * When the store is observed and an item is removed in the store this method is called to remove the
		 * corresponding render item. This can be redefined but must not be called directly.
		 * @param {Event} event The "remove" dstore/Observable event.
		 * @private
		 */
		_itemRemoved: function (event) {
			// summary:
			//
			// event: Event
			//
			// tags:
			//		protected
			if (event.previousIndex !== undefined) {
				this.itemRemoved(event.previousIndex, this.renderItems);
				this.renderItems = this.renderItems;
			}
			// if no previousIndex the items is removed outside of the range we monitor so we don't care
		},

		/**
		 * When the store is observed and an item is updated in the store this method is called to update the
		 * corresponding render item. This can be redefined but must not be called directly.
		 * @param {Event} event The "update" dstore/Observable event
		 * @private
		 */
		_itemUpdated: function (event) {
			if (event.index === undefined) {
				// this is actually a remove
				this.itemRemoved(event.previousIndex, this.renderItems);
			} else if (event.previousIndex === undefined) {
				// this is actually a add
				this.itemAdded(event.index, this.itemToRenderItem(event.target), this.renderItems);
			} else if (event.index !== event.previousIndex) {
				// this is a move
				this.itemMoved(event.previousIndex, event.index, this.itemToRenderItem(event.target), this.renderItems);
			} else {
				// we want to keep the same item object and mixin new values into old object
				this.itemUpdated(event.index, this.itemToRenderItem(event.target), this.renderItems);
			}
			// set back the modified items property
			this.renderItems = this.renderItems;
		},

		/**
		 * When the store is observed and an item is added in the store this method is called to add the
		 * corresponding render item. This can be redefined but must not be called directly.
		 * @param {Event} event The "add" dstore/Observable event.
		 * @private
		 */
		_itemAdded: function (event) {
			if (event.index !== undefined) {
				this.itemAdded(event.index, this.itemToRenderItem(event.target), this.renderItems);
				// set back the modified items property
				this.renderItems = this.renderItems;
			}
			// if no index the item is added outside of the range we monitor so we don't care
		}
	});
});
<|endoftext|>


<commit_before>/* $This file is distributed under the terms of the license in /doc/license.txt$ */

$(document).ready(function(){

  // Confirmation alert for photo deletion in image upload and individual templates
  $('#photoUploadDefaultImage a.thumbnail').click(function(){
      var answer = confirm('Are you sure you want to delete your photo?');
      return answer;
  });
});<commit_msg>NIHVIVO-1776: Refactored alert error message when user delete individual's photo.<commit_after>/* $This file is distributed under the terms of the license in /doc/license.txt$ */

$(document).ready(function(){

  // Confirmation alert for photo deletion in image upload and individual templates
  $('#photoUploadDefaultImage a.thumbnail, a.delete-mainImage').click(function(){
      var answer = confirm('Are you sure you want to delete this photo?');
      return answer;
  });
});<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2019 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.comics.v2',
  name: 'DAOCreateView',
  extends: 'foam.u2.View',

  topics: [
    'finished',
    'throwError'
  ],

  documentation: `
    A configurable view to create an instance of a specified model
  `,

  axioms: [
    foam.pattern.Faceted.create()
  ],

  css:`
    ^ {
      padding: 32px
    }

    ^ .foam-u2-ActionView-back {
      display: flex;
      align-items: center;
    }

    ^account-name {
      font-size: 36px;
      font-weight: 600;
    }

    ^create-view-container {
      margin: auto;
    }
  `,

  requires: [
    'foam.u2.layout.Cols',
    'foam.u2.layout.Rows',
    'foam.u2.ControllerMode',
    'foam.u2.dialog.NotificationMessage'
  ],
  imports: [
    'stack'
  ],
  exports: [
    'controllerMode'
  ],
  properties: [
    {
      class: 'FObjectProperty',
      name: 'data'
    },
    {
      class: 'FObjectProperty',
      of: 'foam.comics.v2.DAOControllerConfig',
      name: 'config'
    },
    {
      name: 'controllerMode',
      factory: function() {
        return this.ControllerMode.CREATE;
      }
    },
    {
      class: 'foam.u2.ViewSpecWithJava',
      name: 'viewView',
      expression: function() {
        return foam.u2.detail.SectionedDetailView;
      }
    }
  ],
  actions: [
    {
      name: 'save',
      code: function() {
        this.config.dao.put(this.data).then((o) => {
          this.data = o;
          this.finished.pub();
          this.stack.back();
        }, (e) => {
          this.throwError.pub(e);
          this.add(this.NotificationMessage.create({
            message: e.message,
            type: 'error'
          }));
        });
      }
    },
  ],
  methods: [
    function initE() {
      var self = this;
      this.SUPER();
      this
        .addClass(this.myClass())
        .add(self.slot(function(config$viewBorder, config$browseTitle) {
          return self.E()
            .start(self.Rows)
              .start(self.Rows)
                // we will handle this in the StackView instead
                .startContext({ data: self.stack })
                    .tag(self.stack.BACK, {
                      buttonStyle: foam.u2.ButtonStyle.TERTIARY,
                      icon: 'images/back-icon.svg'
                    })
                .endContext()
                .start(self.Cols).style({ 'align-items': 'center' })
                  .start()
                    .add(`Create your ${config$browseTitle}`)
                      .addClass(this.myClass('account-name'))
                  .end()
                  .startContext({ data: self }).add(self.SAVE).endContext()
                .end()
              .end()
              .start(config$viewBorder)
                .start().addClass(this.myClass('create-view-container'))
                  .tag(this.viewView, { data$: self.data$ })
                .end()
              .end();
        }));
    }
  ]
});<commit_msg>Make all lifecycleAware object set lifecycleState to PENDING upon creation<commit_after>/**
 * @license
 * Copyright 2019 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.comics.v2',
  name: 'DAOCreateView',
  extends: 'foam.u2.View',

  requires: [
    'foam.nanos.auth.LifecycleState'
  ],

  topics: [
    'finished',
    'throwError'
  ],

  documentation: `
    A configurable view to create an instance of a specified model
  `,

  axioms: [
    foam.pattern.Faceted.create()
  ],

  css:`
    ^ {
      padding: 32px
    }

    ^ .foam-u2-ActionView-back {
      display: flex;
      align-items: center;
    }

    ^account-name {
      font-size: 36px;
      font-weight: 600;
    }

    ^create-view-container {
      margin: auto;
    }
  `,

  requires: [
    'foam.u2.layout.Cols',
    'foam.u2.layout.Rows',
    'foam.u2.ControllerMode',
    'foam.u2.dialog.NotificationMessage'
  ],
  imports: [
    'stack'
  ],
  exports: [
    'controllerMode'
  ],
  properties: [
    {
      class: 'FObjectProperty',
      name: 'data'
    },
    {
      class: 'FObjectProperty',
      of: 'foam.comics.v2.DAOControllerConfig',
      name: 'config'
    },
    {
      name: 'controllerMode',
      factory: function() {
        return this.ControllerMode.CREATE;
      }
    },
    {
      class: 'foam.u2.ViewSpecWithJava',
      name: 'viewView',
      expression: function() {
        return foam.u2.detail.SectionedDetailView;
      }
    }
  ],
  actions: [
    {
      name: 'save',
      code: function() {
        var clonedData = this.data.clone();

        if ( foam.nanos.auth.LifecycleAware.isInstance(clonedData) ) {
          clonedData.lifecycleState = foam.nanos.auth.LifecycleState.PENDING;
        }

        this.config.dao.put(clonedData).then((o) => {
          this.data = o;
          this.finished.pub();
          this.stack.back();
        }, (e) => {
          this.throwError.pub(e);
          this.add(this.NotificationMessage.create({
            message: e.message,
            type: 'error'
          }));
        });
      }
    },
  ],
  methods: [
    function initE() {
      var self = this;
      this.SUPER();
      this
        .addClass(this.myClass())
        .add(self.slot(function(config$viewBorder, config$browseTitle) {
          return self.E()
            .start(self.Rows)
              .start(self.Rows)
                // we will handle this in the StackView instead
                .startContext({ data: self.stack })
                    .tag(self.stack.BACK, {
                      buttonStyle: foam.u2.ButtonStyle.TERTIARY,
                      icon: 'images/back-icon.svg'
                    })
                .endContext()
                .start(self.Cols).style({ 'align-items': 'center' })
                  .start()
                    .add(`Create your ${config$browseTitle}`)
                      .addClass(this.myClass('account-name'))
                  .end()
                  .startContext({ data: self }).add(self.SAVE).endContext()
                .end()
              .end()
              .start(config$viewBorder)
                .start().addClass(this.myClass('create-view-container'))
                  .tag(this.viewView, { data$: self.data$ })
                .end()
              .end();
        }));
    }
  ]
});<|endoftext|>