<commit_before>'use strict'
import React from 'react'
import PropTypes from 'prop-types'
import DisplayColumn from './DisplayColumn'

const FeatureDisplay = (props) => {
  const {
    feature,
    applyStory,
    stories,
    clearStory,
    moveThumb,
    holdThumb,
    stopMove,
  } = props
  const columns = parseInt(feature.columns)
  let bsClass

  switch (columns) {
    case 2:
      bsClass = 'col-sm-6'
      break
    case 3:
      bsClass = 'col-sm-4'
      break
    case 4:
      bsClass = 'col-sm-3'
      break
  }

  let columnContent = []
  let previousEmpty = false
  let currentEntry = {}
  for (let i = 0; i < columns; i++) {
    currentEntry = feature.entries[i]
    columnContent.push(
      <DisplayColumn
        key={i}
        bsClass={bsClass}
        previousEmpty={previousEmpty}
        format={feature.format}
        entry={currentEntry}
        stories={stories}
        applyStory={applyStory.bind(null, i)}
        clearStory={clearStory.bind(null, i)}
        stopMove={stopMove}
        thumbnailForm={props.thumbnailForm.bind(null, i)}
        moveThumb={moveThumb.bind(null, i)}
        holdThumb={holdThumb.bind(null, i)}/>
    )
    if (currentEntry.id == 0) {
      previousEmpty = true
    }
  }

  return (

    {columnContent}

)
}

FeatureDisplay.propTypes = {
  feature: PropTypes.object,
  stories: PropTypes.array,
  applyStory: PropTypes.func,
  clearStory: PropTypes.func,
  holdThumb: PropTypes.func,
  moveThumb: PropTypes.func,
  thumbnailForm: PropTypes.func,
  stopMove: PropTypes.func
}

FeatureDisplay.defaultTypes = {}

export default FeatureDisplay
<commit_msg>Previously used stories don't appear as options.<commit_after>'use strict'
import React from 'react'
import PropTypes from 'prop-types'
import DisplayColumn from './DisplayColumn'

const FeatureDisplay = (props) => {
  const {
    feature,
    applyStory,
    stories,
    clearStory,
    moveThumb,
    holdThumb,
    stopMove,
  } = props
  const columns = parseInt(feature.columns)
  let bsClass

  switch (columns) {
    case 2:
      bsClass = 'col-sm-6'
      break
    case 3:
      bsClass = 'col-sm-4'
      break
    case 4:
      bsClass = 'col-sm-3'
      break
  }

  let columnContent = []
  let previousEmpty = false
  let currentEntry = {}

  let tempStoryList = []
  for (let x = 0; x < stories.length; x++) {
    tempStoryList[x] = stories[x]
  }

  for (let i = 0; i < columns; i++) {
    currentEntry = feature.entries[i]
    for (let j = 0; j < tempStoryList.length; j++) {
      if(tempStoryList[j].id == currentEntry.entryId) {
        tempStoryList.splice(j,1)
      }
    }
    columnContent.push(
      <DisplayColumn
        key={i}
        bsClass={bsClass}
        previousEmpty={previousEmpty}
        format={feature.format}
        entry={currentEntry}
        stories={tempStoryList}
        applyStory={applyStory.bind(null, i)}
        clearStory={clearStory.bind(null, i)}
        stopMove={stopMove}
        thumbnailForm={props.thumbnailForm.bind(null, i)}
        moveThumb={moveThumb.bind(null, i)}
        holdThumb={holdThumb.bind(null, i)}/>
    )
    if (currentEntry.entryId == 0) {
      previousEmpty = true
    }
  }
  return (

    {columnContent}

)
}

FeatureDisplay.propTypes = {
  feature: PropTypes.object,
  stories: PropTypes.array,
  applyStory: PropTypes.func,
  clearStory: PropTypes.func,
  holdThumb: PropTypes.func,
  moveThumb: PropTypes.func,
  thumbnailForm: PropTypes.func,
  stopMove: PropTypes.func
}

FeatureDisplay.defaultTypes = {}

export default FeatureDisplay
<|endoftext|>


<commit_before>Behaviour.register({
	'.TableListField .externallink' : {
		onclick: function(e) {
			window.open(e.target.href);
			Event.stop(e);
			return false;
		}
	}
});

CMSWorkflow = {
	/**
	 * Prompt for input from the user and then submit the given form via ajax.
	 */
	submitWithPromptedMessage : function(form, button, msgVar, msgPrompt) {
		var messageEl = CMSWorkflow.createPromptElement(msgVar, msgPrompt);
		form.appendChild(messageEl);

		Ajax.SubmitForm(form, button, {
			onSuccess: Ajax.Evaluator,
			onFailure: ajaxErrorHandler
		});

		// Once Ajax.SubmitForm has been calld, this element is no longer necessary
		form.removeChild(messageEl);
	},

	createPromptElement: function(varName, promptText) {
		var message = prompt(promptText, "");
		var messageEl = document.createElement("input");
		messageEl.type = "hidden";
		messageEl.name = varName;
		messageEl.value = message;
		return messageEl;
	},

	/**
	 * Simple behaviour for an ajax button
	 */
	WorkflowButton : {
		onclick: function() {
			$('Form_EditForm').changeDetection_fieldsToIgnore['EmbargoExpiryTZConverter_TZ'] = true;
			$('Form_EditForm').changeDetection_fieldsToIgnore['EmbargoExpiryTZConverter_From_Date'] = true;
			$('Form_EditForm').changeDetection_fieldsToIgnore['EmbargoExpiryTZConverter_From_Time'] = true;
			$('Form_EditForm').changeDetection_fieldsToIgnore['EmbargoDate_Date'] = true;
			$('Form_EditForm').changeDetection_fieldsToIgnore['EmbargoDate_Time'] = true;
			$('Form_EditForm').changeDetection_fieldsToIgnore['ExpiryDate_Date'] = true;
			$('Form_EditForm').changeDetection_fieldsToIgnore['ExpiryDate_Time'] = true;
			$('Form_EditForm').changeDetection_fieldsToIgnore['WorkflowComment'] = true;

			if ($('Form_EditForm').isChanged()) {
				if(!confirm('You have unsaved changes. You will lose them if you click OK.')) return false;
			}

			Ajax.SubmitForm($('Form_EditForm'), this.name, {
				onSuccess: Ajax.Evaluator,
				onFailure: ajaxErrorHandler
			});
			return false;
		}
	},

	showHideExpiry: {
		onclick: function() {
			if ($('deleteImmediate').checked) {
				$('expiryField').style.display = 'none';
			} else {
				$('expiryField').style.display = 'block';
			}
		}
	}
};

Behaviour.register({
	'#deleteImmediate' : CMSWorkflow.showHideExpiry,
	'#deleteLater' : CMSWorkflow.showHideExpiry,
	'#Form_EditForm_action_cms_requestedit' : CMSWorkflow.WorkflowButton,
	'#Form_EditForm_action_cms_approve' : CMSWorkflow.WorkflowButton,
	'#Form_EditForm_action_cms_deny' : CMSWorkflow.WorkflowButton,
	'#Form_EditForm_action_cms_comment' : CMSWorkflow.WorkflowButton,
	'#Form_EditForm_action_cms_publish' : CMSWorkflow.WorkflowButton,
	'#WorkflowActions #Form_EditForm_action_cms_requestpublication' : CMSWorkflow.WorkflowButton,
	'#WorkflowActions #Form_EditForm_action_cms_requestdeletefromlive' : CMSWorkflow.WorkflowButton
});

// Create these actions
function action_cms_requestpublication_right(e) {
	if ($('Form_EditForm').isChanged()) {
		if(!confirm('You have unsaved changes. You will lose them if you click to continue requesting publication.')) return false;
	}

	CMSWorkflow.submitWithPromptedMessage(
			$('Form_EditForm'), 'action_cms_requestpublication',
			'WorkflowComment',
			'Please comment on the change you are asking to have published.'
	);
}

function action_cms_requestdeletefromlive_right(e) {
	CMSWorkflow.submitWithPromptedMessage(
			$('Form_EditForm'), 'action_cms_requestdeletefromlive',
			'WorkflowComment',
			'Please comment on why you are asking to have this page deleted.'
	);
}

var EmbargoExpiry = {
	save: function(what, el) {
		EmbargoExpiry.fieldCheck();

		var url = 'admin/cms_setembargoexpiry?wfRequest='+$('WorkflowRequest_ID').value;
		var ids = EmbargoExpiry.ids(what);

		if (what == 'embargo') {
			console.log('embargo date: '+ids.dateField);
			console.log('embargo time: '+ids.timeField);
			url += '&EmbargoDate='+escape($(ids.dateField).value)+'&EmbargoTime='+escape($(ids.timeField).value);
		} else if (what == 'expiry') {
			console.log('expiry date: '+ids.dateField);
			console.log('expiry time: '+ids.timeField);
			url += '&ExpiryDate='+escape($(ids.dateField).value)+'&ExpiryTime='+escape($(ids.timeField).value);
		}

		if ($(ids.dateField).value == '' || $(ids.timeField).value == '') {
			alert('You must fill out the '+what+' date and time fields');
			return;
		}

		$(el.id).className = 'action loading';
		new Ajax.Request(url, {
			method: 'get',
			onSuccess: function(t) {
				data = eval('('+t.responseText+')');
				if (data.status == 'success') {
					$(ids.wholeMessage).style.display = 'block';
					$(ids.dateTime).innerHTML = eval('data.message.'+what);
				} else { EmbargoExpiry.error(t); }
			},
			onFailure: function(t) { EmbargoExpiry.error(t); },
			onComplete: function(t) { $(el.id).className = 'action'; }
		});
	},
	reset: function(what, el) {
		ids = EmbargoExpiry.ids(what);
		var url = 'admin/cms_setembargoexpiry?wfRequest='+$('WorkflowRequest_ID').value;

		$(ids.dateField).value = '';
		$(ids.timeField).value = '';

		EmbargoExpiry.fieldCheck();

		if (what == 'embargo') {
			url += '&ResetEmbargo';
		} else if (what == 'expiry') {
			url += '&ResetExpiry';
		}

		$(el.id).className = 'action loading';
		new Ajax.Request(url, {
			method: 'get',
			onSuccess: function(t) {
				data = eval('('+t.responseText+')');
				if (data.status == 'success') {
					$(ids.wholeMessage).style.display = 'none';
				} else { EmbargoExpiry.error(t); }
			},
			onFailure: function(t) { EmbargoExpiry.error(t); },
			onComplete: function(t) { $(el.id).className = 'action'; }
		});
	},
	error: function(transport) {
		EmbargoExpiry.fieldCheck();
		alert('There was an error processing that request.');
	},
	ids: function(forWhat) {
		switch(forWhat) {
			case 'expiry':
				return {
					resetButton: 'resetExpiryButton',
					saveButton: 'saveExpiryButton',
					dateField: 'ExpiryDate_Date',
					timeField: 'ExpiryDate_Time',
					wholeMessage: 'embargoExpiry-expiryStatus',
					dateTime: 'expiryDate'
				};
			case 'embargo':
				return {
					resetButton: 'resetEmbargoButton',
					saveButton: 'saveEmbargoButton',
					dateField: 'EmbargoDate_Date',
					timeField: 'EmbargoDate_Time',
					wholeMessage: 'embargoExpiry-embargoStatus',
					dateTime: 'embargoDate'
				};
		}
	},
	eButton: function(id) {
		$(id).className = $(id).className.replace(/disabled/, '');
		$(id).disabled = false;
	},
	dButton: function(id) {
		$(id).className = $(id).className+" disabled";
		$(id).disabled = true;
	},
	fieldCheck: function() {
		ids = EmbargoExpiry.ids('embargo');

		if ($(ids.dateField).value == '' || $(ids.timeField).value == '') {
			EmbargoExpiry.dButton(ids.saveButton);
			EmbargoExpiry.dButton(ids.resetButton);
		} else {
			EmbargoExpiry.eButton(ids.saveButton);
			EmbargoExpiry.eButton(ids.resetButton);
		}

		ids = EmbargoExpiry.ids('expiry');
		if ($(ids.dateField).value == '' || $(ids.timeField).value == '') {
			EmbargoExpiry.dButton(ids.saveButton);
			EmbargoExpiry.dButton(ids.resetButton);
		} else {
			EmbargoExpiry.eButton(ids.saveButton);
			EmbargoExpiry.eButton(ids.resetButton);
		}

		window.setTimeout('EmbargoExpiry.fieldCheck();', 1000);
	}
};

