<commit_before>import { map, prop, groupBy, sum, mapObjIndexed, values, pipe, uniq, pick, sortBy, reverse, find, filter, contains, isNil, concat, indexOf, remove, reduce, where, equals, omit, merge } from 'ramda'
import * as taskRecipes from '../../tasks/data/recipes'

import getCurrentOrderApplicableOrderIntentsFlattened from '../../ordering/getters/getCurrentOrderApplicableOrderIntentsFlattened'

const feathersKnex = require('feathers-knex')
const { iff } = require('feathers-hooks-common')

module.exports = function () {
  const app = this
  const db = app.get('db')

  const name = 'taskWorks'
  const options = { Model: db, name }

  app.use(name, feathersKnex(options))
  app.service(name).hooks(hooks)
}

const hooks = {
  before: {
  },
  after: {
    create: [
      iff(taskRecipeIsCompleteOrderSetup, createCastOrderIntentTaskPlan),
      iff(taskRecipeIsCompleteOrderSetup, createCommitOrderTaskPlan),
      iff(taskRecipeIsCloseOrder, createCastOrderIntentTaskWorks),
      iff(taskRecipeIsCloseOrder, createOrderPlans)
    ]
  },
  error: {}
}

function taskRecipeIsCompleteOrderSetup (hook) {
  return hook.data.taskRecipeId === 'completeOrderSetupWithPrereqs'
    || hook.data.taskRecipeId === 'completeOrderSetup'
}

function taskRecipeIsCloseOrder (hook) {
  return hook.data.taskRecipeId === 'closeOrder'
}

function createOrderPlans (hook) {
  const taskPlans = hook.app.service('taskPlans')
  const orders = hook.app.service('orders')
  const orderIntents = hook.app.service('orderIntents')
  const priceSpecs = hook.app.service('priceSpecs')
  const orderPlans = hook.app.service('orderPlans')

  const getUniqProductIds = pipe(map(prop('productId')), uniq)

  return taskPlans.get(hook.data.taskPlanId)
    .then((taskPlan) => {
      return orders.get(taskPlan.params.orderId)
      .then((order) => {
        return orderIntents.find({
          query: {
            orderId: order.id
          }
        })
      })
      .then((queriedOrderIntents) => {
        return priceSpecs.find({
          query: {
            productId: {
              $in: getUniqProductIds(queriedOrderIntents)
            }
          }
        })
        .then((queriedPriceSpecs) => {
          // need 'state' and 'props' objects for the getters
          const state = {
            orderIntents: queriedOrderIntents,
            priceSpecs: queriedPriceSpecs
          }
          const props = { taskPlan }

          const flattenedOrderIntents = getCurrentOrderApplicableOrderIntentsFlattened(state, props)
          return Promise.all(
            flattenedOrderIntents.map((orderIntent) => {
              return orderPlans.create(
                omit(['id', 'desiredQuantity'], merge(orderIntent, { quantity: orderIntent.desiredQuantity }))
              )
            })
          )
        })
      })
    })
    .then(() => hook)
}

function createCastOrderIntentTaskPlan (hook) {
  const taskPlans = hook.app.service('taskPlans')
  const orders = hook.app.service('orders')
  const relationships = hook.app.service('relationships')
  const taskRecipeId = taskRecipes.castIntent.id
  const completeOrderTaskPlanId = hook.data.taskPlanId

  return taskPlans.get(completeOrderTaskPlanId)
  .then((completeOrderTaskPlan) => {
    const orderId = completeOrderTaskPlan.params.orderId
    return orders.get(orderId)
  })
  .then((order) => {
    const { id, consumerAgentId, supplierAgentId, adminAgentId } = order
    const params = { orderId: id, consumerAgentId, supplierAgentId, adminAgentId }
    return Promise.all([
      Promise.resolve(params),
      relationships.find({
        query: {
          sourceId: consumerAgentId,
          relationshipType: 'member'
        }
      })
    ])
  })
  .then(([params, relationships]) => {
    return Promise.all(
      map((relationship) => {
        const assigneeId = prop('targetId', relationship)
        return taskPlans.create({ taskRecipeId, params, assigneeId })
      }, relationships)
    )
  })
  .then(() => hook)
}

function createCommitOrderTaskPlan (hook) {
  const taskPlans = hook.app.service('taskPlans')
  const orders = hook.app.service('orders')
  const taskRecipeId = taskRecipes.commitOrder.id
  const completedTaskPlanId = hook.result.taskPlanId

  return taskPlans.get(completedTaskPlanId)
    .then((completedTaskPlan) => {
      const { orderId } = completedTaskPlan.params
      return orders.get(orderId)
    })
    .then((order) => {
      const { id, adminAgentId } = order
      const params = {
        orderId: id,
      }
      return taskPlans.create({ taskRecipeId, params, assigneeId: adminAgentId })
    })
    .then(() => {
      return hook
    })
}

function createCastOrderIntentTaskWorks (hook) {
  // order has closed, get all relevant taskPlans, create a taskWork for em
  const taskPlans = hook.app.service('taskPlans')
  const taskWorks = hook.app.service('taskWorks')
  const closeOrderTaskPlanId = hook.data.taskPlanId

  return taskPlans.get(closeOrderTaskPlanId)
    .then((closeOrderTaskPlan) => {
      const orderId = closeOrderTaskPlan.params.orderId
      // const params = { orderId, consumerAgentId: groupId, supplierAgentId }

      // TODO: IK: feels like a pretty sub-optimal way of querying, probably makes sense to have orderId as it's own column
      return taskPlans.find({
        query: {
          taskRecipeId: 'castIntent'
          // TODO: IK: can't compare JSON values in postgres, need another way to do it
          // params: {
          //   $like: `%"orderId":${orderId}%`
          // }
        }
      })
        .then((taskPlans) => {
          // TODO: IK: can't compare JSON values in postgres, need another way to do it
          const currentOrderTaskPlans = filter((plan) => { return plan.params.orderId === orderId }, taskPlans)
          return Promise.all(currentOrderTaskPlans.map((taskPlan) => {
            const { id, taskRecipeId, assigneeId, params } = taskPlan
            return taskWorks.create({
              taskPlanId: id,
              taskRecipeId,
              workerAgentId: assigneeId,
              params
            })
          }))
        })
    })
    .then(() => hook)
}
<commit_msg>feat(orders): fire welcome/start order emails at point of starting order. includes relevant queries, and token creation<commit_after>import { map, prop, groupBy, sum, mapObjIndexed, values, pipe, uniq, pick, sortBy, reverse, find, filter, contains, isNil, concat, indexOf, remove, reduce, where, equals, omit, merge, not } from 'ramda'
import * as taskRecipes from '../../tasks/data/recipes'

import getCurrentOrderApplicableOrderIntentsFlattened from '../../ordering/getters/getCurrentOrderApplicableOrderIntentsFlattened'

const feathersKnex = require('feathers-knex')
const { iff } = require('feathers-hooks-common')

module.exports = function () {
  const app = this
  const db = app.get('db')

  const name = 'taskWorks'
  const options = { Model: db, name }

  app.use(name, feathersKnex(options))
  app.service(name).hooks(hooks)
}

const hooks = {
  before: {
  },
  after: {
    create: [
      iff(taskRecipeIsCompleteOrderSetup, createCastOrderIntentTaskPlan),
      iff(taskRecipeIsCompleteOrderSetup, createCommitOrderTaskPlan),
      iff(taskRecipeIsStartOrder, sendStartOrderEmails),
        // iff(userHasNotSetPassword,
        //   sendWelcomeEmail,
        //   sendOrderEmail)
        // ),
      iff(taskRecipeIsCloseOrder, createCastOrderIntentTaskWorks),
      iff(taskRecipeIsCloseOrder, createOrderPlans)
    ]
  },
  error: {}
}

function taskRecipeIsCompleteOrderSetup (hook) {
  return hook.data.taskRecipeId === 'completeOrderSetupWithPrereqs'
    || hook.data.taskRecipeId === 'completeOrderSetup'
}

function taskRecipeIsStartOrder (hook) {
  return hook.data.taskRecipeId === 'startOrder'
}

function taskRecipeIsCloseOrder (hook) {
  return hook.data.taskRecipeId === 'closeOrder'
}

function createOrderPlans (hook) {
  const taskPlans = hook.app.service('taskPlans')
  const orders = hook.app.service('orders')
  const orderIntents = hook.app.service('orderIntents')
  const priceSpecs = hook.app.service('priceSpecs')
  const orderPlans = hook.app.service('orderPlans')

  const getUniqProductIds = pipe(map(prop('productId')), uniq)

  return taskPlans.get(hook.data.taskPlanId)
    .then((taskPlan) => {
      return orders.get(taskPlan.params.orderId)
      .then((order) => {
        return orderIntents.find({
          query: {
            orderId: order.id
          }
        })
      })
      .then((queriedOrderIntents) => {
        return priceSpecs.find({
          query: {
            productId: {
              $in: getUniqProductIds(queriedOrderIntents)
            }
          }
        })
        .then((queriedPriceSpecs) => {
          // need 'state' and 'props' objects for the getters
          const state = {
            orderIntents: queriedOrderIntents,
            priceSpecs: queriedPriceSpecs
          }
          const props = { taskPlan }

          const flattenedOrderIntents = getCurrentOrderApplicableOrderIntentsFlattened(state, props)
          return Promise.all(
            flattenedOrderIntents.map((orderIntent) => {
              return orderPlans.create(
                omit(['id', 'desiredQuantity'], merge(orderIntent, { quantity: orderIntent.desiredQuantity }))
              )
            })
          )
        })
      })
    })
    .then(() => hook)
}

function sendStartOrderEmails (hook) {
  console.log('sendStartOrderEmails', hook.data)
  const taskPlans = hook.app.service('taskPlans')
  const orders = hook.app.service('orders')
  const relationships = hook.app.service('relationships')
  // const agents = hook.app.service('agents')
  const credentials = hook.app.service('credentials')
  const tokens = hook.app.service('tokens')

  const hasPassword = (credential) => {
    console.log('checking if credential has password', credential)
    return not(isNil(credential.password))
  }

  const sendEmailBasedOnPasswordStatus = (credential) => {
    if (hasPassword(credential)) {
      console.log('has password, sending start order email')
    } else {
      console.log(`doesn't have password, sending password email`)
    }
  }

  return taskPlans.get(hook.data.taskPlanId)
  .then((taskPlanResult) => {
    console.log('found taskPlan', taskPlanResult)
    return orders.get(taskPlanResult.params.orderId)
    .then((orderResult) => {
      console.log('related order', orderResult)
      return relationships.find({
        query: {
          sourceId: orderResult.consumerAgentId,
          relationshipType: 'member'
        }
      })
      .then((relationshipResults) => {
        console.log('member relationshipResults', relationshipResults)
        const agentIds = map((r) => r.targetId, relationshipResults)
        console.log('agentIds', agentIds)
        return credentials.find({
          query: {
            agentId: {
              $in: agentIds
            }
          }
        })
        .then((credentialResults) => {
          console.log('member credentialResults', credentialResults)
          return Promise.all(map(sendEmailBasedOnPasswordStatus, credentialResults))
          .then(() => {
            return hook
          })
        })
      })
    })
  })
}

