<commit_before>'use strict';

function EnemyManager(game, bulletManager)
{
var self = this;
var enemyDictionary = { hellbug: {sprite: 'enemy_hellbug',
																	movementScheme: 'random',
																	shootingScheme: ['radial', 5, 'enemyBullet1'],
																	maxSpeed: 90,
																	moveRate: 1200,
																	fireRate: 4000,
																	hitColor: 0xCC0000,
																	maxHealth: 10},
												skeleton: {sprite: 'enemy_skeleton',
																	movementScheme: 'chargeSingle',
																	shootingScheme: ['directedBurst', 3, 'enemyBullet3'],

																	maxSpeed: 60,
																	moveRate: 800,
																	fireRate: 4500,
																	hitColor: 0xCC0000,
																	maxHealth: 10},
												slasher: {sprite: 'enemy_slasher',
																	movementScheme: 'chargeSingle',
																	shootingScheme: ['slasherShot', 1, 'enemyBullet2'],
																	maxSpeed: 180,
																	moveRate: 500,
																	fireRate: 500,
																	hitColor: 0xCC0000,
																	maxHealth: 5}}

var bossDictionary = { tentacle: {sprite: 'enemy_tentaclemonster',
																	 movementSchemes: ['wobble', 'shake', 'chargeDirection'],
																	 attackSchemes: ['spiral', 'deflect', 'shotgun', 'stream'],
																	 hitColor: 0xCC0000}}

self.enemyGroup = game.add.group(); // Group manages sprites
self.enemyGroup.enableBody = true;
self.enemyGroup.physicsBodyType = Phaser.Physics.ARCADE;

var MAX_SPAWNCOOLDOWN = 2200;
var spawnCooldown = 2000;
var nextSpawn = 0;
var MaxEnemiesToSpawn = 0;
var enemiesToSpawn = 1;

var enemyScalingCoefficient = 8;

self.enemyList = []; // List manages Enemy objects

var maxEnemies;

var playerAmount;

self.enemyPool = enemyScalingCoefficient;

self.update = function (players)
	{
	maxEnemies = enemyScalingCoefficient * Object.keys(players).length;
	playerAmount = Object.keys(players).length;
	if (self.enemyPool > 0 && playerAmount > 0 && game.time.now > nextSpawn)
		{
		// Determine how many mobs to spawn
		MaxEnemiesToSpawn = Math.round(playerAmount/2);
		enemiesToSpawn = game.rnd.integerInRange(1, MaxEnemiesToSpawn);

		// Spawn new mobs
		for (var i = enemiesToSpawn- 1; i >= 0; i--) {
			var randomPos = {x: game.camera.x + game.rnd.integerInRange(0, game.camera.width), y: game.camera.y + game.rnd.integerInRange(0, game.camera.height)};
			var newEnemy = new Enemy(pickRandomFromDictionary(enemyDictionary), game, bulletManager, players, randomPos);
			self.enemyGroup.add(newEnemy.sprite);
			newEnemy.sprite.body.collideWorldBounds = true;
			self.enemyList.push(newEnemy);
		}
		spawnCooldown = MAX_SPAWNCOOLDOWN-(playerAmount*200);
		if(spawnCooldown < 1000) spawnCooldown = 1000;
		nextSpawn = game.time.now + spawnCooldown;
		}

	//Check if any enemy in enemylist has been killed and update enemies

	for (var i = 0; i < self.enemyList.length; i++)
		{
		if (self.enemyList[i].sprite.dead === true)
			{
			self.enemyList[i].kill();
			self.enemyList.splice(i,1);
			} else {
			self.enemyList[i].update(players);
			}
		}

	//Update pool

	self.enemyPool = maxEnemies - self.enemyGroup.length;
	};

self.createBoss = function (bossType, bossPos)
	{
	if (bossDictionary[bossType] != undefined)
		{
		var bossMonster = new BossMonster(bossDictionary[bossType], game, bulletManager, bossPos);
		self.enemyGroup.add(bossMonster.sprite);
		bossMonster.sprite.body.collideWorldBounds = true;
		self.enemyList.push(bossMonster);
		spawnCooldown = 100000;
		}
	};

var pickRandomFromDictionary = function (dict)
	{
	var keys = Object.keys(dict);
	var object
	object = dict[keys[ keys.length * Math.random() << 0]];
	return object;
	};
}

<commit_msg>Enemies no longer spawn during the bossfight<commit_after>'use strict';

function EnemyManager(game, bulletManager)
{
var self = this;
var enemyDictionary = { hellbug: {sprite: 'enemy_hellbug',
																	movementScheme: 'random',
																	shootingScheme: ['radial', 5, 'enemyBullet1'],
																	maxSpeed: 90,
																	moveRate: 1200,
																	fireRate: 4000,
																	hitColor: 0xCC0000,
																	maxHealth: 10},
												skeleton: {sprite: 'enemy_skeleton',
																	movementScheme: 'chargeSingle',
																	shootingScheme: ['directedBurst', 3, 'enemyBullet3'],

																	maxSpeed: 60,
																	moveRate: 800,
																	fireRate: 4500,
																	hitColor: 0xCC0000,
																	maxHealth: 10},
												slasher: {sprite: 'enemy_slasher',
																	movementScheme: 'chargeSingle',
																	shootingScheme: ['slasherShot', 1, 'enemyBullet2'],
																	maxSpeed: 180,
																	moveRate: 500,
																	fireRate: 500,
																	hitColor: 0xCC0000,
																	maxHealth: 5}}

var bossDictionary = { tentacle: {sprite: 'enemy_tentaclemonster',
																	 movementSchemes: ['wobble', 'shake', 'chargeDirection'],
																	 attackSchemes: ['spiral', 'deflect', 'shotgun', 'stream'],
																	 hitColor: 0xCC0000}}

self.enemyGroup = game.add.group(); // Group manages sprites
self.enemyGroup.enableBody = true;
self.enemyGroup.physicsBodyType = Phaser.Physics.ARCADE;

var MAX_SPAWNCOOLDOWN = 2200;
var spawnCooldown = 2000;
var nextSpawn = 0;
var MaxEnemiesToSpawn = 0;
var enemiesToSpawn = 1;
var SPAWNING = true;

var enemyScalingCoefficient = 8;

self.enemyList = []; // List manages Enemy objects

var maxEnemies;

var playerAmount;

self.enemyPool = enemyScalingCoefficient;

self.update = function (players)
	{
	maxEnemies = enemyScalingCoefficient * Object.keys(players).length;
	playerAmount = Object.keys(players).length;
	if (self.enemyPool > 0 && playerAmount > 0 && game.time.now > nextSpawn && SPAWNING == true)
		{
		// Determine how many mobs to spawn
		MaxEnemiesToSpawn = Math.round(playerAmount/2);
		enemiesToSpawn = game.rnd.integerInRange(1, MaxEnemiesToSpawn);

		// Spawn new mobs
		for (var i = enemiesToSpawn- 1; i >= 0; i--) {
			var randomPos = {x: game.camera.x + game.rnd.integerInRange(0, game.camera.width), y: game.camera.y + game.rnd.integerInRange(0, game.camera.height)};
			var newEnemy = new Enemy(pickRandomFromDictionary(enemyDictionary), game, bulletManager, players, randomPos);
			self.enemyGroup.add(newEnemy.sprite);
			newEnemy.sprite.body.collideWorldBounds = true;
			self.enemyList.push(newEnemy);
		}
		spawnCooldown = MAX_SPAWNCOOLDOWN-(playerAmount*200);
		if(spawnCooldown < 1000) spawnCooldown = 1000;
		nextSpawn = game.time.now + spawnCooldown;
		}

	//Check if any enemy in enemylist has been killed and update enemies

	for (var i = 0; i < self.enemyList.length; i++)
		{
		if (self.enemyList[i].sprite.dead === true)
			{
			self.enemyList[i].kill();
			self.enemyList.splice(i,1);
			} else {
			self.enemyList[i].update(players);
			}
		}

	//Update pool

	self.enemyPool = maxEnemies - self.enemyGroup.length;
	};

self.createBoss = function (bossType, bossPos)
	{
	if (bossDictionary[bossType] != undefined)
		{
		var bossMonster = new BossMonster(bossDictionary[bossType], game, bulletManager, bossPos);
		self.enemyGroup.add(bossMonster.sprite);
		bossMonster.sprite.body.collideWorldBounds = true;
		self.enemyList.push(bossMonster);
		SPAWNING = false;
		}
	};

var pickRandomFromDictionary = function (dict)
	{
	var keys = Object.keys(dict);
	var object
	object = dict[keys[ keys.length * Math.random() << 0]];
	return object;
	};
}

<|endoftext|>


<commit_before>
var g_radioCallback;

function setRadioStream(sourceUrl) {
    var audio = $("#radiocontrols");
    $("#radiosource").attr("src", sourceUrl);
    audio[0].pause();
    audio[0].load();
    audio[0].play();
};

function refreshPlayerData() {
   console.log("refreshing the player data");

   var jqxhr = $.getJSON('http://api.jamendo.com/v3.0/radios/stream?client_id='+g_clientId+'&id='+sessionStorage.radioId+'&callback=?', null, null,'application/json')
   .done(function(result) {
      console.log(result.results[0]);
      window.clearInterval(g_radioCallback);
      g_radioCallback = window.setInterval("refreshPlayerData()",result.results[0].callmeback);
      setPlayerInformation(result.results[0].playingnow);
   })
   .fail(function() { console.log( "error" ); });
};