function action_publish_right(e) {
	var messageEl = CMSWorkflow.createPromptElement('WorkflowComment', 'Please comment on this publication, if applicable.');
	$('Form_EditForm').appendChild(messageEl);
	$('Form_EditForm_action_publish').value = ss.i18n._t('CMSMAIN.PUBLISHING');
	$('Form_EditForm_action_publish').className = 'action loading';
	$('Form_EditForm').save(false, null, 'cms_publishwithcomment', true);
	$('Form_EditForm').removeChild(messageEl);
}
<commit_msg>http://mingle.silverstripe.com/projects/air_nz_cms_enhancements/cards/209 - Fixed change detection of form to correctly ignore embargo/expiry fields. (from r88915)<commit_after>Behaviour.register({
	'.TableListField .externallink' : {
		onclick: function(e) {
			window.open(e.target.href);
			Event.stop(e);
			return false;
		}
	}
});

CMSWorkflow = {
	/**
	 * Prompt for input from the user and then submit the given form via ajax.
	 */
	submitWithPromptedMessage : function(form, button, msgVar, msgPrompt) {
		var messageEl = CMSWorkflow.createPromptElement(msgVar, msgPrompt);
		form.appendChild(messageEl);

		Ajax.SubmitForm(form, button, {
			onSuccess: Ajax.Evaluator,
			onFailure: ajaxErrorHandler
		});

		// Once Ajax.SubmitForm has been calld, this element is no longer necessary
		form.removeChild(messageEl);
	},

	createPromptElement: function(varName, promptText) {
		var message = prompt(promptText, "");
		var messageEl = document.createElement("input");
		messageEl.type = "hidden";
		messageEl.name = varName;
		messageEl.value = message;
		return messageEl;
	},

	/**
	 * Simple behaviour for an ajax button
	 */
	WorkflowButton : {
		onclick: function() {
			$('Form_EditForm').changeDetection_fieldsToIgnore['EmbargoExpiryTZConverter_TZ'] = true;
			$('Form_EditForm').changeDetection_fieldsToIgnore['EmbargoExpiryTZConverter_From_Date'] = true;
			$('Form_EditForm').changeDetection_fieldsToIgnore['EmbargoExpiryTZConverter_From_Time'] = true;
			$('Form_EditForm').changeDetection_fieldsToIgnore['EmbargoDate[Date]'] = true;
			$('Form_EditForm').changeDetection_fieldsToIgnore['EmbargoDate[Time]'] = true;
			$('Form_EditForm').changeDetection_fieldsToIgnore['ExpiryDate[Date]'] = true;
			$('Form_EditForm').changeDetection_fieldsToIgnore['ExpiryDate[Time]'] = true;
			$('Form_EditForm').changeDetection_fieldsToIgnore['WorkflowComment'] = true;

			if ($('Form_EditForm').isChanged()) {
				if(!confirm('You have unsaved changes. You will lose them if you click OK.')) return false;
			}

			Ajax.SubmitForm($('Form_EditForm'), this.name, {
				onSuccess: Ajax.Evaluator,
				onFailure: ajaxErrorHandler
			});
			return false;
		}
	},

	showHideExpiry: {
		onclick: function() {
			if ($('deleteImmediate').checked) {
				$('expiryField').style.display = 'none';
			} else {
				$('expiryField').style.display = 'block';
			}
		}
	}
};

Behaviour.register({
	'#deleteImmediate' : CMSWorkflow.showHideExpiry,
	'#deleteLater' : CMSWorkflow.showHideExpiry,
	'#Form_EditForm_action_cms_requestedit' : CMSWorkflow.WorkflowButton,
	'#Form_EditForm_action_cms_approve' : CMSWorkflow.WorkflowButton,
	'#Form_EditForm_action_cms_deny' : CMSWorkflow.WorkflowButton,
	'#Form_EditForm_action_cms_comment' : CMSWorkflow.WorkflowButton,
	'#Form_EditForm_action_cms_publish' : CMSWorkflow.WorkflowButton,
	'#WorkflowActions #Form_EditForm_action_cms_requestpublication' : CMSWorkflow.WorkflowButton,
	'#WorkflowActions #Form_EditForm_action_cms_requestdeletefromlive' : CMSWorkflow.WorkflowButton
});

// Create these actions
function action_cms_requestpublication_right(e) {
	if ($('Form_EditForm').isChanged()) {
		if(!confirm('You have unsaved changes. You will lose them if you click to continue requesting publication.')) return false;
	}

	CMSWorkflow.submitWithPromptedMessage(
			$('Form_EditForm'), 'action_cms_requestpublication',
			'WorkflowComment',
			'Please comment on the change you are asking to have published.'
	);
}

function action_cms_requestdeletefromlive_right(e) {
	CMSWorkflow.submitWithPromptedMessage(
			$('Form_EditForm'), 'action_cms_requestdeletefromlive',
			'WorkflowComment',
			'Please comment on why you are asking to have this page deleted.'
	);
}

var EmbargoExpiry = {
	save: function(what, el) {
		EmbargoExpiry.fieldCheck();

		var url = 'admin/cms_setembargoexpiry?wfRequest='+$('WorkflowRequest_ID').value;
		var ids = EmbargoExpiry.ids(what);

		if (what == 'embargo') {
			console.log('embargo date: '+ids.dateField);
			console.log('embargo time: '+ids.timeField);
			url += '&EmbargoDate='+escape($(ids.dateField).value)+'&EmbargoTime='+escape($(ids.timeField).value);
		} else if (what == 'expiry') {
			console.log('expiry date: '+ids.dateField);
			console.log('expiry time: '+ids.timeField);
			url += '&ExpiryDate='+escape($(ids.dateField).value)+'&ExpiryTime='+escape($(ids.timeField).value);
		}

		if ($(ids.dateField).value == '' || $(ids.timeField).value == '') {
			alert('You must fill out the '+what+' date and time fields');
			return;
		}

		$(el.id).className = 'action loading';
		new Ajax.Request(url, {
			method: 'get',
			onSuccess: function(t) {
				data = eval('('+t.responseText+')');
				if (data.status == 'success') {
					$(ids.wholeMessage).style.display = 'block';
					$(ids.dateTime).innerHTML = eval('data.message.'+what);
				} else { EmbargoExpiry.error(t); }
			},
			onFailure: function(t) { EmbargoExpiry.error(t); },
			onComplete: function(t) { $(el.id).className = 'action'; }
		});
	},
	reset: function(what, el) {
		ids = EmbargoExpiry.ids(what);
		var url = 'admin/cms_setembargoexpiry?wfRequest='+$('WorkflowRequest_ID').value;

		$(ids.dateField).value = '';
		$(ids.timeField).value = '';

		EmbargoExpiry.fieldCheck();

		if (what == 'embargo') {
			url += '&ResetEmbargo';
		} else if (what == 'expiry') {
			url += '&ResetExpiry';
		}

		$(el.id).className = 'action loading';
		new Ajax.Request(url, {
			method: 'get',
			onSuccess: function(t) {
				data = eval('('+t.responseText+')');
				if (data.status == 'success') {
					$(ids.wholeMessage).style.display = 'none';
				} else { EmbargoExpiry.error(t); }
			},
			onFailure: function(t) { EmbargoExpiry.error(t); },
			onComplete: function(t) { $(el.id).className = 'action'; }
		});
	},
	error: function(transport) {
		EmbargoExpiry.fieldCheck();
		alert('There was an error processing that request.');
	},
	ids: function(forWhat) {
		switch(forWhat) {
			case 'expiry':
				return {
					resetButton: 'resetExpiryButton',
					saveButton: 'saveExpiryButton',
					dateField: 'ExpiryDate_Date',
					timeField: 'ExpiryDate_Time',
					wholeMessage: 'embargoExpiry-expiryStatus',
					dateTime: 'expiryDate'
				};
			case 'embargo':
				return {
					resetButton: 'resetEmbargoButton',
					saveButton: 'saveEmbargoButton',
					dateField: 'EmbargoDate_Date',
					timeField: 'EmbargoDate_Time',
					wholeMessage: 'embargoExpiry-embargoStatus',
					dateTime: 'embargoDate'
				};
		}
	},
	eButton: function(id) {
		$(id).className = $(id).className.replace(/disabled/, '');
		$(id).disabled = false;
	},
	dButton: function(id) {
		$(id).className = $(id).className+" disabled";
		$(id).disabled = true;
	},
	fieldCheck: function() {
		ids = EmbargoExpiry.ids('embargo');

		if ($(ids.dateField).value == '' || $(ids.timeField).value == '') {
			EmbargoExpiry.dButton(ids.saveButton);
			EmbargoExpiry.dButton(ids.resetButton);
		} else {
			EmbargoExpiry.eButton(ids.saveButton);
			EmbargoExpiry.eButton(ids.resetButton);
		}

		ids = EmbargoExpiry.ids('expiry');
		if ($(ids.dateField).value == '' || $(ids.timeField).value == '') {
			EmbargoExpiry.dButton(ids.saveButton);
			EmbargoExpiry.dButton(ids.resetButton);
		} else {
			EmbargoExpiry.eButton(ids.saveButton);
			EmbargoExpiry.eButton(ids.resetButton);
		}

		window.setTimeout('EmbargoExpiry.fieldCheck();', 1000);
	}
};

function action_publish_right(e) {
	var messageEl = CMSWorkflow.createPromptElement('WorkflowComment', 'Please comment on this publication, if applicable.');
	$('Form_EditForm').appendChild(messageEl);
	$('Form_EditForm_action_publish').value = ss.i18n._t('CMSMAIN.PUBLISHING');
	$('Form_EditForm_action_publish').className = 'action loading';
	$('Form_EditForm').save(false, null, 'cms_publishwithcomment', true);
	$('Form_EditForm').removeChild(messageEl);
}
<|endoftext|>


<commit_before>'use strict'
var Promise = require('./promise')
var FastQueue = require('./fast-queue')
var iterator = require('./util').iterator
var INTERNAL = require('./util').INTERNAL
var NOOP = function () {}