function createPatchCredentialsTokenAndInviteMail (hook) {
  // userHasNotSetPassword { taskPlanId: 80,
  // taskRecipeId: 'startOrder',
  // workerAgentId: 1,
  // params: {} }
  const appConfig = hook.app.get('app')
  const { id, agentId, email } = hook.result

  // 1. find order with adminAgentId: workerAgentId,
  // taskPlanId: taskPlanId

  // 2. get buying group from order, consumerGroupId

  // 3. find all members (Agents) for this buying group, through relationships,
  // where sourceId: consumerGroupId,
  // relationshipType: 'member',
  // get all targetIds

  // 6. get all Credentials for these Agents

  // 5. check each Credential with this function

  // TODO: IK: get the agent name, admin name, group name
  return hook.app.service('tokens').create({
    agentId,
    service: 'credentials',
    method: 'patch',
    params: { serviceId: id }
  })
  // TODO: add .env variables to change from 'Tapin' in copy
  .then((token) => {
    return hook.app.service('mailer').create({
      from: `${appConfig.email}`,
      to: email || 'no@email.com',
      subject: `You're invited to join ${appConfig.name}!`,
      html: `
        Hi. You're invited to join a group on ${appConfig.name}!

        ${appConfig.bodyText}

        Click here to set your password and start buying together!
      `
    })
  })
  .then(() => hook)
}

function createCastOrderIntentTaskPlan (hook) {
  const taskPlans = hook.app.service('taskPlans')
  const orders = hook.app.service('orders')
  const relationships = hook.app.service('relationships')
  const taskRecipeId = taskRecipes.castIntent.id
  const completeOrderTaskPlanId = hook.data.taskPlanId

  return taskPlans.get(completeOrderTaskPlanId)
  .then((completeOrderTaskPlan) => {
    const orderId = completeOrderTaskPlan.params.orderId
    return orders.get(orderId)
  })
  .then((order) => {
    const { id, consumerAgentId, supplierAgentId, adminAgentId } = order
    const params = { orderId: id, consumerAgentId, supplierAgentId, adminAgentId }
    return Promise.all([
      Promise.resolve(params),
      relationships.find({
        query: {
          sourceId: consumerAgentId,
          relationshipType: 'member'
        }
      })
    ])
  })
  .then(([params, relationships]) => {
    return Promise.all(
      map((relationship) => {
        const assigneeId = prop('targetId', relationship)
        return taskPlans.create({ taskRecipeId, params, assigneeId })
      }, relationships)
    )
  })
  .then(() => hook)
}

function createCommitOrderTaskPlan (hook) {
  const taskPlans = hook.app.service('taskPlans')
  const orders = hook.app.service('orders')
  const taskRecipeId = taskRecipes.commitOrder.id
  const completedTaskPlanId = hook.result.taskPlanId

  return taskPlans.get(completedTaskPlanId)
    .then((completedTaskPlan) => {
      const { orderId } = completedTaskPlan.params
      return orders.get(orderId)
    })
    .then((order) => {
      const { id, adminAgentId } = order
      const params = {
        orderId: id,
      }
      return taskPlans.create({ taskRecipeId, params, assigneeId: adminAgentId })
    })
    .then(() => {
      return hook
    })
}

function createCastOrderIntentTaskWorks (hook) {
  // order has closed, get all relevant taskPlans, create a taskWork for em
  const taskPlans = hook.app.service('taskPlans')
  const taskWorks = hook.app.service('taskWorks')
  const closeOrderTaskPlanId = hook.data.taskPlanId

  return taskPlans.get(closeOrderTaskPlanId)
    .then((closeOrderTaskPlan) => {
      const orderId = closeOrderTaskPlan.params.orderId
      // const params = { orderId, consumerAgentId: groupId, supplierAgentId }

      // TODO: IK: feels like a pretty sub-optimal way of querying, probably makes sense to have orderId as it's own column
      return taskPlans.find({
        query: {
          taskRecipeId: 'castIntent'
          // TODO: IK: can't compare JSON values in postgres, need another way to do it
          // params: {
          //   $like: `%"orderId":${orderId}%`
          // }
        }
      })
        .then((taskPlans) => {
          // TODO: IK: can't compare JSON values in postgres, need another way to do it
          const currentOrderTaskPlans = filter((plan) => { return plan.params.orderId === orderId }, taskPlans)
          return Promise.all(currentOrderTaskPlans.map((taskPlan) => {
            const { id, taskRecipeId, assigneeId, params } = taskPlan
            return taskWorks.create({
              taskPlanId: id,
              taskRecipeId,
              workerAgentId: assigneeId,
              params
            })
          }))
        })
    })
    .then(() => hook)
}
<|endoftext|>


<commit_before>define('lyria/achievement/manager', ['jquery', 'lyria/achievement', 'lyria/template/engine', 'lyria/template/list', 'lyria/localization'], function($, Achievement, TemplateEngine, templateList, Localization) {

  var achievementStore = {};

  var AchievementManager = {
    localization: new Localization(),
    add: function(achievement) {
      if ( achievement instanceof Achievement) {
        if (!Object.hasOwnProperty.call(achievementStore, achievement.name)) {
          achievementStore[achievement.name] = achievement;
        }
      }
    },
    progressSeparator: '/',
    viewport: null,
    remove: function(achName) {
      if (Object.hasOwnProperty.call(achievementStore, achName)) {
        delete achievementStore[achName];
      }
    },
    list: function() {
      //TemplateEngine.compile()
    },
    show: function(achName) {
      var currentAchievement = achievementStore[achName];

      if (currentAchievement == null) {
        throw new Error('Achievement ' + achName + ' not found.');
      }

      var title = (AchievementManager.localization.exists(currentAchievement.name)) ? AchievementManager.localization.t(currentAchievement.name) : currentAchievement.name;
      var description = (AchievementManager.localization.exists(currentAchievement.name + '-description')) ? AchievementManager.localization.t(currentAchievement.name + '-description') : currentAchievement.description;

      var achTemplate = TemplateEngine.compile(templateList['achievement'])({
        id: currentAchievement.id,
        className: currentAchievement.name,
        title: title,
        description: description,
        offscreen: true,
        progressable: (currentAchievement.progress.max > 0 && currentAchievement.progress.max !== 1),
        max: currentAchievement.progress.max,
        current: currentAchievement.progress.current,
        separator: AchievementManager.progressSeparator
      });

      if (AchievementManager.viewport == null) {
        $('body').append(achTemplate);
      } else {
        AchievementManager.viewport.$element.append(achTemplate);
      }

      var $currentAchievement = $('#' + currentAchievement.id);

      // Refactor this if lyria/tween is available
      $currentAchievement.css({
        opacity: 0.0
      });
      $currentAchievement.animate({
        opacity: 1.0
      }, 600).delay(2000).animate({
        opacity: 0.0
      }, 600, function() {
        $currentAchievement.remove();
      });
    },
    toJSON: function() {
      var key, value;
      var result = {};

      for (key in achievementStore) {
        value = achievementStore[key];
        result[key] = value.toJSON();
      }

      return result;
    },
    toString: function() {
      var result = '';

      try {
        result = JSON.stringify(AchievementManager.toJSON());
      } catch (e) {
        throw new Error('Error while serializing achievements in AchievementManager: ' + e);
      }
      return result;
    },
    fromJSON: function(achievements) {
      var key, value;

      for (key in achievements) {
        value = achievements[key];
        AchievementManager.add(value);
      }
    },
    fromString: function(achievements) {
      var deserializedValue = {};

      try {
        deserializedValue = JSON.parse(achievements);
      } catch (e) {
        throw new Error('Error while deserializing achivements in AchievementManager: ' + e);
      }

      return AchievementManager.fromJSON(deserializedValue);
    }
  };

  Object.defineProperty(AchievementManager, 'length', {
    get: function() {
      return Object.keys(achievementStore).length;
    }
  });

  Object.defineProperty(AchievementManager, 'unlockedCount', {
    get: function() {
      var counter = 0;

      $.each(achievementStore, function(key, value) {
        if (value.unlocked) {
          counter++;
        }
      });

      return counter;
    }
  });

  Object.defineProperty(AchievementManager, 'store', {
    get: function() {
      return achievementStore;
    }
  });

  return AchievementManager;

});
<commit_msg>lyria/achievement/manager is now an anonymous module<commit_after>define(['jquery', 'lyria/achievement', 'lyria/template/engine', 'lyria/template/list', 'lyria/localization'], function($, Achievement, TemplateEngine, templateList, Localization) {

  var achievementStore = {};

  var AchievementManager = {
    localization: new Localization(),
    add: function(achievement) {
      if ( achievement instanceof Achievement) {
        if (!Object.hasOwnProperty.call(achievementStore, achievement.name)) {
          achievementStore[achievement.name] = achievement;
        }
      }
    },
    progressSeparator: '/',
    viewport: null,
    remove: function(achName) {
      if (Object.hasOwnProperty.call(achievementStore, achName)) {
        delete achievementStore[achName];
      }
    },
    list: function() {
      //TemplateEngine.compile()
    },
    show: function(achName) {
      var currentAchievement = achievementStore[achName];

      if (currentAchievement == null) {
        throw new Error('Achievement ' + achName + ' not found.');
      }

      var title = (AchievementManager.localization.exists(currentAchievement.name)) ? AchievementManager.localization.t(currentAchievement.name) : currentAchievement.name;
      var description = (AchievementManager.localization.exists(currentAchievement.name + '-description')) ? AchievementManager.localization.t(currentAchievement.name + '-description') : currentAchievement.description;

      var achTemplate = TemplateEngine.compile(templateList['achievement'])({
        id: currentAchievement.id,
        className: currentAchievement.name,
        title: title,
        description: description,
        offscreen: true,
        progressable: (currentAchievement.progress.max > 0 && currentAchievement.progress.max !== 1),
        max: currentAchievement.progress.max,
        current: currentAchievement.progress.current,
        separator: AchievementManager.progressSeparator
      });

      if (AchievementManager.viewport == null) {
        $('body').append(achTemplate);
      } else {
        AchievementManager.viewport.$element.append(achTemplate);
      }

      var $currentAchievement = $('#' + currentAchievement.id);

      // Refactor this if lyria/tween is available
      $currentAchievement.css({
        opacity: 0.0
      });
      $currentAchievement.animate({
        opacity: 1.0
      }, 600).delay(2000).animate({
        opacity: 0.0
      }, 600, function() {
        $currentAchievement.remove();
      });
    },
    toJSON: function() {
      var key, value;
      var result = {};

      for (key in achievementStore) {
        value = achievementStore[key];
        result[key] = value.toJSON();
      }

      return result;
    },
    toString: function() {
      var result = '';

      try {
        result = JSON.stringify(AchievementManager.toJSON());
      } catch (e) {
        throw new Error('Error while serializing achievements in AchievementManager: ' + e);
      }
      return result;
    },
    fromJSON: function(achievements) {
      var key, value;

      for (key in achievements) {
        value = achievements[key];
        AchievementManager.add(value);
      }
    },
    fromString: function(achievements) {
      var deserializedValue = {};

      try {
        deserializedValue = JSON.parse(achievements);
      } catch (e) {
        throw new Error('Error while deserializing achivements in AchievementManager: ' + e);
      }

      return AchievementManager.fromJSON(deserializedValue);
    }
  };

  Object.defineProperty(AchievementManager, 'length', {
    get: function() {
      return Object.keys(achievementStore).length;
    }
  });

  Object.defineProperty(AchievementManager, 'unlockedCount', {
    get: function() {
      var counter = 0;

      $.each(achievementStore, function(key, value) {
        if (value.unlocked) {
          counter++;
        }
      });

      return counter;
    }
  });

  Object.defineProperty(AchievementManager, 'store', {
    get: function() {
      return achievementStore;
    }
  });

  return AchievementManager;

});
<|endoftext|>