function setPlayerInformation(nowPlaying){
   $("#trackcover").attr("src", nowPlaying.track_image);
   $('#artist').empty();
   $('#artist').append('
'+nowPlaying.artist_name + ' - '+ nowPlaying.track_name+'
');
   $('#album').empty();
   $('#album').append('
from album: '+nowPlaying.album_name + '
');
};

$( document ).delegate("#radioPage", "pagebeforeshow", function()  {

   var jqxhr = $.getJSON('http://api.jamendo.com/v3.0/radios/stream?client_id='+g_clientId+'&id='+sessionStorage.radioId+'&callback=?', null, null,'application/json')
   .done(function(result) {

      //write the header
      $('#radioHeader #radioName').text(result.results[0].dispname);

      console.log(result.results[0]);
      setRadioStream(result.results[0].stream);

      g_radioCallback = window.setInterval("refreshPlayerData()",result.results[0].callmeback);
      setPlayerInformation(result.results[0].playingnow);
   })
   .fail(function() { console.log( "error" ); });
});

$( document ).delegate("#radioPage", "pagebeforehide", function()  {
   console.log("pausing the audio");
   var audio = $("#radiocontrols");
   audio[0].pause();
   window.clearInterval(g_radioCallback);
});
<commit_msg>Update radioPage.js<commit_after>
var g_radioCallback;
var g_myaudio
function setRadioStream(sourceUrl) {
    try {
    g_myaudio = new Audio(sourceUrl);
    g_myaudio.id = 'playerMyAdio';
    g_myaudio.play();
   } catch (e) {
    alert('no audio support!');
  }
    /*
    var audio = $("#radiocontrols");
    $("#radiosource").attr("src", sourceUrl);
    audio[0].pause();
    audio[0].load();
    audio[0].play();*/
};

function refreshPlayerData() {
   console.log("refreshing the player data");

   var jqxhr = $.getJSON('http://api.jamendo.com/v3.0/radios/stream?client_id='+g_clientId+'&id='+sessionStorage.radioId+'&callback=?', null, null,'application/json')
   .done(function(result) {
      console.log(result.results[0]);
      window.clearInterval(g_radioCallback);
      g_radioCallback = window.setInterval("refreshPlayerData()",result.results[0].callmeback);
      setPlayerInformation(result.results[0].playingnow);
   })
   .fail(function() { console.log( "error" ); });
};

function setPlayerInformation(nowPlaying){
   $("#trackcover").attr("src", nowPlaying.track_image);
   $('#artist').empty();
   $('#artist').append('
'+nowPlaying.artist_name + ' - '+ nowPlaying.track_name+'
');
   $('#album').empty();
   $('#album').append('
from album: '+nowPlaying.album_name + '
');
};

$( document ).delegate("#radioPage", "pagebeforeshow", function()  {

   var jqxhr = $.getJSON('http://api.jamendo.com/v3.0/radios/stream?client_id='+g_clientId+'&id='+sessionStorage.radioId+'&callback=?', null, null,'application/json')
   .done(function(result) {

      //write the header
      $('#radioHeader #radioName').text(result.results[0].dispname);

      console.log(result.results[0]);
      setRadioStream(result.results[0].stream);

      g_radioCallback = window.setInterval("refreshPlayerData()",result.results[0].callmeback);
      setPlayerInformation(result.results[0].playingnow);
   })
   .fail(function() { console.log( "error" ); });
});

$( document ).delegate("#radioPage", "pagebeforehide", function()  {
   g_myaudio.pause();
   /*
   var audio = $("#radiocontrols");
   audio[0].pause();
   window.clearInterval(g_radioCallback);*/
});
<|endoftext|>


<commit_before>'use strict';
this.serverAddress = '192.168.1.5';
this.controllerPort = 8081;
this.gamePort = 8082;
<commit_msg>Forgot testing ip in serverconfig<commit_after>'use strict';
this.serverAddress = 'localhost';
this.controllerPort = 8081;
this.gamePort = 8082;
<|endoftext|>


<commit_before>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import React from 'react';
import PropTypes from 'prop-types';

import { View } from 'react-native';

import { GenericPage } from './GenericPage';

import { createRecord } from '../database';
import { formatDate, parsePositiveInteger, sortDataBy } from '../utilities';
import { buttonStrings, modalStrings, pageInfoStrings, tableStrings } from '../localization';
import { AutocompleteSelector, PageButton, PageInfo, TextEditor } from '../widgets';
import { BottomConfirmModal, PageContentModal } from '../widgets/modals';

import globalStyles from '../globalStyles';

const DATA_TYPES_SYNCHRONISED = ['TransactionItem', 'TransactionBatch', 'Item', 'ItemBatch'];
const MODAL_KEYS = {
  COMMENT_EDIT: 'commentEdit',
  THEIR_REF_EDIT: 'theirRefEdit',
  ITEM_SELECT: 'itemSelect',
};

export class CustomerInvoicePage extends GenericPage {
  constructor(props) {
    super(props);
    this.state = {
      selection: [],
      modalKey: null,
      modalIsOpen: false,
    };
    this.dataFilters = {
      searchTerm: '',
      sortBy: 'itemName',
      isAscending: true,
    };
  }

  updateDataFilters = (newSearchTerm, newSortBy, newIsAscending) => {
    // (... != null) checks for null or undefined (implicitly type coerced to null).
    if (newSearchTerm != null) this.dataFilters.searchTerm = newSearchTerm;
    if (newSortBy != null) this.dataFilters.sortBy = newSortBy;
    if (newIsAscending != null) this.dataFilters.isAscending = newIsAscending;
  };

  /**
   * Returns updated data fitlered by |searchTerm| and ordered by |sortBy| and |isAscending|.
   */
  refreshData = (newSearchTerm, newSortBy, newIsAscending) => {
    this.updateDataFilters(newSearchTerm, newSortBy, newIsAscending);
    const { searchTerm, sortBy, isAscending } = this.dataFilters;
    const data = this.props.transaction.items.filtered(
      'item.name BEGINSWITH[c] $0 OR item.code BEGINSWITH[c] $0',
      searchTerm
    );
    let sortDataType;
    switch (sortBy) {
      case 'itemCode':
      case 'itemName':
        sortDataType = 'string';
        break;
      case 'availableQuantity':
      case 'totalQuantity':
        sortDataType = 'number';
        break;
      default:
        sortDataType = 'realm';
    }
    this.setState({
      data: sortDataBy(data, sortBy, sortDataType, isAscending),
    });
  };

  onAddMasterItems = () => {
    this.props.runWithLoadingIndicator(() => {
      this.props.database.write(() => {
        this.props.transaction.addItemsFromMasterList(this.props.database);
        this.props.database.save('Transaction', this.props.transaction);
      });
      this.refreshData();
    });
  };

  /**
   * Respond to the user editing the number in the number received column.
   *
   * @param   {string}  key              Should always be |totalQuantity|.
   * @param   {object}  transactionItem  The transaction item from the row being edited.
   * @param   {string}  newValue         The value the user entered in the cell.
   * @return  {none}
   */
  onEndEditing = (key, transactionItem, newValue) => {
    if (key !== 'totalQuantity') return;
    const { database } = this.props;
    database.write(() => {
      transactionItem.setTotalQuantity(database, parsePositiveInteger(newValue));
      database.save('TransactionItem', transactionItem);
    });
  };

  onDeleteConfirm = () => {
    const { selection } = this.state;
    const { transaction, database } = this.props;

    database.write(() => {
      transaction.removeItemsById(database, selection);
      database.save('Transaction', transaction);
    });
    this.setState({ selection: [] }, this.refreshData);
  };

  onDeleteCancel = () => {
    this.setState({ selection: [] }, this.refreshData);
  };

  onSelectionChange = newSelection => {
    this.setState({ selection: newSelection });
  };

  openModal = key => {
    this.setState({ modalKey: key, modalIsOpen: true });
  };

  closeModal = () => {
    this.setState({ modalIsOpen: false });
  };

  openItemSelector = () => {
    this.openModal(MODAL_KEYS.ITEM_SELECT);
  };

  openCommentEditor = () => {
    this.openModal(MODAL_KEYS.COMMENT_EDIT);
  };

  openTheirRefEditor = () => {
    this.openModal(MODAL_KEYS.THEIR_REF_EDIT);
  };

  getModalTitle = () => {
    const { ITEM_SELECT, COMMENT_EDIT, THEIR_REF_EDIT } = MODAL_KEYS;
    switch (this.state.modalKey) {
      default:
      case ITEM_SELECT:
        return modalStrings.search_for_an_item_to_add;
      case COMMENT_EDIT:
        return modalStrings.edit_the_invoice_comment;
      case THEIR_REF_EDIT:
        return modalStrings.edit_their_reference;
    }
  };

  renderPageInfo = () => {
    const { transaction } = this.props;
    const infoColumns = [
      [
        {
          title: `${pageInfoStrings.entry_date}:`,
          info: formatDate(transaction.entryDate) || 'N/A',
        },
        {
          title: `${pageInfoStrings.confirm_date}:`,
          info: formatDate(transaction.confirmDate),
        },
        {
          title: `${pageInfoStrings.entered_by}:`,
          info: transaction.enteredBy && transaction.enteredBy.username,
        },
      ],
      [
        {
          title: `${pageInfoStrings.customer}:`,
          info: transaction.otherParty && transaction.otherParty.name,
        },
        {
          title: `${pageInfoStrings.their_ref}:`,
          info: transaction.theirRef,
          onPress: this.openTheirRefEditor,
          editableType: 'text',
        },
        {
          title: `${pageInfoStrings.comment}:`,
          info: transaction.comment,
          onPress: this.openCommentEditor,
          editableType: 'text',
        },
      ],
    ];
    return <PageInfo columns={infoColumns} isEditingDisabled={transaction.isFinalised} />;
  };

  renderCell = (key, transactionItem) => {
    switch (key) {
      default:
        return transactionItem[key];
      case 'totalQuantity':
        return {
          type: this.props.transaction.isFinalised ? 'text' : 'editable',
          cellContents: transactionItem.totalQuantity,
        };
      case 'remove':
        return {
          type: 'checkable',
          icon: 'md-remove-circle',
          isDisabled: this.props.transaction.isFinalised,
        };
    }
  };

  renderModalContent = () => {
    const { ITEM_SELECT, COMMENT_EDIT, THEIR_REF_EDIT } = MODAL_KEYS;
    const { database, transaction } = this.props;
    switch (this.state.modalKey) {
      default:
      case ITEM_SELECT:
        return (
          <AutocompleteSelector
            options={database.objects('Item')}
            queryString="name BEGINSWITH[c] $0 OR code BEGINSWITH[c] $0"
            queryStringSecondary="name CONTAINS[c] $0"
            sortByString="name"
            onSelect={item => {
              database.write(() => {
                if (!transaction.hasItem(item)) {
                  createRecord(database, 'TransactionItem', transaction, item);
                }
              });
              this.refreshData();
              this.closeModal();
            }}
            renderLeftText={item => `${item.name}`}
            renderRightText={item => `${item.totalQuantity}`}
          />
        );
      case COMMENT_EDIT:
        return (
          <TextEditor
            text={transaction.comment}
            onEndEditing={newComment => {
              if (newComment !== transaction.comment) {
                database.write(() => {
                  transaction.comment = newComment;
                  database.save('Transaction', transaction);
                });
              }
              this.closeModal();
            }}
          />
        );
      case THEIR_REF_EDIT:
        return (
          <TextEditor
            text={transaction.theirRef}
            onEndEditing={newTheirRef => {
              if (newTheirRef !== transaction.theirRef) {
                database.write(() => {
                  transaction.theirRef = newTheirRef;
                  database.save('Transaction', transaction);
                });
              }
              this.closeModal();
            }}
          />
        );
    }
  };

  renderButtons = () => (
    <View style={globalStyles.verticalContainer}>
      <PageButton
        style={globalStyles.topButton}
        text={buttonStrings.new_item}
        onPress={this.openItemSelector}
        isDisabled={this.props.transaction.isFinalised}
      />
      <PageButton
        text={buttonStrings.add_master_list_items}
        onPress={this.onAddMasterItems}
        isDisabled={this.props.transaction.isFinalised}
      />
    </View>
  );

  render() {
    return (
      <GenericPage
        data={this.state.data}
        refreshData={this.refreshData}
        renderCell={this.renderCell}
        renderTopLeftComponent={this.renderPageInfo}
        renderTopRightComponent={this.renderButtons}
        onRowPress={this.onRowPress}
        onSelectionChange={this.onSelectionChange}
        onEndEditing={this.onEndEditing}
        defaultSortKey={this.dataFilters.sortBy}
        defaultSortDirection={this.dataFilters.isAscending ? 'ascending' : 'descending'}
        columns={[
          {
            key: 'itemCode',
            width: 2,
            title: tableStrings.item_code,
            sortable: true,
          },
          {
            key: 'itemName',
            width: 4,
            title: tableStrings.item_name,
            sortable: true,
          },
          {
            key: 'availableQuantity',
            width: 2,
            title: tableStrings.available_stock,
            sortable: true,
            alignText: 'right',
          },
          {
            key: 'totalQuantity',
            width: 2,
            title: tableStrings.quantity,
            sortable: true,
            alignText: 'right',
          },
          {
            key: 'remove',
            width: 1,
            title: tableStrings.remove,
            alignText: 'center',
          },
        ]}
        dataTypesSynchronised={DATA_TYPES_SYNCHRONISED}
        finalisableDataType="Transaction"
        database={this.props.database}
        selection={this.state.selection}
        {...this.props.genericTablePageStyles}
        topRoute={this.props.topRoute}
      >
        <BottomConfirmModal
          isOpen={this.state.selection.length > 0 && !this.props.transaction.isFinalised}
          questionText={modalStrings.remove_these_items}
          onCancel={() => this.onDeleteCancel()}
          onConfirm={() => this.onDeleteConfirm()}
          confirmText={modalStrings.remove}
        />
        <PageContentModal
          isOpen={this.state.modalIsOpen && !this.props.transaction.isFinalised}
          onClose={this.closeModal}
          title={this.getModalTitle()}
        >
          {this.renderModalContent()}
        </PageContentModal>
      </GenericPage>
    );
  }
}

/* eslint-disable react/forbid-prop-types */
CustomerInvoicePage.propTypes = {
  database: PropTypes.object.isRequired,
  genericTablePageStyles: PropTypes.object,
  topRoute: PropTypes.bool,
  transaction: PropTypes.object.isRequired,
};

/**
 * Check whether a given customer invoice is safe to be finalised. If safe to finalise,
 * return null, else return an appropriate error message.
 *
 * @param   {object}  customerInvoice  The customer invoice to check.
 * @return  {string}                   Error message if unsafe to finalise, else null.
 */
export function checkForFinaliseError(customerInvoice) {
  if (customerInvoice.items.length === 0) {
    return modalStrings.add_at_least_one_item_before_finalising;
  }

  if (customerInvoice.totalQuantity === 0) {
    return modalStrings.record_stock_to_issue_before_finalising;
  }

  return null;
}
<commit_msg>refactor to customerInvoicePage to hooks API<commit_after>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import React, { useState } from 'react';
import PropTypes from 'prop-types';

import { View } from 'react-native';

import { GenericPage } from './GenericPage';

import { createRecord } from '../database';
import { formatDate, parsePositiveInteger, sortDataBy } from '../utilities';
import { buttonStrings, modalStrings, pageInfoStrings, tableStrings } from '../localization';
import { AutocompleteSelector, PageButton, PageInfo, TextEditor } from '../widgets';
import { BottomConfirmModal, PageContentModal } from '../widgets/modals';

import globalStyles from '../globalStyles';

const DATA_TYPES_SYNCHRONISED = ['TransactionItem', 'TransactionBatch', 'Item', 'ItemBatch'];
const MODAL_KEYS = {
  COMMENT_EDIT: 'commentEdit',
  THEIR_REF_EDIT: 'theirRefEdit',
  ITEM_SELECT: 'itemSelect',
};

export const CustomerInvoicePage = props => {
  const [state, setState] = useState({
    selection: [],
    modalKey: null,
    modalIsOpen: false,
  });
  const dataFilters = {
    searchTerm: '',
    sortBy: 'itemName',
    isAscending: true,
  };
  const { selection, modalKey, modalIsOpen, data } = state;
  const { transaction, database, genericTablePageStyles, topRoute } = props;
  const { searchTerm, sortBy, isAscending } = dataFilters;
  const { ITEM_SELECT, COMMENT_EDIT, THEIR_REF_EDIT } = MODAL_KEYS;

  const updateDataFilters = (newSearchTerm, newSortBy, newIsAscending) => {
    // (... != null) checks for null or undefined (implicitly type coerced to null).
    if (newSearchTerm != null) dataFilters.searchTerm = newSearchTerm;
    if (newSortBy != null) dataFilters.sortBy = newSortBy;
    if (newIsAscending != null) dataFilters.isAscending = newIsAscending;
  };

  /**
   * Returns updated data fitlered by |searchTerm| and ordered by |sortBy| and |isAscending|.
   */
  const refreshData = (newSearchTerm, newSortBy, newIsAscending) => {
    updateDataFilters(newSearchTerm, newSortBy, newIsAscending);
    const newData = props.transaction.items.filtered(
      'item.name BEGINSWITH[c] $0 OR item.code BEGINSWITH[c] $0',
      searchTerm
    );
    let sortDataType;
    switch (sortBy) {
      case 'itemCode':
      case 'itemName':
        sortDataType = 'string';
        break;
      case 'availableQuantity':
      case 'totalQuantity':
        sortDataType = 'number';
        break;
      default:
        sortDataType = 'realm';
    }
    setState({
      ...state,
      data: sortDataBy(newData, sortBy, sortDataType, isAscending),
    });
  };

  const onAddMasterItems = () => {
    props.runWithLoadingIndicator(() => {
      props.database.write(() => {
        props.transaction.addItemsFromMasterList(props.database);
        props.database.save('Transaction', props.transaction);
      });
      refreshData();
    });
  };

  /**
   * Respond to the user editing the number in the number received column.
   *
   * @param   {string}  key              Should always be |totalQuantity|.
   * @param   {object}  transactionItem  The transaction item from the row being edited.
   * @param   {string}  newValue         The value the user entered in the cell.
   * @return  {none}
   */
  const onEndEditing = (key, transactionItem, newValue) => {
    if (key !== 'totalQuantity') return;
    database.write(() => {
      transactionItem.setTotalQuantity(database, parsePositiveInteger(newValue));
      database.save('TransactionItem', transactionItem);
    });
  };

  const onDeleteConfirm = () => {
    database.write(() => {
      transaction.removeItemsById(database, selection);
      database.save('Transaction', transaction);
    });
    setState(...state, { selection: [] }, refreshData);
  };

  const onDeleteCancel = () => {
    setState(...state, { selection: [] }, refreshData);
  };

  const onSelectionChange = newSelection => {
    setState(...state, { selection: newSelection });
  };

  const openModal = key => {
    setState(...state, { modalKey: key, modalIsOpen: true });
  };

  const closeModal = () => {
    setState(...state, { modalIsOpen: false });
  };

  const openItemSelector = () => {
    openModal(ITEM_SELECT);
  };

  const openCommentEditor = () => {
    openModal(COMMENT_EDIT);
  };

  const openTheirRefEditor = () => {
    openModal(THEIR_REF_EDIT);
  };

  const getModalTitle = () => {
    switch (modalKey) {
      default:
      case ITEM_SELECT:
        return modalStrings.search_for_an_item_to_add;
      case COMMENT_EDIT:
        return modalStrings.edit_the_invoice_comment;
      case THEIR_REF_EDIT:
        return modalStrings.edit_their_reference;
    }
  };

  const renderPageInfo = () => {
    const infoColumns = [
      [
        {
          title: `${pageInfoStrings.entry_date}:`,
          info: formatDate(transaction.entryDate) || 'N/A',
        },
        {
          title: `${pageInfoStrings.confirm_date}:`,
          info: formatDate(transaction.confirmDate),
        },
        {
          title: `${pageInfoStrings.entered_by}:`,
          info: transaction.enteredBy && transaction.enteredBy.username,
        },
      ],
      [
        {
          title: `${pageInfoStrings.customer}:`,
          info: transaction.otherParty && transaction.otherParty.name,
        },
        {
          title: `${pageInfoStrings.their_ref}:`,
          info: transaction.theirRef,
          onPress: openTheirRefEditor,
          editableType: 'text',
        },
        {
          title: `${pageInfoStrings.comment}:`,
          info: transaction.comment,
          onPress: openCommentEditor,
          editableType: 'text',
        },
      ],
    ];
    return <PageInfo columns={infoColumns} isEditingDisabled={transaction.isFinalised} />;
  };

  const renderCell = (key, transactionItem) => {
    switch (key) {
      default:
        return transactionItem[key];
      case 'totalQuantity':
        return {
          type: props.transaction.isFinalised ? 'text' : 'editable',
          cellContents: transactionItem.totalQuantity,
        };
      case 'remove':
        return {
          type: 'checkable',
          icon: 'md-remove-circle',
          isDisabled: props.transaction.isFinalised,
        };
    }
  };

  const renderModalContent = () => {
    switch (modalKey) {
      default:
      case ITEM_SELECT:
        return (
          <AutocompleteSelector
            options={database.objects('Item')}
            queryString="name BEGINSWITH[c] $0 OR code BEGINSWITH[c] $0"
            queryStringSecondary="name CONTAINS[c] $0"
            sortByString="name"
            onSelect={item => {
              database.write(() => {
                if (!transaction.hasItem(item)) {
                  createRecord(database, 'TransactionItem', transaction, item);
                }
              });
              refreshData();
              closeModal();
            }}
            renderLeftText={item => `${item.name}`}
            renderRightText={item => `${item.totalQuantity}`}
          />
        );
      case COMMENT_EDIT:
        return (
          <TextEditor
            text={transaction.comment}
            onEndEditing={newComment => {
              if (newComment !== transaction.comment) {
                database.write(() => {
                  transaction.comment = newComment;
                  database.save('Transaction', transaction);
                });
              }
              closeModal();
            }}
          />
        );
      case THEIR_REF_EDIT:
        return (
          <TextEditor
            text={transaction.theirRef}
            onEndEditing={newTheirRef => {
              if (newTheirRef !== transaction.theirRef) {
                database.write(() => {
                  transaction.theirRef = newTheirRef;
                  database.save('Transaction', transaction);
                });
              }
              closeModal();
            }}
          />
        );
    }
  };

  const renderButtons = () => (
    <View style={globalStyles.verticalContainer}>
      <PageButton
        style={globalStyles.topButton}
        text={buttonStrings.new_item}
        onPress={openItemSelector}
        isDisabled={transaction.isFinalised}
      />
      <PageButton
        text={buttonStrings.add_master_list_items}
        onPress={onAddMasterItems}
        isDisabled={transaction.isFinalised}
      />
    </View>
  );

  return (
    <GenericPage
      data={data}
      refreshData={refreshData}
      renderCell={renderCell}
      renderTopLeftComponent={renderPageInfo}
      renderTopRightComponent={renderButtons}
      onSelectionChange={onSelectionChange}
      onEndEditing={onEndEditing}
      defaultSortKey={dataFilters.sortBy}
      defaultSortDirection={dataFilters.isAscending ? 'ascending' : 'descending'}
      columns={[
        {
          key: 'itemCode',
          width: 2,
          title: tableStrings.item_code,
          sortable: true,
        },
        {
          key: 'itemName',
          width: 4,
          title: tableStrings.item_name,
          sortable: true,
        },
        {
          key: 'availableQuantity',
          width: 2,
          title: tableStrings.available_stock,
          sortable: true,
          alignText: 'right',
        },
        {
          key: 'totalQuantity',
          width: 2,
          title: tableStrings.quantity,
          sortable: true,
          alignText: 'right',
        },
        {
          key: 'remove',
          width: 1,
          title: tableStrings.remove,
          alignText: 'center',
        },
      ]}
      dataTypesSynchronised={DATA_TYPES_SYNCHRONISED}
      finalisableDataType="Transaction"
      database={database}
      selection={selection}
      {...genericTablePageStyles}
      topRoute={topRoute}
    >
      <BottomConfirmModal
        isOpen={selection.length > 0 && !transaction.isFinalised}
        questionText={modalStrings.remove_these_items}
        onCancel={() => onDeleteCancel()}
        onConfirm={() => onDeleteConfirm()}
        confirmText={modalStrings.remove}
      />
      <PageContentModal
        isOpen={modalIsOpen && !transaction.isFinalised}
        onClose={closeModal}
        title={getModalTitle()}
      >
        {renderModalContent()}
      </PageContentModal>
    </GenericPage>
  );
};

/* eslint-disable react/forbid-prop-types */
CustomerInvoicePage.propTypes = {
  database: PropTypes.object.isRequired,
  genericTablePageStyles: PropTypes.object.isRequired,
  runWithLoadingIndicator: PropTypes.func.isRequired,
  topRoute: PropTypes.bool.isRequired,
  transaction: PropTypes.object.isRequired,
};

/**
 * Check whether a given customer invoice is safe to be finalised. If safe to finalise,
 * return null, else return an appropriate error message.
 *
 * @param   {object}  customerInvoice  The customer invoice to check.
 * @return  {string}                   Error message if unsafe to finalise, else null.
 */
export function checkForFinaliseError(customerInvoice) {
  if (customerInvoice.items.length === 0) {
    return modalStrings.add_at_least_one_item_before_finalising;
  }

  if (customerInvoice.totalQuantity === 0) {
    return modalStrings.record_stock_to_issue_before_finalising;
  }

  return null;
}
<|endoftext|>


<commit_before>/* eslint-disable import/prefer-default-export */
/* eslint-disable react/forbid-prop-types */
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import { View } from 'react-native';
import { SearchBar } from 'react-native-ui-components';

import { MODAL_KEYS, getModalTitle } from '../utilities';
import { buttonStrings, modalStrings } from '../localization';
import { UIDatabase } from '../database';

import {
  focusCell,
  focusNext,
  selectRow,
  deselectRow,
  deselectAll,
  sortData,
  filterData,
  closeBasicModal,
  editComment,
  editTheirRef,
  openBasicModal,
  refreshData,
  editTransactionBatchQuantity,
  deleteTransactionBatchesById,
  editTransactionBatchExpiryDate,
  addTransactionBatch,
} from './dataTableUtilities/actions';
import usePageReducer from '../hooks/usePageReducer';

import {
  AutocompleteSelector,
  PageButton,
  PageInfo,
  TextEditor,
  CheckedComponent,
  UncheckedComponent,
  DisabledCheckedComponent,
  DisabledUncheckedComponent,
  NewExpiryDateInput,
} from '../widgets';
import { BottomConfirmModal, PageContentModal } from '../widgets/modals';
import {
  DataTable,
  Row,
  Cell,
  EditableCell,
  CheckableCell,
  DataTableHeaderRow,
} from '../widgets/DataTable';
import DataTablePageView from './containers/DataTablePageView';

import globalStyles, { SUSSOL_ORANGE, newDataTableStyles, newPageStyles } from '../globalStyles';

const keyExtractor = item => item.id;

export const SupplierInvoicePage = ({ routeName, transaction }) => {
  const [state, dispatch, instantDebouncedDispatch, debouncedDispatch] = usePageReducer(routeName, {
    pageObject: transaction,
    backingData: transaction.getTransactionBatches(UIDatabase),
    data: transaction
      .getTransactionBatches(UIDatabase)
      .sorted('itemName')
      .slice(),
    keyExtractor,
    dataState: new Map(),
    currentFocusedRowKey: null,
    searchTerm: '',
    filterDataKeys: ['itemName'],
    sortBy: 'itemName',
    isAscending: true,
    modalKey: '',
    hasSelection: false,
  });

  const {
    data,
    dataState,
    sortBy,
    isAscending,
    columns,
    modalKey,
    pageInfo,
    pageObject,
    hasSelection,
    backingData,
  } = state;

  const { isFinalised, comment, theirRef } = pageObject;
  const { ITEM_SELECT, COMMENT_EDIT, THEIR_REF_EDIT } = MODAL_KEYS;

  if (isFinalised && data.length !== backingData.length) dispatch(refreshData());

  const getItemLayout = useCallback((_, index) => {
    const { height } = newDataTableStyles.row;
    return {
      length: height,
      offset: height * index,
      index,
    };
  }, []);

  const renderPageInfo = useCallback(
    () => <PageInfo columns={pageInfo(pageObject, dispatch)} isEditingDisabled={isFinalised} />,
    [comment, theirRef, isFinalised]
  );

  const renderCells = useCallback(
    (rowData, rowState = {}, rowKey) => {
      const {
        cellContainer,
        editableCellText,
        editableCellUnfocused,
        editableCellTextView,
        cellText,
        touchableCellContainer,
      } = newDataTableStyles;
      return columns.map(({ key: colKey, type, width, alignText }, index) => {
        const isLastCell = index === columns.length - 1;
        const isDisabled = isFinalised || (rowState && rowState.isDisabled);
        switch (type) {
          case 'editable':
            return (
              <EditableCell
                key={colKey}
                value={rowData[colKey]}
                rowKey={rowKey}
                columnKey={colKey}
                editAction={editTransactionBatchQuantity}
                isFocused={colKey === (rowState && rowState.focusedColumn)}
                isDisabled={isDisabled}
                focusAction={focusCell}
                focusNextAction={focusNext}
                dispatch={dispatch}
                width={width}
                viewStyle={cellContainer[alignText || 'left']}
                textViewStyle={editableCellTextView}
                isLastCell={isLastCell}
                keyboardType="numeric"
                textInputStyle={cellText[alignText || 'left']}
                textStyle={editableCellUnfocused[alignText || 'left']}
                cellTextStyle={editableCellText}
              />
            );
          case 'checkable':
            return (
              <CheckableCell
                key={colKey}
                rowKey={rowKey}
                columnKey={colKey}
                isChecked={rowState && rowState.isSelected}
                isDisabled={isDisabled}
                CheckedComponent={CheckedComponent}
                UncheckedComponent={UncheckedComponent}
                DisabledCheckedComponent={DisabledCheckedComponent}
                DisabledUncheckedComponent={DisabledUncheckedComponent}
                onCheckAction={selectRow}
                onUncheckAction={deselectRow}
                dispatch={dispatch}
                containerStyle={touchableCellContainer}
                width={width}
                isLastCell={isLastCell}
              />
            );
          case 'date':
            return (
              <NewExpiryDateInput
                key={colKey}
                value={rowData[colKey]}
                rowKey={rowKey}
                columnKey={colKey}
                editAction={editTransactionBatchExpiryDate}
                isFocused={colKey === (rowState && rowState.focusedColumn)}
                isDisabled={isDisabled}
                focusAction={focusCell}
                focusNextAction={focusNext}
                dispatch={dispatch}
                width={width}
                isLastCell={isLastCell}
              />
            );
          default:
            return (
              <Cell
                key={colKey}
                value={rowData[colKey]}
                width={width}
                viewStyle={cellContainer[alignText || 'left']}
                textStyle={cellText[alignText || 'left']}
                isLastCell={isLastCell}
              />
            );
        }
      });
    },
    [isFinalised]
  );

  const renderRow = useCallback(
    listItem => {
      const { item, index } = listItem;
      const rowKey = keyExtractor(item);
      const { row, alternateRow } = newDataTableStyles;
      return (
        <Row
          rowData={data[index]}
          rowState={dataState.get(rowKey)}
          rowKey={rowKey}
          renderCells={renderCells}
          style={index % 2 === 0 ? alternateRow : row}
          debug
        />
      );
    },
    [data, dataState, renderCells]
  );

  const renderModalContent = () => {
    switch (modalKey) {
      case ITEM_SELECT:
        return (
          <AutocompleteSelector
            options={UIDatabase.objects('Item')}
            queryString="name BEGINSWITH[c] $0 OR code BEGINSWITH[c] $0"
            queryStringSecondary="name CONTAINS[c] $0"
            sortByString="name"
            onSelect={item => dispatch(addTransactionBatch(item))}
            renderLeftText={item => `${item.name}`}
            renderRightText={item => `${item.totalQuantity}`}
          />
        );
      case COMMENT_EDIT:
        return (
          <TextEditor
            text={comment}
            onEndEditing={value => dispatch(editComment(value, 'Transaction'))}
          />
        );
      case THEIR_REF_EDIT:
        return (
          <TextEditor
            text={theirRef}
            onEndEditing={value => dispatch(editTheirRef(value, 'Transaction'))}
          />
        );
      default:
        return null;
    }
  };

  const renderButtons = () => {
    const { verticalContainer, topButton } = globalStyles;
    return (
      <View style={verticalContainer}>
        <PageButton
          style={topButton}
          text={buttonStrings.new_item}
          onPress={() => dispatch(openBasicModal(ITEM_SELECT))}
          isDisabled={isFinalised}
        />
      </View>
    );
  };

  const renderHeader = () => (
    <DataTableHeaderRow
      columns={columns}
      dispatch={instantDebouncedDispatch}
      sortAction={sortData}
      isAscending={isAscending}
      sortBy={sortBy}
    />
  );

  const {
    newPageTopSectionContainer,
    newPageTopLeftSectionContainer,
    newPageTopRightSectionContainer,
    searchBar,
  } = newPageStyles;
  return (
    <DataTablePageView>
      <View style={newPageTopSectionContainer}>
        <View style={newPageTopLeftSectionContainer}>
          {renderPageInfo()}
          <SearchBar
            onChange={value => debouncedDispatch(filterData(value))}
            style={searchBar}
            color={SUSSOL_ORANGE}
            placeholder=""
          />
        </View>
        <View style={newPageTopRightSectionContainer}>{renderButtons()}</View>
      </View>
      <DataTable
        data={data}
        extraData={dataState}
        renderRow={renderRow}
        renderHeader={renderHeader}
        keyExtractor={keyExtractor}
        getItemLayout={getItemLayout}
      />
      <BottomConfirmModal
        isOpen={hasSelection}
        questionText={modalStrings.remove_these_items}
        onCancel={() => dispatch(deselectAll())}
        onConfirm={() => dispatch(deleteTransactionBatchesById('Transaction'))}
        confirmText={modalStrings.remove}
      />
      <PageContentModal
        isOpen={!!modalKey}
        onClose={() => dispatch(closeBasicModal())}
        title={getModalTitle(modalKey)}
      >
        {renderModalContent()}
      </PageContentModal>
    </DataTablePageView>
  );
};

SupplierInvoicePage.propTypes = {
  routeName: PropTypes.string.isRequired,
  transaction: PropTypes.object.isRequired,
};
<commit_msg>Add update to supplierinvoicepage<commit_after>/* eslint-disable import/prefer-default-export */
/* eslint-disable react/forbid-prop-types */
/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import React, { useCallback } from 'react';
import PropTypes from 'prop-types';
import { View } from 'react-native';
import { SearchBar } from 'react-native-ui-components';
import { recordKeyExtractor, getItemLayout } from './dataTableUtilities/utilities';

import { MODAL_KEYS, getModalTitle } from '../utilities';
import { buttonStrings, modalStrings } from '../localization';
import { UIDatabase } from '../database';

import {
  focusCell,
  focusNext,
  selectRow,
  deselectRow,
  deselectAll,
  sortData,
  filterData,
  closeBasicModal,
  editComment,
  editTheirRef,
  openBasicModal,
  refreshData,
  editTransactionBatchQuantity,
  deleteTransactionBatchesById,
  editTransactionBatchExpiryDate,
  addTransactionBatch,
} from './dataTableUtilities/actions';
import usePageReducer from '../hooks/usePageReducer';

import {
  AutocompleteSelector,
  PageButton,
  PageInfo,
  TextEditor,
  CheckedComponent,
  UncheckedComponent,
  DisabledCheckedComponent,
  DisabledUncheckedComponent,
  NewExpiryDateInput,
} from '../widgets';
import { BottomConfirmModal, PageContentModal } from '../widgets/modals';
import {
  DataTable,
  Row,
  Cell,
  EditableCell,
  CheckableCell,
  DataTableHeaderRow,
} from '../widgets/DataTable';
import DataTablePageView from './containers/DataTablePageView';

import globalStyles, { SUSSOL_ORANGE, newDataTableStyles, newPageStyles } from '../globalStyles';

const keyExtractor = item => item.id;

export const SupplierInvoicePage = ({ routeName, transaction }) => {
  const [state, dispatch, instantDebouncedDispatch, debouncedDispatch] = usePageReducer(routeName, {
    pageObject: transaction,
    backingData: transaction.getTransactionBatches(UIDatabase),
    data: transaction
      .getTransactionBatches(UIDatabase)
      .sorted('itemName')
      .slice(),
    keyExtractor: recordKeyExtractor,
    dataState: new Map(),
    currentFocusedRowKey: null,
    searchTerm: '',
    filterDataKeys: ['itemName'],
    sortBy: 'itemName',
    isAscending: true,
    modalKey: '',
    hasSelection: false,
  });

  const {
    data,
    dataState,
    sortBy,
    isAscending,
    columns,
    modalKey,
    pageInfo,
    pageObject,
    hasSelection,
    backingData,
  } = state;

  const { isFinalised, comment, theirRef } = pageObject;
  const { ITEM_SELECT, COMMENT_EDIT, THEIR_REF_EDIT } = MODAL_KEYS;

  if (isFinalised && data.length !== backingData.length) dispatch(refreshData());

  const renderPageInfo = useCallback(
    () => <PageInfo columns={pageInfo(pageObject, dispatch)} isEditingDisabled={isFinalised} />,
    [comment, theirRef, isFinalised]
  );

  const renderCells = useCallback(
    (rowData, rowState = {}, rowKey) => {
      const {
        cellContainer,
        editableCellText,
        editableCellUnfocused,
        editableCellTextView,
        cellText,
        touchableCellContainer,
      } = newDataTableStyles;
      return columns.map(({ key: colKey, type, width, alignText }, index) => {
        const isLastCell = index === columns.length - 1;
        const isDisabled = isFinalised || (rowState && rowState.isDisabled);
        switch (type) {
          case 'editable':
            return (
              <EditableCell
                key={colKey}
                value={rowData[colKey]}
                rowKey={rowKey}
                columnKey={colKey}
                editAction={editTransactionBatchQuantity}
                isFocused={colKey === (rowState && rowState.focusedColumn)}
                isDisabled={isDisabled}
                focusAction={focusCell}
                focusNextAction={focusNext}
                dispatch={dispatch}
                width={width}
                viewStyle={cellContainer[alignText || 'left']}
                textViewStyle={editableCellTextView}
                isLastCell={isLastCell}
                keyboardType="numeric"
                textInputStyle={cellText[alignText || 'left']}
                textStyle={editableCellUnfocused[alignText || 'left']}
                cellTextStyle={editableCellText}
              />
            );
          case 'checkable':
            return (
              <CheckableCell
                key={colKey}
                rowKey={rowKey}
                columnKey={colKey}
                isChecked={rowState && rowState.isSelected}
                isDisabled={isDisabled}
                CheckedComponent={CheckedComponent}
                UncheckedComponent={UncheckedComponent}
                DisabledCheckedComponent={DisabledCheckedComponent}
                DisabledUncheckedComponent={DisabledUncheckedComponent}
                onCheckAction={selectRow}
                onUncheckAction={deselectRow}
                dispatch={dispatch}
                containerStyle={touchableCellContainer}
                width={width}
                isLastCell={isLastCell}
              />
            );
          case 'date':
            return (
              <NewExpiryDateInput
                key={colKey}
                value={rowData[colKey]}
                rowKey={rowKey}
                columnKey={colKey}
                editAction={editTransactionBatchExpiryDate}
                isFocused={colKey === (rowState && rowState.focusedColumn)}
                isDisabled={isDisabled}
                focusAction={focusCell}
                focusNextAction={focusNext}
                dispatch={dispatch}
                width={width}
                isLastCell={isLastCell}
              />
            );
          default:
            return (
              <Cell
                key={colKey}
                value={rowData[colKey]}
                width={width}
                viewStyle={cellContainer[alignText || 'left']}
                textStyle={cellText[alignText || 'left']}
                isLastCell={isLastCell}
              />
            );
        }
      });
    },
    [isFinalised]
  );

  const renderRow = useCallback(
    listItem => {
      const { item, index } = listItem;
      const rowKey = keyExtractor(item);
      const { row, alternateRow } = newDataTableStyles;
      return (
        <Row
          rowData={data[index]}
          rowState={dataState.get(rowKey)}
          rowKey={rowKey}
          renderCells={renderCells}
          style={index % 2 === 0 ? alternateRow : row}
          debug
        />
      );
    },
    [data, dataState, renderCells]
  );

  const renderModalContent = () => {
    switch (modalKey) {
      case ITEM_SELECT:
        return (
          <AutocompleteSelector
            options={UIDatabase.objects('Item')}
            queryString="name BEGINSWITH[c] $0 OR code BEGINSWITH[c] $0"
            queryStringSecondary="name CONTAINS[c] $0"
            sortByString="name"
            onSelect={item => dispatch(addTransactionBatch(item))}
            renderLeftText={item => `${item.name}`}
            renderRightText={item => `${item.totalQuantity}`}
          />
        );
      case COMMENT_EDIT:
        return (
          <TextEditor
            text={comment}
            onEndEditing={value => dispatch(editComment(value, 'Transaction'))}
          />
        );
      case THEIR_REF_EDIT:
        return (
          <TextEditor
            text={theirRef}
            onEndEditing={value => dispatch(editTheirRef(value, 'Transaction'))}
          />
        );
      default:
        return null;
    }
  };

  const renderButtons = () => {
    const { verticalContainer, topButton } = globalStyles;
    return (
      <View style={verticalContainer}>
        <PageButton
          style={topButton}
          text={buttonStrings.new_item}
          onPress={() => dispatch(openBasicModal(ITEM_SELECT))}
          isDisabled={isFinalised}
        />
      </View>
    );
  };

  const renderHeader = () => (
    <DataTableHeaderRow
      columns={columns}
      dispatch={instantDebouncedDispatch}
      sortAction={sortData}
      isAscending={isAscending}
      sortBy={sortBy}
    />
  );

  const memoizedGetItemLayout = useCallback(getItemLayout, []);

  const {
    newPageTopSectionContainer,
    newPageTopLeftSectionContainer,
    newPageTopRightSectionContainer,
    searchBar,
  } = newPageStyles;
  return (
    <DataTablePageView>
      <View style={newPageTopSectionContainer}>
        <View style={newPageTopLeftSectionContainer}>
          {renderPageInfo()}
          <SearchBar
            onChange={value => debouncedDispatch(filterData(value))}
            style={searchBar}
            color={SUSSOL_ORANGE}
            placeholder=""
          />
        </View>
        <View style={newPageTopRightSectionContainer}>{renderButtons()}</View>
      </View>
      <DataTable
        data={data}
        extraData={dataState}
        renderRow={renderRow}
        renderHeader={renderHeader}
        keyExtractor={keyExtractor}
        getItemLayout={memoizedGetItemLayout}
      />
      <BottomConfirmModal
        isOpen={hasSelection}
        questionText={modalStrings.remove_these_items}
        onCancel={() => dispatch(deselectAll())}
        onConfirm={() => dispatch(deleteTransactionBatchesById('Transaction'))}
        confirmText={modalStrings.remove}
      />
      <PageContentModal
        isOpen={!!modalKey}
        onClose={() => dispatch(closeBasicModal())}
        title={getModalTitle(modalKey)}
      >
        {renderModalContent()}
      </PageContentModal>
    </DataTablePageView>
  );
};

SupplierInvoicePage.propTypes = {
  routeName: PropTypes.string.isRequired,
  transaction: PropTypes.object.isRequired,
};
<|endoftext|>


<commit_before>/***
|''Name''|TiddlySpaceConfig|
|''Version''|0.5.1|
|''Description''|TiddlySpace configuration|
|''Status''|@@beta@@|
|''Source''|http://github.com/TiddlySpace/tiddlyspace/raw/master/src/plugins/TiddlySpaceConfig.js|
|''Requires''|TiddlyWebConfig ServerSideSavingPlugin|
!Code
***/
//{{{
(function($) {

var recipe = config.defaultCustomFields["server.workspace"].split("recipes/")[1];

// hijack search macro to add custom attributes for mobile devices
var _search = config.macros.search.handler;
config.macros.search.handler = function(place, macroName, params) {
	_search.apply(this, arguments);
	$(".searchField:input", place).
		attr({ autocapitalize: "off", autocorrect: "off" });
};

// arg is either a container name or a tiddler object
// if fuzzy is truthy, space may be inferred from workspace (for new tiddlers)
// returns space object or false
var determineSpace = function(arg, fuzzy) {
	if(typeof arg == "string") { // container name
		var space = split(arg, "_", "r");
		return ["public", "private"].contains(space.type) ? space : false;
	} else if(arg) { // tiddler
		var container = determineContainer(arg, fuzzy);
		return container ? determineSpace(container.name, fuzzy) : false;
	} else {
		return false;
	}
};

// if fuzzy is truthy, container may be inferred from workspace for new tiddlers
// returns container object or false
var determineContainer = function(tiddler, fuzzy) { // TODO: expose?
	var bag = tiddler.fields["server.bag"];
	var recipe = tiddler.fields["server.recipe"]; // XXX: unused/irrelevant/redundant!?
	if(bag) {
		return { type: "bag", name: bag };
	} else if(recipe) {
		return { type: "recipe", name: recipe };
	} else if(fuzzy) { // new tiddler
		var workspace = tiddler.fields["server.workspace"];
		if(workspace) {
			var container = split(workspace, "/", "l");
			return ["bags", "recipes"].contains(container.type) ? container : false;
		} else {
			return false;
		}
	} else {
		return false;
	}
};

// hijack removeTiddlerCallback to restore tiddler from recipe cascade
var sssp = config.extensions.ServerSideSavingPlugin;
var _removeTiddlerCallback = sssp.removeTiddlerCallback;
sssp.removeTiddlerCallback = function(context, userParams) {
	var title = context.tiddler.title;
	var recipe = context.tiddler.fields["server.recipe"];
	_removeTiddlerCallback.apply(this, arguments);
	if(recipe) {
		context.workspace = "recipes/" + recipe;
		var callback = function(context, userParams) {
			if(context.status) {
				store.saveTiddler(context.tiddler);
			} else {
				store.notify(title, true);
			}
		};
		context.adaptor.getTiddler(title, context, null, callback);
	}
};

// splits a string once using delimiter
// mode "l" splits at the first, "r" at the last occurrence
// returns an object with members type and name
var split = function(str, sep, mode) {
	mode = mode == "r" ? "pop" : "shift"; // TODO: use +/-1 instead of "l"/"r"?
	var arr = str.split(sep);
	var type = arr.length > 1 ? arr[mode]() : null;
	return { type: type, name: arr.join(sep) };
};

// hijack saveTiddler to accept Tiddler instance
var _saveTiddler = TiddlyWiki.prototype.saveTiddler;
TiddlyWiki.prototype.saveTiddler = function(title, newTitle, newBody, modifier,
		modified, tags, fields, clearChangeCount, created, creator) {
	if(title instanceof Tiddler) { // overloading first argument
		var t = $.extend(new Tiddler(title.title), title);
		t = _saveTiddler.apply(this, [t.title, t.title, t.text, t.modifier,
			t.modified, t.tags, t.fields, false, t.created, t.creator]);
		return t;
	} else {
		return _saveTiddler.apply(this, arguments);
	}
};

var plugin = config.extensions.tiddlyspace = {
	currentSpace: determineSpace(recipe),
	coreBags: ["system", "tiddlyspace"],

	determineSpace: determineSpace,
	isValidSpaceName: function(name) {
		return name.match(/^[a-z][0-9a-z\-]*[0-9a-z]$/) ? true : false;
	},
	getAvatar: function(host, space) {
		host = host ? this.getHost(host) : "";
		var bag = space.name ? "%0_public".format([space.name]) : "tiddlyspace";
		return "%0/bags/%1/tiddlers/SiteIcon".format([host, bag]);
	},
	getHost: function(host, subdomain) {
		subdomain = subdomain ? subdomain + "." : "";
		var url = "%0://%1%2".format([host.scheme, subdomain, host.host]);
		var port = host.port;
		if(port && !["80", "443"].contains(port)) {
			url += ":" + port;
		}
		return url;
	}
};

var tweb = config.extensions.tiddlyweb;
tweb.serverPrefix = tweb.host.split("/")[3] || ""; // XXX: assumes root handler
tweb.getStatus(function(status) {
	var url = plugin.getHost(status.server_host);
	tweb.status.server_host.url = url;
});

// set global read-only mode based on membership heuristics
var indicator = store.getTiddler("SiteTitle") || tiddler;
readOnly = !(recipe.split("_").pop() == "private" ||
	tweb.hasPermission("write", indicator));

// ensure backstage is always initialized
// required to circumvent TiddlyWiki's read-only based handling
config.macros.backstageInit = {
	init: function() {
		showBackstage = true;
	}
};

// register style sheet for backstage separately (important)
store.addNotification("StyleSheetBackstage", refreshStyles);

})(jQuery);
//}}}
<commit_msg>fixed getAvatar's use of getHost<commit_after>/***
|''Name''|TiddlySpaceConfig|
|''Version''|0.5.2|
|''Description''|TiddlySpace configuration|
|''Status''|@@beta@@|
|''Source''|http://github.com/TiddlySpace/tiddlyspace/raw/master/src/plugins/TiddlySpaceConfig.js|
|''Requires''|TiddlyWebConfig ServerSideSavingPlugin|
!Code
***/
//{{{
(function($) {

var recipe = config.defaultCustomFields["server.workspace"].split("recipes/")[1];

// hijack search macro to add custom attributes for mobile devices
var _search = config.macros.search.handler;
config.macros.search.handler = function(place, macroName, params) {
	_search.apply(this, arguments);
	$(".searchField:input", place).
		attr({ autocapitalize: "off", autocorrect: "off" });
};

// arg is either a container name or a tiddler object
// if fuzzy is truthy, space may be inferred from workspace (for new tiddlers)
// returns space object or false
var determineSpace = function(arg, fuzzy) {
	if(typeof arg == "string") { // container name
		var space = split(arg, "_", "r");
		return ["public", "private"].contains(space.type) ? space : false;
	} else if(arg) { // tiddler
		var container = determineContainer(arg, fuzzy);
		return container ? determineSpace(container.name, fuzzy) : false;
	} else {
		return false;
	}
};

// if fuzzy is truthy, container may be inferred from workspace for new tiddlers
// returns container object or false
var determineContainer = function(tiddler, fuzzy) { // TODO: expose?
	var bag = tiddler.fields["server.bag"];
	var recipe = tiddler.fields["server.recipe"]; // XXX: unused/irrelevant/redundant!?
	if(bag) {
		return { type: "bag", name: bag };
	} else if(recipe) {
		return { type: "recipe", name: recipe };
	} else if(fuzzy) { // new tiddler
		var workspace = tiddler.fields["server.workspace"];
		if(workspace) {
			var container = split(workspace, "/", "l");
			return ["bags", "recipes"].contains(container.type) ? container : false;
		} else {
			return false;
		}
	} else {
		return false;
	}
};

// hijack removeTiddlerCallback to restore tiddler from recipe cascade
var sssp = config.extensions.ServerSideSavingPlugin;
var _removeTiddlerCallback = sssp.removeTiddlerCallback;
sssp.removeTiddlerCallback = function(context, userParams) {
	var title = context.tiddler.title;
	var recipe = context.tiddler.fields["server.recipe"];
	_removeTiddlerCallback.apply(this, arguments);
	if(recipe) {
		context.workspace = "recipes/" + recipe;
		var callback = function(context, userParams) {
			if(context.status) {
				store.saveTiddler(context.tiddler);
			} else {
				store.notify(title, true);
			}
		};
		context.adaptor.getTiddler(title, context, null, callback);
	}
};

// splits a string once using delimiter
// mode "l" splits at the first, "r" at the last occurrence
// returns an object with members type and name
var split = function(str, sep, mode) {
	mode = mode == "r" ? "pop" : "shift"; // TODO: use +/-1 instead of "l"/"r"?
	var arr = str.split(sep);
	var type = arr.length > 1 ? arr[mode]() : null;
	return { type: type, name: arr.join(sep) };
};

// hijack saveTiddler to accept Tiddler instance
var _saveTiddler = TiddlyWiki.prototype.saveTiddler;
TiddlyWiki.prototype.saveTiddler = function(title, newTitle, newBody, modifier,
		modified, tags, fields, clearChangeCount, created, creator) {
	if(title instanceof Tiddler) { // overloading first argument
		var t = $.extend(new Tiddler(title.title), title);
		t = _saveTiddler.apply(this, [t.title, t.title, t.text, t.modifier,
			t.modified, t.tags, t.fields, false, t.created, t.creator]);
		return t;
	} else {
		return _saveTiddler.apply(this, arguments);
	}
};

var plugin = config.extensions.tiddlyspace = {
	currentSpace: determineSpace(recipe),
	coreBags: ["system", "tiddlyspace"],

	determineSpace: determineSpace,
	isValidSpaceName: function(name) {
		return name.match(/^[a-z][0-9a-z\-]*[0-9a-z]$/) ? true : false;
	},
	getAvatar: function(host, space) {
		host = host ? this.getHost(host, space.name) : "";
		var bag = space.name ? "%0_public".format([space.name]) : "tiddlyspace";
		return "%0/bags/%1/tiddlers/SiteIcon".format([host, bag]);
	},
	getHost: function(host, subdomain) {
		subdomain = subdomain ? subdomain + "." : "";
		var url = "%0://%1%2".format([host.scheme, subdomain, host.host]);
		var port = host.port;
		if(port && !["80", "443"].contains(port)) {
			url += ":" + port;
		}
		return url;
	}
};

var tweb = config.extensions.tiddlyweb;
tweb.serverPrefix = tweb.host.split("/")[3] || ""; // XXX: assumes root handler
tweb.getStatus(function(status) {
	var url = plugin.getHost(status.server_host);
	tweb.status.server_host.url = url;
});

// set global read-only mode based on membership heuristics
var indicator = store.getTiddler("SiteTitle") || tiddler;
readOnly = !(recipe.split("_").pop() == "private" ||
	tweb.hasPermission("write", indicator));

// ensure backstage is always initialized
// required to circumvent TiddlyWiki's read-only based handling
config.macros.backstageInit = {
	init: function() {
		showBackstage = true;
	}
};

// register style sheet for backstage separately (important)
store.addNotification("StyleSheetBackstage", refreshStyles);

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


<commit_before>/***
|''Name''|TiddlySpaceConfig|
|''Version''|0.5.4|
|''Description''|TiddlySpace configuration|
|''Status''|@@beta@@|
|''Source''|http://github.com/TiddlySpace/tiddlyspace/raw/master/src/plugins/TiddlySpaceConfig.js|
|''Requires''|TiddlyWebConfig ServerSideSavingPlugin|
!Code
***/
//{{{
(function($) {

var tweb = config.extensions.tiddlyweb;

var recipe = config.defaultCustomFields["server.workspace"].split("recipes/")[1];

// hijack search macro to add custom attributes for mobile devices
var _search = config.macros.search.handler;
config.macros.search.handler = function(place, macroName, params) {
	_search.apply(this, arguments);
	$(".searchField:input", place).
		attr({ autocapitalize: "off", autocorrect: "off" });
};

// arg is either a container name or a tiddler object
// if fuzzy is truthy, space may be inferred from workspace (for new tiddlers)
// returns space object or false
var determineSpace = function(arg, fuzzy) {
	if(typeof arg == "string") { // container name
		var space = split(arg, "_", "r");
		return ["public", "private"].contains(space.type) ? space : false;
	} else if(arg) { // tiddler
		var container = determineContainer(arg, fuzzy);
		return container ? determineSpace(container.name, fuzzy) : false;
	} else {
		return false;
	}
};

// if fuzzy is truthy, container may be inferred from workspace for new tiddlers
// returns container object or false
var determineContainer = function(tiddler, fuzzy) { // TODO: expose?
	var bag = tiddler.fields["server.bag"];
	var recipe = tiddler.fields["server.recipe"]; // XXX: unused/irrelevant/redundant!?
	if(bag) {
		return { type: "bag", name: bag };
	} else if(recipe) {
		return { type: "recipe", name: recipe };
	} else if(fuzzy) { // new tiddler
		var workspace = tiddler.fields["server.workspace"];
		if(workspace) {
			var container = split(workspace, "/", "l");
			return ["bags", "recipes"].contains(container.type) ? container : false;
		} else {
			return false;
		}
	} else {
		return false;
	}
};

// hijack removeTiddlerCallback to restore tiddler from recipe cascade -- TODO: move into TiddlyWebWiki?
var sssp = config.extensions.ServerSideSavingPlugin;
var _removeTiddlerCallback = sssp.removeTiddlerCallback;
sssp.removeTiddlerCallback = function(context, userParams) {
	var title = context.tiddler.title;
	var recipe = context.tiddler.fields["server.recipe"];
	_removeTiddlerCallback.apply(this, arguments);
	if(recipe) {
		context.workspace = "recipes/" + recipe;
		var callback = function(context, userParams) {
			if(context.status) {
				store.saveTiddler(context.tiddler);
			} else {
				store.notify(title, true);
			}
		};
		context.adaptor.getTiddler(title, context, null, callback);
	}
};

// hijack getUserInfo to treat unknown users as anonymous
var _getUserInfo = tweb.getUserInfo;
tweb.getUserInfo = function(callback) {
	_getUserInfo.call(this, function(user) {
		if(!user.anon) { // double-check whether the user is real
			$.ajax({
				url: "%0/users/%1".format([tweb.host, user.name]),
				type: "GET",
				success: function(data, status, xhr) {
					user.anon = false;
				},
				error: function(xhr, error, exc) {
					user.anon = true;
				},
				complete: function(xhr, status) {
					callback(user);
				}
			});
		} else {
			callback(user);
		}
	});
};

// splits a string once using delimiter
// mode "l" splits at the first, "r" at the last occurrence
// returns an object with members type and name
var split = function(str, sep, mode) {
	mode = mode == "r" ? "pop" : "shift"; // TODO: use +/-1 instead of "l"/"r"?
	var arr = str.split(sep);
	var type = arr.length > 1 ? arr[mode]() : null;
	return { type: type, name: arr.join(sep) };
};

// hijack saveTiddler to accept Tiddler instance
// optionally triggers autoSaveChanges if second argument is true
var _saveTiddler = TiddlyWiki.prototype.saveTiddler;
TiddlyWiki.prototype.saveTiddler = function(title, newTitle, newBody, modifier,
		modified, tags, fields, clearChangeCount, created, creator) {
	if(title instanceof Tiddler) { // overloading first argument
		var t = $.extend(new Tiddler(title.title), title);
		t = _saveTiddler.apply(this, [t.title, t.title, t.text, t.modifier,
			t.modified, t.tags, t.fields, false, t.created, t.creator]);
		if(newTitle === true) { // overloading second argument
			autoSaveChanges(null, [t]);
		}
		return t;
	} else {
		return _saveTiddler.apply(this, arguments);
	}
};

var plugin = config.extensions.tiddlyspace = {
	currentSpace: determineSpace(recipe),
	coreBags: ["system", "tiddlyspace"],

	determineSpace: determineSpace,
	isValidSpaceName: function(name) {
		return name.match(/^[a-z][0-9a-z\-]*[0-9a-z]$/) ? true : false;
	},
	// returns the URL for a space's avatar (SiteIcon) based on a server_host
	// object and an optional space name
	// optional nocors argument prevents cross-domain URLs from being generated
	getAvatar: function(host, space, nocors) {
		if(space && typeof space != "string") { // backwards compatibility -- XXX: deprecated
			space = space.name;
		}
		var subdomain = nocors ? plugin.currentSpace.name : space;
		host = host ? this.getHost(host, subdomain) : "";
		var bag = space ? "%0_public".format([space]) : "tiddlyspace";
		return "%0/bags/%1/tiddlers/SiteIcon".format([host, bag]);
	},
	// returns the URL based on a server_host object (scheme, host, port) and an
	// optional subdomain
	getHost: function(host, subdomain) {
		subdomain = subdomain ? subdomain + "." : "";
		var url = "%0://%1%2".format([host.scheme, subdomain, host.host]);
		var port = host.port;
		if(port && !["80", "443"].contains(port)) {
			url += ":" + port;
		}
		return url;
	}
};

tweb.serverPrefix = tweb.host.split("/")[3] || ""; // XXX: assumes root handler
tweb.getStatus(function(status) {
	var url = plugin.getHost(status.server_host);
	tweb.status.server_host.url = url;
});

// set global read-only mode based on membership heuristics
var indicator = store.getTiddler("SiteTitle") || tiddler;
readOnly = !(recipe.split("_").pop() == "private" ||
	tweb.hasPermission("write", indicator));

// ensure backstage is always initialized
// required to circumvent TiddlyWiki's read-only based handling
config.macros.backstageInit = {
	init: function() {
		showBackstage = true;
	}
};

// register style sheet for backstage separately (important)
store.addNotification("StyleSheetBackstage", refreshStyles);

})(jQuery);
//}}}
<commit_msg>Revert "augmented saveTiddler method to optionally trigger autoSaveChanges"<commit_after>/***
|''Name''|TiddlySpaceConfig|
|''Version''|0.5.3|
|''Description''|TiddlySpace configuration|
|''Status''|@@beta@@|
|''Source''|http://github.com/TiddlySpace/tiddlyspace/raw/master/src/plugins/TiddlySpaceConfig.js|
|''Requires''|TiddlyWebConfig ServerSideSavingPlugin|
!Code
***/
//{{{
(function($) {

var tweb = config.extensions.tiddlyweb;

var recipe = config.defaultCustomFields["server.workspace"].split("recipes/")[1];

// hijack search macro to add custom attributes for mobile devices
var _search = config.macros.search.handler;
config.macros.search.handler = function(place, macroName, params) {
	_search.apply(this, arguments);
	$(".searchField:input", place).
		attr({ autocapitalize: "off", autocorrect: "off" });
};

// arg is either a container name or a tiddler object
// if fuzzy is truthy, space may be inferred from workspace (for new tiddlers)
// returns space object or false
var determineSpace = function(arg, fuzzy) {
	if(typeof arg == "string") { // container name
		var space = split(arg, "_", "r");
		return ["public", "private"].contains(space.type) ? space : false;
	} else if(arg) { // tiddler
		var container = determineContainer(arg, fuzzy);
		return container ? determineSpace(container.name, fuzzy) : false;
	} else {
		return false;
	}
};

// if fuzzy is truthy, container may be inferred from workspace for new tiddlers
// returns container object or false
var determineContainer = function(tiddler, fuzzy) { // TODO: expose?
	var bag = tiddler.fields["server.bag"];
	var recipe = tiddler.fields["server.recipe"]; // XXX: unused/irrelevant/redundant!?
	if(bag) {
		return { type: "bag", name: bag };
	} else if(recipe) {
		return { type: "recipe", name: recipe };
	} else if(fuzzy) { // new tiddler
		var workspace = tiddler.fields["server.workspace"];
		if(workspace) {
			var container = split(workspace, "/", "l");
			return ["bags", "recipes"].contains(container.type) ? container : false;
		} else {
			return false;
		}
	} else {
		return false;
	}
};

// hijack removeTiddlerCallback to restore tiddler from recipe cascade -- TODO: move into TiddlyWebWiki?
var sssp = config.extensions.ServerSideSavingPlugin;
var _removeTiddlerCallback = sssp.removeTiddlerCallback;
sssp.removeTiddlerCallback = function(context, userParams) {
	var title = context.tiddler.title;
	var recipe = context.tiddler.fields["server.recipe"];
	_removeTiddlerCallback.apply(this, arguments);
	if(recipe) {
		context.workspace = "recipes/" + recipe;
		var callback = function(context, userParams) {
			if(context.status) {
				store.saveTiddler(context.tiddler);
			} else {
				store.notify(title, true);
			}
		};
		context.adaptor.getTiddler(title, context, null, callback);
	}
};

// hijack getUserInfo to treat unknown users as anonymous
var _getUserInfo = tweb.getUserInfo;
tweb.getUserInfo = function(callback) {
	_getUserInfo.call(this, function(user) {
		if(!user.anon) { // double-check whether the user is real
			$.ajax({
				url: "%0/users/%1".format([tweb.host, user.name]),
				type: "GET",
				success: function(data, status, xhr) {
					user.anon = false;
				},
				error: function(xhr, error, exc) {
					user.anon = true;
				},
				complete: function(xhr, status) {
					callback(user);
				}
			});
		} else {
			callback(user);
		}
	});
};

// splits a string once using delimiter
// mode "l" splits at the first, "r" at the last occurrence
// returns an object with members type and name
var split = function(str, sep, mode) {
	mode = mode == "r" ? "pop" : "shift"; // TODO: use +/-1 instead of "l"/"r"?
	var arr = str.split(sep);
	var type = arr.length > 1 ? arr[mode]() : null;
	return { type: type, name: arr.join(sep) };
};

// hijack saveTiddler to accept Tiddler instance
var _saveTiddler = TiddlyWiki.prototype.saveTiddler;
TiddlyWiki.prototype.saveTiddler = function(title, newTitle, newBody, modifier,
		modified, tags, fields, clearChangeCount, created, creator) {
	if(title instanceof Tiddler) { // overloading first argument
		var t = $.extend(new Tiddler(title.title), title);
		t = _saveTiddler.apply(this, [t.title, t.title, t.text, t.modifier,
			t.modified, t.tags, t.fields, false, t.created, t.creator]);
		return t;
	} else {
		return _saveTiddler.apply(this, arguments);
	}
};

var plugin = config.extensions.tiddlyspace = {
	currentSpace: determineSpace(recipe),
	coreBags: ["system", "tiddlyspace"],

	determineSpace: determineSpace,
	isValidSpaceName: function(name) {
		return name.match(/^[a-z][0-9a-z\-]*[0-9a-z]$/) ? true : false;
	},
	// returns the URL for a space's avatar (SiteIcon) based on a server_host
	// object and an optional space name
	// optional nocors argument prevents cross-domain URLs from being generated
	getAvatar: function(host, space, nocors) {
		if(space && typeof space != "string") { // backwards compatibility -- XXX: deprecated
			space = space.name;
		}
		var subdomain = nocors ? plugin.currentSpace.name : space;
		host = host ? this.getHost(host, subdomain) : "";
		var bag = space ? "%0_public".format([space]) : "tiddlyspace";
		return "%0/bags/%1/tiddlers/SiteIcon".format([host, bag]);
	},
	// returns the URL based on a server_host object (scheme, host, port) and an
	// optional subdomain
	getHost: function(host, subdomain) {
		subdomain = subdomain ? subdomain + "." : "";
		var url = "%0://%1%2".format([host.scheme, subdomain, host.host]);
		var port = host.port;
		if(port && !["80", "443"].contains(port)) {
			url += ":" + port;
		}
		return url;
	}
};

tweb.serverPrefix = tweb.host.split("/")[3] || ""; // XXX: assumes root handler
tweb.getStatus(function(status) {
	var url = plugin.getHost(status.server_host);
	tweb.status.server_host.url = url;
});

// set global read-only mode based on membership heuristics
var indicator = store.getTiddler("SiteTitle") || tiddler;
readOnly = !(recipe.split("_").pop() == "private" ||
	tweb.hasPermission("write", indicator));

// ensure backstage is always initialized
// required to circumvent TiddlyWiki's read-only based handling
config.macros.backstageInit = {
	init: function() {
		showBackstage = true;
	}
};

// register style sheet for backstage separately (important)
store.addNotification("StyleSheetBackstage", refreshStyles);

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


<commit_before>import debug from 'debug';
import React from 'react';
import { connect } from 'react-redux';

import Logo from './logo';
import Footer from './footer.js';
import Sidebar from './sidebar.js';
import HelpPanel from './help-panel';
import Search from './search';
import Status from './status.js';
import Topologies from './topologies.js';
import TopologyOptions from './topology-options.js';
import { getApiDetails, getTopologies } from '../utils/web-api-utils';
import { focusSearch, pinNextMetric, hitBackspace, hitEnter, hitEsc, unpinMetric,
  selectMetric, toggleHelp } from '../actions/app-actions';
import Details from './details';
import Nodes from './nodes';
import MetricSelector from './metric-selector';
import NetworkSelector from './networks-selector';
import EmbeddedTerminal from './embedded-terminal';
import { getRouter } from '../utils/router-utils';
import DebugToolbar, { showingDebugToolbar,
  toggleDebugToolbar } from './debug-toolbar.js';
import { getUrlState } from '../utils/router-utils';
import { getActiveTopologyOptions } from '../utils/topology-utils';

const BACKSPACE_KEY_CODE = 8;
const ENTER_KEY_CODE = 13;
const ESC_KEY_CODE = 27;
const keyPressLog = debug('scope:app-key-press');

class App extends React.Component {

  constructor(props, context) {
    super(props, context);
    this.onKeyPress = this.onKeyPress.bind(this);
    this.onKeyUp = this.onKeyUp.bind(this);
  }

  componentDidMount() {
    window.addEventListener('keypress', this.onKeyPress);
    window.addEventListener('keyup', this.onKeyUp);

    getRouter(this.props.dispatch, this.props.urlState).start({hashbang: true});
    if (!this.props.routeSet) {
      // dont request topologies when already done via router
      getTopologies(this.props.activeTopologyOptions, this.props.dispatch);
    }
    getApiDetails(this.props.dispatch);
  }

  componentWillUnmount() {
    window.removeEventListener('keypress', this.onKeyPress);
    window.removeEventListener('keyup', this.onKeyUp);
  }

  onKeyUp(ev) {
    const { showingTerminal } = this.props;
    keyPressLog('onKeyUp', 'keyCode', ev.keyCode, ev);

    // don't get esc in onKeyPress
    if (ev.keyCode === ESC_KEY_CODE) {
      this.props.dispatch(hitEsc());
    } else if (ev.keyCode === ENTER_KEY_CODE) {
      this.props.dispatch(hitEnter());
    } else if (ev.keyCode === BACKSPACE_KEY_CODE) {
      this.props.dispatch(hitBackspace());
    } else if (ev.code === 'KeyD' && ev.ctrlKey && !showingTerminal) {
      toggleDebugToolbar();
      this.forceUpdate();
    }
  }

  onKeyPress(ev) {
    const { dispatch, searchFocused } = this.props;
    //
    // keyup gives 'key'
    // keypress gives 'char'
    // Distinction is important for international keyboard layouts where there
    // is often a different {key: char} mapping.
    //
    if (!searchFocused) {
      keyPressLog('onKeyPress', 'keyCode', ev.keyCode, ev);
      const char = String.fromCharCode(ev.charCode);
      if (char === '<') {
        dispatch(pinNextMetric(-1));
      } else if (char === '>') {
        dispatch(pinNextMetric(1));
      } else if (char === 'q') {
        dispatch(unpinMetric());
        dispatch(selectMetric(null));
      } else if (char === '/') {
        ev.preventDefault();
        dispatch(focusSearch());
      } else if (char === '?') {
        dispatch(toggleHelp());
      }
    }
  }

  render() {
    const { showingDetails, showingHelp, showingMetricsSelector, showingNetworkSelector,
     showingTerminal } = this.props;

    return (

        {showingDebugToolbar() && <DebugToolbar />}

        {showingHelp && <HelpPanel />}

        {showingDetails && }

        {showingTerminal && <EmbeddedTerminal />}

            <svg width="100%" height="100%" viewBox="0 0 1089 217">
              <Logo />
            </svg>

          <Search />
          <Topologies />

        <Nodes />

        <Sidebar>
          <Status />
          {showingMetricsSelector && <MetricSelector />}
          {showingNetworkSelector && <NetworkSelector />}
          <TopologyOptions />
        </Sidebar>

    );
  }
}

function mapStateToProps(state) {
  return {
    activeTopologyOptions: getActiveTopologyOptions(state),
    routeSet: state.get('routeSet'),
    searchFocused: state.get('searchFocused'),
    searchQuery: state.get('searchQuery'),
    showingDetails: state.get('nodeDetails').size > 0,
    showingHelp: state.get('showingHelp'),
    showingMetricsSelector: state.get('availableCanvasMetrics').count() > 0,
    showingNetworkSelector: state.get('availableNetworks').count() > 0,
    showingTerminal: state.get('controlPipes').size > 0,
    urlState: getUrlState(state)
  };
}

export default connect(
  mapStateToProps
)(App);
<commit_msg>Hide logo when in frame<commit_after>import debug from 'debug';
import React from 'react';
import { connect } from 'react-redux';

import Logo from './logo';
import Footer from './footer.js';
import Sidebar from './sidebar.js';
import HelpPanel from './help-panel';
import Search from './search';
import Status from './status.js';
import Topologies from './topologies.js';
import TopologyOptions from './topology-options.js';
import { getApiDetails, getTopologies } from '../utils/web-api-utils';
import { focusSearch, pinNextMetric, hitBackspace, hitEnter, hitEsc, unpinMetric,
  selectMetric, toggleHelp } from '../actions/app-actions';
import Details from './details';
import Nodes from './nodes';
import MetricSelector from './metric-selector';
import NetworkSelector from './networks-selector';
import EmbeddedTerminal from './embedded-terminal';
import { getRouter } from '../utils/router-utils';
import DebugToolbar, { showingDebugToolbar,
  toggleDebugToolbar } from './debug-toolbar.js';
import { getUrlState } from '../utils/router-utils';
import { getActiveTopologyOptions } from '../utils/topology-utils';

const BACKSPACE_KEY_CODE = 8;
const ENTER_KEY_CODE = 13;
const ESC_KEY_CODE = 27;
const keyPressLog = debug('scope:app-key-press');

class App extends React.Component {

  constructor(props, context) {
    super(props, context);
    this.onKeyPress = this.onKeyPress.bind(this);
    this.onKeyUp = this.onKeyUp.bind(this);
  }

  componentDidMount() {
    window.addEventListener('keypress', this.onKeyPress);
    window.addEventListener('keyup', this.onKeyUp);

    getRouter(this.props.dispatch, this.props.urlState).start({hashbang: true});
    if (!this.props.routeSet) {
      // dont request topologies when already done via router
      getTopologies(this.props.activeTopologyOptions, this.props.dispatch);
    }
    getApiDetails(this.props.dispatch);
  }

  componentWillUnmount() {
    window.removeEventListener('keypress', this.onKeyPress);
    window.removeEventListener('keyup', this.onKeyUp);
  }

  onKeyUp(ev) {
    const { showingTerminal } = this.props;
    keyPressLog('onKeyUp', 'keyCode', ev.keyCode, ev);

    // don't get esc in onKeyPress
    if (ev.keyCode === ESC_KEY_CODE) {
      this.props.dispatch(hitEsc());
    } else if (ev.keyCode === ENTER_KEY_CODE) {
      this.props.dispatch(hitEnter());
    } else if (ev.keyCode === BACKSPACE_KEY_CODE) {
      this.props.dispatch(hitBackspace());
    } else if (ev.code === 'KeyD' && ev.ctrlKey && !showingTerminal) {
      toggleDebugToolbar();
      this.forceUpdate();
    }
  }

  onKeyPress(ev) {
    const { dispatch, searchFocused } = this.props;
    //
    // keyup gives 'key'
    // keypress gives 'char'
    // Distinction is important for international keyboard layouts where there
    // is often a different {key: char} mapping.
    //
    if (!searchFocused) {
      keyPressLog('onKeyPress', 'keyCode', ev.keyCode, ev);
      const char = String.fromCharCode(ev.charCode);
      if (char === '<') {
        dispatch(pinNextMetric(-1));
      } else if (char === '>') {
        dispatch(pinNextMetric(1));
      } else if (char === 'q') {
        dispatch(unpinMetric());
        dispatch(selectMetric(null));
      } else if (char === '/') {
        ev.preventDefault();
        dispatch(focusSearch());
      } else if (char === '?') {
        dispatch(toggleHelp());
      }
    }
  }

  render() {
    const { showingDetails, showingHelp, showingMetricsSelector, showingNetworkSelector,
     showingTerminal } = this.props;
    const isIframe = window !== window.top;

    return (

        {showingDebugToolbar() && <DebugToolbar />}

        {showingHelp && <HelpPanel />}

        {showingDetails && }

        {showingTerminal && <EmbeddedTerminal />}

            {!isIframe && <svg width="100%" height="100%" viewBox="0 0 1089 217">
              <Logo />
            </svg>}

          <Search />
          <Topologies />

        <Nodes />

        <Sidebar>
          <Status />
          {showingMetricsSelector && <MetricSelector />}
          {showingNetworkSelector && <NetworkSelector />}
          <TopologyOptions />
        </Sidebar>

    );
  }
}

function mapStateToProps(state) {
  return {
    activeTopologyOptions: getActiveTopologyOptions(state),
    routeSet: state.get('routeSet'),
    searchFocused: state.get('searchFocused'),
    searchQuery: state.get('searchQuery'),
    showingDetails: state.get('nodeDetails').size > 0,
    showingHelp: state.get('showingHelp'),
    showingMetricsSelector: state.get('availableCanvasMetrics').count() > 0,
    showingNetworkSelector: state.get('availableNetworks').count() > 0,
    showingTerminal: state.get('controlPipes').size > 0,
    urlState: getUrlState(state)
  };
}

export default connect(
  mapStateToProps
)(App);
<|endoftext|>