function PromiseStream(source) {
	this._state = $STREAM_OPEN
	this._queue = new FastQueue
	this._concurrency = Infinity
	this._processing = 0
	this._closedPromise = new Promise(INTERNAL)
	this._process = null
	this._pipedStream = null // Streams with _pipedStream have _process, but not necessarily the reverse.
	this._flush = _flushQueue
	// See if improvements can be made with waiting on promises too late (i.e., it it's says processing but it isn't really; maybe always queue values that are unresolved promises)
	// Could signal desiredSize as (highWaterMark - processing - queue._length)
	// Provide reading methods that don't pipe to a new stream; possibilities:
	// - drain() -> emitter
	// - merge() -> promise of array
	// - reduce() -> promise of value
	// - read() -> promise of {value, done}, for single-item pulling

	if (source === INTERNAL) {
		this._removeListeners = NOOP
	} else {
		var self = this, index = 0, a, b, c
		source.on('data', a = function (data) {self._write(Promise.resolve(data), index++)})
		source.on('end', b = function () {self._end()})
		source.on('error', c = function (reason) {self._error(reason)})
		this._removeListeners = function () {
			source.removeListener('data', a)
			source.removeListener('end', b)
			source.removeListener('error', c)
			self._removeListeners = NOOP
		}
	}
}
PromiseStream.from = function (iterable) {
	var stream = new PromiseStream(INTERNAL)
	stream._switchToIteratorMode(iterable)
	return stream
}
PromiseStream.prototype.cancel = function () {
	if (this._state === $STREAM_CLOSED) {
		return this._closedPromise
	}
	this._pipedStream && this._pipedStream.cancel()
	this._state = $STREAM_CLOSED
	this._closedPromise._resolve(true)
	this._processing = 0
	this._cleanup()
	return this._closedPromise
}
PromiseStream.prototype.map = function (concurrency, handler) {
	if (this._state === $STREAM_CLOSED) {throw new TypeError('This stream is closed.')}
	if (this._process) {throw new TypeError('This stream already has a destination.')}
	if (arguments.length < 2) {handler = concurrency; concurrency = Infinity}
	if (typeof handler !== 'function') {throw new TypeError('Expected argument to be a function.')}
	this._concurrency = Math.max(1, Math.floor(concurrency)) || Infinity
	var dest = new PromiseStream(INTERNAL)
	this._closedPromise._state |= $SUPPRESS_UNHANDLED_REJECTIONS
	this._pipedStream = dest
	this._process = MapProcess(this, dest, handler)
	this._flush()
	return dest
}
PromiseStream.prototype.forEach = function (concurrency, handler) {
	if (this._state === $STREAM_CLOSED) {throw new TypeError('This stream is closed.')}
	if (this._process) {throw new TypeError('This stream already has a destination.')}
	if (arguments.length < 2) {handler = concurrency; concurrency = Infinity}
	if (typeof handler !== 'function') {throw new TypeError('Expected argument to be a function.')}
	this._concurrency = Math.max(1, Math.floor(concurrency)) || Infinity
	var dest = new PromiseStream(INTERNAL)
	this._closedPromise._state |= $SUPPRESS_UNHANDLED_REJECTIONS
	this._pipedStream = dest
	this._process = ForEachProcess(this, dest, handler)
	this._flush()
	return dest
}
PromiseStream.prototype.filter = function (concurrency, handler) {
	if (this._state === $STREAM_CLOSED) {throw new TypeError('This stream is closed.')}
	if (this._process) {throw new TypeError('This stream already has a destination.')}
	if (arguments.length < 2) {handler = concurrency; concurrency = Infinity}
	if (typeof handler !== 'function') {throw new TypeError('Expected argument to be a function.')}
	this._concurrency = Math.max(1, Math.floor(concurrency)) || Infinity
	var dest = new PromiseStream(INTERNAL)
	this._closedPromise._state |= $SUPPRESS_UNHANDLED_REJECTIONS
	this._pipedStream = dest
	this._process = FilterProcess(this, dest, handler)
	this._flush()
	return dest
}
PromiseStream.prototype.sort = function () {
	if (this._state === $STREAM_CLOSED) {throw new TypeError('This stream is closed.')}
	if (this._process) {throw new TypeError('This stream already has a destination.')}
	var dest = new PromiseStream(INTERNAL)
	this._closedPromise._state |= $SUPPRESS_UNHANDLED_REJECTIONS
	this._pipedStream = dest
	this._process = SortProcess(this, dest)
	this._flush()
	return dest
}
Object.defineProperty(PromiseStream.prototype, 'closed', {
	enumerable: true, configurable: true,
	get: function () {return this._closedPromise}
})

// ========== Private methods ==========

// Used for pushing data into the stream (not used in iterable mode).
PromiseStream.prototype._write = function (promise, index) {
	if (this._state !== $STREAM_OPEN) {return}
	if (this._process && this._processing < this._concurrency) {
		this._process(promise, index)
		++this._processing
	} else {
		this._queue.push(promise.catchLater())
		this._queue.push(index)
	}
}

// Used to indicate that there will be no more data added to the stream.
PromiseStream.prototype._end = function () {
	if (this._state === $STREAM_CLOSED) {return}
	if (this._process && this._processing === 0) {
		this._pipedStream && this._pipedStream._end()
		this._state = $STREAM_CLOSED
		this._closedPromise._resolve(false)
		this._cleanup()
	} else {
		this._state = $STREAM_CLOSING
	}
}

// Used to indicate that an error has occured, and the stream should immediately close.
PromiseStream.prototype._error = function (reason) {
	if (this._state === $STREAM_CLOSED) {return}
	this._pipedStream && this._pipedStream._error(reason)
	this._state = $STREAM_CLOSED
	this._closedPromise._reject(reason)
	this._processing = 0
	this._cleanup()
}

// Switches the stream into iterable mode.
// Data will be pulled from an iterable, instead of pushed by an outside source.
PromiseStream.prototype._switchToIteratorMode = function (iterable) {
	if (this._state !== $STREAM_OPEN) {return}
	if (Array.isArray(iterable)) {
		this._queue = new ArrayIterator(iterable)
	} else {
		var it = getIterator(iterable)
		if (it === IS_ERROR) {
			return this._error(LAST_ERROR)
		}
		this._queue = it
	}
	this._nextIndex = 0
	this._flush = _flushIterator
	this._process && this._flush()
}

// Releases internal resources. Should only be used after the stream is CLOSED.
PromiseStream.prototype._cleanup = function () {
	this._queue = null
	this._process = null
	this._pipedStream = null
	this._removeListeners()
}

// Indicates that a single item is done processing.
// If there are queued items (or items available in the iterable), they will be
// flushed and processed until the concurrency limit is reached once again.
// This method should not be invoked if the stream is CLOSED.
PromiseStream.prototype._finishProcess = function () {
	--this._processing
	this._flush()
}

// Flushes and processes the iterable until the concurrency limit is reached,
// or until the entire iterable has been flushed.
function _flushIterator() {
	if (this._state === $STREAM_CLOSED) {return}
	for (; this._processing < this._concurrency; ++this._processing) {
		var data = getNext(this._queue)
		if (data === IS_ERROR) {
			this._error(LAST_ERROR)
			break
		}
		if (data === IS_DONE) {
			this._end()
			break
		}
		this._process(Promise.resolve(data), this._nextIndex++)
	}
}

// Flushes and processes the queue until the concurrency limit is reached,
// or until the entire queue has been flushed.
function _flushQueue() {
	if (this._state === $STREAM_CLOSED) {return}
	for (; this._queue._length > 0 && this._processing < this._concurrency; ++this._processing) {
		this._process(this._queue.shift(), this._queue.shift())
	}
	if (this._state === $STREAM_CLOSING && this._processing === 0) {
		this._end()
	}
}

// ========== Pipe Processes ==========
// It is important that no processes fulfill or reject synchronously

function MapProcess(source, dest, handler) {
	function onFulfilled(value, index) {
		if (source._state === $STREAM_CLOSED) {return}
		dest._write(Promise.resolve(value), index)
		source._finishProcess()
	}
	function onRejected(reason) {source._error(reason)}
	return function (promise, index) {
		promise._then(handler, undefined, index)._then(onFulfilled, onRejected, index)
	}
}
function ForEachProcess(source, dest, handler) {
	function onFulfilled(value, original) {
		if (source._state === $STREAM_CLOSED) {return}
		dest._write(original.promise, original.index)
		source._finishProcess()
	}
	function onRejected(reason) {source._error(reason)}
	return function (promise, index) {
		promise._then(handler, undefined, index)._then(onFulfilled, onRejected, {promise: promise, index: index})
	}
}
function FilterProcess(source, dest, handler) {
	function onFulfilled(value, original) {
		if (source._state === $STREAM_CLOSED) {return}
		value && dest._write(original.promise, original.index)
		source._finishProcess()
	}
	function onRejected(reason) {source._error(reason)}
	return function (promise, index) {
		promise._then(handler, undefined, index)._then(onFulfilled, onRejected, {promise: promise, index: index})
	}
}
function SortProcess(source, dest) {
	function onFulfilled(value, index) {
		if (source._state === $STREAM_CLOSED) {return}
		dest._write(value, index)
		source._finishProcess()
	}
	function onRejected(reason) {source._error(reason)}
	var nextIndex = 0
	var map = {}
	function sort(value, index) {
		if (index === nextIndex) {
			onFulfilled(value, nextIndex++)
			while (nextIndex in map) {
				onFulfilled(map[nextIndex], nextIndex++)
				map[nextIndex] = undefined
			}
		} else {
			map[index] = value
		}
	}
	return function (promise, index) {
		promise._then(sort, onRejected, index)
	}
}

// ========== Extracted try-catch methods ==========
var LAST_ERROR = null
var IS_ERROR = {}
var IS_DONE = {}
function getNext(iterator) {
	try {
		var next = iterator.next()
		return next.done ? IS_DONE : next.value
	} catch (ex) {
		LAST_ERROR = ex
		return IS_ERROR
	}
}
function getIterator(iterable) {
	try {
		if (iterator && iterable != null && typeof iterable[iterator] === 'function') {
			return iterable[iterator]()
		}
	} catch (ex) {
		LAST_ERROR = ex
		return IS_ERROR
	}
}

// ========== ArrayIterator ==========
function ArrayIterator(array) {
	this._array = array
	this._index = 0
}
ArrayIterator.prototype.next = function () {
	return this._index < this._array.length
		? {value: this._array[this._index++], done: false}
		: (this._index = NaN, {value: undefined, done: true})
}

// ========== Extend Promise API ==========
Promise.Stream = PromiseStream
Promise.prototype.stream = function () {
	var stream = new PromiseStream(INTERNAL)
	this._then(function (iterable) {
		stream._switchToIteratorMode(iterable)
	}, function (reason) {
		stream._error(reason)
	})
	return stream
}

<commit_msg>tweaks<commit_after>'use strict'
var Promise = require('./promise')
var FastQueue = require('./fast-queue')
var iterator = require('./util').iterator
var INTERNAL = require('./util').INTERNAL
var NOOP = function () {}