<commit_before>/**
 * Provides Form and input features.
 *
 * @module furnace
 * @submodule furnace-forms
 */
import Panel from './panel';
import Lookup from 'furnace-forms/utils/lookup-class';
import Proxy from 'furnace-forms/proxy';

/**
 * Form control component proxy
 *
 * @class Form
 * @namespace Furnace.Forms.Controls
 * @extends Furnace.Forms.Controls.Abstract
 * @protected
 */
var Form = Panel.extend({
	_component: 'form',

	_componentType: 'forms',

	_syncFromSource : true,

	_syncToSource : true,

	_observer : null,

	_validationCache : null,

	_messageCache : null,

	_setValidations : function(result,silent) {
		var validations=result.get('validations');
		var messages=result.get('messages');

		this._validationCache=validations;
		this._messageCache=messages;

		for(var name in validations) {
			if(this._controlsByPath[name]!==undefined) {
				this._controlsByPath[name].invoke('setValid',validations[name]);
				if(messages[name]!==undefined) {
					this._controlsByPath[name].invoke('setMessages',messages[name],silent);
				}
				else {
					this._controlsByPath[name].invoke('setMessages',null);
				}
			}
		}
	},

	_name: Ember.computed.oneWay('_modelName'),

	_modelPath : Ember.computed('_form,_name',function() {
		if(!this._form)
			return this.get('_name');
		else
			return this._panel.get('_path');
		return '';
	}),

	_controlsByPath: null,

	_forms: null,

	actions : {
		validate : function(paths) {

			this._validate(paths);

		},

		reset : function(action) {
			this._reset();
			if(this._observer)
				this._observer.run();
			if(action)
				this.send(action,form);
		},

		submit: function(action) {
			action= action===undefined  ?  'submit' : action;
			var form=this;
			this._validate().then(function(valid) {
				if(valid) {
					form._submit();
					form.get('target').send(action,form);
				} else {
					// Notice about invalid for development?
					Ember.Logger.info(form+' was submitted but was marked invalid');
				}

			});

		}
	},

	_registerForm: function(form) {
		this._forms.pushObject(form);
		if(this._form) {
			this._form._registerForm(form);
		}
	},

	_unregisterForm: function(form) {
		this._forms.removeObject(form);
		if(this._form) {
			this._form._unregisterForm(form);
		}
	},

	_registerControl: function(control,prefix) {
		var name= this.get('_modelName');
		if(prefix)
			name+='.'+prefix;
		// We should register ourself nameless. If there is a parent it should register with our name.
		if(control!==this) {
			// If a control has path, register it with that path
			if(control._path) {
				name+='.'+control._path;
			}
			// If not, the control is a root for a model, i think we should register it by its name
			else {
				name+='.'+control.get('_name');
			}
		} else {
			if(!name)
				name='form';
		}

		Ember.assert("Could not determine name for control "+control,name);
		if(this._controlsByPath[name]===undefined)
			this._controlsByPath[name]=Ember.A();

		this._controlsByPath[name].pushObject(control);

		if(this._validationCache[name]!==undefined)
			control.setValid(this._validationCache[name]);
		if(this._messageCache[name]!==undefined)
			control.setMessages(this._messageCache[name],true);

		if(this._form) {
			if(control===this) {
				this._form._registerForm(control);
			}
			else {
				this._form._registerControl(control, this._panel._path);
			}
		}
	},

	_unregisterControl: function(control,prefix) {
		var name= this.get('_modelName');
		if(prefix)
			name+='.'+prefix;
		if(control!==this) {
			if(control._path) {
				name+='.'+control._path;
			}
			// If not, the control is a root for a model, i think we should register it by its name
			else {
				name+='.'+control.get('_name');
			}
		}else {
			if(!name)
				name='form';
		}

		Ember.warn(this+" cannot unregister control "+control+" with name "+name,this._controlsByPath[name]!==undefined);
		if(this._controlsByPath[name]!==undefined)
			this._controlsByPath[name].removeObject(control);
		else {
			console.log(this._controlsByPath);
		}
		if(this._form) {
			if(control===this) {
				this._form._unregisterForm(control);
			} else {
				this._form._unregisterControl(control, this._panel._path);
			}
		}
	},

	_proxy : function() {
		var properties={}
		this.get('inputControls').forEach(function(control) {
			properties[control._name]=Ember.computed(function(key) {
				return control.get('value');
			}).volatile();
		});
		return Ember.ObjectProxy.extend(properties).create({content: this['for']});
	}.property('for'),

	validator : function() {
		// Always return our validator name, regardless of parent form validators
//		if(this._form && this._form.get("_validator."+this._path)) {
//			return this._form.get("_validator."+this._path);
//		}
		return this.constructor.typeKey;
	}.property('for'),

	_validate : function(paths) {
		var form=this;
		var target=this.get('for');
		var modelName=this.get('_modelName');
		var validator=this.get('_validator');
		var promisses=Ember.A();
		if(this._panel && this._panel._isFormOption) {
			if(!this._panel.get('selected'))
				return new Ember.RSVP.Promise(function(resolve) {
					resolve(true);
				});
		}
		if(!validator) {
			return new Ember.RSVP.Promise(function(resolve) {
				resolve(true);
			});
		}
		if(paths) {
			if(typeof paths==='string'){
				paths=Ember.A(paths);
			}
			paths.toArray().forEach(function(path) {
				var _path=path.replace(/^_form\./,'');
				var input=form.get(_path);
				if(input) {
					paths.removeObject(path);
					validator=input.get('_form._validator');
					target=input.get('_form.for');
					path=input.get('_path');
					if(path) {
						Ember.assert('No target for validation of path ' + path + " in form " +form,target);
						target=target.get(path);
						if(validator)
							validator=validator.get(path);
						if(validator) {
							modelName=modelName+'.'+path;
							promisses.pushObject(validator.validate(target,modelName))
						}
					}
					if(input instanceof Form) {
						promisses.pushObject(input._validate());
					}
				} else {
					Ember.warn('Tried to validate path "'+_path+'" but it does not exist in '+form);
				}

			});
			promisses.pushObjects(this._forms.invoke('_validate',paths));
		} else {
			promisses.pushObject(validator.validate(target,modelName));
			promisses.pushObjects(this._forms.filterBy('isEnabled',true).invoke('_validate'));
		}
		return Ember.RSVP.all(promisses).then(function(result){
			var outcome=true;
			result.forEach(function(result) {
				if(typeof result==='boolean') {
					if(!result) {
						outcome=false;
						return false;
					}
					return true;
				}
				if(!result.hasFinished()) {
					outcome=false;
					return false;
				} else {
					form._setValidations(result);
					result.reset();
					if(!result.isValid()) {
						outcome = false;
					}
				}
			});
			return outcome;

		});
	},

	_reset:function(modelChanged) {
		this._validationCache={};
		this._messageCache={};
		this._super(modelChanged);
		this._forms.invoke('_reset',modelChanged);
	},

	_validator: function() {
		var validator=this.get('validator');
		if(typeof validator==='object') {
			return validator;
		}
		else {
			var fn=this.container.lookup('validator:lookup');
			if(fn) {
				try {
					return fn.call(this,validator);
				}
				catch(e) {
					Ember.warn('A form tried to resolve a validator for "'+validator+'" but got the following error: '+e.message);
					return null;
				}
			}
			return null;
		}
	}.property('validator'),

	'for' : null,

	init : function() {
		this._forms=Ember.A();
		this._controlsByPath={};
		this._validationCache={};
		this._messageCache={};

		if(!this._syncFromSource || !this._syncToSource) {
			var _for=this.get('for') ;
			this.reopen({
				'for' : Ember.computed(function(key,value) {
							if(value) {
								if(!this._syncFromSource || !this._syncToSource) {
									return Proxy.create({_content:value,
										_syncFromSource: this._syncFromSource,
										_syncToSource: this._syncToSource});
								}
								return value;
							}
							return null;
						}),
			});
			this.set('for',_for);
		}

		this._super();
		this.set('_path',null);

//		var targetObject=this._syncToSource ? this['for'] : this.get('_proxy');
		Ember.run.scheduleOnce('afterRender',this,function() {
			var validator=this.get('_validator');
			// Initialize validaton if a validator was resolved and we're the root form, or the root form validator has no validation for our path
			if(validator && (!this._form || !(this._form.get("_validator."+this._path) instanceof validator.constructor))) {
				var form= this;
				this._observer = validator.observe(this,'for',function(result,sender,key) {
					var silent=sender===null || sender===form;
					// If our target model changed, immediate show validations unless it's a new model
					if(silent && (form.get('for.isDirty') && !form.get('for.isNew')))
						silent=false;
//					Ember.debug(form);
//					console.log('Ran validation',sender ? sender.toString() : null,key,silent);
//					console.log(result.get('validations'));
//					console.log(result.get('messages'));
//					Ember.debug(sender);
//					console.log('--------------');

					form._setValidations(result,silent);

					// We only want to receive changed status on next run, so reset the result if it has finished
					if(result.hasFinished())
						result.reset();
				}, this.get('_modelName'));

				if(this.get('for'))
					this._observer.run();
			}
		});
	},

	_forObserver : Ember.observer('for',function(){
		this._reset(true);
	}),

	willSubmit : null,

	didSubmit : null,

	_submit : function() {
		this._apply();
	},

	submit : function(action) {
		this.send('submit',action);
	},

//	unregisterComponent:function(component) {
//		this._super(component);
//		if(this._components.length===0) {
//			Ember.run.later(this,function(){
//				this.destroy();
//			});
//		}
//	},

	destroy: function() {
		if(this._observer) {
			this._observer.destroy();
		}
		this._super();
	},

	getComponent : function() {
		var component=this.getComponentClass();
		return component.extend({_debugContainerKey : this._debugContainerKey});
	},

//	getComponentClass : function(context,contextName) {
//		var component=this._component
//		if(typeof component ==="string") {
//			component = Lookup.call(context,this._component,'form');
//		}
//		if(this._extend) {
//			var typeKey=component.typeKey;
//			component= component.extend(this._extend);
//			component.typeKey=typeKey;
//		}
//
//		return component;
//
//	},

}).reopenClass({
	async : function() {
		this.reopen({
			_syncFromSource : false,

			_syncToSource : false,
		});
		return this;
	}
});
export default Form;<commit_msg>Fixed an issue with validation messages not being applied to all controls when more than one control would attach to the same property<commit_after>/**
 * Provides Form and input features.
 *
 * @module furnace
 * @submodule furnace-forms
 */