function PromiseStream(source) {
	this._state = $STREAM_OPEN
	this._queue = new FastQueue
	this._concurrency = Infinity
	this._processing = 0
	this._closedPromise = new Promise(INTERNAL)
	this._process = null
	this._pipedStream = null // Streams with _pipedStream have _process, but not necessarily the reverse.
	this._flush = _flushQueue
	// Benchmark different sort methods (2 queues, hash map)
	// See if improvements can be made with waiting on promises too late (i.e., it it's says processing but it isn't really; maybe always queue values that are unresolved promises)
	// Could signal desiredSize as (highWaterMark - processing - queue._length)
	// Provide reading methods that don't pipe to a new stream; possibilities:
	// - drain() -> emitter
	// - merge() -> promise of array
	// - reduce() -> promise of value
	// - read() -> promise of {value, done}, for single-item pulling

	if (source === INTERNAL) {
		this._removeListeners = NOOP
	} else {
		var self = this, index = 0, a, b, c
		source.on('data', a = function (data) {self._write(Promise.resolve(data), index++)})
		source.on('end', b = function () {self._end()})
		source.on('error', c = function (reason) {self._error(reason)})
		this._removeListeners = function () {
			source.removeListener('data', a)
			source.removeListener('end', b)
			source.removeListener('error', c)
			self._removeListeners = NOOP
		}
	}
}
PromiseStream.from = function (iterable) {
	var stream = new PromiseStream(INTERNAL)
	stream._switchToIteratorMode(iterable)
	return stream
}
PromiseStream.prototype.cancel = function () {
	if (this._state === $STREAM_CLOSED) {
		return this._closedPromise
	}
	this._pipedStream && this._pipedStream.cancel()
	this._state = $STREAM_CLOSED
	this._closedPromise._resolve(true)
	this._processing = 0
	this._cleanup()
	return this._closedPromise
}
PromiseStream.prototype.map = function (concurrency, handler) {
	if (this._state === $STREAM_CLOSED) {throw new TypeError('This stream is closed.')}
	if (this._process) {throw new TypeError('This stream already has a destination.')}
	if (arguments.length < 2) {handler = concurrency; concurrency = Infinity}
	if (typeof handler !== 'function') {throw new TypeError('Expected argument to be a function.')}
	this._concurrency = Math.max(1, Math.floor(concurrency)) || Infinity
	var dest = new PromiseStream(INTERNAL)
	this._closedPromise._state |= $SUPPRESS_UNHANDLED_REJECTIONS
	this._pipedStream = dest
	this._process = MapProcess(this, dest, handler)
	this._flush()
	return dest
}
PromiseStream.prototype.forEach = function (concurrency, handler) {
	if (this._state === $STREAM_CLOSED) {throw new TypeError('This stream is closed.')}
	if (this._process) {throw new TypeError('This stream already has a destination.')}
	if (arguments.length < 2) {handler = concurrency; concurrency = Infinity}
	if (typeof handler !== 'function') {throw new TypeError('Expected argument to be a function.')}
	this._concurrency = Math.max(1, Math.floor(concurrency)) || Infinity
	var dest = new PromiseStream(INTERNAL)
	this._closedPromise._state |= $SUPPRESS_UNHANDLED_REJECTIONS
	this._pipedStream = dest
	this._process = ForEachProcess(this, dest, handler)
	this._flush()
	return dest
}
PromiseStream.prototype.filter = function (concurrency, handler) {
	if (this._state === $STREAM_CLOSED) {throw new TypeError('This stream is closed.')}
	if (this._process) {throw new TypeError('This stream already has a destination.')}
	if (arguments.length < 2) {handler = concurrency; concurrency = Infinity}
	if (typeof handler !== 'function') {throw new TypeError('Expected argument to be a function.')}
	this._concurrency = Math.max(1, Math.floor(concurrency)) || Infinity
	var dest = new PromiseStream(INTERNAL)
	this._closedPromise._state |= $SUPPRESS_UNHANDLED_REJECTIONS
	this._pipedStream = dest
	this._process = FilterProcess(this, dest, handler)
	this._flush()
	return dest
}
PromiseStream.prototype.sort = function () {
	if (this._state === $STREAM_CLOSED) {throw new TypeError('This stream is closed.')}
	if (this._process) {throw new TypeError('This stream already has a destination.')}
	var dest = new PromiseStream(INTERNAL)
	this._closedPromise._state |= $SUPPRESS_UNHANDLED_REJECTIONS
	this._pipedStream = dest
	this._process = SortProcess(this, dest)
	this._flush()
	return dest
}
Object.defineProperty(PromiseStream.prototype, 'closed', {
	enumerable: true, configurable: true,
	get: function () {return this._closedPromise}
})

// ========== Private methods ==========

// Used for pushing data into the stream (not used in iterable mode).
PromiseStream.prototype._write = function (promise, index) {
	if (this._state !== $STREAM_OPEN) {return}
	if (this._process && this._processing < this._concurrency) {
		this._process(promise, index)
		++this._processing
	} else {
		this._queue.push(promise.catchLater())
		this._queue.push(index)
	}
}

// Used to indicate that there will be no more data added to the stream.
PromiseStream.prototype._end = function () {
	if (this._state === $STREAM_CLOSED) {return}
	if (this._process && this._processing === 0) {
		this._pipedStream && this._pipedStream._end()
		this._state = $STREAM_CLOSED
		this._closedPromise._resolve(false)
		this._cleanup()
	} else {
		this._state = $STREAM_CLOSING
	}
}

// Used to indicate that an error has occured, and the stream should immediately close.
PromiseStream.prototype._error = function (reason) {
	if (this._state === $STREAM_CLOSED) {return}
	this._pipedStream && this._pipedStream._error(reason)
	this._state = $STREAM_CLOSED
	this._closedPromise._reject(reason)
	this._processing = 0
	this._cleanup()
}

// Switches the stream into iterable mode.
// Data will be pulled from an iterable, instead of pushed by an outside source.
PromiseStream.prototype._switchToIteratorMode = function (iterable) {
	if (this._state !== $STREAM_OPEN) {return}
	if (Array.isArray(iterable)) {
		this._queue = new ArrayIterator(iterable)
	} else {
		var it = getIterator(iterable)
		if (it === IS_ERROR) {
			return this._error(LAST_ERROR)
		}
		this._queue = it
	}
	this._nextIndex = 0
	this._flush = _flushIterator
	this._process && this._flush()
}

// Releases internal resources. Should only be used after the stream is CLOSED.
PromiseStream.prototype._cleanup = function () {
	this._queue = null
	this._process = null
	this._pipedStream = null
	this._removeListeners()
}

// Indicates that a single item is done processing.
// If there are queued items (or items available in the iterable), they will be
// flushed and processed until the concurrency limit is reached once again.
// This method should not be invoked if the stream is CLOSED.
PromiseStream.prototype._finishProcess = function () {
	--this._processing
	this._flush()
}

// Flushes and processes the iterable until the concurrency limit is reached,
// or until the entire iterable has been flushed.
function _flushIterator() {
	if (this._state === $STREAM_CLOSED) {return}
	for (; this._processing < this._concurrency; ++this._processing) {
		var data = getNext(this._queue)
		if (data === IS_ERROR) {
			this._error(LAST_ERROR)
			break
		}
		if (data === IS_DONE) {
			this._end()
			break
		}
		this._process(Promise.resolve(data), this._nextIndex++)
	}
}

// Flushes and processes the queue until the concurrency limit is reached,
// or until the entire queue has been flushed.
function _flushQueue() {
	if (this._state === $STREAM_CLOSED) {return}
	for (; this._queue._length > 0 && this._processing < this._concurrency; ++this._processing) {
		this._process(this._queue.shift(), this._queue.shift())
	}
	if (this._state === $STREAM_CLOSING && this._processing === 0) {
		this._end()
	}
}

// ========== Pipe Processes ==========
// It is important that no processes fulfill or reject synchronously

function MapProcess(source, dest, handler) {
	function onFulfilled(value, index) {
		if (source._state === $STREAM_CLOSED) {return}
		dest._write(Promise.resolve(value), index)
		source._finishProcess()
	}
	function onRejected(reason) {source._error(reason)}
	return function (promise, index) {
		promise._then(handler, undefined, index)._then(onFulfilled, onRejected, index)
	}
}
function ForEachProcess(source, dest, handler) {
	function onFulfilled(value, original) {
		if (source._state === $STREAM_CLOSED) {return}
		dest._write(original.promise, original.index)
		source._finishProcess()
	}
	function onRejected(reason) {source._error(reason)}
	return function (promise, index) {
		promise._then(handler, undefined, index)._then(onFulfilled, onRejected, {promise: promise, index: index})
	}
}
function FilterProcess(source, dest, handler) {
	function onFulfilled(value, original) {
		if (source._state === $STREAM_CLOSED) {return}
		value && dest._write(original.promise, original.index)
		source._finishProcess()
	}
	function onRejected(reason) {source._error(reason)}
	return function (promise, index) {
		promise._then(handler, undefined, index)._then(onFulfilled, onRejected, {promise: promise, index: index})
	}
}
function SortProcess(source, dest) {
	function onFulfilled(promise, index) {
		if (source._state === $STREAM_CLOSED) {return}
		dest._write(promise, index)
		source._finishProcess()
	}
	function onRejected(reason) {source._error(reason)}
	var nextIndex = 0
	var map = {}
	function sort(value, original) {
		if (original.index === nextIndex) {
			onFulfilled(original.promise, nextIndex++)
			while (nextIndex in map) {
				onFulfilled(map[nextIndex], nextIndex++)
				map[nextIndex] = undefined
			}
		} else {
			map[original.index] = original.promise
		}
	}
	return function (promise, index) {
		promise._then(sort, onRejected, {promise: promise, index: index})
	}
}

// ========== Extracted try-catch methods ==========
var LAST_ERROR = null
var IS_ERROR = {}
var IS_DONE = {}
function getNext(iterator) {
	try {
		var next = iterator.next()
		return next.done ? IS_DONE : next.value
	} catch (ex) {
		LAST_ERROR = ex
		return IS_ERROR
	}
}
function getIterator(iterable) {
	try {
		if (iterator && iterable != null && typeof iterable[iterator] === 'function') {
			return iterable[iterator]()
		}
	} catch (ex) {
		LAST_ERROR = ex
		return IS_ERROR
	}
}

// ========== ArrayIterator ==========
function ArrayIterator(array) {
	this._array = array
	this._index = 0
}
ArrayIterator.prototype.next = function () {
	return this._index < this._array.length
		? {value: this._array[this._index++], done: false}
		: (this._index = NaN, {value: undefined, done: true})
}

// ========== Extend Promise API ==========
Promise.Stream = PromiseStream
Promise.prototype.stream = function () {
	var stream = new PromiseStream(INTERNAL)
	this._then(function (iterable) {
		stream._switchToIteratorMode(iterable)
	}, function (reason) {
		stream._error(reason)
	})
	return stream
}

<|endoftext|>


<commit_before>import { computed, observable, autorun } from 'mobx';
import path from 'path';
import normalizeUrl from 'normalize-url';

const debug = require('debug')('Franz:Service');

export default class Service {
  id = '';

  recipe = '';

  webview = null;

  timer = null;

  events = {};

  isAttached = false;

  @observable isActive = false; // Is current webview active

  @observable name = '';

  @observable unreadDirectMessageCount = 0;

  @observable unreadIndirectMessageCount = 0;

  @observable order = 99;

  @observable isEnabled = true;

  @observable isMuted = false;

  @observable team = '';

  @observable customUrl = '';

  @observable isNotificationEnabled = true;

  @observable isBadgeEnabled = true;

  @observable isIndirectMessageBadgeEnabled = true;

  @observable iconUrl = '';

  @observable hasCustomUploadedIcon = false;

  @observable hasCrashed = false;

  @observable isDarkModeEnabled = false;

  @observable spellcheckerLanguage = null;

  @observable isFirstLoad = true;

  @observable isLoading = true;

  @observable isError = false;

  @observable errorMessage = '';

  constructor(data, recipe) {
    if (!data) {
      console.error('Service config not valid');
      return null;
    }

    if (!recipe) {
      console.error('Service recipe not valid');
      return null;
    }

    this.id = data.id || this.id;
    this.name = data.name || this.name;
    this.team = data.team || this.team;
    this.customUrl = data.customUrl || this.customUrl;
    // this.customIconUrl = data.customIconUrl || this.customIconUrl;
    this.iconUrl = data.iconUrl || this.iconUrl;

    this.order = data.order !== undefined
      ? data.order : this.order;

    this.isEnabled = data.isEnabled !== undefined
      ? data.isEnabled : this.isEnabled;

    this.isNotificationEnabled = data.isNotificationEnabled !== undefined
      ? data.isNotificationEnabled : this.isNotificationEnabled;

    this.isBadgeEnabled = data.isBadgeEnabled !== undefined
      ? data.isBadgeEnabled : this.isBadgeEnabled;

    this.isIndirectMessageBadgeEnabled = data.isIndirectMessageBadgeEnabled !== undefined
      ? data.isIndirectMessageBadgeEnabled : this.isIndirectMessageBadgeEnabled;

    this.isMuted = data.isMuted !== undefined ? data.isMuted : this.isMuted;

    this.isDarkModeEnabled = data.isDarkModeEnabled !== undefined ? data.isDarkModeEnabled : this.isDarkModeEnabled;

    this.hasCustomUploadedIcon = data.hasCustomIcon !== undefined ? data.hasCustomIcon : this.hasCustomUploadedIcon;

    this.proxy = data.proxy !== undefined ? data.proxy : this.proxy;

    this.spellcheckerLanguage = data.spellcheckerLanguage !== undefined ? data.spellcheckerLanguage : this.spellcheckerLanguage;

    this.recipe = recipe;

    autorun(() => {
      if (!this.isEnabled) {
        this.webview = null;
        this.isAttached = false;
        this.unreadDirectMessageCount = 0;
        this.unreadIndirectMessageCount = 0;
      }
    });
  }