import Panel from './panel';
import Lookup from 'furnace-forms/utils/lookup-class';
import Proxy from 'furnace-forms/proxy';
import Ember from 'ember';
/**
 * Form control component proxy
 *
 * @class Form
 * @namespace Furnace.Forms.Controls
 * @extends Furnace.Forms.Controls.Abstract
 * @protected
 */
var Form = Panel.extend({
	_component: 'form',

	_componentType: 'forms',

	_syncFromSource : true,

	_syncToSource : true,

	_observer : null,

	_validationCache : null,

	_messageCache : null,

	_setValidations : function(result,silent) {
		var validations=result.get('validations');
		var messages=result.get('messages');

		this._validationCache=validations;
		this._messageCache=messages;

		for(var name in validations) {
			if(this._controlsByPath[name]!==undefined) {
				this._controlsByPath[name].invoke('setValid',validations[name]);
				if(messages[name]!==undefined) {
					this._controlsByPath[name].forEach(function(control){
						control.setMessages(messages[name].toArray(),silent);
					});
				}
				else {
					this._controlsByPath[name].invoke('setMessages',null);
				}
			}
		}
	},

	_name: Ember.computed.oneWay('_modelName'),

	_modelPath : Ember.computed('_form,_name',function() {
		if(!this._form)
			return this.get('_name');
		else
			return this._panel.get('_path');
		return '';
	}),

	_controlsByPath: null,

	_forms: null,

	actions : {
		validate : function(paths) {

			this._validate(paths);

		},

		reset : function(action) {
			this._reset();
			if(this._observer)
				this._observer.run();
			if(action)
				this.send(action,form);
		},

		submit: function(action) {
			action= action===undefined  ?  'submit' : action;
			var form=this;
			this._validate().then(function(valid) {
				if(valid) {
					form._submit();
					form.get('target').send(action,form);
				} else {
					// Notice about invalid for development?
					Ember.Logger.info(form+' was submitted but was marked invalid');
				}

			});

		}
	},

	_registerForm: function(form) {
		this._forms.pushObject(form);
		if(this._form) {
			this._form._registerForm(form);
		}
	},

	_unregisterForm: function(form) {
		this._forms.removeObject(form);
		if(this._form) {
			this._form._unregisterForm(form);
		}
	},

	_registerControl: function(control,prefix) {
		var name= this.get('_modelName');
		if(prefix)
			name+='.'+prefix;
		// We should register ourself nameless. If there is a parent it should register with our name.
		if(control!==this) {
			// If a control has path, register it with that path
			if(control._path) {
				name+='.'+control._path;
			}
			// If not, the control is a root for a model, i think we should register it by its name
			else {
				name+='.'+control.get('_name');
			}
		} else {
			if(!name)
				name='form';
		}

		Ember.assert("Could not determine name for control "+control,name);
		if(this._controlsByPath[name]===undefined)
			this._controlsByPath[name]=Ember.A();

		this._controlsByPath[name].pushObject(control);

		if(this._validationCache[name]!==undefined)
			control.setValid(this._validationCache[name]);
		if(this._messageCache[name]!==undefined)
			control.setMessages(this._messageCache[name],true);

		if(this._form) {
			if(control===this) {
				this._form._registerForm(control);
			}
			else {
				this._form._registerControl(control, this._panel._path);
			}
		}
	},

	_unregisterControl: function(control,prefix) {
		var name= this.get('_modelName');
		if(prefix)
			name+='.'+prefix;
		if(control!==this) {
			if(control._path) {
				name+='.'+control._path;
			}
			// If not, the control is a root for a model, i think we should register it by its name
			else {
				name+='.'+control.get('_name');
			}
		}else {
			if(!name)
				name='form';
		}

		Ember.warn(this+" cannot unregister control "+control+" with name "+name,this._controlsByPath[name]!==undefined);
		if(this._controlsByPath[name]!==undefined)
			this._controlsByPath[name].removeObject(control);
		else {
			console.log(this._controlsByPath);
		}
		if(this._form) {
			if(control===this) {
				this._form._unregisterForm(control);
			} else {
				this._form._unregisterControl(control, this._panel._path);
			}
		}
	},

	_proxy : function() {
		var properties={}
		this.get('inputControls').forEach(function(control) {
			properties[control._name]=Ember.computed(function(key) {
				return control.get('value');
			}).volatile();
		});
		return Ember.ObjectProxy.extend(properties).create({content: this['for']});
	}.property('for'),

	validator : function() {
		// Always return our validator name, regardless of parent form validators
//		if(this._form && this._form.get("_validator."+this._path)) {
//			return this._form.get("_validator."+this._path);
//		}
		return this.constructor.typeKey;
	}.property('for'),

	_validate : function(paths) {
		var form=this;
		var target=this.get('for');
		var modelName=this.get('_modelName');
		var validator=this.get('_validator');
		var promisses=Ember.A();
		if(this._panel && this._panel._isFormOption) {
			if(!this._panel.get('selected'))
				return new Ember.RSVP.Promise(function(resolve) {
					resolve(true);
				});
		}
		if(!validator) {
			return new Ember.RSVP.Promise(function(resolve) {
				resolve(true);
			});
		}
		if(paths) {
			if(typeof paths==='string'){
				paths=Ember.A(paths);
			}
			paths.toArray().forEach(function(path) {
				var _path=path.replace(/^_form\./,'');
				var input=form.get(_path);
				if(input) {
					paths.removeObject(path);
					validator=input.get('_form._validator');
					target=input.get('_form.for');
					path=input.get('_path');
					if(path) {
						Ember.assert('No target for validation of path ' + path + " in form " +form,target);
						target=target.get(path);
						if(validator)
							validator=validator.get(path);
						if(validator) {
							modelName=modelName+'.'+path;
							promisses.pushObject(validator.validate(target,modelName))
						}
					}
					if(input instanceof Form) {
						promisses.pushObject(input._validate());
					}
				} else {
					Ember.warn('Tried to validate path "'+_path+'" but it does not exist in '+form);
				}

			});
			promisses.pushObjects(this._forms.invoke('_validate',paths));
		} else {
			promisses.pushObject(validator.validate(target,modelName));
			promisses.pushObjects(this._forms.filterBy('isEnabled',true).invoke('_validate'));
		}
		return Ember.RSVP.all(promisses).then(function(result){
			var outcome=true;
			result.forEach(function(result) {
				if(typeof result==='boolean') {
					if(!result) {
						outcome=false;
						return false;
					}
					return true;
				}
				if(!result.hasFinished()) {
					outcome=false;
					return false;
				} else {
					form._setValidations(result);
					result.reset();
					if(!result.isValid()) {
						outcome = false;
					}
				}
			});
			return outcome;

		});
	},

	_reset:function(modelChanged) {
		this._validationCache={};
		this._messageCache={};
		this._super(modelChanged);
		this._forms.invoke('_reset',modelChanged);
	},

	_validator: function() {
		var validator=this.get('validator');
		if(typeof validator==='object') {
			return validator;
		}
		else {
			var fn=this.container.lookup('validator:lookup');
			if(fn) {
				try {
					return fn.call(this,validator);
				}
				catch(e) {
					Ember.warn('A form tried to resolve a validator for "'+validator+'" but got the following error: '+e.message);
					return null;
				}
			}
			return null;
		}
	}.property('validator'),

	'for' : null,

	init : function() {
		this._forms=Ember.A();
		this._controlsByPath={};
		this._validationCache={};
		this._messageCache={};

		if(!this._syncFromSource || !this._syncToSource) {
			var _for=this.get('for') ;
			this.reopen({
				'for' : Ember.computed(function(key,value) {
							if(value) {
								if(!this._syncFromSource || !this._syncToSource) {
									return Proxy.create({_content:value,
										_syncFromSource: this._syncFromSource,
										_syncToSource: this._syncToSource});
								}
								return value;
							}
							return null;
						}),
			});
			this.set('for',_for);
		}

		this._super();
		this.set('_path',null);

//		var targetObject=this._syncToSource ? this['for'] : this.get('_proxy');
		Ember.run.scheduleOnce('afterRender',this,function() {
			var validator=this.get('_validator');
			// Initialize validaton if a validator was resolved and we're the root form, or the root form validator has no validation for our path
			if(validator && (!this._form || !(this._form.get("_validator."+this._path) instanceof validator.constructor))) {
				var form= this;
				this._observer = validator.observe(this,'for',function(result,sender,key) {
					var silent=sender===null || sender===form;
					// If our target model changed, immediate show validations unless it's a new model
					if(silent && (form.get('for.isDirty') && !form.get('for.isNew')))
						silent=false;
//					Ember.debug(form);
//					console.log('Ran validation',sender ? sender.toString() : null,key,silent);
//					console.log(result.get('validations'));
//					console.log(result.get('messages'));
//					Ember.debug(sender);
//					console.log('--------------');

					form._setValidations(result,silent);

					// We only want to receive changed status on next run, so reset the result if it has finished
					if(result.hasFinished())
						result.reset();
				}, this.get('_modelName'));

				if(this.get('for'))
					this._observer.run();
			}
		});
	},

	_forObserver : Ember.observer('for',function(){
		this._reset(true);
	}),

	willSubmit : null,

	didSubmit : null,

	_submit : function() {
		this._apply();
	},

	submit : function(action) {
		this.send('submit',action);
	},

//	unregisterComponent:function(component) {
//		this._super(component);
//		if(this._components.length===0) {
//			Ember.run.later(this,function(){
//				this.destroy();
//			});
//		}
//	},

	destroy: function() {
		if(this._observer) {
			this._observer.destroy();
		}
		this._super();
	},

	getComponent : function() {
		var component=this.getComponentClass();
		return component.extend({_debugContainerKey : this._debugContainerKey});
	},

//	getComponentClass : function(context,contextName) {
//		var component=this._component
//		if(typeof component ==="string") {
//			component = Lookup.call(context,this._component,'form');
//		}
//		if(this._extend) {
//			var typeKey=component.typeKey;
//			component= component.extend(this._extend);
//			component.typeKey=typeKey;
//		}
//
//		return component;
//
//	},

}).reopenClass({
	async : function() {
		this.reopen({
			_syncFromSource : false,

			_syncToSource : false,
		});
		return this;
	}
});
export default Form;<|endoftext|>


<commit_before>/*
 * Licensed to the Sakai Foundation (SF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The SF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */
/*global Config, $, jQuery, get_cookie, delete_cookie, set_cookie, window, alert */

var sakai = sakai || {};

/**
 * @name sakai.topnavigation
 *
 * @class topnavigation
 *
 * @description
 * Initialize the topnavigation widget
 *
 * @version 0.0.1
 * @param {String} tuid Unique id of the widget
 * @param {Boolean} showSettings Show the settings of the widget or not
 */
sakai.topnavigation = function(tuid, showSettings){

    /////////////////////////////
    // Configuration variables //
    /////////////////////////////

    var currentChatStatus = "";

    // Links and labels
    var hiLabel = "#hispan";
    var myprofileName = "#myprofile_name";
    var onlineButton = "#online_button";
    var pictureHolder = "#picture_holder";
    var showOnlineLink = "#show_online";
    var userIdLabel = "#userid";

    // User Link
    var userLink = "#user_link";
    var userLinkMenu = userLink + "_menu";
    var userLinkMenuLink = userLink + "_menu" + " a";

    // Navigation
    var nav = "#nav";
    var navContentMediaLink = "#nav_content_media_link";
    var navCoursesSitesLink = "#nav_courses_sites_link";
    var navCoursesSitesLinkClass = "#nav_courses_sites_link";
    var navPeopleLink = "#nav_people_link";
    var navPeopleLinkClass = "nav_people_link";
    var navMySakaiLink = "#nav_my_sakai_link";
    var navCalendarLink = "#nav_calendar_link";
    var navSelectedNavItemClass = "explore_nav_selected";

    // Messages
    var chatUnreadMessages = "#chat_unreadMessages";

    // Search
    var $general_search_form = $("#general_search_container form");
    var $general_search_input = $("#general_search_input");
    var $general_search_default_value = $("#general_search_default_value");
    var generalSearchSubmitButton = "#general_search_submit_button";
    var searchFocus = false;

    // Containers
    var exploreNavigationContainer = "#explore_nav_container";

    // CSS Classes
    var searchInputFocusClass = "search_input_focus";
    var chatAvailableStatusClass = "chat_available_status";
    var chatAvailableStatusClassOnline = chatAvailableStatusClass + "_online";
    var chatAvailableStatusClassBusy = chatAvailableStatusClass + "_busy";
    var chatAvailableStatusClassOffline = chatAvailableStatusClass + "_offline";

    var userLinkChatStatusClass = ".user_link_chat_status";

    ///////////////////////
    // Utility functions //
    ///////////////////////

    /**
     * Get the number of messages that are unread and show it.
     */
    var getCountUnreadMessages = function(){
        //we have the number of unread messages as a part of the me-feed
        //so get it directly from me object.
        $(chatUnreadMessages).text(sakai.data.me.messages.unread);
    };

    /**
     * Update a certain element with a specific chatstatus
     * @param {Object} element Element that needs to be updated
     * @param {String} chatstatus The chatstatus that needs to be added
     */
    var updateChatStatusElement = function(element, chatstatus){
        element.removeClass(chatAvailableStatusClassOnline);
        element.removeClass(chatAvailableStatusClassBusy);
        element.removeClass(chatAvailableStatusClassOffline);
        element.addClass(chatAvailableStatusClass + "_" + chatstatus);
    };

    /**
     * Update the status on the page by firing an event that handles this
     */
    var updateChatStatus = function(){
        // Trigger the chat_status_change event to update other widgets
        $(window).trigger("chat_status_change", currentChatStatus);
    };

    /**
     * Set the chatstatus of the user
     * @param {String} chatstatus The chatstatus which should be
     * online/offline or busy
     */
    var sendChatStatus = function(chatstatus){
        currentChatStatus = chatstatus;

        var data = {
            "chatstatus": chatstatus,
            "_charset_": "utf-8"
        };

        $.ajax({
            url: "/~" + sakai.data.me.profile["rep:userId"] + "/public/authprofile",
            type: "POST",
            data: data,
            success: function(data){
                updateChatStatus();
            },
            error: function(xhr, textStatus, thrownError){
                alert("An error occurend when sending the status to the server.");
            }
        });
    };

    /**
     * Show or hide the user link menu
     * @param {Boolean} hideOnly
     *  true: Hide the menu only
     *  false: Show or hide the menu depending if it's already visible
     */
    var showHideUserLinkMenu = function(hideOnly){
        if ($(userLinkMenu).is(":visible") || hideOnly) {
            $(userLinkMenu).hide();
        }
        else {
            $(userLinkMenu).css("left", Math.round($(userLink).offset().left) + "px");
            $(userLinkMenu).css("top", (Math.round($(userLink).offset().top) + $(userLink).height() + 2) + "px");
            $(userLinkMenu).css("width", ($(userLink).width() + 10) + "px");
            $(userLinkMenu).show();
        }
    };

    /**
     * Add binding to some elements
     */
    var addBinding = function(){
        $(userLink).bind("click", function(){
            showHideUserLinkMenu(false);
        });

        $(userLinkChatStatusClass).bind("click", function(ev){
            showHideUserLinkMenu(false);
            var clicked = ev.currentTarget.id.split("_")[ev.currentTarget.id.split("_").length - 1];
            sendChatStatus(clicked);
        });

        $(document).bind("click", function(e){
            var $clicked = $(e.target);

            // Check if one of the parents is the userLink
            if (!$clicked.parents().is(userLink)) {
                showHideUserLinkMenu(true);
            }
        });

        $(window).bind("chat_status_change", function(event, currentChatStatus){
            updateChatStatusElement($(userLink), currentChatStatus);
        });
    };

    ////////////////
    // NAVIGATION //
    ///////////////

    /**
     * Select the page in the top navigation where you are currently on.
     * This will apply a class to the selected navigation item based on the current URL
     * @returns void;
     */
    var determineCurrentNav = function(){
        var windowLocationPath = window.location.pathname.toLowerCase();

        // Remove all selected classes from nav elements
        $(nav + " " + navSelectedNavItemClass).removeClass(navSelectedNavItemClass);

        // My Sakai
        if ((windowLocationPath.indexOf(sakai.config.URL.MY_DASHBOARD_URL) !== -1) || (windowLocationPath.indexOf(sakai.config.URL.PUBLIC_MY_DASHBOARD_URL) !== -1)) {
            $(navMySakaiLink).addClass(navSelectedNavItemClass);
            return;
        }

        // Content & Media
        if ((windowLocationPath.indexOf(sakai.config.URL.CONTENT_MEDIA_URL) !== -1) || (windowLocationPath.indexOf(sakai.config.URL.PUBLIC_CONTENT_MEDIA_URL) !== -1)) {
            $(navContentMediaLink).addClass(navSelectedNavItemClass);
            return;
        }

        // People
        if ((windowLocationPath.indexOf(sakai.config.URL.PEOPLE_URL) !== -1) || (windowLocationPath.indexOf(sakai.config.URL.PUBLIC_PEOPLE_URL) !== -1)) {
            $(navPeopleLink).addClass(navSelectedNavItemClass);
            return;
        }

        // Courses & Sites
        if ((windowLocationPath.indexOf(sakai.config.URL.COURSES_SITES_URL) !== -1) || (windowLocationPath.indexOf(sakai.config.URL.PUBLIC_COURSES_SITES_URL) !== -1) || (windowLocationPath.indexOf("/sites/") !== -1)) {
            $(navCoursesSitesLink).addClass(navSelectedNavItemClass);
            return;
        }

        // Calendar
        if ((windowLocationPath.indexOf(sakai.config.URL.SEARCH_GENERAL_URL) !== -1) || (windowLocationPath.indexOf(sakai.config.URL.SEARCH_PEOPLE_URL) !== -1) || (windowLocationPath.indexOf(sakai.config.URL.SEARCH_SITES_URL) !== -1) || (windowLocationPath.indexOf(sakai.config.URL.SEARCH_CONTENT_URL) !== -1) || (windowLocationPath.indexOf(sakai.config.URL.PUBLIC_SEARCH_URL) !== -1)) {
            $(navCalendarLink).addClass(navSelectedNavItemClass);
            return;
        }

    };

    ////////////
    // SEARCH //
    ////////////

    /**
     * Execute the search for the value that is in the search input field
     */
    var doSearch = function(){
        var tosearch = $general_search_input.val();
        // Disable search button
        $(generalSearchSubmitButton).attr("disabled", true);
        // Check whether the search term is different than the default text. If not,
        // we do a search for *
        if (!tosearch || tosearch === $general_search_default_value.text()){
            tosearch = "*";
        }
        // Redirecting back to the general search page. This expects the URL to be
        // in a format like this one: page.html#pageid|searchstring
        document.location = sakai.config.URL.SEARCH_GENERAL_URL + "#q=" + tosearch;
        // Only enable button if the location is the search page
        if (window.location.pathname.split("/")[2] === "search.html") {
            $(generalSearchSubmitButton).attr("disabled", false);
        }
    };

    /**
     * If this is the first time the field gets focus, we'll make his text color black
     * and remove the default value
     */
    $general_search_input.bind("focus", function(ev){
        if (!searchFocus) {
            $general_search_input.val("").addClass(searchInputFocusClass);
            searchFocus = true;
        }
    });

    /**
     * If we leave the field without filling out anything, we'll reinsert the default
     * search inputbox value
     */
    $general_search_input.bind("blur", function(ev){

        if (!$general_search_input.val()) {
            $general_search_input.removeClass(searchInputFocusClass);
            $general_search_input.val($general_search_default_value.text());
            searchFocus = false;
        }
    });

    /**
     * Bind the submit event to the search form
     * This event is triggered when you hit enter in the input field and
     * when you click on the submit button
     */
    $general_search_form.bind("submit", function(){

        doSearch();
        return false;
    });

    /**
     * Switch navigation bar to anonymous mode
     * @returns void
     */
    var switchToAnonymousMode = function(){

        // Show Nav Container
        $(exploreNavigationContainer).show();

        // Hide things which are irrelvant for Anonymous user
        $(".personal .mail").hide();
        $(".personal .sign_out").hide();
        $("#user_link_container").hide();

        // Show anonymous elements
        $("#other_logins_button_container").show();
        $(".help").addClass("help_none");

        // if config.js is set to external, register link is hidden
        if(!sakai.config.Authentication.internal) {
            $("#register_button_container").hide();
        }
        else {
            $("#register_button_container").show();
        }
        $("#login_button_container").show();

        // Set up public nav links
        $("#nav_my_sakai_link a").attr("href", sakai.config.URL.PUBLIC_MY_DASHBOARD_URL);
        $("#nav_content_media_link a").attr("href", sakai.config.URL.PUBLIC_CONTENT_MEDIA_URL_PAGE);
        $("#nav_people_link a").attr("href", sakai.config.URL.PUBLIC_PEOPLE_URL);
        $("#nav_courses_sites_link a").attr("href", sakai.config.URL.PUBLIC_COURSES_SITES_URL);
        $("#nav_search_link a").attr("href", sakai.config.URL.PUBLIC_SEARCH_URL_PAGE);

        // Make the login page redirect to the current page after login
        $(".log_in").attr("href", $(".log_in").attr("href") + "?url=" + escape(window.location.pathname + window.location.search + window.location.hash));

    };

    /**
     * Set the presence for the current user
     * We need to do this in order to let the server know the user is still available for chatting.
     */
    var setPresence = function(){

        var data = {
            "sakai:status": "online",
            "_charset_": "utf-8"
        };

        $.ajax({
            url: sakai.config.URL.PRESENCE_SERVICE,
            type: "POST",
            success: function(){
                setTimeout(setPresence, 120000);
            },
            data: data
        });
    };

    /**
     * Parse the chatstatus for a user
     * @param {String} chatStatus The chatstatus which should be
     * online, busy or offline
     */
    var parseChatStatus = function(chatStatus){
        // Check if the status is defined
        if (!chatStatus) {
            chatStatus = "online";
        }
        return chatStatus;
    };

    /**
     * Get the chat status for the current user
     */
    var getChatStatus = function(){
        if (sakai.data.me.profile) {
            currentChatStatus = parseChatStatus(sakai.data.me.profile.chatstatus);
        }
        else {
            currentChatStatus = "online";
        }
        updateChatStatus();
    };

    ///////////////////////
    // Initial functions //
    ///////////////////////

    /**
     * Contains all the functions and methods that need to be
     * executed on the initial load of the page
     */
    var doInit = function(){
        var obj = {};
        var menulinks = [];

        for (var i in sakai.config.Navigation) {
            var temp = new Object();
            temp.url = sakai.config.Navigation[i].url;
            temp.label = sakai.api.i18n.General.getValueForKey(sakai.config.Navigation[i].label);
            temp.cleanurl = temp.url || "";
            if (temp.cleanurl) {
                if (temp.cleanurl.indexOf('?') && temp.cleanurl.indexOf('?') > 0) {
                    temp.cleanurl = temp.cleanurl.substring(0, temp.cleanurl.indexOf('?'));
                }
                if (temp.cleanurl.indexOf('#') && temp.cleanurl.indexOf('#') > 0) {
                    temp.cleanurl = temp.cleanurl.substring(0, temp.cleanurl.indexOf('#'));
                }
            }
            if (i == 0) {
                temp.firstlink = true;
            }
            else {
                temp.firstlink = false;
            }
            menulinks.push(temp);
        }
        obj.links = menulinks;
        // Get navigation and render menu template
        $(".explore").html($.TemplateRenderer("navigation_template", obj));

        var person = sakai.data.me;

        $(exploreNavigationContainer).show();

        // Fill in the name of the user in the different fields
        if (sakai.api.User.getDisplayName(person.profile) !== "") {
            $(userIdLabel).text(sakai.api.User.getDisplayName(person.profile));
            $(hiLabel).text(sakai.api.User.getProfileBasicElementValue(person.profile, "firstName"));
        }

        // Show the profile picture on the dashboard page
        /** TODO : Remove the lines beneath if this functionality is inside changepic.js */
        if (person.profile.picture) {
            var picture = $.parseJSON(person.profile.picture);
            if (picture.name) {
                $(pictureHolder).attr("src", "/~" + sakai.data.me.user.userid + "/public/" + picture.name);
            }
        }

        // Highlight current nav item
        determineCurrentNav();

        // Set presence and bind things
        addBinding();
        getCountUnreadMessages();
        setPresence();

        // Get chat status
        getChatStatus();
    };

    if (sakai.data.me.user.anon) {
        // If a user is not logged in -> switch to anonymous mode
        switchToAnonymousMode();
    }
    else {
        doInit();
    }

};
sakai.api.Widgets.widgetLoader.informOnLoad("topnavigation");<commit_msg>NOJIRA - Follow up commit to fix top navigation for anonymous users<commit_after>/*
 * Licensed to the Sakai Foundation (SF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The SF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License. You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied. See the License for the
 * specific language governing permissions and limitations under the License.
 */