  @computed get shareWithWebview() {
    return {
      spellcheckerLanguage: this.spellcheckerLanguage,
      isDarkModeEnabled: this.isDarkModeEnabled,
    };
  }

  @computed get url() {
    if (this.recipe.hasCustomUrl && this.customUrl) {
      let url;
      try {
        url = normalizeUrl(this.customUrl, { stripWWW: false });
      } catch (err) {
        console.error(`Service (${this.recipe.name}): '${this.customUrl}' is not a valid Url.`);
      }

      if (typeof this.recipe.buildUrl === 'function') {
        url = this.recipe.buildUrl(url);
      }

      return url;
    }

    if (this.recipe.hasTeamId && this.team) {
      return this.recipe.serviceURL.replace('{teamId}', this.team);
    }

    return this.recipe.serviceURL;
  }

  @computed get icon() {
    if (this.iconUrl) {
      return this.iconUrl;
    }

    return path.join(this.recipe.path, 'icon.svg');
  }

  @computed get hasCustomIcon() {
    return Boolean(this.iconUrl);
  }

  @computed get iconPNG() {
    return path.join(this.recipe.path, 'icon.png');
  }

  @computed get userAgent() {
    let userAgent = window.navigator.userAgent;
    if (typeof this.recipe.overrideUserAgent === 'function') {
      userAgent = this.recipe.overrideUserAgent();
    }

    return userAgent;
  }

  initializeWebViewEvents({ handleIPCMessage, openWindow }) {
    this.webview.addEventListener('ipc-message', e => handleIPCMessage({
      serviceId: this.id,
      channel: e.channel,
      args: e.args,
    }));

    this.webview.addEventListener('new-window', (event, url, frameName, options) => openWindow({
      event,
      url,
      frameName,
      options,
    }));

    this.webview.addEventListener('did-start-loading', () => {
      this.hasCrashed = false;
      this.isLoading = true;
      this.isError = false;
    });

    const didLoad = () => {
      this.isLoading = false;

      if (!this.isError) {
        this.isFirstLoad = false;
      }
    };

    this.webview.addEventListener('did-frame-finish-load', didLoad.bind(this));
    this.webview.addEventListener('did-navigate', didLoad.bind(this));

    this.webview.addEventListener('did-fail-load', (event) => {
      debug('Service failed to load', this.name, event);
      if (event.isMainFrame) {
        this.isError = true;
        this.errorMessage = event.errorDescription;
        this.isLoading = false;
      }
    });

    this.webview.addEventListener('crashed', () => {
      debug('Service crashed', this.name);
      this.hasCrashed = true;
    });
  }

  initializeWebViewListener() {
    if (this.webview && this.recipe.events) {
      Object.keys(this.recipe.events).forEach((eventName) => {
        const eventHandler = this.recipe[this.recipe.events[eventName]];
        if (typeof eventHandler === 'function') {
          this.webview.addEventListener(eventName, eventHandler);
        }
      });
    }
  }

  resetMessageCount() {
    this.unreadDirectMessageCount = 0;
    this.unreadIndirectMessageCount = 0;
  }
}
<commit_msg>Fix issue with error view and "user aborted" requests<commit_after>import { computed, observable, autorun } from 'mobx';
import path from 'path';
import normalizeUrl from 'normalize-url';

const debug = require('debug')('Franz:Service');

export default class Service {
  id = '';

  recipe = '';

  webview = null;

  timer = null;

  events = {};

  isAttached = false;

  @observable isActive = false; // Is current webview active

  @observable name = '';

  @observable unreadDirectMessageCount = 0;

  @observable unreadIndirectMessageCount = 0;

  @observable order = 99;

  @observable isEnabled = true;

  @observable isMuted = false;

  @observable team = '';

  @observable customUrl = '';

  @observable isNotificationEnabled = true;

  @observable isBadgeEnabled = true;

  @observable isIndirectMessageBadgeEnabled = true;

  @observable iconUrl = '';

  @observable hasCustomUploadedIcon = false;

  @observable hasCrashed = false;

  @observable isDarkModeEnabled = false;

  @observable spellcheckerLanguage = null;

  @observable isFirstLoad = true;

  @observable isLoading = true;

  @observable isError = false;

  @observable errorMessage = '';

  constructor(data, recipe) {
    if (!data) {
      console.error('Service config not valid');
      return null;
    }

    if (!recipe) {
      console.error('Service recipe not valid');
      return null;
    }

    this.id = data.id || this.id;
    this.name = data.name || this.name;
    this.team = data.team || this.team;
    this.customUrl = data.customUrl || this.customUrl;
    // this.customIconUrl = data.customIconUrl || this.customIconUrl;
    this.iconUrl = data.iconUrl || this.iconUrl;

    this.order = data.order !== undefined
      ? data.order : this.order;

    this.isEnabled = data.isEnabled !== undefined
      ? data.isEnabled : this.isEnabled;

    this.isNotificationEnabled = data.isNotificationEnabled !== undefined
      ? data.isNotificationEnabled : this.isNotificationEnabled;

    this.isBadgeEnabled = data.isBadgeEnabled !== undefined
      ? data.isBadgeEnabled : this.isBadgeEnabled;

    this.isIndirectMessageBadgeEnabled = data.isIndirectMessageBadgeEnabled !== undefined
      ? data.isIndirectMessageBadgeEnabled : this.isIndirectMessageBadgeEnabled;

    this.isMuted = data.isMuted !== undefined ? data.isMuted : this.isMuted;

    this.isDarkModeEnabled = data.isDarkModeEnabled !== undefined ? data.isDarkModeEnabled : this.isDarkModeEnabled;

    this.hasCustomUploadedIcon = data.hasCustomIcon !== undefined ? data.hasCustomIcon : this.hasCustomUploadedIcon;

    this.proxy = data.proxy !== undefined ? data.proxy : this.proxy;

    this.spellcheckerLanguage = data.spellcheckerLanguage !== undefined ? data.spellcheckerLanguage : this.spellcheckerLanguage;

    this.recipe = recipe;

    autorun(() => {
      if (!this.isEnabled) {
        this.webview = null;
        this.isAttached = false;
        this.unreadDirectMessageCount = 0;
        this.unreadIndirectMessageCount = 0;
      }
    });
  }

  @computed get shareWithWebview() {
    return {
      spellcheckerLanguage: this.spellcheckerLanguage,
      isDarkModeEnabled: this.isDarkModeEnabled,
    };
  }

  @computed get url() {
    if (this.recipe.hasCustomUrl && this.customUrl) {
      let url;
      try {
        url = normalizeUrl(this.customUrl, { stripWWW: false });
      } catch (err) {
        console.error(`Service (${this.recipe.name}): '${this.customUrl}' is not a valid Url.`);
      }

      if (typeof this.recipe.buildUrl === 'function') {
        url = this.recipe.buildUrl(url);
      }

      return url;
    }

    if (this.recipe.hasTeamId && this.team) {
      return this.recipe.serviceURL.replace('{teamId}', this.team);
    }

    return this.recipe.serviceURL;
  }

  @computed get icon() {
    if (this.iconUrl) {
      return this.iconUrl;
    }

    return path.join(this.recipe.path, 'icon.svg');
  }

  @computed get hasCustomIcon() {
    return Boolean(this.iconUrl);
  }

  @computed get iconPNG() {
    return path.join(this.recipe.path, 'icon.png');
  }

  @computed get userAgent() {
    let userAgent = window.navigator.userAgent;
    if (typeof this.recipe.overrideUserAgent === 'function') {
      userAgent = this.recipe.overrideUserAgent();
    }

    return userAgent;
  }

  initializeWebViewEvents({ handleIPCMessage, openWindow }) {
    this.webview.addEventListener('ipc-message', e => handleIPCMessage({
      serviceId: this.id,
      channel: e.channel,
      args: e.args,
    }));

    this.webview.addEventListener('new-window', (event, url, frameName, options) => openWindow({
      event,
      url,
      frameName,
      options,
    }));

    this.webview.addEventListener('did-start-loading', () => {
      this.hasCrashed = false;
      this.isLoading = true;
      this.isError = false;
    });

    const didLoad = () => {
      this.isLoading = false;

      if (!this.isError) {
        this.isFirstLoad = false;
      }
    };

    this.webview.addEventListener('did-frame-finish-load', didLoad.bind(this));
    this.webview.addEventListener('did-navigate', didLoad.bind(this));

    this.webview.addEventListener('did-fail-load', (event) => {
      debug('Service failed to load', this.name, event);
      if (event.isMainFrame && event.errorCode !== -3) {
        this.isError = true;
        this.errorMessage = event.errorDescription;
        this.isLoading = false;
      }
    });

    this.webview.addEventListener('crashed', () => {
      debug('Service crashed', this.name);
      this.hasCrashed = true;
    });
  }

  initializeWebViewListener() {
    if (this.webview && this.recipe.events) {
      Object.keys(this.recipe.events).forEach((eventName) => {
        const eventHandler = this.recipe[this.recipe.events[eventName]];
        if (typeof eventHandler === 'function') {
          this.webview.addEventListener(eventName, eventHandler);
        }
      });
    }
  }

  resetMessageCount() {
    this.unreadDirectMessageCount = 0;
    this.unreadIndirectMessageCount = 0;
  }
}
<|endoftext|>