/*global Config, $, jQuery, get_cookie, delete_cookie, set_cookie, window, alert */

var sakai = sakai || {};

/**
 * @name sakai.topnavigation
 *
 * @class topnavigation
 *
 * @description
 * Initialize the topnavigation widget
 *
 * @version 0.0.1
 * @param {String} tuid Unique id of the widget
 * @param {Boolean} showSettings Show the settings of the widget or not
 */
sakai.topnavigation = function(tuid, showSettings){

    /////////////////////////////
    // Configuration variables //
    /////////////////////////////

    var currentChatStatus = "";

    // Links and labels
    var hiLabel = "#hispan";
    var myprofileName = "#myprofile_name";
    var onlineButton = "#online_button";
    var pictureHolder = "#picture_holder";
    var showOnlineLink = "#show_online";
    var userIdLabel = "#userid";

    // User Link
    var userLink = "#user_link";
    var userLinkMenu = userLink + "_menu";
    var userLinkMenuLink = userLink + "_menu" + " a";

    // Navigation
    var nav = "#nav";
    var navContentMediaLink = "#nav_content_media_link";
    var navCoursesSitesLink = "#nav_courses_sites_link";
    var navCoursesSitesLinkClass = "#nav_courses_sites_link";
    var navPeopleLink = "#nav_people_link";
    var navPeopleLinkClass = "nav_people_link";
    var navMySakaiLink = "#nav_my_sakai_link";
    var navCalendarLink = "#nav_calendar_link";
    var navSelectedNavItemClass = "explore_nav_selected";

    // Messages
    var chatUnreadMessages = "#chat_unreadMessages";

    // Search
    var $general_search_form = $("#general_search_container form");
    var $general_search_input = $("#general_search_input");
    var $general_search_default_value = $("#general_search_default_value");
    var generalSearchSubmitButton = "#general_search_submit_button";
    var searchFocus = false;

    // Containers
    var exploreNavigationContainer = "#explore_nav_container";

    // CSS Classes
    var searchInputFocusClass = "search_input_focus";
    var chatAvailableStatusClass = "chat_available_status";
    var chatAvailableStatusClassOnline = chatAvailableStatusClass + "_online";
    var chatAvailableStatusClassBusy = chatAvailableStatusClass + "_busy";
    var chatAvailableStatusClassOffline = chatAvailableStatusClass + "_offline";

    var userLinkChatStatusClass = ".user_link_chat_status";

    ///////////////////////
    // Utility functions //
    ///////////////////////

    /**
     * Get the number of messages that are unread and show it.
     */
    var getCountUnreadMessages = function(){
        //we have the number of unread messages as a part of the me-feed
        //so get it directly from me object.
        $(chatUnreadMessages).text(sakai.data.me.messages.unread);
    };

    /**
     * Update a certain element with a specific chatstatus
     * @param {Object} element Element that needs to be updated
     * @param {String} chatstatus The chatstatus that needs to be added
     */
    var updateChatStatusElement = function(element, chatstatus){
        element.removeClass(chatAvailableStatusClassOnline);
        element.removeClass(chatAvailableStatusClassBusy);
        element.removeClass(chatAvailableStatusClassOffline);
        element.addClass(chatAvailableStatusClass + "_" + chatstatus);
    };

    /**
     * Update the status on the page by firing an event that handles this
     */
    var updateChatStatus = function(){
        // Trigger the chat_status_change event to update other widgets
        $(window).trigger("chat_status_change", currentChatStatus);
    };

    /**
     * Set the chatstatus of the user
     * @param {String} chatstatus The chatstatus which should be
     * online/offline or busy
     */
    var sendChatStatus = function(chatstatus){
        currentChatStatus = chatstatus;

        var data = {
            "chatstatus": chatstatus,
            "_charset_": "utf-8"
        };

        $.ajax({
            url: "/~" + sakai.data.me.profile["rep:userId"] + "/public/authprofile",
            type: "POST",
            data: data,
            success: function(data){
                updateChatStatus();
            },
            error: function(xhr, textStatus, thrownError){
                alert("An error occurend when sending the status to the server.");
            }
        });
    };

    /**
     * Show or hide the user link menu
     * @param {Boolean} hideOnly
     *  true: Hide the menu only
     *  false: Show or hide the menu depending if it's already visible
     */
    var showHideUserLinkMenu = function(hideOnly){
        if ($(userLinkMenu).is(":visible") || hideOnly) {
            $(userLinkMenu).hide();
        }
        else {
            $(userLinkMenu).css("left", Math.round($(userLink).offset().left) + "px");
            $(userLinkMenu).css("top", (Math.round($(userLink).offset().top) + $(userLink).height() + 2) + "px");
            $(userLinkMenu).css("width", ($(userLink).width() + 10) + "px");
            $(userLinkMenu).show();
        }
    };

    /**
     * Add binding to some elements
     */
    var addBinding = function(){
        $(userLink).bind("click", function(){
            showHideUserLinkMenu(false);
        });

        $(userLinkChatStatusClass).bind("click", function(ev){
            showHideUserLinkMenu(false);
            var clicked = ev.currentTarget.id.split("_")[ev.currentTarget.id.split("_").length - 1];
            sendChatStatus(clicked);
        });

        $(document).bind("click", function(e){
            var $clicked = $(e.target);

            // Check if one of the parents is the userLink
            if (!$clicked.parents().is(userLink)) {
                showHideUserLinkMenu(true);
            }
        });

        $(window).bind("chat_status_change", function(event, currentChatStatus){
            updateChatStatusElement($(userLink), currentChatStatus);
        });
    };

    ////////////////
    // NAVIGATION //
    ///////////////

    /**
     * Select the page in the top navigation where you are currently on.
     * This will apply a class to the selected navigation item based on the current URL
     * @returns void;
     */
    var determineCurrentNav = function(){
        var windowLocationPath = window.location.pathname.toLowerCase();

        // Remove all selected classes from nav elements
        $(nav + " " + navSelectedNavItemClass).removeClass(navSelectedNavItemClass);

        // My Sakai
        if ((windowLocationPath.indexOf(sakai.config.URL.MY_DASHBOARD_URL) !== -1) || (windowLocationPath.indexOf(sakai.config.URL.PUBLIC_MY_DASHBOARD_URL) !== -1)) {
            $(navMySakaiLink).addClass(navSelectedNavItemClass);
            return;
        }

        // Content & Media
        if ((windowLocationPath.indexOf(sakai.config.URL.CONTENT_MEDIA_URL) !== -1) || (windowLocationPath.indexOf(sakai.config.URL.PUBLIC_CONTENT_MEDIA_URL) !== -1)) {
            $(navContentMediaLink).addClass(navSelectedNavItemClass);
            return;
        }

        // People
        if ((windowLocationPath.indexOf(sakai.config.URL.PEOPLE_URL) !== -1) || (windowLocationPath.indexOf(sakai.config.URL.PUBLIC_PEOPLE_URL) !== -1)) {
            $(navPeopleLink).addClass(navSelectedNavItemClass);
            return;
        }

        // Courses & Sites
        if ((windowLocationPath.indexOf(sakai.config.URL.COURSES_SITES_URL) !== -1) || (windowLocationPath.indexOf(sakai.config.URL.PUBLIC_COURSES_SITES_URL) !== -1) || (windowLocationPath.indexOf("/sites/") !== -1)) {
            $(navCoursesSitesLink).addClass(navSelectedNavItemClass);
            return;
        }

        // Calendar
        if ((windowLocationPath.indexOf(sakai.config.URL.SEARCH_GENERAL_URL) !== -1) || (windowLocationPath.indexOf(sakai.config.URL.SEARCH_PEOPLE_URL) !== -1) || (windowLocationPath.indexOf(sakai.config.URL.SEARCH_SITES_URL) !== -1) || (windowLocationPath.indexOf(sakai.config.URL.SEARCH_CONTENT_URL) !== -1) || (windowLocationPath.indexOf(sakai.config.URL.PUBLIC_SEARCH_URL) !== -1)) {
            $(navCalendarLink).addClass(navSelectedNavItemClass);
            return;
        }

    };

    ////////////
    // SEARCH //
    ////////////

    /**
     * Execute the search for the value that is in the search input field
     */
    var doSearch = function(){
        var tosearch = $general_search_input.val();
        // Disable search button
        $(generalSearchSubmitButton).attr("disabled", true);
        // Check whether the search term is different than the default text. If not,
        // we do a search for *
        if (!tosearch || tosearch === $general_search_default_value.text()){
            tosearch = "*";
        }
        // Redirecting back to the general search page. This expects the URL to be
        // in a format like this one: page.html#pageid|searchstring
        document.location = sakai.config.URL.SEARCH_GENERAL_URL + "#q=" + tosearch;
        // Only enable button if the location is the search page
        if (window.location.pathname.split("/")[2] === "search.html") {
            $(generalSearchSubmitButton).attr("disabled", false);
        }
    };

    /**
     * If this is the first time the field gets focus, we'll make his text color black
     * and remove the default value
     */
    $general_search_input.bind("focus", function(ev){
        if (!searchFocus) {
            $general_search_input.val("").addClass(searchInputFocusClass);
            searchFocus = true;
        }
    });

    /**
     * If we leave the field without filling out anything, we'll reinsert the default
     * search inputbox value
     */
    $general_search_input.bind("blur", function(ev){

        if (!$general_search_input.val()) {
            $general_search_input.removeClass(searchInputFocusClass);
            $general_search_input.val($general_search_default_value.text());
            searchFocus = false;
        }
    });

    /**
     * Bind the submit event to the search form
     * This event is triggered when you hit enter in the input field and
     * when you click on the submit button
     */
    $general_search_form.bind("submit", function(){

        doSearch();
        return false;
    });

    /**
     * Switch navigation bar to anonymous mode
     * @returns void
     */
    var switchToAnonymousMode = function(){

        // Show Nav Container
        $(exploreNavigationContainer).show();

        // Hide things which are irrelvant for Anonymous user
        $(".personal .mail").hide();
        $(".personal .sign_out").hide();
        $(".help").hide();
        $("#user_link_container").hide();

        // Show anonymous elements
        $("#other_logins_button_container").show();
        $(".log_in").addClass("help_none");

        // if config.js is set to external, register link is hidden
        if(!sakai.config.Authentication.internal) {
            $("#register_button_container").hide();
        }
        else {
            $("#register_button_container").show();
        }
        $("#login_button_container").show();

        // Set up public nav links
        $("#nav_my_sakai_link a").attr("href", sakai.config.URL.PUBLIC_MY_DASHBOARD_URL);
        $("#nav_content_media_link a").attr("href", sakai.config.URL.PUBLIC_CONTENT_MEDIA_URL_PAGE);
        $("#nav_people_link a").attr("href", sakai.config.URL.PUBLIC_PEOPLE_URL);
        $("#nav_courses_sites_link a").attr("href", sakai.config.URL.PUBLIC_COURSES_SITES_URL);
        $("#nav_search_link a").attr("href", sakai.config.URL.PUBLIC_SEARCH_URL_PAGE);

        // Make the login page redirect to the current page after login
        $(".log_in").attr("href", $(".log_in").attr("href") + "?url=" + escape(window.location.pathname + window.location.search + window.location.hash));

    };

    /**
     * Set the presence for the current user
     * We need to do this in order to let the server know the user is still available for chatting.
     */
    var setPresence = function(){

        var data = {
            "sakai:status": "online",
            "_charset_": "utf-8"
        };

        $.ajax({
            url: sakai.config.URL.PRESENCE_SERVICE,
            type: "POST",
            success: function(){
                setTimeout(setPresence, 120000);
            },
            data: data
        });
    };

    /**
     * Parse the chatstatus for a user
     * @param {String} chatStatus The chatstatus which should be
     * online, busy or offline
     */
    var parseChatStatus = function(chatStatus){
        // Check if the status is defined
        if (!chatStatus) {
            chatStatus = "online";
        }
        return chatStatus;
    };

    /**
     * Get the chat status for the current user
     */
    var getChatStatus = function(){
        if (sakai.data.me.profile) {
            currentChatStatus = parseChatStatus(sakai.data.me.profile.chatstatus);
        }
        else {
            currentChatStatus = "online";
        }
        updateChatStatus();
    };

    ///////////////////////
    // Initial functions //
    ///////////////////////

    /**
     * Contains all the functions and methods that need to be
     * executed on the initial load of the page
     */
    var doInit = function(){
        var obj = {};
        var menulinks = [];

        for (var i in sakai.config.Navigation) {
            var temp = new Object();
            temp.url = sakai.config.Navigation[i].url;
            temp.label = sakai.api.i18n.General.getValueForKey(sakai.config.Navigation[i].label);
            temp.cleanurl = temp.url || "";
            if (temp.cleanurl) {
                if (temp.cleanurl.indexOf('?') && temp.cleanurl.indexOf('?') > 0) {
                    temp.cleanurl = temp.cleanurl.substring(0, temp.cleanurl.indexOf('?'));
                }
                if (temp.cleanurl.indexOf('#') && temp.cleanurl.indexOf('#') > 0) {
                    temp.cleanurl = temp.cleanurl.substring(0, temp.cleanurl.indexOf('#'));
                }
            }
            if (i == 0) {
                temp.firstlink = true;
            }
            else {
                temp.firstlink = false;
            }
            menulinks.push(temp);
        }
        obj.links = menulinks;
        // Get navigation and render menu template
        $(".explore").html($.TemplateRenderer("navigation_template", obj));

        var person = sakai.data.me;

        $(exploreNavigationContainer).show();

        // Fill in the name of the user in the different fields
        if (sakai.api.User.getDisplayName(person.profile) !== "") {
            $(userIdLabel).text(sakai.api.User.getDisplayName(person.profile));
            $(hiLabel).text(sakai.api.User.getProfileBasicElementValue(person.profile, "firstName"));
        }

        // Show the profile picture on the dashboard page
        /** TODO : Remove the lines beneath if this functionality is inside changepic.js */
        if (person.profile.picture) {
            var picture = $.parseJSON(person.profile.picture);
            if (picture.name) {
                $(pictureHolder).attr("src", "/~" + sakai.data.me.user.userid + "/public/" + picture.name);
            }
        }

        // Highlight current nav item
        determineCurrentNav();

        // Set presence and bind things
        addBinding();
        getCountUnreadMessages();
        setPresence();

        // Get chat status
        getChatStatus();
    };

    if (sakai.data.me.user.anon) {
        // If a user is not logged in -> switch to anonymous mode
        switchToAnonymousMode();
    }
    else {
        doInit();
    }

};
sakai.api.Widgets.widgetLoader.informOnLoad("topnavigation");<|endoftext|>


<commit_before>WshClass = function() {
}
WshClass.prototype = new Process();
WshClass.prototype.state = 0;
WshClass.prototype.Environment = function() {
}
WshClass.prototype.Environment.array = new Array();
WshClass.prototype.input = new Array();
WshClass.prototype.lastExitCode = 0;
WshClass.prototype.childList = new Array();
WshClass.prototype.main = function(args) {
	console.log("wsh: adding to scheduler job list");
	Kernel.Scheduler.add(this);
	this.username = args['1'];
	this.Environment.array['HOME'] = args[2];
	this.Environment.array['PWD'] = args[2];
}
WshClass.prototype.tick = function() {
	var stdout = this.files['stdout'];
	var stdin = this.files['stdin'];
	switch(this.state) {
	case 0:
		stdout.write("Welcome to WishOS 0.1 (WOSKernel 0.1)\n\n");
		console.log("wsh: loading profile");
		this.files['profile.d'] = new File("/etc/profile.d/env.json");
		var array = JSON.parse(this.files['profile.d'].read());
		for (var i = 0; i < array.length; i++) {
			while(array[i][1].indexOf("\\033") != -1)
				array[i][1] = array[i][1].replace("\\033", "\033");
			this.Environment.array[array[i][0]] = array[i][1];
		}
		var files = Kernel.Filesystem.getDirectory(this.Environment.array['HOME']);
		if (files.error) {
			stdout.write("\033[31mHome directory not found. Using / instead.\n\n");
			this.Environment.array['HOME'] = "/";
			this.Environment.array['PWD'] = "/";
		}
		this.state++;
		break;
	case 1:
		var prompt = this.Environment.array['PS1'];
		while (prompt.indexOf("\\w") != -1)
			prompt = prompt.replace("\\w", this.Environment.array['PWD'].replace(this.Environment.array['HOME'], "~/"));
		while (prompt.indexOf("\\u") != -1)
			prompt = prompt.replace("\\u", this.username);
		while (prompt.indexOf("\\u") != -1)
			prompt = prompt.replace("\\u", OS.hostname);
		while (prompt.indexOf("\\$") != -1)
			prompt = prompt.replace("\\$", (this.username == "root") ? "#" : "$");
		while (prompt.indexOf("\\#") != -1)
			prompt = prompt.replace("\\#", (this.lastExitCode == 0) ? "" : this.lastExitCode);
		stdout.write(prompt);
		this.state++;
		break;
	case 2:
		var code = stdin.read();
		if (!code)
			break;
		if (KeyCodes.isBackspace(code)) {
			if (!this.input.length)
				break;
			this.input.pop();
			stdout.write("\033[1D \033[1D");
			break;
		}
		if (KeyCodes.isEnter(code)) {
			stdout.write("\n");
			this.parseLine();
			break;
		}
		var char = KeyCodes.normalKey(code);
		this.input.push(char);
		stdout.write(char);
		break;
	case 3:
		break;
	case 4:
		this.state = 1;
		var params = this.input.join("").split(" ");
		for (var i = 0; i < params.length; i++)
			if (params[i].length == 0)
				params.splice(i, 1);
		stdout.write("wsh: command not found: " + params[0] + "\n");
		this.lastExitCode = 127;
		this.input = new Array();
		break;
	default:
		break;
	}
}
WshClass.prototype.parseLine = function() {
	this.state = 1;
	var command = this.input.join("");
	var params = command.split(" ");
	for(var i = 0; i < params.length; i++)
		if (params[i].length == 0)
			params.splice(i, 1);
	if (params.length == 0) {
		return;
	}

	for (var i = 0; i < params.length; i++) {
		if (params[i][0] == "$") {
			if (this.Environment.array[params[i].substring(1)])
				params[i] = this.Environment.array[params[i].substring(1)];
		}
	}

	if (params[0].indexOf("=") != -1) {
		do {
			var array = params[0].split("=");
			if (!array[0].length) {
				params[0].substring(1);
				break;
			}
			var name = array[0];
			array.splice(0, 1);
			var value = array.join("=");
			this.Environment.array[name] = value;
			params.splice(0, 1);
		} while (false);
	}

	var ok = false;
	var name = params[0];
	var file = "";
	if (name.substring(0, 1) == "/")
		file = name + ".js";
	else if (name.indexOf("/") != -1)
		file = this.Environment.array['PWD'] + name + ".js";
	else {
		var paths = this.Environment.array['PATH'].split(":");
		for (var i = 0; i < paths.length; i++) {
			file = paths[i] + "/" + name + ".js";
			console.log("wsh: trying: " + file);
			if (this.tryFile(file)) {
				ok = true;
				break;
			}
		}

	}
	if (this.tryFile(file)) {
		console.log("wsh: files exists... yay....");
		ok = true;
	}
	if (!ok) {
		this.state = 4;
		return;
	}
	this.input = new Array();

	this.state = 3;
	var pathArray = new Array();
	pathArray[0] = ["stdout", this.files['stdout'].path];
	pathArray[1] = ["stdin", this.files['stdin'].path];
	var s = "";
	s += "var func = function () { ";
	s += "	try {";
	s += "		var prog = new " + Kernel.ProcessManager.getClassNameFromFileName(file) + "();";
	s += "	} catch (exception) {";
	s += "		console.dir(exception);";
	s += "	}";
	s += "	prog.init(" + this.pid + ");";
	s += "	var paths = JSON.parse('" + JSON.stringify(pathArray) + "');";
	s += "	for(var i = 0; i < paths.length; i++) {";
	s += "		prog.files[paths[i][0]] = Kernel.Filesystem.getFile(paths[i][1]);";
	s += "	}";
	s += "	Kernel.ProcessManager.add(prog);";
	s += "	console.log(\"wsh: start command '" + file + "'...\");";
	s += "	try {";
	s += "		prog.main(JSON.parse('" + JSON.stringify(params) + "'));";
	s += "	} catch (exception) {";
	s += "		console.dir(exception);";
	s += "	}";
	s += "}";
	eval(s);
	Kernel.ProcessManager.load(file, func);
}
WshClass.prototype.tryFile = function(name) {
	var file = new File(name);
	return file.exists();
}
WshClass.prototype.signalHandler = function(signal) {
	switch(signal) {
	case SIGCHLD:
		var newChildList = Kernel.ProcessManager.getAllChilds(this.pid);
		var oldChilds = this.childList.diff(newChildList);
		var newChilds = newChildList.diff(this.childList);
		if (newChilds.length)
			console.log("wsh: we got " + newChilds.length + " new kid(s)... : )")
		if (oldChilds.length) {
			console.log("wsh: we lost " + oldChilds.length + " kid(s)");
			this.state = 1;
		}
		for (var i = 0; i < oldChilds.length; i++) {
			this.lastExitCode = oldChilds[i].exitCode;
			Kernel.ProcessManager.remove(oldChilds[i]);
		}
		this.childList = newChildList;
		break;
	case SIGHUP:
		//break;
	case SIGALRM:
		//break;
	case SIGTERM:
		//break;
	case SIGXCPU:
		//break;
	case SIGUSR1:
		//break;
	case SIGUSR2:
		//break;
	default: //SIGKILL
		console.log("PID " + this.pid + " got Signal " + signal);
		this.exit(1);
		break;
	}
}
<commit_msg>bug fix<commit_after>WshClass = function() {
}
WshClass.prototype = new Process();
WshClass.prototype.state = 0;
WshClass.prototype.Environment = function() {
}
WshClass.prototype.Environment.array = new Array();
WshClass.prototype.input = new Array();
WshClass.prototype.lastExitCode = 0;
WshClass.prototype.childList = new Array();
WshClass.prototype.main = function(args) {
	console.log("wsh: adding to scheduler job list");
	Kernel.Scheduler.add(this);
	this.username = args['1'];
	this.Environment.array['HOME'] = args[2];
	this.Environment.array['PWD'] = args[2];
}
WshClass.prototype.tick = function() {
	var stdout = this.files['stdout'];
	var stdin = this.files['stdin'];
	switch(this.state) {
	case 0:
		stdout.write("Welcome to WishOS 0.1 (WOSKernel 0.1)\n\n");
		console.log("wsh: loading profile");
		this.files['profile.d'] = new File("/etc/profile.d/env.json");
		var array = JSON.parse(this.files['profile.d'].read());
		for (var i = 0; i < array.length; i++) {
			while(array[i][1].indexOf("\\033") != -1)
				array[i][1] = array[i][1].replace("\\033", "\033");
			this.Environment.array[array[i][0]] = array[i][1];
		}
		var files = Kernel.Filesystem.getDirectory(this.Environment.array['HOME']);
		if (files.error) {
			stdout.write("\033[31mHome directory not found. Using / instead.\n\n");
			this.Environment.array['HOME'] = "/";
			this.Environment.array['PWD'] = "/";
		}
		this.state++;
		break;
	case 1:
		var prompt = this.Environment.array['PS1'];
		while (prompt.indexOf("\\w") != -1)
			prompt = prompt.replace("\\w", this.Environment.array['PWD'].replace(this.Environment.array['HOME'], "~/"));
		while (prompt.indexOf("\\u") != -1)
			prompt = prompt.replace("\\u", this.username);
		while (prompt.indexOf("\\u") != -1)
			prompt = prompt.replace("\\u", OS.hostname);
		while (prompt.indexOf("\\$") != -1)
			prompt = prompt.replace("\\$", (this.username == "root") ? "#" : "$");
		while (prompt.indexOf("\\#") != -1)
			prompt = prompt.replace("\\#", (this.lastExitCode == 0) ? "" : this.lastExitCode);
		stdout.write(prompt);
		this.state++;
		break;
	case 2:
		var code = stdin.read();
		if (!code)
			break;
		if (KeyCodes.isBackspace(code)) {
			if (!this.input.length)
				break;
			this.input.pop();
			stdout.write("\033[1D \033[1D");
			break;
		}
		if (KeyCodes.isEnter(code)) {
			stdout.write("\n");
			this.parseLine();
			break;
		}
		var char = KeyCodes.normalKey(code);
		this.input.push(char);
		stdout.write(char);
		break;
	case 3:
		break;
	case 4:
		this.state = 1;
		var params = this.input.join("").split(" ");
		for (var i = 0; i < params.length; i++)
			if (params[i].length == 0)
				params.splice(i, 1);
		stdout.write("wsh: command not found: " + params[0] + "\n");
		this.lastExitCode = 127;
		this.input = new Array();
		break;
	default:
		break;
	}
}
WshClass.prototype.parseLine = function() {
	this.state = 1;
	var command = this.input.join("");
	var params = command.split(" ");
	for(var i = 0; i < params.length; i++)
		if (params[i].length == 0)
			params.splice(i, 1);

	for (var i = 0; i < params.length; i++) {
		if (params[i][0] == "$") {
			if (this.Environment.array[params[i].substring(1)])
				params[i] = this.Environment.array[params[i].substring(1)];
		}
	}

	if (params[0].indexOf("=") != -1) {
		do {
			var array = params[0].split("=");
			if (!array[0].length) {
				params[0].substring(1);
				break;
			}
			var name = array[0];
			array.splice(0, 1);
			var value = array.join("=");
			this.Environment.array[name] = value;
			params.splice(0, 1);
		} while (false);
	}

	for(var i = 0; i < params.length; i++)
		if (params[i].length == 0)
			params.splice(i, 1);

	if (params.length == 0) {
		this.input = new Array();
		return;
	}

	var ok = false;
	var name = params[0];
	var file = "";
	if (name.substring(0, 1) == "/")
		file = name + ".js";
	else if (name.indexOf("/") != -1)
		file = this.Environment.array['PWD'] + name + ".js";
	else {
		var paths = this.Environment.array['PATH'].split(":");
		for (var i = 0; i < paths.length; i++) {
			file = paths[i] + "/" + name + ".js";
			console.log("wsh: trying: " + file);
			if (this.tryFile(file)) {
				ok = true;
				break;
			}
		}

	}
	if (this.tryFile(file)) {
		console.log("wsh: files exists... yay....");
		ok = true;
	}
	if (!ok) {
		this.state = 4;
		return;
	}
	this.input = new Array();

	this.state = 3;
	var pathArray = new Array();
	pathArray[0] = ["stdout", this.files['stdout'].path];
	pathArray[1] = ["stdin", this.files['stdin'].path];
	var s = "";
	s += "var func = function () { ";
	s += "	try {";
	s += "		var prog = new " + Kernel.ProcessManager.getClassNameFromFileName(file) + "();";
	s += "	} catch (exception) {";
	s += "		console.dir(exception);";
	s += "	}";
	s += "	prog.init(" + this.pid + ");";
	s += "	var paths = JSON.parse('" + JSON.stringify(pathArray) + "');";
	s += "	for(var i = 0; i < paths.length; i++) {";
	s += "		prog.files[paths[i][0]] = Kernel.Filesystem.getFile(paths[i][1]);";
	s += "	}";
	s += "	Kernel.ProcessManager.add(prog);";
	s += "	console.log(\"wsh: start command '" + file + "'...\");";
	s += "	try {";
	s += "		prog.main(JSON.parse('" + JSON.stringify(params) + "'));";
	s += "	} catch (exception) {";
	s += "		console.dir(exception);";
	s += "	}";
	s += "}";
	eval(s);
	Kernel.ProcessManager.load(file, func);
}
WshClass.prototype.tryFile = function(name) {
	var file = new File(name);
	return file.exists();
}
WshClass.prototype.signalHandler = function(signal) {
	switch(signal) {
	case SIGCHLD:
		var newChildList = Kernel.ProcessManager.getAllChilds(this.pid);
		var oldChilds = this.childList.diff(newChildList);
		var newChilds = newChildList.diff(this.childList);
		if (newChilds.length)
			console.log("wsh: we got " + newChilds.length + " new kid(s)... : )")
		if (oldChilds.length) {
			console.log("wsh: we lost " + oldChilds.length + " kid(s)");
			this.state = 1;
		}
		for (var i = 0; i < oldChilds.length; i++) {
			this.lastExitCode = oldChilds[i].exitCode;
			Kernel.ProcessManager.remove(oldChilds[i]);
		}
		this.childList = newChildList;
		break;
	case SIGHUP:
		//break;
	case SIGALRM:
		//break;
	case SIGTERM:
		//break;
	case SIGXCPU:
		//break;
	case SIGUSR1:
		//break;
	case SIGUSR2:
		//break;
	default: //SIGKILL
		console.log("PID " + this.pid + " got Signal " + signal);
		this.exit(1);
		break;
	}
}
<|endoftext|>