<commit_before>;(function(exports) {
  var Isla, _, multimethod;
  if(typeof module !== 'undefined' && module.exports) { // node
    Isla = require('../node_modules/isla/src/isla.js').Isla;
    codeAnalyzer = require('../src/code-analyzer').codeAnalyzer;
  } else { // browser
    Isla = window.Isla;
    codeAnalyzer = window.codeAnalyzer;
  }

  var nodeDescriber = {
    describe: function(text, index, env) {
      var syntaxNode = getSyntaxNode(text, index);
      if (syntaxNode.syntax === "variable") {
        var val;
        if (syntaxNode.node.tag === "scalar") { // x is a t
          val = Isla.Interpreter.evaluateValue(syntaxNode.node, env).val;
        } else if (syntaxNode.node.tag === "identifier") {
          // x age is '1' - x not wrapped in scalar tag so can't evaluate
          // if tried to use tok one level up (in obj tag), would get attr val
          val = Isla.Interpreter.resolve({ ref:syntaxNode.node.c[0] }, env);
        }

        return { body: describeValue(val, env) };
      } else if (syntaxNode.syntax === "attribute") {
        var lineNumber = codeAnalyzer.getLineNumber(text, index);
        var line = codeAnalyzer.getLine(text, lineNumber);
        var tokens = codeAnalyzer.expressionTokens(line);
        var objToken = tokens[getTokenIndex(text, index)];
        var val = Isla.Interpreter.evaluateValue(objToken.c[0], env).val;
        return { body: describeValue(val, env) };
      } else {
        return undefined;
      }
    },
  };

  var getSyntaxNode = function(text, index) {
    var lineNumber = codeAnalyzer.getLineNumber(text, index);
    var line = codeAnalyzer.getLine(text, lineNumber);
    var syntaxTokens = codeAnalyzer.expressionSyntaxTokens(line);
    var syntaxTokenIndex = codeAnalyzer.getSyntaxTokenIndex(text, index);
    return syntaxTokens[syntaxTokenIndex];
  };

  var describeValue = function(val, env) {
    var description = "Has no value, yet.";
    if (val !== undefined) {
      // resolve any refs - non-refs will pass untouched
      val = Isla.Interpreter.resolve(val, env);

      description = val.toString();
      if(Isla.Utils.type(val) === "String") {
        description = "'" + val + "'";
      }
    }

    return description;
  }

  // hacky fn that returns mhich of main expr tokens the index falls in
  // does not dive below surface into sub tokens
  var getTokenIndex = function(text, index) {
    var tokenIndex = undefined;
    var lineNumber = codeAnalyzer.getLineNumber(text, index);
    var line = codeAnalyzer.getLine(text, lineNumber);
    var tokens = codeAnalyzer.expressionTokens(line);
    if (tokens !== undefined) {
      var lineIndex = codeAnalyzer.getLineIndex(text, index);
      tokenIndex = 0;
      for (var i = 0; i < tokens.length; i++) {
        var tok = tokens[i];
        if(lineIndex >= tok.index) {
          tokenIndex = i;
        }
      }
    }

    return tokenIndex;
  };

  exports.nodeDescriber = nodeDescriber;
})(typeof exports === 'undefined' ? this : exports)
<commit_msg>Make describeValue fn code clearer.<commit_after>;(function(exports) {
  var Isla, _, multimethod;
  if(typeof module !== 'undefined' && module.exports) { // node
    Isla = require('../node_modules/isla/src/isla.js').Isla;
    codeAnalyzer = require('../src/code-analyzer').codeAnalyzer;
  } else { // browser
    Isla = window.Isla;
    codeAnalyzer = window.codeAnalyzer;
  }

  var nodeDescriber = {
    describe: function(text, index, env) {
      var syntaxNode = getSyntaxNode(text, index);
      if (syntaxNode.syntax === "variable") {
        var val;
        if (syntaxNode.node.tag === "scalar") { // x is a t
          val = Isla.Interpreter.evaluateValue(syntaxNode.node, env).val;
        } else if (syntaxNode.node.tag === "identifier") {
          // x age is '1' - x not wrapped in scalar tag so can't evaluate
          // if tried to use tok one level up (in obj tag), would get attr val
          val = Isla.Interpreter.resolve({ ref:syntaxNode.node.c[0] }, env);
        }

        return { body: describeValue(val, env) };
      } else if (syntaxNode.syntax === "attribute") {
        var lineNumber = codeAnalyzer.getLineNumber(text, index);
        var line = codeAnalyzer.getLine(text, lineNumber);
        var tokens = codeAnalyzer.expressionTokens(line);
        var objToken = tokens[getTokenIndex(text, index)];
        var val = Isla.Interpreter.evaluateValue(objToken.c[0], env).val;
        return { body: describeValue(val, env) };
      } else {
        return undefined;
      }
    },
  };

  var getSyntaxNode = function(text, index) {
    var lineNumber = codeAnalyzer.getLineNumber(text, index);
    var line = codeAnalyzer.getLine(text, lineNumber);
    var syntaxTokens = codeAnalyzer.expressionSyntaxTokens(line);
    var syntaxTokenIndex = codeAnalyzer.getSyntaxTokenIndex(text, index);
    return syntaxTokens[syntaxTokenIndex];
  };

  var describeValue = function(val, env) {
    var description = "Has no value, yet.";
    if (val !== undefined) {
      // resolve any refs - non-refs will pass untouched
      resolvedVal = Isla.Interpreter.resolve(val, env);
      if(Isla.Utils.type(resolvedVal) === "String") {
        description = "'" + resolvedVal + "'";
      } else {
        description = resolvedVal.toString();
      }
    }

    return description;
  }

  // hacky fn that returns mhich of main expr tokens the index falls in
  // does not dive below surface into sub tokens
  var getTokenIndex = function(text, index) {
    var tokenIndex = undefined;
    var lineNumber = codeAnalyzer.getLineNumber(text, index);
    var line = codeAnalyzer.getLine(text, lineNumber);
    var tokens = codeAnalyzer.expressionTokens(line);
    if (tokens !== undefined) {
      var lineIndex = codeAnalyzer.getLineIndex(text, index);
      tokenIndex = 0;
      for (var i = 0; i < tokens.length; i++) {
        var tok = tokens[i];
        if(lineIndex >= tok.index) {
          tokenIndex = i;
        }
      }
    }

    return tokenIndex;
  };

  exports.nodeDescriber = nodeDescriber;
})(typeof exports === 'undefined' ? this : exports)
<|endoftext|>


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

(function(scope, testing) {

  function numberToString(x) {
    return x.toFixed(3).replace('.000', '');
  }

  function clamp(min, max, x) {
    return Math.min(max, Math.max(min, x));
  }

  function parseNumber(string) {
    if (/^\s*[-+]?(\d*\.)?\d+\s*$/.test(string))
      return Number(string);
  }

  function mergeNumbers(left, right) {
    return [left, right, numberToString];
  }

  // FIXME: This should probably go in it's own handler.
  function mergeFlex(left, right) {
    if (left == 0)
      return;
    return mergeNumbers(left, right);
  }

  // FIXME: This should probably go in it's own handler.
  function mergeZoom(left, right) {
    return [left, right, function(x) {
      // It seems that 1e-7 is the smallest value that blink CSS accepts as positive.
      return String(clamp(0.000001, Infinity, x)).slice(0, 9);
    }];
  }

  function clampedMergeNumbers(min, max) {
    return function(left, right) {
      return [left, right, function(x) {
        return numberToString(clamp(min, max, x));
      }];
    };
  }

  function round(left, right) {
    return [left, right, Math.round];
  }

  scope.clamp = clamp;
  scope.addPropertiesHandler(parseNumber, clampedMergeNumbers(0, Infinity), ['border-image-width']);
  scope.addPropertiesHandler(parseNumber, clampedMergeNumbers(0, 1), ['opacity', 'shape-image-threshold']);
  scope.addPropertiesHandler(parseNumber, mergeFlex, ['flex-grow', 'flex-shrink']);
  scope.addPropertiesHandler(parseNumber, mergeZoom, ['zoom']);
  scope.addPropertiesHandler(parseNumber, round, ['orphans', 'widows', 'z-index']);

  scope.parseNumber = parseNumber;
  scope.mergeNumbers = mergeNumbers;
  scope.numberToString = numberToString;

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

(function(scope, testing) {

  function numberToString(x) {
    return x.toFixed(3).replace('.000', '');
  }

  function clamp(min, max, x) {
    return Math.min(max, Math.max(min, x));
  }

  function parseNumber(string) {
    if (/^\s*[-+]?(\d*\.)?\d+\s*$/.test(string))
      return Number(string);
  }

  function mergeNumbers(left, right) {
    return [left, right, numberToString];
  }

  // FIXME: This should probably go in it's own handler.
  function mergeFlex(left, right) {
    if (left == 0)
      return;
    return mergeNumbers(left, right);
  }

  // FIXME: This should go in it's own handler. Zoom can also have percent
  // values, which are currently not supported.
  function mergeZoom(left, right) {
    return [left, right, function(x) {
      // It seems that 1e-7 is the smallest value that blink CSS accepts as positive.
      return String(clamp(0.000001, Infinity, x)).slice(0, 9);
    }];
  }

  function clampedMergeNumbers(min, max) {
    return function(left, right) {
      return [left, right, function(x) {
        return numberToString(clamp(min, max, x));
      }];
    };
  }

  function round(left, right) {
    return [left, right, Math.round];
  }

  scope.clamp = clamp;
  scope.addPropertiesHandler(parseNumber, clampedMergeNumbers(0, Infinity), ['border-image-width']);
  scope.addPropertiesHandler(parseNumber, clampedMergeNumbers(0, 1), ['opacity', 'shape-image-threshold']);
  scope.addPropertiesHandler(parseNumber, mergeFlex, ['flex-grow', 'flex-shrink']);
  scope.addPropertiesHandler(parseNumber, mergeZoom, ['zoom']);
  scope.addPropertiesHandler(parseNumber, round, ['orphans', 'widows', 'z-index']);

  scope.parseNumber = parseNumber;
  scope.mergeNumbers = mergeNumbers;
  scope.numberToString = numberToString;

})(webAnimationsMinifill, webAnimationsTesting);
<|endoftext|>


<commit_before>const controller = require("../_controller");

function WScriptShell() {
	this.environment = (x) => {
		if (x.toLowerCase() === "system")
			return (argument) => {
				argument = argument.toLowerCase();
				switch (argument) {
					case "comspec":
						return "%SystemRoot%\\system32\\cmd.exe";
					case "os":
						return "Windows_NT";
					case "processor_architecture":
						// Emulate a 32-bit environment for maximum compatibility
						return "x86";
					default:
						controller.kill(`Unknown parameter ${argument} for WScriptShell.Environment.System`);
				}
			};
		return `(Environment variable ${x})`;
	};
	this.specialfolders = (x) => `(Special folder ${x})`;
	this.createshortcut = () => ({});
	this.expandenvironmentstrings = (path) => {
		path = path.replace(/%TE?MP%/gi, "C:\\DOCUME~1\\MyUsername\\LOCALS~1\\Temp");
		path = path.replace(/%PROCESSOR_REVISION%/gi, "0209");
		path = path.replace(/%USERPROFILE%/gi, "C:\\Users\\MyUsername");

		// %APPDATA% equals C:\Documents and Settings\{username}\Application Data on Windows XP,
		// but C:\Users\{username}\AppData\Roaming on Win Vista and above
		if (process.argv.indexOf("--windows-xp") === -1)
			path = path.replace(/%APPDATA%/gi, "C:\\Documents and Settings\\MyUsername\\Application Data");
		else
			path = path.replace(/%APPDATA%/gi, "C:\\Users\\MyUsername\\AppData\\Roaming");
		return path;
	};
	this.exec = this.run = function(...args) {
		const command = args.join(" ");
		const filename = controller.getUUID();
		console.log(`Executing ${controller.directory + filename} in the WScript shell`);
		controller.logSnippet(filename, {as: "WScript code"}, command);
		if (process.argv.indexOf("--no-shell-error") === -1)
			throw new Error("If you can read this, re-run box.js with the --no-shell-error flag.");
	};
	this.regread = (key) => {
		key = key.toUpperCase();
		console.log(`Reading registry key ${key}`);
		switch (key) {
			case "HKEY_LOCAL_MACHINE\\SOFTWARE\\MICROSOFT\\WINDOWS NT\\CURRENTVERSION\\CURRENTVERSION":
				return "5.1";
			case "HKLM\\SOFTWARE\\MICROSOFT\\WINDOWS NT\\CURRENTVERSION\\SYSTEMROOT":
				return "C:\\WINDOWS";
			default:
				console.log("Unknown registry key!");
				return;
		}
	};
	this.regwrite = (key, value, type = "(unspecified)") => console.log(`Setting registry key ${key} to ${value} of type ${type}`);
	this.popup = function(text, a, title = "[Untitled]", b) {
		if (process.argv.indexOf("--no-echo") === -1) {
			console.log(`Script opened a popup window: title "${title}", text "${text}"`);
			console.log("Add flag --no-echo to disable this.");
		}
		return true; // Emulates a click
	};
}

module.exports = function(name) {
	return new Proxy(new WScriptShell(name), {
		get: function(target, name) {
			name = name.toLowerCase();
			switch (name) {
				default:
					if (!(name in target)) {
						controller.kill(`WScriptShell.${name} not implemented!`);
					}
					return target[name];
			}
		},
		set: function(a, b, c) {
			b = b.toLowerCase();
			if (c.length < 1024)
				console.log(`WScriptShell[${b}] = ${c};`);
			a[b] = c;
			return true;
		},
	});
};
<commit_msg>Add new env vars; MyUsername -> User; add debug info<commit_after>const controller = require("../_controller");

function WScriptShell() {
	this.environment = (x) => {
		if (x.toLowerCase() === "system")
			return (argument) => {
				argument = argument.toLowerCase();
				switch (argument) {
					case "comspec":
						return "%SystemRoot%\\system32\\cmd.exe";
					case "os":
						return "Windows_NT";
					case "processor_architecture":
						// Emulate a 32-bit environment for maximum compatibility
						return "x86";
					default:
						controller.kill(`Unknown parameter ${argument} for WScriptShell.Environment.System`);
				}
			};
		return `(Environment variable ${x})`;
	};
	this.specialfolders = (x) => `(Special folder ${x})`;
	this.createshortcut = () => ({});
	this.expandenvironmentstrings = (path) => {
		path = path.replace(/%COMPUTERNAME%/gi, "USER-PC");
		path = path.replace(/%PROCESSOR_REVISION%/gi, "0209");
		path = path.replace(/%PROGRAMDATA%/gi, "C:\\ProgramData");
		path = path.replace(/%TE?MP%/gi, "C:\\DOCUME~1\\User\\LOCALS~1\\Temp");
		path = path.replace(/%USERNAME%/gi, "User");
		path = path.replace(/%USERPROFILE%/gi, "C:\\Users\\User");
		path = path.replace(/%WINDIR%/gi, "C:\\WINDOWS");

		// %APPDATA% equals C:\Documents and Settings\{username}\Application Data on Windows XP,
		// but C:\Users\{username}\AppData\Roaming on Win Vista and above
		if (process.argv.indexOf("--windows-xp") === -1)
			path = path.replace(/%APPDATA%/gi, "C:\\Documents and Settings\\User\\Application Data");
		else
			path = path.replace(/%APPDATA%/gi, "C:\\Users\\User\\AppData\\Roaming");

		if (/%\w+%/i.test(path)) {
			console.log("Possibly failed to expand environment strings in " + path);
		}

		return path;
	};
	this.exec = this.run = function(...args) {
		const command = args.join(" ");
		const filename = controller.getUUID();
		console.log(`Executing ${controller.directory + filename} in the WScript shell`);
		controller.logSnippet(filename, {as: "WScript code"}, command);
		if (process.argv.indexOf("--no-shell-error") === -1)
			throw new Error("If you can read this, re-run box.js with the --no-shell-error flag.");
	};
	this.regread = (key) => {
		key = key.toUpperCase();
		console.log(`Reading registry key ${key}`);
		switch (key) {
			case "HKEY_LOCAL_MACHINE\\SOFTWARE\\MICROSOFT\\WINDOWS NT\\CURRENTVERSION\\CURRENTVERSION":
				return "5.1";
			case "HKLM\\SOFTWARE\\MICROSOFT\\WINDOWS NT\\CURRENTVERSION\\SYSTEMROOT":
				return "C:\\WINDOWS";
			default:
				console.log("Unknown registry key!");
				return;
		}
	};
	this.regwrite = (key, value, type = "(unspecified)") => console.log(`Setting registry key ${key} to ${value} of type ${type}`);
	this.popup = function(text, a, title = "[Untitled]", b) {
		if (process.argv.indexOf("--no-echo") === -1) {
			console.log(`Script opened a popup window: title "${title}", text "${text}"`);
			console.log("Add flag --no-echo to disable this.");
		}
		return true; // Emulates a click
	};
}

module.exports = function(name) {
	return new Proxy(new WScriptShell(name), {
		get: function(target, name) {
			name = name.toLowerCase();
			switch (name) {
				default:
					if (!(name in target)) {
						controller.kill(`WScriptShell.${name} not implemented!`);
					}
					return target[name];
			}
		},
		set: function(a, b, c) {
			b = b.toLowerCase();
			if (c.length < 1024)
				console.log(`WScriptShell[${b}] = ${c};`);
			a[b] = c;
			return true;
		},
	});
};
<|endoftext|>


<commit_before>// Use Bootstrap table
$("table").addClass("table table-hover").wrap("
");

// Remove valign="top" and align="right"
$("td, th").removeAttr("align").removeAttr("valign");

// Replace the first  into
$("td:first-child").changeElementType("");

// Move all rows in

$("table").prependIfNotExist("tbody", "
").append($("tr"));

// Move table header in

$("table").prependIfNotExist("thead", "
").append($("tr").first());

// Clear the icon column of the header
$("th").first().empty();

// Trim each cells inner html
$("td, th").each(function() {
	$(this).html($(this).html().trim());
});

const icon_classe_by_type = {
	"default": "fa-fw far fa-file",
	"directory": "fa-fw fas fa-folder",
	"archive": "fa-fw far fa-file-archive",
	"audio": "fa-fw far fa-file-audio",
	"code": "fa-fw far fa-file-code",
	"excel": "fa-fw far fa-file-excel",
	"image": "fa-fw far fa-file-image",
	"pdf": "fa-fw far fa-file-pdf",
	"powerpoint": "fa-fw far fa-file-powerpoint",
	"text": "fa-fw far fa-file-alt",
	"video": "fa-fw far fa-file-video",
	"word": "fa-fw far fa-file-word",
};

const m_server = moment(server_date, moment.ISO_8601);

// Parse each row
$("tbody > tr").each(function(index) {
	var icon = $("th", this).first();
	var date = $("td", this).eq(-2);
	var size = $("td", this).last();

	// Read the type from the  alt text
	var type = $("img", icon).attr("alt").replace(/[\[\] ]+/g, "");

	// Replace the icon
	if (type in icon_classe_by_type) {
		icon.html("");
	} else {
		icon.html("");
	}

	// Beautify date
	var m = moment(date.html(), "YYYY-MM-DD HH:mm");

	if (moment().diff(m, "days", true) < 1) {
		date.html(m.from(m_server));

	} else if (moment().diff(m, "weeks", true) < 1) {
		date.html(m.format("dddd LT"));

	} else {
		date.html(m.format("LLL"));
	}

	// Remove the parent row
	if (type === "parent") {
		$(this).remove();

	// Update directory style
	} else if (type === "directory") {
		size.empty();
	}
});

// Get the path segments and remove empty fields
const path_segments = window.location.pathname.split("/").filter(Boolean);

// Create a name / url object for each path segments
var path_url = "/";
var path_urls = [{name: "home", url: path_url}];
path_segments.forEach(function(segment) {
	path_url += segment + "/";
	path_urls.push({name: decodeURIComponent(segment), url: path_url});
});

// Create the breadcrumb elements for each previous objects
path_urls.forEach(function(url) {
	var a = $("").attr("href", url.url).html(url.name);
	var li = $("
").addClass("breadcrumb-item").append(a);
	$(".breadcrumb").append(li);
});

// Set the last breadcrumb item as active
$(".breadcrumb li").last().addClass("active").children("a").contents().unwrap();

// Show main
$("main").show();
<commit_msg>breadcrumb: use a home icon<commit_after>// Use Bootstrap table
$("table").addClass("table table-hover").wrap("
");

// Remove valign="top" and align="right"
$("td, th").removeAttr("align").removeAttr("valign");

// Replace the first  into
$("td:first-child").changeElementType("");

// Move all rows in

$("table").prependIfNotExist("tbody", "
").append($("tr"));

// Move table header in

$("table").prependIfNotExist("thead", "
").append($("tr").first());

// Clear the icon column of the header
$("th").first().empty();

// Trim each cells inner html
$("td, th").each(function() {
	$(this).html($(this).html().trim());
});

const icon_classe_by_type = {
	"default": "fa-fw far fa-file",
	"directory": "fa-fw fas fa-folder",
	"archive": "fa-fw far fa-file-archive",
	"audio": "fa-fw far fa-file-audio",
	"code": "fa-fw far fa-file-code",
	"excel": "fa-fw far fa-file-excel",
	"image": "fa-fw far fa-file-image",
	"pdf": "fa-fw far fa-file-pdf",
	"powerpoint": "fa-fw far fa-file-powerpoint",
	"text": "fa-fw far fa-file-alt",
	"video": "fa-fw far fa-file-video",
	"word": "fa-fw far fa-file-word",
};

const m_server = moment(server_date, moment.ISO_8601);

// Parse each row
$("tbody > tr").each(function(index) {
	var icon = $("th", this).first();
	var date = $("td", this).eq(-2);
	var size = $("td", this).last();

	// Read the type from the  alt text
	var type = $("img", icon).attr("alt").replace(/[\[\] ]+/g, "");

	// Replace the icon
	if (type in icon_classe_by_type) {
		icon.html("");
	} else {
		icon.html("");
	}

	// Beautify date
	var m = moment(date.html(), "YYYY-MM-DD HH:mm");

	if (moment().diff(m, "days", true) < 1) {
		date.html(m.from(m_server));

	} else if (moment().diff(m, "weeks", true) < 1) {
		date.html(m.format("dddd LT"));

	} else {
		date.html(m.format("LLL"));
	}

	// Remove the parent row
	if (type === "parent") {
		$(this).remove();

	// Update directory style
	} else if (type === "directory") {
		size.empty();
	}
});

// Get the path segments and remove empty fields
const path_segments = window.location.pathname.split("/").filter(Boolean);

// Create a name / url object for each path segments
var path_url = "/";
var path_urls = [{name: "", url: path_url}];
path_segments.forEach(function(segment) {
	path_url += segment + "/";
	path_urls.push({name: decodeURIComponent(segment), url: path_url});
});

// Create the breadcrumb elements for each previous objects
path_urls.forEach(function(url) {
	var a = $("").attr("href", url.url).html(url.name);
	var li = $("
").addClass("breadcrumb-item").append(a);
	$(".breadcrumb").append(li);
});

// Set the last breadcrumb item as active
$(".breadcrumb li").last().addClass("active").children("a").contents().unwrap();

// Show main
$("main").show();
<|endoftext|>


<commit_before>import Ember from 'ember';

export default Ember.Controller.extend({
  open: false,

  openClass: Ember.computed('open', function() {
    return this.get('open') ? 'open' : '';
  }),
  session: Ember.inject.service('session'),

  isSign: Ember.computed('session.isAuthenticated', function() {
    return this.get('session.isAuthenticated') ? '' : 'wrapper__no-sign';
  }),

  actions: {
    invalidateSession() {
      this.get('session').invalidate();
    },
    slide() {
      this.toggleProperty('open');
    },
    hideMenu() {
      this.set('open', false);
    }
  }

});
<commit_msg>Move session injection to the top of the app controller<commit_after>import Ember from 'ember';

export default Ember.Controller.extend({
  session: Ember.inject.service('session'),
  open: false,

  openClass: Ember.computed('open', function() {
    return this.get('open') ? 'open' : '';
  }),

  isSign: Ember.computed('session.isAuthenticated', function() {
    return this.get('session.isAuthenticated') ? '' : 'wrapper__no-sign';
  }),

  actions: {
    invalidateSession() {
      this.get('session').invalidate();
    },
    slide() {
      this.toggleProperty('open');
    },
    hideMenu() {
      this.set('open', false);
    }
  }

});
<|endoftext|>


<commit_before>angular.module('profitFactory', ['firebase'])

.factory('profitFactory', ['$firebaseArray', function ($firebaseArray){
  var profitFactory = {};

  profitFactory.getVisualData = function(org, account){
    var ref = new Firebase('https://bizgramer.firebaseio.com/'+org+'/BizData/'+account+'/');
    data_array = $firebaseArray(ref);
    return data_array;
  };

  profitFactory.ProfitChart = function(dates) {
    var layers = function (dates) {
      console.log("dates length ", dates.length);
      //layer 1 is total cost of sales and total expenses
      layerone = [];
      layertwo = [];
      layerthree = [];
      for(var i = 0; i < dates.length; i++){
        //negatives is layer 1)
        //console.log("cost of sales ", dates[i]["Total Cost of Sales"])
        layerone.push(parseInt(dates[i]["Total Cost of Sales"],10));
        layertwo.push(parseInt(dates[i]["Total Expenses"],10));
        layerthree.push(parseInt(dates[i]["Total Income"],10));
      }
      var threelayers = [];
      layerone = layerone.map(function(d, i) { return {x: i, y: d }; });
      layertwo = layertwo.map(function(d, i) { return {x: i, y: d }; });
      layerthree = layerthree.map(function(d, i) { return {x: i, y: d }; });

      threelayers.push(layerone, layertwo, layerthree);

      console.log(threelayers);
      var stack = d3.layout.stack();
      var layers = stack(threelayers);
      return layers;
    };
    layers = layers(dates);
  console.log("layers ", layers);

  var profit = function(dates) {
    var profit = [];
    for(var i = 0; i < dates.length; i++){
      profit.push(parseInt(dates[i]["Net Earnings"],10));
    }
    profit = profit.map(function(d, i) { return {x: i, y: d}; });
    return profit;
  };
  profit = profit(dates);
  console.log("profit ", profit);
  var n = 3, // number of layers
      m = 11;

  //set svg measurements
  var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov"];
  var margin = {top: 40, right: 10, bottom: 20, left: 50},
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

  var extents = [];
  extents = extents.concat(d3.extent(layers[0].map(function(d) {return d.y; })));
  extents = extents.concat(d3.extent(layers[1].map(function(d) {return d.y; })));
  extents = extents.concat(d3.extent(layers[2].map(function(d) {return d.y; })));
  //console.log(extents);
  //console.log(d3.extent(layers, function(layer) { return d3.extent(layer, function(d) {return d.y;  }) }));
  var yGroupExtents = d3.extent(extents);
  console.log("yGroupExtents ", yGroupExtents);

  //setting the x and y scales and axises
  var yScale = d3.scale.linear()
    .domain(yGroupExtents)
    .range([height, 0]);
  //for data less than zero.
  var y0 = function() {
    //console.log("Y scale value ", yScale(0))
    return yScale(0);
  };
  var yAxis = d3.svg.axis().scale(yScale).tickSize(1)
    .tickPadding(0).orient("left");

  var xScale = d3.scale.ordinal().domain(d3.range(m)).rangeRoundBands([0, width], 0.08);
  var xordinal = d3.scale.ordinal().domain(months).rangeRoundBands([0, width], 0.08);
  var xAxis = d3.svg.axis()
    .scale(xordinal)
    .tickSize(1)
    .tickPadding(3)
    .orient("bottom");
  d3.svg.axis()
    .scale(xordinal)
    .tickValues(null)
    .tickSize(1)
    .tickPadding(3)
    .orient("bottom");

  var color = d3.scale.linear()
    .domain([0, n - 1])
    .range(['#FF69B4', "#008000"]);

  var svg = d3.select("#vis").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis);
    //zero line
    svg.append("line")
        // .attr("class", "x zero axis")
        .attr('x1', 0)
        .attr('x2', 960-margin.right)
        .attr('y1', y0())
        .attr('y2', y0())
        .attr("stroke", "black")
        .attr("stroke-width", 1);
        // .attr("transform", "translate(0," + y0() + ")")
        // .call(zeroAxis);
    //yAxis
    svg.append("g")
        .attr("class", "y axis")
        .call(yAxis);

  function transition(data) {
    var layer = svg.selectAll(".layer")
                .data(data);
    //
      layer.exit().remove();

      layer.enter().append("g")
        .attr("class", "layer")
        .style("fill", function(d, i) { return color(i); });

    var rect = layer.selectAll("rect")
        .data(function(d) { console.log(d); return d; });
        //update the old elements as needed
        rect.attr("x", function(d, i, j) { return xScale(d.x) + xScale.rangeBand() / n * j; })
        .attr("y",  y0(0))
        .attr("width", xScale.rangeBand() / n)
        .attr("height", 0)
      .transition()
        .attr("y", function(d) { return d.y < 0 ? y0() : yScale(d.y); })
        .attr("height", function(d) { return Math.abs( yScale(d.y) - y0() ); });
        // .attr("x", function(d, i, j) { return xScale(d.x) + xScale.rangeBand() / n * j; })
        // .attr("y", function(d) { return d.y < 0 ? y0() : yScale(d.y); })
        // .attr("width", xScale.rangeBand() / n)
        // .attr("height", function(d) { return Math.abs( yScale(d.y) - y0() ); });

      rect.exit().remove();
        //create new elements as needed.
      rect.enter().append("rect")
        .attr("x", function(d, i, j) { return xScale(d.x) + xScale.rangeBand() / n * j; })
        .attr("y",  y0(0))
        .attr("width", xScale.rangeBand() / n)
        .attr("height", 0)
      .transition()
        .attr("y", function(d) { return d.y < 0 ? y0() : yScale(d.y); })
        .attr("height", function(d) { return Math.abs( yScale(d.y) - y0() ); });
    }

  var change = function() {

    console.log("registered change");
    console.log(this.value);

    if(this.value === "grouped") {
      console.log(layers);
      transition(layers);
    }
    else {
      console.log("Entering ELse");
      //console.log(profit)
      transition([profit]);
      console.log("here");
    }
  };
// var timeout = setTimeout(function() {
//   d3.select("input[value=\"stacked\"]").property("checked", true).each(change);
// }, 2000);

  d3.selectAll("input").on("change", change );

  };

  return profitFactory;
}]);
<commit_msg>profitFact<commit_after>angular.module('profitFactory', ['firebase'])

.factory('profitFactory', ['$firebaseArray', function ($firebaseArray){
  var profitFactory = {};

  profitFactory.getVisualData = function(org, account){
    var ref = new Firebase('https://bizgramer.firebaseio.com/'+org+'/BizData/'+account+'/');
    data_array = $firebaseArray(ref);
    return data_array;
  };

  profitFactory.ProfitChart = function(dates) {
    var createLayers = function (dates) {
      console.log("dates length ", dates.length);
      //layer 1 is total cost of sales and total expenses
      layerone = [];
      layertwo = [];
      layerthree = [];
      for(var i = 0; i < dates.length; i++){
        //negatives is layer 1)
        //console.log("cost of sales ", dates[i]["Total Cost of Sales"])
        layerone.push(parseInt(dates[i]["Total Cost of Sales"],10));
        layertwo.push(parseInt(dates[i]["Total Expenses"],10));
        layerthree.push(parseInt(dates[i]["Total Income"],10));
      }
      var threelayers = [];
      layerone = layerone.map(function(d, i) { return {x: i, y: d }; });
      layertwo = layertwo.map(function(d, i) { return {x: i, y: d }; });
      layerthree = layerthree.map(function(d, i) { return {x: i, y: d }; });

      threelayers.push(layerone, layertwo, layerthree);

      console.log(threelayers);
      var stack = d3.layout.stack();
      var layers = stack(threelayers);
      return layers;
    };

    var layers = createLayers(dates);

  console.log("layers ", layers);

  var createProfit = function(dates) {
    var profit = [];
    for(var i = 0; i < dates.length; i++){
      profit.push(parseInt(dates[i]["Net Earnings"],10));
    }
    profit = profit.map(function(d, i) { return {x: i, y: d}; });
    return profit;
  };

  var profit = createProfit(dates);

  console.log("profit ", profit);
  var n = 3, // number of layers
      m = 11;

  //set svg measurements
  var months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov"];
  var margin = {top: 40, right: 10, bottom: 20, left: 50},
    width = 960 - margin.left - margin.right,
    height = 500 - margin.top - margin.bottom;

  var extents = [];
  extents = extents.concat(d3.extent(layers[0].map(function(d) {return d.y; })));
  extents = extents.concat(d3.extent(layers[1].map(function(d) {return d.y; })));
  extents = extents.concat(d3.extent(layers[2].map(function(d) {return d.y; })));
  //console.log(extents);
  //console.log(d3.extent(layers, function(layer) { return d3.extent(layer, function(d) {return d.y;  }) }));
  var yGroupExtents = d3.extent(extents);
  console.log("yGroupExtents ", yGroupExtents);

  //setting the x and y scales and axises
  var yScale = d3.scale.linear()
    .domain(yGroupExtents)
    .range([height, 0]);
  //for data less than zero.
  var y0 = function() {
    //console.log("Y scale value ", yScale(0))
    return yScale(0);
  };
  var yAxis = d3.svg.axis().scale(yScale).tickSize(1)
    .tickPadding(0).orient("left");

  var xScale = d3.scale.ordinal().domain(d3.range(m)).rangeRoundBands([0, width], 0.08);
  var xordinal = d3.scale.ordinal().domain(months).rangeRoundBands([0, width], 0.08);
  var xAxis = d3.svg.axis()
    .scale(xordinal)
    .tickSize(1)
    .tickPadding(3)
    .orient("bottom");
  d3.svg.axis()
    .scale(xordinal)
    .tickValues(null)
    .tickSize(1)
    .tickPadding(3)
    .orient("bottom");

  var color = d3.scale.linear()
    .domain([0, n - 1])
    .range(['#FF69B4', "#008000"]);

  var svg = d3.select("#vis").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis);
    //zero line
    svg.append("line")
        // .attr("class", "x zero axis")
        .attr('x1', 0)
        .attr('x2', 960-margin.right)
        .attr('y1', y0())
        .attr('y2', y0())
        .attr("stroke", "black")
        .attr("stroke-width", 1);
        // .attr("transform", "translate(0," + y0() + ")")
        // .call(zeroAxis);
    //yAxis
    svg.append("g")
        .attr("class", "y axis")
        .call(yAxis);

  function transition(data) {
    var layer = svg.selectAll(".layer")
                .data(data);
    //
      layer.exit().remove();

      layer.enter().append("g")
        .attr("class", "layer")
        .style("fill", function(d, i) { return color(i); });

    var rect = layer.selectAll("rect")
        .data(function(d) { return d; });

        //update the old elements as needed
        rect.attr("x", function(d, i, j) { return xScale(d.x) + xScale.rangeBand() / n * j; })
        .attr("y",  y0(0))
        .attr("width", xScale.rangeBand() / n)
        .attr("height", 0)
      .transition()
        .attr("y", function(d) { return d.y < 0 ? y0() : yScale(d.y); })
        .attr("height", function(d) { return Math.abs( yScale(d.y) - y0() ); });
        // .attr("x", function(d, i, j) { return xScale(d.x) + xScale.rangeBand() / n * j; })
        // .attr("y", function(d) { return d.y < 0 ? y0() : yScale(d.y); })
        // .attr("width", xScale.rangeBand() / n)
        // .attr("height", function(d) { return Math.abs( yScale(d.y) - y0() ); });

      rect.exit().remove();
        //create new elements as needed.
      rect.enter().append("rect")
        .attr("x", function(d, i, j) { return xScale(d.x) + xScale.rangeBand() / n * j; })
        .attr("y",  y0(0))
        .attr("width", xScale.rangeBand() / n)
        .attr("height", 0)
      .transition()
        .attr("y", function(d) { return d.y < 0 ? y0() : yScale(d.y); })
        .attr("height", function(d) { return Math.abs( yScale(d.y) - y0() ); });
    }

  var change = function() {

    if(this.value === "grouped") {

      transition(layers);
    }
    else {

      //console.log(profit)
      transition([profit]);

    }
  };
// var timeout = setTimeout(function() {
//   d3.select("input[value=\"stacked\"]").property("checked", true).each(change);
// }, 2000);

  d3.selectAll("input").on("change", change );

  };

  return profitFactory;
}]);
<|endoftext|>