<commit_before>"use strict";

function AudioPlayer(parent)
{
	Element.call(this, parent);

	//WebAudio context
	this.context = THREE.AudioContext.getContext();

	this.element.style.overflow = "visible";

	//Timer
	this.timer = document.createElement("div");
	this.timer.style.position = "absolute";
	this.timer.style.display = "flex";
	this.timer.style.justifyContent = "center";
	this.timer.style.alignItems = "center";
	this.timer.style.width = "40px";
	this.timer.style.height = "100%";
	this.timer.style.right = "0px";
	this.timer.innerHTML = "00:00";
	this.element.appendChild(this.timer);

	//Button
	this.button = document.createElement("button");
	this.button.style.position = "absolute";
	this.button.style.cursor = "pointer";
	this.button.style.background = "transparent";
	this.button.style.border = "none";
	this.button.style.outline = "none";
	this.element.appendChild(this.button);

	//Icon
	this.icon = document.createElement("img");
	this.icon.style.position = "absolute";
	this.icon.style.left = "15%";
	this.icon.style.top = "15%";
	this.icon.style.width = "70%";
	this.icon.style.height = "70%";
	this.icon.src = Editor.filePath + "icons/misc/play.png";
	this.button.appendChild(this.icon);

	//Track
	this.track = document.createElement("div");
	this.track.style.position = "absolute";
	this.track.style.backgroundColor = Editor.theme.audioTrack;
	this.track.style.cursor = "pointer";
	this.element.appendChild(this.track);

	//Progress
	this.progress = document.createElement("div");
	this.progress.style.pointerEvents = "none";
	this.progress.style.position = "absolute";
	this.progress.style.backgroundColor = Editor.theme.audioProgress;
	this.progress.style.height = "100%";
	this.track.appendChild(this.progress);

	//Scrubber
	this.scrubber = document.createElement("div");
	this.scrubber.style.position = "absolute";
	this.scrubber.style.backgroundColor = Editor.theme.audioScrubber;
	this.scrubber.style.cursor = "pointer";
	this.scrubber.style.width = "6px";
	this.track.appendChild(this.scrubber);

	//Audio source and buffer
	this.buffer = null;
	this.source = null;

	//Playback control
	this.time = 0;
	this.startTime = 0;
	this.playing = false;
	this.loop = false;

	//Drag control
	this.seekStart = 0;
	this.seekTime = 0;
	this.seekProgress = 0;
	this.dragging = false;

	//Self pointer
	var self = this;

	this.button.onclick = function()
	{
		self.toggle();
	};

	this.scrubber.onmousedown = function(event)
	{
		self.dragging = true;
		self.seekStart = event.pageX;
		self.seekTime = self.time;
	};

	this.track.onmousemove = function(event)
	{
		if(self.dragging && self.buffer !== null)
		{
			var progress = event.layerX / this.offsetWidth;
			self.time = progress * self.buffer.duration;

			if(self.playing)
			{
				self.play(self.time);
			}

			progress *= 100;
			self.progress.style.width = progress + "%";
			self.scrubber.style.left = progress + "%";
		}
	};

	this.track.onclick = function(event)
	{
		if(self.dragging && self.buffer !== null)
		{
			var progress = event.layerX / this.offsetWidth;

			console.log(progress);
		}
	};

	//Event manager
	this.manager = new EventManager();
	this.manager.add(window, "mousemove", function(event)
	{
		if(self.dragging && self.buffer !== null)
		{
			self.seekProgress = (event.pageX - self.seekStart) / (self.size.x - self.size.y * 1.1);
			self.seekProgress += self.seekTime / self.buffer.duration;

			if(self.seekProgress < 0)
			{
				self.seekProgress = 0;
			}
			else if(self.seekProgress > 1)
			{
				self.seekProgress = 1;
			}

			self.progress.style.width = (self.seekProgress * 100) + "%";
			self.scrubber.style.left = self.progress.style.width;
		}
	});

	this.manager.add(window, "mouseup", function(event)
	{
		if(self.dragging)
		{
			self.time = self.seekProgress * self.buffer.duration;
			self.dragging = false;

			if(self.playing)
			{
				self.play(self.time);
			}
		}
	});

	this.manager.create();

	//Update elements
	function draw()
	{
		if(self.playing)
		{
			self.time = self.context.currentTime - self.startTime;

			var seconds = Math.round(self.time % 60);
			if(seconds < 10)
			{
				seconds = "0" + seconds;
			}

			var minutes = Math.round(self.time / 60);
			if(minutes < 10)
			{
				minutes = "0" + minutes;
			}

			self.timer.innerHTML = minutes + ":" + seconds;

			if(self.buffer !== null)
			{
				if(self.time >= self.buffer.duration)
				{
					self.stop();
				}

				var progress = (self.time / self.buffer.duration) * 100;

				if(!self.dragging)
				{
					self.progress.style.width = progress + "%";
					self.scrubber.style.left = progress + "%";
				}
			}
		}

		if(self.parent !== null)
		{
			requestAnimationFrame(draw);
		}
	}
	draw();
}

AudioPlayer.prototype = Object.create(Element.prototype);

//Decode audio
AudioPlayer.prototype.setAudioBuffer = function(buffer, onLoad)
{
	var self = this;

	this.context.decodeAudioData(buffer.slice(0), function(buffer)
	{
		self.buffer = buffer;

		if(onLoad !== undefined)
		{
			onLoad(buffer);
		}
	}.bind(this));
};

//Connect audio source
AudioPlayer.prototype.connect = function()
{
	if(this.playing)
	{
		this.pause();
	}

	this.source = this.context.createBufferSource();
	this.source.buffer = this.buffer;
	this.source.connect(this.context.destination);
};

//Disconnect source
AudioPlayer.prototype.disconnect = function()
{
	this.source.disconnect();
};

//Play audio
AudioPlayer.prototype.play = function(time)
{
	this.connect();

	if(time !== undefined)
	{
		this.time = time;
	}

	this.source.loop = this.loop;
	this.startTime = this.context.currentTime - this.time;
	this.source.start(this.context.currentTime, this.time);
	this.playing = true;

	this.icon.src = Editor.filePath + "icons/misc/pause.png";
};

//Pause audio
AudioPlayer.prototype.pause = function()
{
	if(this.playing)
	{
		this.playing = false;
		this.source.stop();
		this.time = this.context.currentTime - this.startTime;

		this.icon.src = Editor.filePath + "icons/misc/play.png";
	}
};

//Stop audio playback
AudioPlayer.prototype.stop = function()
{
	if(this.playing)
	{
		this.source.stop();
		this.time = 0;
		this.playing = false;

		this.icon.src = Editor.filePath + "icons/misc/play.png";
	}
};

//Seek time
AudioPlayer.prototype.seek = function(time)
{
	if(this.playing)
	{
		this.play(time);
	}
	else
	{
		this.time = time;
	}
};

//Toggle play/pause
AudioPlayer.prototype.toggle = function()
{
	if(!this.playing)
	{
		this.play();
	}
	else
	{
		this.pause();
	}
};

//Remove element
AudioPlayer.prototype.destroy = function()
{
	try
	{
		//Remove event listeners
		this.manager.destroy();

		//Stop audio playback
		this.disconnect();
		this.stop();

		//Remove element
		if(this.parent.contains(this.element))
		{
			this.parent.removeChild(this.element);
		}
		this.parent = null;
	}
	catch(e){}
};

//Update division Size
AudioPlayer.prototype.updateInterface = function()
{
	//Visibility
	if(this.visible)
	{
		this.element.style.visibility = "visible";
	}
	else
	{
		this.element.style.visibility = "hidden";
	}

	//Element
	this.element.style.top = this.position.y + "px";
	this.element.style.left = this.position.x + "px";
	this.element.style.width = this.size.x + "px";
	this.element.style.height = this.size.y + "px";

	//Button
	this.button.style.width = this.element.style.height;
	this.button.style.height = this.element.style.height;

	//Track
	this.track.style.top = (this.size.y * 0.4) + "px";
	this.track.style.left = (this.size.y * 1.05) + "px";
	this.track.style.width = (this.size.x - this.size.y * 1.5 - 35) + "px";
	this.track.style.height = (this.size.y * 0.3) + "px";

	//Scrubber
	this.scrubber.style.height = (this.size.y * 0.8) + "px";
	this.scrubber.style.top = (-this.size.y * 0.3) + "px";
};
<commit_msg>Improved audio player seek<commit_after>"use strict";

function AudioPlayer(parent)
{
	Element.call(this, parent);

	this.element.style.overflow = "visible";

	//Self pointer
	var self = this;

	//WebAudio context
	this.context = THREE.AudioContext.getContext();

	//Timer
	this.timer = document.createElement("div");
	this.timer.style.position = "absolute";
	this.timer.style.display = "flex";
	this.timer.style.justifyContent = "center";
	this.timer.style.alignItems = "center";
	this.timer.style.width = "40px";
	this.timer.style.height = "100%";
	this.timer.style.right = "0px";
	this.timer.innerHTML = "00:00";
	this.element.appendChild(this.timer);

	//Button
	this.button = document.createElement("button");
	this.button.style.position = "absolute";
	this.button.style.cursor = "pointer";
	this.button.style.background = "transparent";
	this.button.style.border = "none";
	this.button.style.outline = "none";
	this.element.appendChild(this.button);
	this.button.onclick = function()
	{
		self.toggle();
	};

	//Icon
	this.icon = document.createElement("img");
	this.icon.style.position = "absolute";
	this.icon.style.left = "15%";
	this.icon.style.top = "15%";
	this.icon.style.width = "70%";
	this.icon.style.height = "70%";
	this.icon.src = Editor.filePath + "icons/misc/play.png";
	this.button.appendChild(this.icon);

	//Track
	this.track = document.createElement("div");
	this.track.style.position = "absolute";
	this.track.style.backgroundColor = Editor.theme.audioTrack;
	this.track.style.cursor = "pointer";
	this.element.appendChild(this.track);

	//Progress
	this.progress = document.createElement("div");
	this.progress.style.pointerEvents = "none";
	this.progress.style.position = "absolute";
	this.progress.style.backgroundColor = Editor.theme.audioProgress;
	this.progress.style.height = "100%";
	this.track.appendChild(this.progress);

	//Scrubber
	this.scrubber = document.createElement("div");
	this.scrubber.style.position = "absolute";
	this.scrubber.style.backgroundColor = Editor.theme.audioScrubber;
	this.scrubber.style.cursor = "pointer";
	this.scrubber.style.width = "6px";
	this.track.appendChild(this.scrubber);

	//Audio source and buffer
	this.buffer = null;
	this.source = null;

	//Playback control
	this.time = 0;
	this.startTime = 0;
	this.playing = false;
	this.loop = false;

	//Drag control
	this.seekStart = 0;
	this.seekTime = 0;
	this.seekProgress = 0;
	this.dragging = false;

	this.scrubber.onmousedown = function(event)
	{
		self.seekStart = event.pageX;
		self.seekTime = self.time;
		self.dragging = true;
		self.manager.create();
	};

	//Event manager
	this.manager = new EventManager();
	this.manager.add(window, "mousemove", function(event)
	{
		self.seekProgress = (event.pageX - self.seekStart) / (self.size.x - self.size.y);
		self.seekProgress += self.seekTime / self.buffer.duration;

		if(self.seekProgress < 0)
		{
			self.seekProgress = 0;
		}
		else if(self.seekProgress > 1)
		{
			self.seekProgress = 1;
		}

		self.progress.style.width = (self.seekProgress * 100) + "%";
		self.scrubber.style.left = self.progress.style.width;
	});

	this.manager.add(window, "mouseup", function(event)
	{
		self.dragging = false;
		self.time = self.seekProgress * self.buffer.duration;

		if(self.playing)
		{
			self.play(self.time);
		}

		self.manager.destroy();
	});

	this.track.onmousedown = function(event)
	{
		var progress = event.layerX / this.offsetWidth;

		self.seekProgress = progress;
		self.time = progress * self.buffer.duration;

		self.progress.style.width = (self.seekProgress * 100) + "%";
		self.scrubber.style.left = self.progress.style.width;

		if(self.playing)
		{
			self.play(self.time);
		}
	};

	//Update elements
	function draw()
	{
		if(self.playing)
		{
			self.time = self.context.currentTime - self.startTime;

			var seconds = Math.round(self.time % 60);
			if(seconds < 10)
			{
				seconds = "0" + seconds;
			}

			var minutes = Math.round(self.time / 60);
			if(minutes < 10)
			{
				minutes = "0" + minutes;
			}

			self.timer.innerHTML = minutes + ":" + seconds;

			if(self.buffer !== null)
			{
				if(self.time >= self.buffer.duration)
				{
					self.stop();
				}

				var progress = (self.time / self.buffer.duration) * 100;

				if(!self.dragging)
				{
					self.progress.style.width = progress + "%";
					self.scrubber.style.left = progress + "%";
				}
			}
		}

		if(self.parent !== null)
		{
			requestAnimationFrame(draw);
		}
	}
	draw();
}

AudioPlayer.prototype = Object.create(Element.prototype);

//Decode audio
AudioPlayer.prototype.setAudioBuffer = function(buffer, onLoad)
{
	var self = this;

	this.context.decodeAudioData(buffer.slice(0), function(buffer)
	{
		self.buffer = buffer;

		if(onLoad !== undefined)
		{
			onLoad(buffer);
		}
	}.bind(this));
};

//Connect audio source
AudioPlayer.prototype.connect = function()
{
	if(this.playing)
	{
		this.pause();
	}

	this.source = this.context.createBufferSource();
	this.source.buffer = this.buffer;
	this.source.connect(this.context.destination);
};

//Disconnect source
AudioPlayer.prototype.disconnect = function()
{
	this.source.disconnect();
};

//Play audio
AudioPlayer.prototype.play = function(time)
{
	this.connect();

	if(time !== undefined)
	{
		this.time = time;
	}

	this.source.loop = this.loop;
	this.startTime = this.context.currentTime - this.time;
	this.source.start(this.context.currentTime, this.time);
	this.playing = true;

	this.icon.src = Editor.filePath + "icons/misc/pause.png";
};

//Pause audio
AudioPlayer.prototype.pause = function()
{
	if(this.playing)
	{
		this.playing = false;
		this.source.stop();
		this.time = this.context.currentTime - this.startTime;

		this.icon.src = Editor.filePath + "icons/misc/play.png";
	}
};

//Stop audio playback
AudioPlayer.prototype.stop = function()
{
	if(this.playing)
	{
		this.source.stop();
		this.time = 0;
		this.playing = false;

		this.icon.src = Editor.filePath + "icons/misc/play.png";
	}
};

//Seek time
AudioPlayer.prototype.seek = function(time)
{
	if(this.playing)
	{
		this.play(time);
	}
	else
	{
		this.time = time;
	}
};

//Toggle play/pause
AudioPlayer.prototype.toggle = function()
{
	if(!this.playing)
	{
		this.play();
	}
	else
	{
		this.pause();
	}
};

//Remove element
AudioPlayer.prototype.destroy = function()
{
	//Stop audio playback
	this.disconnect();
	this.stop();

	//Remove element
	if(this.parent !== null && this.parent.contains(this.element))
	{
		this.parent.removeChild(this.element);
		this.parent = null;
	}
};

//Update division Size
AudioPlayer.prototype.updateInterface = function()
{
	//Visibility
	if(this.visible)
	{
		this.element.style.visibility = "visible";
		this.element.style.top = this.position.y + "px";
		this.element.style.left = this.position.x + "px";
		this.element.style.width = this.size.x + "px";
		this.element.style.height = this.size.y + "px";

		//Button
		this.button.style.width = this.element.style.height;
		this.button.style.height = this.element.style.height;

		//Track
		this.track.style.top = (this.size.y * 0.4) + "px";
		this.track.style.left = (this.size.y * 1.05) + "px";
		this.track.style.width = (this.size.x - this.size.y * 1.5 - 35) + "px";
		this.track.style.height = (this.size.y * 0.3) + "px";

		//Scrubber
		this.scrubber.style.height = (this.size.y * 0.8) + "px";
		this.scrubber.style.top = (-this.size.y * 0.3) + "px";
	}
	else
	{
		this.element.style.visibility = "hidden";
	}
};
<|endoftext|>


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

import React, { useCallback, useMemo, useState } from 'react';
import PropTypes from 'prop-types';
import { StyleSheet, Text, View, TouchableOpacity, ToastAndroid } from 'react-native';

import moment from 'moment';
import currency from '../../localization/currency';

import { UIDatabase } from '../../database';
import {
  CASH_TRANSACTION_FIELDS,
  CASH_TRANSACTION_TYPES,
} from '../../utilities/modules/dispensary/constants';

import { ToggleBar } from '../ToggleBar';
import { ModalContainer } from './ModalContainer';
import { BottomModalContainer, BottomTextEditor, BottomCurrencyEditor } from '../bottomModals';
import { GenericChoiceList, AutocompleteSelector } from '../modalChildren';
import { PageButton } from '../PageButton';
import { PencilIcon, ChevronDownIcon } from '../icons';

import globalStyles, { WARM_GREY, SUSSOL_ORANGE, COMPONENT_HEIGHT } from '../../globalStyles';
import { buttonStrings, dispensingStrings, generalStrings } from '../../localization';
import { FlexRow } from '../FlexRow';

export const CashTransactionModal = ({ onConfirm }) => {
  const [name, setName] = useState(null);
  const [isCashIn, setIsCashIn] = useState(true);
  const [amount, setAmount] = useState(null);
  const [paymentType, setPaymentType] = useState(null);
  const [reason, setReason] = useState(null);
  const [description, setDescription] = useState(null);

  const [amountBuffer, setAmountBuffer] = useState(currency(0).format());
  const [descriptionBuffer, setDescriptionBuffer] = useState('');

  const [isNameModalOpen, setIsNameModalOpen] = useState(false);
  const [isAmountModalOpen, setIsAmountModalOpen] = useState(false);
  const [isPaymentTypeModalOpen, setIsPaymentTypeModalOpen] = useState(false);
  const [isReasonModalOpen, setIsReasonModalOpen] = useState(false);
  const [isDescriptionModalOpen, setIsDescriptionModalOpen] = useState(false);

  const balance = React.useMemo(() => {
    const receiptBalance = UIDatabase.objects('Transaction')
      .filtered('type == "receipt" && paymentType.code == $0', paymentType?.code)
      .sum('subtotal');

    const paymentBalance = UIDatabase.objects('Transaction')
      .filtered('type == "payment" && paymentType.code == $0', paymentType?.code)
      .sum('subtotal');

    return currency(receiptBalance - paymentBalance);
  }, [paymentType]);

  const names = useMemo(() => UIDatabase.objects('CashTransactionName'), []);
  const type = useMemo(
    () => (isCashIn ? CASH_TRANSACTION_TYPES.CASH_IN : CASH_TRANSACTION_TYPES.CASH_OUT),
    [isCashIn]
  );
  const paymentTypes = useMemo(() => UIDatabase.objects('PaymentType'), []);
  const reasons = useMemo(() => UIDatabase.objects('CashTransactionReason'), []);

  const isValidTransaction = useMemo(
    () =>
      !!name && !!amount && currency(amount) > currency(0) && (isCashIn || !!reason) && paymentType,
    [name, amount, reason, isCashIn, paymentType]
  );

  const isModalOpen = useMemo(
    () =>
      isNameModalOpen ||
      isAmountModalOpen ||
      isPaymentTypeModalOpen ||
      isReasonModalOpen ||
      isDescriptionModalOpen,
    [
      isNameModalOpen,
      isAmountModalOpen,
      isPaymentTypeModalOpen,
      isReasonModalOpen,
      isDescriptionModalOpen,
    ]
  );

  const onCreate = useCallback(() => {
    if (amount.value > balance.value && !isCashIn) {
      ToastAndroid.show(dispensingStrings.unable_to_create_withdrawl, ToastAndroid.LONG);
    } else {
      onConfirm({ name, type, amount: amount.value, paymentType, reason, description });
    }
  }, [name, type, amount, paymentType, reason, description, balance]);

  const onChangeText = useMemo(() => text => setDescriptionBuffer(text), []);
  const onChangeAmount = useMemo(() => value => setAmountBuffer(value), []);

  const renderNameLeftText = useCallback(
    ({ firstName, lastName, name: fullName, isPatient }) =>
      isPatient ? `${lastName}, ${firstName}` : `${fullName}`,
    []
  );

  const renderNameRightText = useCallback(({ isCustomer, isSupplier, isPatient, dateOfBirth }) => {
    if (isCustomer) {
      if (isPatient) {
        return `${dispensingStrings.patient} \n ${dispensingStrings.date_of_birth}: ${
          dateOfBirth ? moment(dateOfBirth).format('DD/MM/YYYY') : generalStrings.not_available
        }`;
      }
      return dispensingStrings.customer;
    }
    if (isSupplier) {
      return dispensingStrings.supplier;
    }
    return '';
  }, []);

  const resetBottomModal = () => {
    setIsNameModalOpen(false);
    setIsAmountModalOpen(false);
    setIsPaymentTypeModalOpen(false);
    setIsReasonModalOpen(false);
    setIsDescriptionModalOpen(false);
  };

  const onPressName = () => {
    resetBottomModal();
    setIsNameModalOpen(true);
  };

  const onPressAmount = () => {
    resetBottomModal();
    if (amount) {
      setAmountBuffer(amount.format(false));
    }
    setIsAmountModalOpen(true);
  };

  const onPressPaymentType = () => {
    resetBottomModal();
    setIsPaymentTypeModalOpen(true);
  };

  const onPressReason = () => {
    resetBottomModal();
    setIsReasonModalOpen(true);
  };

  const onPressDescription = () => {
    resetBottomModal();
    if (description) {
      setDescriptionBuffer(description);
    }
    setIsDescriptionModalOpen(true);
  };

  const onSubmitName = nameItem => {
    setName(nameItem);
    setIsNameModalOpen(false);
  };

  const onCloseName = () => setIsNameModalOpen(false);

  const onSubmitAmount = () => {
    setAmount(currency(amountBuffer));
    setIsAmountModalOpen(false);
  };

  const onSubmitPaymentType = ({ item: paymentTypeItem }) => {
    setPaymentType(paymentTypeItem);
    setIsPaymentTypeModalOpen(false);
  };

  const onSubmitReason = ({ item: reasonItem }) => {
    setReason(reasonItem);
    setIsReasonModalOpen(false);
  };

  const onSubmitDescription = () => {
    setDescription(descriptionBuffer);
    setIsDescriptionModalOpen(false);
  };

  const nameText = useMemo(() => name?.name ?? dispensingStrings.choose_a_name, [name]);
  const amountText = useMemo(() => amount?.format(false) ?? dispensingStrings.enter_the_amount, [
    amount,
  ]);
  const paymentTypeText = useMemo(
    () => paymentType?.description ?? dispensingStrings.choose_a_payment_type,
    [paymentType]
  );
  const reasonText = useMemo(() => reason?.title ?? dispensingStrings.choose_a_reason, [reason]);
  const descriptionText = useMemo(() => description ?? dispensingStrings.enter_a_description, [
    description,
  ]);
  const toggleTransactionType = () => setIsCashIn(!isCashIn);

  const toggles = useMemo(
    () => [
      { text: dispensingStrings.cash_in, onPress: toggleTransactionType, isOn: isCashIn },
      { text: dispensingStrings.cash_out, onPress: toggleTransactionType, isOn: !isCashIn },
    ],
    [isCashIn]
  );

  const PressReason = useCallback(
    () =>
      isCashIn ? null : (
        <TouchableOpacity style={localStyles.containerStyle} onPress={onPressReason}>
          <View style={localStyles.textContainerStyle}>
            <Text style={localStyles.textStyle}>{reasonText}</Text>
          </View>
          <View style={localStyles.iconContainerStyle}>
            <ChevronDownIcon />
          </View>
        </TouchableOpacity>
      ),
    [isCashIn, reason]
  );

  const ConfirmButton = useCallback(
    () =>
      isModalOpen || (
        <PageButton
          text={buttonStrings.confirm}
          onPress={onCreate}
          isDisabled={!isValidTransaction}
          disabledColor={WARM_GREY}
          style={localStyles.okButton}
          textStyle={localStyles.pageButtonTextStyle}
        />
      ),
    [isModalOpen]
  );

  return (
    <>
      <FlexRow justifyContent="center">
        <View style={{ maxWidth: 300 }}>
          <ToggleBar style={localStyles.toggleBarStyle} toggles={toggles} />
        </View>
      </FlexRow>
      <TouchableOpacity style={localStyles.containerStyle} onPress={onPressName}>
        <View style={localStyles.textContainerStyle}>
          <Text style={localStyles.textStyle}>{nameText}</Text>
        </View>
        <View style={localStyles.iconContainerStyle}>
          <ChevronDownIcon />
        </View>
      </TouchableOpacity>
      <TouchableOpacity style={localStyles.containerStyle} onPress={onPressAmount}>
        <View style={localStyles.textContainerStyle}>
          <Text style={localStyles.textStyle}>{amountText}</Text>
        </View>
        <View style={localStyles.iconContainerStyle}>
          <PencilIcon />
        </View>
      </TouchableOpacity>
      <TouchableOpacity style={localStyles.containerStyle} onPress={onPressPaymentType}>
        <View style={localStyles.textContainerStyle}>
          <Text style={localStyles.textStyle}>{paymentTypeText}</Text>
        </View>
        <View style={localStyles.iconContainerStyle}>
          <ChevronDownIcon />
        </View>
      </TouchableOpacity>

      <PressReason />
      <TouchableOpacity style={localStyles.containerStyle} onPress={onPressDescription}>
        <View style={localStyles.textContainerStyle}>
          <Text style={localStyles.textStyle}>{descriptionText}</Text>
        </View>
        <View style={localStyles.iconContainerStyle}>
          <PencilIcon />
        </View>
      </TouchableOpacity>
      <ConfirmButton />
      <BottomCurrencyEditor
        isOpen={isAmountModalOpen}
        buttonText={buttonStrings.confirm}
        value={amountBuffer}
        placeholder={dispensingStrings.enter_the_amount}
        onChangeText={onChangeAmount}
        onConfirm={onSubmitAmount}
      />
      <BottomModalContainer
        isOpen={isPaymentTypeModalOpen}
        modalStyle={localStyles.bottomModalContainerStyle}
      >
        <GenericChoiceList
          data={paymentTypes}
          keyToDisplay={CASH_TRANSACTION_FIELDS.PAYMENT_TYPE}
          onPress={onSubmitPaymentType}
          highlightValue={paymentType?.title}
        />
      </BottomModalContainer>
      <BottomModalContainer
        isOpen={isReasonModalOpen}
        modalStyle={localStyles.bottomModalContainerStyle}
      >
        <GenericChoiceList
          data={reasons}
          keyToDisplay={CASH_TRANSACTION_FIELDS.REASON}
          onPress={onSubmitReason}
          highlightValue={reason?.title}
        />
      </BottomModalContainer>
      <BottomTextEditor
        isOpen={isDescriptionModalOpen}
        buttonText={buttonStrings.confirm}
        value={descriptionBuffer}
        placeholder={dispensingStrings.enter_a_description}
        onChangeText={onChangeText}
        onConfirm={onSubmitDescription}
      />
      <ModalContainer
        title={dispensingStrings.choose_a_name}
        isVisible={isNameModalOpen}
        onClose={onCloseName}
      >
        <AutocompleteSelector
          options={names}
          queryString="name CONTAINS[c] $0"
          sortKeyString="name"
          renderLeftText={renderNameLeftText}
          renderRightText={renderNameRightText}
          onSelect={onSubmitName}
        />
      </ModalContainer>
    </>
  );
};

CashTransactionModal.defaultProps = {};

CashTransactionModal.propTypes = {
  onConfirm: PropTypes.func.isRequired,
};

const localStyles = StyleSheet.create({
  containerStyle: {
    flexDirection: 'row',
    width: '100%',
    justifyContent: 'center',
    alignItems: 'center',
    minHeight: '10%',
  },
  bottomModalContainerStyle: {
    height: 120,
  },
  textContainerStyle: { width: '30%', justifyContent: 'center' },
  iconContainerStyle: { width: '5%', justifyContent: 'flex-end', alignItems: 'flex-end' },
  toggleBarStyle: { marginBottom: COMPONENT_HEIGHT },
  textStyle: { color: 'white' },
  okButton: {
    ...globalStyles.button,
    backgroundColor: SUSSOL_ORANGE,
    alignSelf: 'center',
    marginTop: 60,
  },
  pageButtonTextStyle: {
    ...globalStyles.buttonText,
    color: 'white',
    fontSize: 14,
  },
});
<commit_msg>Fix cash transaction toggle margin<commit_after>/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import React, { useCallback, useMemo, useState } from 'react';
import PropTypes from 'prop-types';
import { StyleSheet, Text, View, TouchableOpacity, ToastAndroid } from 'react-native';

import moment from 'moment';
import currency from '../../localization/currency';

import { UIDatabase } from '../../database';
import {
  CASH_TRANSACTION_FIELDS,
  CASH_TRANSACTION_TYPES,
} from '../../utilities/modules/dispensary/constants';

import { ToggleBar } from '../ToggleBar';
import { ModalContainer } from './ModalContainer';
import { BottomModalContainer, BottomTextEditor, BottomCurrencyEditor } from '../bottomModals';
import { GenericChoiceList, AutocompleteSelector } from '../modalChildren';
import { PageButton } from '../PageButton';
import { PencilIcon, ChevronDownIcon } from '../icons';

import globalStyles, { WARM_GREY, SUSSOL_ORANGE, COMPONENT_HEIGHT } from '../../globalStyles';
import { buttonStrings, dispensingStrings, generalStrings } from '../../localization';
import { FlexRow } from '../FlexRow';

export const CashTransactionModal = ({ onConfirm }) => {
  const [name, setName] = useState(null);
  const [isCashIn, setIsCashIn] = useState(true);
  const [amount, setAmount] = useState(null);
  const [paymentType, setPaymentType] = useState(null);
  const [reason, setReason] = useState(null);
  const [description, setDescription] = useState(null);

  const [amountBuffer, setAmountBuffer] = useState(currency(0).format());
  const [descriptionBuffer, setDescriptionBuffer] = useState('');

  const [isNameModalOpen, setIsNameModalOpen] = useState(false);
  const [isAmountModalOpen, setIsAmountModalOpen] = useState(false);
  const [isPaymentTypeModalOpen, setIsPaymentTypeModalOpen] = useState(false);
  const [isReasonModalOpen, setIsReasonModalOpen] = useState(false);
  const [isDescriptionModalOpen, setIsDescriptionModalOpen] = useState(false);

  const balance = React.useMemo(() => {
    const receiptBalance = UIDatabase.objects('Transaction')
      .filtered('type == "receipt" && paymentType.code == $0', paymentType?.code)
      .sum('subtotal');

    const paymentBalance = UIDatabase.objects('Transaction')
      .filtered('type == "payment" && paymentType.code == $0', paymentType?.code)
      .sum('subtotal');

    return currency(receiptBalance - paymentBalance);
  }, [paymentType]);

  const names = useMemo(() => UIDatabase.objects('CashTransactionName'), []);
  const type = useMemo(
    () => (isCashIn ? CASH_TRANSACTION_TYPES.CASH_IN : CASH_TRANSACTION_TYPES.CASH_OUT),
    [isCashIn]
  );
  const paymentTypes = useMemo(() => UIDatabase.objects('PaymentType'), []);
  const reasons = useMemo(() => UIDatabase.objects('CashTransactionReason'), []);

  const isValidTransaction = useMemo(
    () =>
      !!name && !!amount && currency(amount) > currency(0) && (isCashIn || !!reason) && paymentType,
    [name, amount, reason, isCashIn, paymentType]
  );

  const isModalOpen = useMemo(
    () =>
      isNameModalOpen ||
      isAmountModalOpen ||
      isPaymentTypeModalOpen ||
      isReasonModalOpen ||
      isDescriptionModalOpen,
    [
      isNameModalOpen,
      isAmountModalOpen,
      isPaymentTypeModalOpen,
      isReasonModalOpen,
      isDescriptionModalOpen,
    ]
  );

  const onCreate = useCallback(() => {
    if (amount.value > balance.value && !isCashIn) {
      ToastAndroid.show(dispensingStrings.unable_to_create_withdrawl, ToastAndroid.LONG);
    } else {
      onConfirm({ name, type, amount: amount.value, paymentType, reason, description });
    }
  }, [name, type, amount, paymentType, reason, description, balance]);

  const onChangeText = useMemo(() => text => setDescriptionBuffer(text), []);
  const onChangeAmount = useMemo(() => value => setAmountBuffer(value), []);

  const renderNameLeftText = useCallback(
    ({ firstName, lastName, name: fullName, isPatient }) =>
      isPatient ? `${lastName}, ${firstName}` : `${fullName}`,
    []
  );

  const renderNameRightText = useCallback(({ isCustomer, isSupplier, isPatient, dateOfBirth }) => {
    if (isCustomer) {
      if (isPatient) {
        return `${dispensingStrings.patient} \n ${dispensingStrings.date_of_birth}: ${
          dateOfBirth ? moment(dateOfBirth).format('DD/MM/YYYY') : generalStrings.not_available
        }`;
      }
      return dispensingStrings.customer;
    }
    if (isSupplier) {
      return dispensingStrings.supplier;
    }
    return '';
  }, []);

  const resetBottomModal = () => {
    setIsNameModalOpen(false);
    setIsAmountModalOpen(false);
    setIsPaymentTypeModalOpen(false);
    setIsReasonModalOpen(false);
    setIsDescriptionModalOpen(false);
  };

  const onPressName = () => {
    resetBottomModal();
    setIsNameModalOpen(true);
  };

  const onPressAmount = () => {
    resetBottomModal();
    if (amount) {
      setAmountBuffer(amount.format(false));
    }
    setIsAmountModalOpen(true);
  };

  const onPressPaymentType = () => {
    resetBottomModal();
    setIsPaymentTypeModalOpen(true);
  };

  const onPressReason = () => {
    resetBottomModal();
    setIsReasonModalOpen(true);
  };

  const onPressDescription = () => {
    resetBottomModal();
    if (description) {
      setDescriptionBuffer(description);
    }
    setIsDescriptionModalOpen(true);
  };

  const onSubmitName = nameItem => {
    setName(nameItem);
    setIsNameModalOpen(false);
  };

  const onCloseName = () => setIsNameModalOpen(false);

  const onSubmitAmount = () => {
    setAmount(currency(amountBuffer));
    setIsAmountModalOpen(false);
  };

  const onSubmitPaymentType = ({ item: paymentTypeItem }) => {
    setPaymentType(paymentTypeItem);
    setIsPaymentTypeModalOpen(false);
  };

  const onSubmitReason = ({ item: reasonItem }) => {
    setReason(reasonItem);
    setIsReasonModalOpen(false);
  };

  const onSubmitDescription = () => {
    setDescription(descriptionBuffer);
    setIsDescriptionModalOpen(false);
  };

  const nameText = useMemo(() => name?.name ?? dispensingStrings.choose_a_name, [name]);
  const amountText = useMemo(() => amount?.format(false) ?? dispensingStrings.enter_the_amount, [
    amount,
  ]);
  const paymentTypeText = useMemo(
    () => paymentType?.description ?? dispensingStrings.choose_a_payment_type,
    [paymentType]
  );
  const reasonText = useMemo(() => reason?.title ?? dispensingStrings.choose_a_reason, [reason]);
  const descriptionText = useMemo(() => description ?? dispensingStrings.enter_a_description, [
    description,
  ]);
  const toggleTransactionType = () => setIsCashIn(!isCashIn);

  const toggles = useMemo(
    () => [
      { text: dispensingStrings.cash_in, onPress: toggleTransactionType, isOn: isCashIn },
      { text: dispensingStrings.cash_out, onPress: toggleTransactionType, isOn: !isCashIn },
    ],
    [isCashIn]
  );

  const PressReason = useCallback(
    () =>
      isCashIn ? null : (
        <TouchableOpacity style={localStyles.containerStyle} onPress={onPressReason}>
          <View style={localStyles.textContainerStyle}>
            <Text style={localStyles.textStyle}>{reasonText}</Text>
          </View>
          <View style={localStyles.iconContainerStyle}>
            <ChevronDownIcon />
          </View>
        </TouchableOpacity>
      ),
    [isCashIn, reason]
  );

  const ConfirmButton = useCallback(
    () =>
      isModalOpen || (
        <PageButton
          text={buttonStrings.confirm}
          onPress={onCreate}
          isDisabled={!isValidTransaction}
          disabledColor={WARM_GREY}
          style={localStyles.okButton}
          textStyle={localStyles.pageButtonTextStyle}
        />
      ),
    [isModalOpen]
  );

  return (
    <>
      <FlexRow justifyContent="center">
        <View style={localStyles.toggleBarContainerStyle}>
          <ToggleBar style={localStyles.toggleBarStyle} toggles={toggles} />
        </View>
      </FlexRow>
      <TouchableOpacity style={localStyles.containerStyle} onPress={onPressName}>
        <View style={localStyles.textContainerStyle}>
          <Text style={localStyles.textStyle}>{nameText}</Text>
        </View>
        <View style={localStyles.iconContainerStyle}>
          <ChevronDownIcon />
        </View>
      </TouchableOpacity>
      <TouchableOpacity style={localStyles.containerStyle} onPress={onPressAmount}>
        <View style={localStyles.textContainerStyle}>
          <Text style={localStyles.textStyle}>{amountText}</Text>
        </View>
        <View style={localStyles.iconContainerStyle}>
          <PencilIcon />
        </View>
      </TouchableOpacity>
      <TouchableOpacity style={localStyles.containerStyle} onPress={onPressPaymentType}>
        <View style={localStyles.textContainerStyle}>
          <Text style={localStyles.textStyle}>{paymentTypeText}</Text>
        </View>
        <View style={localStyles.iconContainerStyle}>
          <ChevronDownIcon />
        </View>
      </TouchableOpacity>
      <PressReason />
      <TouchableOpacity style={localStyles.containerStyle} onPress={onPressDescription}>
        <View style={localStyles.textContainerStyle}>
          <Text style={localStyles.textStyle}>{descriptionText}</Text>
        </View>
        <View style={localStyles.iconContainerStyle}>
          <PencilIcon />
        </View>
      </TouchableOpacity>
      <ConfirmButton />
      <BottomCurrencyEditor
        isOpen={isAmountModalOpen}
        buttonText={buttonStrings.confirm}
        value={amountBuffer}
        placeholder={dispensingStrings.enter_the_amount}
        onChangeText={onChangeAmount}
        onConfirm={onSubmitAmount}
      />
      <BottomModalContainer
        isOpen={isPaymentTypeModalOpen}
        modalStyle={localStyles.bottomModalContainerStyle}
      >
        <GenericChoiceList
          data={paymentTypes}
          keyToDisplay={CASH_TRANSACTION_FIELDS.PAYMENT_TYPE}
          onPress={onSubmitPaymentType}
          highlightValue={paymentType?.title}
        />
      </BottomModalContainer>
      <BottomModalContainer
        isOpen={isReasonModalOpen}
        modalStyle={localStyles.bottomModalContainerStyle}
      >
        <GenericChoiceList
          data={reasons}
          keyToDisplay={CASH_TRANSACTION_FIELDS.REASON}
          onPress={onSubmitReason}
          highlightValue={reason?.title}
        />
      </BottomModalContainer>
      <BottomTextEditor
        isOpen={isDescriptionModalOpen}
        buttonText={buttonStrings.confirm}
        value={descriptionBuffer}
        placeholder={dispensingStrings.enter_a_description}
        onChangeText={onChangeText}
        onConfirm={onSubmitDescription}
      />
      <ModalContainer
        title={dispensingStrings.choose_a_name}
        isVisible={isNameModalOpen}
        onClose={onCloseName}
      >
        <AutocompleteSelector
          options={names}
          queryString="name CONTAINS[c] $0"
          sortKeyString="name"
          renderLeftText={renderNameLeftText}
          renderRightText={renderNameRightText}
          onSelect={onSubmitName}
        />
      </ModalContainer>
    </>
  );
};

CashTransactionModal.defaultProps = {};

CashTransactionModal.propTypes = {
  onConfirm: PropTypes.func.isRequired,
};

const localStyles = StyleSheet.create({
  containerStyle: {
    flexDirection: 'row',
    width: '100%',
    justifyContent: 'center',
    alignItems: 'center',
    minHeight: '10%',
  },
  toggleBarContainerStyle: {
    marginTop: 50,
    maxWidth: 300,
  },
  bottomModalContainerStyle: {
    height: 120,
  },
  textContainerStyle: { width: '30%', justifyContent: 'center' },
  iconContainerStyle: { width: '5%', justifyContent: 'flex-end', alignItems: 'flex-end' },
  toggleBarStyle: { marginBottom: COMPONENT_HEIGHT },
  textStyle: { color: 'white' },
  okButton: {
    ...globalStyles.button,
    backgroundColor: SUSSOL_ORANGE,
    alignSelf: 'center',
    marginTop: 60,
  },
  pageButtonTextStyle: {
    ...globalStyles.buttonText,
    color: 'white',
    fontSize: 14,
  },
});
<|endoftext|>


<commit_before>import { spy } from 'sinon'
import debounceMiddleware from '../src'
import test from 'ava'

const config = {
  simple: 100,
  nowait: {},
  maxwait: { wait: 100, maxWait: 150 },
}
const nextHandler = debounceMiddleware(config)()

test('returns a function to handle next', t => {
  t.is(typeof nextHandler, 'function')
  t.is(nextHandler.length, 1)
})

test('handle next returns function to handle action', t => {
  const actionHandler = nextHandler(spy())

  t.is(typeof actionHandler, 'function')
  t.is(actionHandler.length, 1)
})

test('calls next when not flux standard action', t => {
  const next = spy()
  const actionHandler = nextHandler(next)
  const action = { id: 1 }

  actionHandler(action)

  t.truthy(next.called)
  t.truthy(next.calledWith({ id: 1 }))
})

test.cb('calls debounce when config is passed a number', t => {
  const next = spy()
  const actionHandler = nextHandler(next)
  const action = { type: 'TEST', meta: { debounce: 'simple' } }

  actionHandler(action)

  t.falsy(next.called)

  setTimeout(() => {
    t.truthy(next.called)
    t.truthy(next.calledWith(action))
    t.end()
  }, 100)
})

test.cb('only calls debounced function once', t => {
  const next = spy()
  const actionHandler = nextHandler(next)
  const action = { type: 'TEST', meta: { debounce: 'simple' } }

  actionHandler(action)
  actionHandler(action)
  actionHandler(action)

  setTimeout(() => {
    t.is(next.callCount, 1)
    t.end()
  }, 100)
})

test.cb('supports an object passed as config to debounce', t => {
  const next = spy()
  const actionHandler = nextHandler(next)
  const action = { type: 'TEST', meta: { debounce: 'nowait' } }

  actionHandler(action)

  setTimeout(() => {
    t.truthy(next.called)
    t.end()
  }, 100)
})

test.cb('supports other lodash.debounce options', t => {
  const next = spy()
  const actionHandler = nextHandler(next)
  const action = { type: 'TEST', meta: { debounce: 'maxwait' } }

  actionHandler(action)

  setTimeout(() => {
    actionHandler(action)
  }, 75)

  setTimeout(() => {
    t.falsy(next.called)
  }, 100)

  setTimeout(() => {
    t.truthy(next.called)
    t.end()
  }, 155)
})

test('skips debounce if not passed matching key', t => {
  const next = spy()
  const actionHandler = nextHandler(next)
  const action = { type: 'TEST', meta: { debounce: 'nomatch' } }

  actionHandler(action)
  t.truthy(next.called)
})
<commit_msg>Attempt to fix build on Travis<commit_after>import { spy } from 'sinon'
import debounceMiddleware from '../src'
import test from 'ava'

const config = {
  simple: 100,
  nowait: {},
  maxwait: { wait: 100, maxWait: 150 },
}
const nextHandler = debounceMiddleware(config)()

test('returns a function to handle next', t => {
  t.is(typeof nextHandler, 'function')
  t.is(nextHandler.length, 1)
})

test('handle next returns function to handle action', t => {
  const actionHandler = nextHandler(spy())

  t.is(typeof actionHandler, 'function')
  t.is(actionHandler.length, 1)
})

test('calls next when not flux standard action', t => {
  const next = spy()
  const actionHandler = nextHandler(next)
  const action = { id: 1 }

  actionHandler(action)

  t.truthy(next.called)
  t.truthy(next.calledWith({ id: 1 }))
})

test.cb('calls debounce when config is passed a number', t => {
  const next = spy()
  const actionHandler = nextHandler(next)
  const action = { type: 'TEST', meta: { debounce: 'simple' } }

  actionHandler(action)

  t.falsy(next.called)

  setTimeout(() => {
    t.truthy(next.called)
    t.truthy(next.calledWith(action))
    t.end()
  }, 105)
})

test.cb('only calls debounced function once', t => {
  const next = spy()
  const actionHandler = nextHandler(next)
  const action = { type: 'TEST', meta: { debounce: 'simple' } }

  actionHandler(action)
  actionHandler(action)
  actionHandler(action)

  setTimeout(() => {
    t.is(next.callCount, 1)
    t.end()
  }, 105)
})

test.cb('supports an object passed as config to debounce', t => {
  const next = spy()
  const actionHandler = nextHandler(next)
  const action = { type: 'TEST', meta: { debounce: 'nowait' } }

  actionHandler(action)

  setTimeout(() => {
    t.truthy(next.called)
    t.end()
  }, 100)
})

test.cb('supports other lodash.debounce options', t => {
  const next = spy()
  const actionHandler = nextHandler(next)
  const action = { type: 'TEST', meta: { debounce: 'maxwait' } }

  actionHandler(action)

  setTimeout(() => {
    actionHandler(action)
  }, 75)

  setTimeout(() => {
    t.falsy(next.called)
  }, 100)

  setTimeout(() => {
    t.truthy(next.called)
    t.end()
  }, 155)
})

test('skips debounce if not passed matching key', t => {
  const next = spy()
  const actionHandler = nextHandler(next)
  const action = { type: 'TEST', meta: { debounce: 'nomatch' } }

  actionHandler(action)
  t.truthy(next.called)
})
<|endoftext|>


<commit_before>'use strict';

// Require Modules
var chai = require('chai'),
    list = require('../../lib/actions/list');

// Chai Settings
chai.config.includeStack = true; // turn on stack trace
chai.config.truncateThreshold = 0; // disable truncating

// Action List Tests
describe('actions/.list', function () {

  describe('constructor()', function () {

    var action = list({});

    it('should return processed DOM tree', function () {
      action.should.be.a('object');
    });

  });

  describe('#getProperties()', function () {

    var props = list.getProperties();

    it('should return props object', function () {
      props.should.be.a('object');
    });

    it('props required to contain "command" property', function () {
      props.should.have.all.keys(['command']);
      props.command.should.be.a('string');
    });

  });
});
<commit_msg>fixes typo in test definition<commit_after>'use strict';

// Require Modules
var chai = require('chai'),
    list = require('../../lib/actions/list');

// Chai Settings
chai.config.includeStack = true; // turn on stack trace
chai.config.truncateThreshold = 0; // disable truncating

// Action List Tests
describe('actions/list', function () {

  describe('constructor()', function () {

    var action = list({});

    it('should return processed DOM tree', function () {
      action.should.be.a('object');
    });

  });

  describe('#getProperties()', function () {

    var props = list.getProperties();

    it('should return props object', function () {
      props.should.be.a('object');
    });

    it('props required to contain "command" property', function () {
      props.should.have.all.keys(['command']);
      props.command.should.be.a('string');
    });

  });
});
<|endoftext|>


<commit_before>/*
 * Paper.js
 *
 * This file is part of Paper.js, a JavaScript Vector Graphics Library,
 * based on Scriptographer.org and designed to be largely API compatible.
 * http://paperjs.org/
 * http://scriptographer.org/
 *
 * Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
 * http://lehni.org/ & http://jonathanpuckey.com/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * All rights reserved.
 */

/**
 * @name PaperScope
 *
 * @class The {@code PaperScope} class represents the scope associated with a
 * Paper context. When working with PaperScript, these scopes are automatically
 * created and their fields and methods become part of the global scope. When
 * working with normal JavaScript files, {@code PaperScope} objects need to be
 * manually created and handled.
 * The global {@link _global_#paper} object is simply a reference to the
 * currently active {@code PaperScope}.
 */
var PaperScope = this.PaperScope = Base.extend(/** @lends PaperScope# */{
	initialize: function(id) {
		this.project = null;
		this.projects = [];
		this.view = null;
		this.views = [];
		this.tool = null;
		this.tools = [];
		this.id = id;
		PaperScope._scopes[id] = this;
	},

	/**
	 * The version of Paper.js, as a float number.
	 *
	 * @type Number
	 */
	version: /*#=*/ options.version,

	/**
	 * The currently active project.
	 * @name PaperScope#project
	 * @type Project
	 */

	/**
	 * The list of all open projects within the current Paper.js context.
	 * @name PaperScope#projects
	 * @type Project[]
	 */

	/**
	 * The active view of the active project.
	 * @name PaperScope#view
	 * @type View
	 */

	/**
	 * The list of view of the active project.
	 * @name PaperScope#views
	 * @type View[]
	 */

	/**
	 * The reference to the active tool.
	 * @name PaperScope#tool
	 * @type Tool
	 */

	/**
	 * The list of available tools.
	 * @name PaperScope#tools
	 * @type Tool[]
	 */

	evaluate: function(code) {
		var res = PaperScript.evaluate(code, this);
		View.updateFocus();
		return res;
	},

	/**
	 * Sets up the scope for a standard project, by creating an empty
	 * {@link Project} object for us, along with a {@link View} for the passed
	 * canvas, both linked to this scope.
	 */
	setup: function(canvas) {
		// We need to set the global paper reference to this scope,
		// since that will be used in the Project constructor to set
		// internal references.
		paper = this;
		new Project();
		if (canvas) {
			// Activate the newly created view straight away
			new View(canvas).activate();
		}
	},

	/**
	 * Injects the paper scope into any other given scope. Can be used for
	 * examle to inject the currently active PaperScope into the window's global
	 * scope, to emulate PaperScript-style globally accessible Paper classes and
	 * objects:
	 *
	 * @example
	 * paper.install(window);
	 */
	install: function(scope) {
		// Use scope as side-car (= 'this' inside iterator), and have it
		// returned at the end.
		return Base.each(this, function(value, key) {
			this[key] = value;
		}, scope);
	},

	clear: function() {
		// Remove all projects, views and tools.
		for (var i = this.projects.length - 1; i >= 0; i--)
			this.projects[i].remove();
		// This also removes the installed event handlers.
		for (var i = this.views.length - 1; i >= 0; i--)
			this.views[i].remove();
		for (var i = this.tools.length - 1; i >= 0; i--)
			this.tools[i].remove();
	},

	remove: function() {
		this.clear();
		delete PaperScope._scopes[this.id];
	},

	_needsRedraw: function() {
		// Make sure we're not looping through the view list each time...
		if (!this._redrawNotified) {
			for (var i = this.views.length - 1; i >= 0; i--)
				this.views[i]._redrawNeeded = true;
			this._redrawNotified = true;
		}
	},

	statics: /** @lends PaperScope */{
		_scopes: {},

		/**
		 * Retrieves a PaperScope object with the given id or associated with
		 * the passed canvas element.
		 *
		 * @param id
		 */
		get: function(id) {
			// If a script tag is passed, get the id from it.
			if (typeof id === 'object')
				id = id.getAttribute('id');
			return this._scopes[id] || null;
		},

		/**
		 * Iterates over all active scopes and calls the passed iterator
		 * function for each of them.
		 *
		 * @param iter the iterator function.
		 */
		each: function(iter) {
			Base.each(this._scopes, iter);
		}
	}
});
<commit_msg>Fix link to global paper variable.<commit_after>/*
 * Paper.js
 *
 * This file is part of Paper.js, a JavaScript Vector Graphics Library,
 * based on Scriptographer.org and designed to be largely API compatible.
 * http://paperjs.org/
 * http://scriptographer.org/
 *
 * Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
 * http://lehni.org/ & http://jonathanpuckey.com/
 *
 * Distributed under the MIT license. See LICENSE file for details.
 *
 * All rights reserved.
 */

/**
 * @name PaperScope
 *
 * @class The {@code PaperScope} class represents the scope associated with a
 * Paper context. When working with PaperScript, these scopes are automatically
 * created and their fields and methods become part of the global scope. When
 * working with normal JavaScript files, {@code PaperScope} objects need to be
 * manually created and handled.
 * The global {@link paper} object is simply a reference to the
 * currently active {@code PaperScope}.
 */
var PaperScope = this.PaperScope = Base.extend(/** @lends PaperScope# */{
	initialize: function(id) {
		this.project = null;
		this.projects = [];
		this.view = null;
		this.views = [];
		this.tool = null;
		this.tools = [];
		this.id = id;
		PaperScope._scopes[id] = this;
	},

	/**
	 * The version of Paper.js, as a float number.
	 *
	 * @type Number
	 */
	version: /*#=*/ options.version,

	/**
	 * The currently active project.
	 * @name PaperScope#project
	 * @type Project
	 */

	/**
	 * The list of all open projects within the current Paper.js context.
	 * @name PaperScope#projects
	 * @type Project[]
	 */

	/**
	 * The active view of the active project.
	 * @name PaperScope#view
	 * @type View
	 */

	/**
	 * The list of view of the active project.
	 * @name PaperScope#views
	 * @type View[]
	 */

	/**
	 * The reference to the active tool.
	 * @name PaperScope#tool
	 * @type Tool
	 */

	/**
	 * The list of available tools.
	 * @name PaperScope#tools
	 * @type Tool[]
	 */

	evaluate: function(code) {
		var res = PaperScript.evaluate(code, this);
		View.updateFocus();
		return res;
	},

	/**
	 * Sets up the scope for a standard project, by creating an empty
	 * {@link Project} object for us, along with a {@link View} for the passed
	 * canvas, both linked to this scope.
	 */
	setup: function(canvas) {
		// We need to set the global paper reference to this scope,
		// since that will be used in the Project constructor to set
		// internal references.
		paper = this;
		new Project();
		if (canvas) {
			// Activate the newly created view straight away
			new View(canvas).activate();
		}
	},

	/**
	 * Injects the paper scope into any other given scope. Can be used for
	 * examle to inject the currently active PaperScope into the window's global
	 * scope, to emulate PaperScript-style globally accessible Paper classes and
	 * objects:
	 *
	 * @example
	 * paper.install(window);
	 */
	install: function(scope) {
		// Use scope as side-car (= 'this' inside iterator), and have it
		// returned at the end.
		return Base.each(this, function(value, key) {
			this[key] = value;
		}, scope);
	},

	clear: function() {
		// Remove all projects, views and tools.
		for (var i = this.projects.length - 1; i >= 0; i--)
			this.projects[i].remove();
		// This also removes the installed event handlers.
		for (var i = this.views.length - 1; i >= 0; i--)
			this.views[i].remove();
		for (var i = this.tools.length - 1; i >= 0; i--)
			this.tools[i].remove();
	},

	remove: function() {
		this.clear();
		delete PaperScope._scopes[this.id];
	},

	_needsRedraw: function() {
		// Make sure we're not looping through the view list each time...
		if (!this._redrawNotified) {
			for (var i = this.views.length - 1; i >= 0; i--)
				this.views[i]._redrawNeeded = true;
			this._redrawNotified = true;
		}
	},

	statics: /** @lends PaperScope */{
		_scopes: {},

		/**
		 * Retrieves a PaperScope object with the given id or associated with
		 * the passed canvas element.
		 *
		 * @param id
		 */
		get: function(id) {
			// If a script tag is passed, get the id from it.
			if (typeof id === 'object')
				id = id.getAttribute('id');
			return this._scopes[id] || null;
		},

		/**
		 * Iterates over all active scopes and calls the passed iterator
		 * function for each of them.
		 *
		 * @param iter the iterator function.
		 */
		each: function(iter) {
			Base.each(this._scopes, iter);
		}
	}
});
<|endoftext|>


<commit_before>/*
 * This exposes data injected during the RTD build into the template. It's
 * provided via the global READTHEDOCS_DATA variable and is exposed here as a
 * module for cleaner usage.
 */

var constants = require('./constants');

var configMethods = {
    is_rtd_like_theme: function () {
        // Returns true for the Read the Docs theme on both sphinx and mkdocs
        if ($('div.rst-other-versions').length === 1) {
            // Crappy heuristic, but people change the theme name
            // So we have to do some duck typing.
            return true;
        }
        return this.theme === constants.THEME_RTD || this.theme === constants.THEME_MKDOCS_RTD;
    },

    is_alabaster_like_theme: function () {
        // Returns true for Alabaster-like themes (eg. flask, celery)
        return constants.ALABASTER_LIKE_THEMES.indexOf(this.get_theme_name()) > -1;
    },

    theme_supports_paid_promo: function () {
        return this.is_rtd_like_theme() || this.is_alabaster_like_theme();
    },

    is_sphinx_builder: function () {
        return (!('builder' in this) || this.builder !== 'mkdocs');
    },

    is_mkdocs_builder: function () {
        return ('builder' in this && this.builder === 'mkdocs');
    },

    get_theme_name: function () {
        return this.theme;
    },

    show_promo: function () {
        return (
            this.api_host === 'https://readthedocs.org' ||
            this.api_host === 'http://127.0.0.1:8000'
        );
    }
};

/*
 * Access READTHEDOCS_DATA on call, not on module load. The reason is that the
 * READTHEDOCS_DATA might not be available during script load time.
 */
function get() {
    // Make `methods` the prototype.
    var config = Object.create(configMethods);

    var defaults = {
        api_host: 'https://readthedocs.org',
        ad_free: false,
    };

    $.extend(config, defaults, window.READTHEDOCS_DATA);

    return config;
}

module.exports = {
    get: get
};
<commit_msg>All themes support paid promos<commit_after>/*
 * This exposes data injected during the RTD build into the template. It's
 * provided via the global READTHEDOCS_DATA variable and is exposed here as a
 * module for cleaner usage.
 */

var constants = require('./constants');

var configMethods = {
    is_rtd_like_theme: function () {
        // Returns true for the Read the Docs theme on both sphinx and mkdocs
        if ($('div.rst-other-versions').length === 1) {
            // Crappy heuristic, but people change the theme name
            // So we have to do some duck typing.
            return true;
        }
        return this.theme === constants.THEME_RTD || this.theme === constants.THEME_MKDOCS_RTD;
    },

    is_alabaster_like_theme: function () {
        // Returns true for Alabaster-like themes (eg. flask, celery)
        return constants.ALABASTER_LIKE_THEMES.indexOf(this.get_theme_name()) > -1;
    },

    is_sphinx_builder: function () {
        return (!('builder' in this) || this.builder !== 'mkdocs');
    },

    is_mkdocs_builder: function () {
        return ('builder' in this && this.builder === 'mkdocs');
    },

    get_theme_name: function () {
        return this.theme;
    },

    show_promo: function () {
        return (
            this.api_host === 'https://readthedocs.org' ||
            this.api_host === 'http://127.0.0.1:8000'
        );
    }
};

/*
 * Access READTHEDOCS_DATA on call, not on module load. The reason is that the
 * READTHEDOCS_DATA might not be available during script load time.
 */
function get() {
    // Make `methods` the prototype.
    var config = Object.create(configMethods);

    var defaults = {
        api_host: 'https://readthedocs.org',
        ad_free: false,
    };

    $.extend(config, defaults, window.READTHEDOCS_DATA);

    return config;
}

module.exports = {
    get: get
};
<|endoftext|>


<commit_before>var _ = require('underscore');
var log = require('../cdb.log');
var util = require('../core/util.js');
var RuleToLegendModelAdapters = require('./legends/rule-to-legend-model-adapters');

function getSubdomain (subdomains, resource) {
  var index = util.crc32(resource) % subdomains.length;
  return subdomains[index];
}

/**
 * This class exposes a method that knows how to set/update the metadata on internal
 * CartoDB.js models that are linked to a "resource" in the Maps API.
 */
var ModelUpdater = function (deps) {
  if (!deps.visModel) {
    throw new Error('visModel is required');
  }
  if (!deps.layerGroupModel) {
    throw new Error('layerGroupModel is required');
  }
  if (!deps.layersCollection) {
    throw new Error('layersCollection is required');
  }
  if (!deps.dataviewsCollection) {
    throw new Error('dataviewsCollection is required');
  }
  if (!deps.analysisCollection) {
    throw new Error('analysisCollection is required');
  }

  this._visModel = deps.visModel;
  this._layerGroupModel = deps.layerGroupModel;
  this._layersCollection = deps.layersCollection;
  this._dataviewsCollection = deps.dataviewsCollection;
  this._analysisCollection = deps.analysisCollection;
};

ModelUpdater.prototype.updateModels = function (windshaftMap, sourceId, forceFetch) {
  this._updateLayerGroupModel(windshaftMap);
  this._updateLayerModels(windshaftMap);
  this._updateDataviewModels(windshaftMap, sourceId, forceFetch);
  this._updateAnalysisModels(windshaftMap);

  this._visModel.setOk();
};

ModelUpdater.prototype._updateLayerGroupModel = function (windshaftMap) {
  var urls = {
    tiles: this._generateTileURLTemplate(windshaftMap),
    subdomains: windshafMap.getSupportedSubdomains(),
    grids: this._calculateGridURLTemplatesForCartoDBLayers(windshaftMap),
    attributes: this._calculateAttributesBaseURLsForCartoDBLayers(windshaftMap),
    image: this._calculateStaticMapURL(windshaftMap)
  };

  this._layerGroupModel.set({
    indexOfLayersInWindshaft: windshaftMap.getLayerIndexesByType('mapnik'),
    urls: urls
  });
};

ModelUpdater.prototype._calculateStaticMapURL = function (windshaftMap) {
  return [
    windshaftMap.getStaticBaseURL(),
    '{z}/{lat}/{lng}/{width}/{height}.{format}'
  ].join('/');
};

ModelUpdater.prototype._calculateTileURLTemplatesForCartoDBLayers = function (windshaftMap) {
  var urlTemplates = [];
  _.each(windshaftMap.getSupportedSubdomains(), function (subdomain) {
    urlTemplates.push(this._generateTileURLTemplate(windshaftMap, subdomain));
  }, this);

  return urlTemplates;
};

ModelUpdater.prototype._generateTileURLTemplate = function (windshaftMap) {
  return windshaftMap.getBaseURL() + '/{layerIndexes}/{z}/{x}/{y}.{format}';
};

ModelUpdater.prototype._calculateGridURLTemplatesForCartoDBLayers = function (windshaftMap) {
  var urlTemplates = [];
  var indexesOfMapnikLayers = windshaftMap.getLayerIndexesByType('mapnik');
  if (indexesOfMapnikLayers.length > 0) {
    _.each(indexesOfMapnikLayers, function (index) {
      var layerUrlTemplates = [];
      _.each(windshaftMap.getSupportedSubdomains(), function (subdomain) {
        layerUrlTemplates.push(this._generateGridURLTemplate(windshaftMap, subdomain, index));
      }, this);
      urlTemplates.push(layerUrlTemplates);
    }, this);
  }
  return urlTemplates;
};

ModelUpdater.prototype._generateGridURLTemplate = function (windshaftMap, subdomain, index) {
  var baseURL = windshaftMap.getBaseURL() + '/' + index + '/{z}/{x}/{y}.grid.json';
  return baseURL.replace('{s}', subdomain);
};

ModelUpdater.prototype._calculateAttributesBaseURLsForCartoDBLayers = function (windshaftMap) {
  var urls = [];
  var indexesOfMapnikLayers = windshaftMap.getLayerIndexesByType('mapnik');
  if (indexesOfMapnikLayers.length > 0) {
    _.each(indexesOfMapnikLayers, function (index) {
      urls.push(this._generateAttributesBaseURL(windshaftMap, index));
    }, this);
  }
  return urls;
};

ModelUpdater.prototype._generateAttributesBaseURL = function (windshaftMap, index) {
  var baseURL = windshaftMap.getBaseURL() + '/' + index + '/attributes';
  if (baseURL.indexOf('{s}') >= 0) {
    var subdomain = getSubdomain(windshaftMap.getSupportedSubdomains(), baseURL);
    baseURL = baseURL.replace('{s}', subdomain);
  }
  return baseURL;
};

ModelUpdater.prototype._updateLayerModels = function (windshaftMap) {
  // CartoDB / mapnik layers
  var indexesOfMapnikLayers = windshaftMap.getLayerIndexesByType('mapnik');
  _.each(this._layersCollection.getCartoDBLayers(), function (layerModel, localLayerIndex) {
    var windshaftMapLayerIndex = indexesOfMapnikLayers[localLayerIndex];
    layerModel.set('meta', windshaftMap.getLayerMetadata(windshaftMapLayerIndex));
    this._updateLegendModels(layerModel, windshaftMapLayerIndex, windshaftMap);

    layerModel.setOk();
  }, this);

  // Torque / torque layers
  var indexesOfTorqueLayers = windshaftMap.getLayerIndexesByType('torque');
  _.each(this._layersCollection.getTorqueLayers(), function (layerModel, localLayerIndex) {
    var windshaftMapLayerIndex = indexesOfTorqueLayers[localLayerIndex];
    layerModel.set('meta', windshaftMap.getLayerMetadata(windshaftMapLayerIndex));
    layerModel.set('subdomains', windshaftMap.getSupportedSubdomains());
    layerModel.set('tileURLTemplates', this._calculateTileURLTemplatesForTorqueLayers(windshaftMap));
    this._updateLegendModels(layerModel, windshaftMapLayerIndex, windshaftMap);

    layerModel.setOk();
  }, this);
};

ModelUpdater.prototype._calculateTileURLTemplatesForTorqueLayers = function (windshaftMap) {
  var urlTemplates = [];
  var indexesOfTorqueLayers = windshaftMap.getLayerIndexesByType('torque');
  if (indexesOfTorqueLayers.length > 0) {
    urlTemplates.push(this._generateTorqueTileURLTemplate(windshaftMap, indexesOfTorqueLayers));
  }
  return urlTemplates;
};

ModelUpdater.prototype._generateTorqueTileURLTemplate = function (windshaftMap, layerIndexes) {
  return windshaftMap.getBaseURL() + '/' + layerIndexes.join(',') + '/{z}/{x}/{y}.json.torque';
};

ModelUpdater.prototype._updateLegendModels = function (layerModel, remoteLayerIndex, windshaftMap) {
  var layerMetadata = windshaftMap.getLayerMetadata(remoteLayerIndex);
  _.each(this._getLayerLegends(layerModel), function (legendModel) {
    this._updateLegendModel(legendModel, layerMetadata);
  }, this);
};

ModelUpdater.prototype._updateLegendModel = function (legendModel, layerMetadata) {
  var cartoCSSRules = layerMetadata && layerMetadata.cartocss_meta && layerMetadata.cartocss_meta.rules;
  try {
    var newLegendAttrs = {
      state: 'success'
    };
    if (cartoCSSRules) {
      var adapter = RuleToLegendModelAdapters.getAdapterForLegend(legendModel);
      var ruleForLegend = _.find(cartoCSSRules, adapter.canAdapt);
      if (ruleForLegend) {
        newLegendAttrs = _.extend(newLegendAttrs, adapter.adapt(ruleForLegend));
      }
    }
    legendModel.set(newLegendAttrs);
  } catch (error) {
    legendModel.set({ state: 'error' });
    log.error("legend of type '" + legendModel.get('type') + "' couldn't be updated: " + error.message);
  }
};

ModelUpdater.prototype._updateDataviewModels = function (windshaftMap, sourceId, forceFetch) {
  this._dataviewsCollection.each(function (dataviewModel) {
    var dataviewMetadata = windshaftMap.getDataviewMetadata(dataviewModel.get('id'));
    if (dataviewMetadata) {
      dataviewModel.set({
        url: dataviewMetadata.url[this._getProtocol()]
      }, {
        sourceId: sourceId,
        forceFetch: forceFetch
      });
    }
  }, this);
};

ModelUpdater.prototype._updateAnalysisModels = function (windshaftMap) {
  this._analysisCollection.each(function (analysisNode) {
    var analysisMetadata = windshaftMap.getAnalysisNodeMetadata(analysisNode.get('id'));
    var attrs;
    if (analysisMetadata) {
      attrs = {
        status: analysisMetadata.status,
        url: analysisMetadata.url[this._getProtocol()],
        query: analysisMetadata.query
      };

      attrs = _.omit(attrs, analysisNode.getParamNames());

      if (analysisMetadata.error_message) {
        attrs = _.extend(attrs, {
          error: {
            message: analysisMetadata.error_message
          }
        });
        analysisNode.set(attrs);
      } else {
        analysisNode.set(attrs);
        analysisNode.setOk();
      }
    }
  }, this);
};

ModelUpdater.prototype._getProtocol = function () {
  // When running tests window.locationn.protocol using the jasmine test runner,
  // window.location.protocol returns 'file:'. This is a little hack to make tests happy.
  if (window.location.protocol === 'file:') {
    return 'http';
  }
  return window.location.protocol.replace(':', '');
};

ModelUpdater.prototype.setErrors = function (errors) {
  _.each(errors, this._setError, this);
  this._setLegendErrors();
};

ModelUpdater.prototype._setError = function (error) {
  if (error.isLayerError()) {
    var layerModel = this._layersCollection.get(error.layerId);
    layerModel && layerModel.setError(error);
  } else if (error.isAnalysisError()) {
    var analysisModel = this._analysisCollection.get(error.analysisId);
    analysisModel && analysisModel.setError(error);
  } else {
    this._visModel.setError(error);
  }
};

ModelUpdater.prototype._setLegendErrors = function () {
  var legendModels = this._layersCollection.chain()
    .map(this._getLayerLegends)
    .compact()
    .flatten()
    .value();

  _.each(legendModels, function (legendModel) {
    legendModel.set('state', 'error');
  });
};

ModelUpdater.prototype._getLayerLegends = function (layerModel) {
  return layerModel.legends && [
    layerModel.legends.bubble,
    layerModel.legends.category,
    layerModel.legends.choropleth
  ];
};
module.exports = ModelUpdater;
<commit_msg>Fixed typo<commit_after>var _ = require('underscore');
var log = require('../cdb.log');
var util = require('../core/util.js');
var RuleToLegendModelAdapters = require('./legends/rule-to-legend-model-adapters');

function getSubdomain (subdomains, resource) {
  var index = util.crc32(resource) % subdomains.length;
  return subdomains[index];
}

/**
 * This class exposes a method that knows how to set/update the metadata on internal
 * CartoDB.js models that are linked to a "resource" in the Maps API.
 */
var ModelUpdater = function (deps) {
  if (!deps.visModel) {
    throw new Error('visModel is required');
  }
  if (!deps.layerGroupModel) {
    throw new Error('layerGroupModel is required');
  }
  if (!deps.layersCollection) {
    throw new Error('layersCollection is required');
  }
  if (!deps.dataviewsCollection) {
    throw new Error('dataviewsCollection is required');
  }
  if (!deps.analysisCollection) {
    throw new Error('analysisCollection is required');
  }

  this._visModel = deps.visModel;
  this._layerGroupModel = deps.layerGroupModel;
  this._layersCollection = deps.layersCollection;
  this._dataviewsCollection = deps.dataviewsCollection;
  this._analysisCollection = deps.analysisCollection;
};

ModelUpdater.prototype.updateModels = function (windshaftMap, sourceId, forceFetch) {
  this._updateLayerGroupModel(windshaftMap);
  this._updateLayerModels(windshaftMap);
  this._updateDataviewModels(windshaftMap, sourceId, forceFetch);
  this._updateAnalysisModels(windshaftMap);

  this._visModel.setOk();
};

ModelUpdater.prototype._updateLayerGroupModel = function (windshaftMap) {
  var urls = {
    tiles: this._generateTileURLTemplate(windshaftMap),
    subdomains: windshaftMap.getSupportedSubdomains(),
    grids: this._calculateGridURLTemplatesForCartoDBLayers(windshaftMap),
    attributes: this._calculateAttributesBaseURLsForCartoDBLayers(windshaftMap),
    image: this._calculateStaticMapURL(windshaftMap)
  };

  this._layerGroupModel.set({
    indexOfLayersInWindshaft: windshaftMap.getLayerIndexesByType('mapnik'),
    urls: urls
  });
};

ModelUpdater.prototype._calculateStaticMapURL = function (windshaftMap) {
  return [
    windshaftMap.getStaticBaseURL(),
    '{z}/{lat}/{lng}/{width}/{height}.{format}'
  ].join('/');
};

ModelUpdater.prototype._calculateTileURLTemplatesForCartoDBLayers = function (windshaftMap) {
  var urlTemplates = [];
  _.each(windshaftMap.getSupportedSubdomains(), function (subdomain) {
    urlTemplates.push(this._generateTileURLTemplate(windshaftMap, subdomain));
  }, this);

  return urlTemplates;
};

ModelUpdater.prototype._generateTileURLTemplate = function (windshaftMap) {
  return windshaftMap.getBaseURL() + '/{layerIndexes}/{z}/{x}/{y}.{format}';
};

ModelUpdater.prototype._calculateGridURLTemplatesForCartoDBLayers = function (windshaftMap) {
  var urlTemplates = [];
  var indexesOfMapnikLayers = windshaftMap.getLayerIndexesByType('mapnik');
  if (indexesOfMapnikLayers.length > 0) {
    _.each(indexesOfMapnikLayers, function (index) {
      var layerUrlTemplates = [];
      _.each(windshaftMap.getSupportedSubdomains(), function (subdomain) {
        layerUrlTemplates.push(this._generateGridURLTemplate(windshaftMap, subdomain, index));
      }, this);
      urlTemplates.push(layerUrlTemplates);
    }, this);
  }
  return urlTemplates;
};

ModelUpdater.prototype._generateGridURLTemplate = function (windshaftMap, subdomain, index) {
  var baseURL = windshaftMap.getBaseURL() + '/' + index + '/{z}/{x}/{y}.grid.json';
  return baseURL.replace('{s}', subdomain);
};

ModelUpdater.prototype._calculateAttributesBaseURLsForCartoDBLayers = function (windshaftMap) {
  var urls = [];
  var indexesOfMapnikLayers = windshaftMap.getLayerIndexesByType('mapnik');
  if (indexesOfMapnikLayers.length > 0) {
    _.each(indexesOfMapnikLayers, function (index) {
      urls.push(this._generateAttributesBaseURL(windshaftMap, index));
    }, this);
  }
  return urls;
};

ModelUpdater.prototype._generateAttributesBaseURL = function (windshaftMap, index) {
  var baseURL = windshaftMap.getBaseURL() + '/' + index + '/attributes';
  if (baseURL.indexOf('{s}') >= 0) {
    var subdomain = getSubdomain(windshaftMap.getSupportedSubdomains(), baseURL);
    baseURL = baseURL.replace('{s}', subdomain);
  }
  return baseURL;
};

ModelUpdater.prototype._updateLayerModels = function (windshaftMap) {
  // CartoDB / mapnik layers
  var indexesOfMapnikLayers = windshaftMap.getLayerIndexesByType('mapnik');
  _.each(this._layersCollection.getCartoDBLayers(), function (layerModel, localLayerIndex) {
    var windshaftMapLayerIndex = indexesOfMapnikLayers[localLayerIndex];
    layerModel.set('meta', windshaftMap.getLayerMetadata(windshaftMapLayerIndex));
    this._updateLegendModels(layerModel, windshaftMapLayerIndex, windshaftMap);

    layerModel.setOk();
  }, this);

  // Torque / torque layers
  var indexesOfTorqueLayers = windshaftMap.getLayerIndexesByType('torque');
  _.each(this._layersCollection.getTorqueLayers(), function (layerModel, localLayerIndex) {
    var windshaftMapLayerIndex = indexesOfTorqueLayers[localLayerIndex];
    layerModel.set('meta', windshaftMap.getLayerMetadata(windshaftMapLayerIndex));
    layerModel.set('subdomains', windshaftMap.getSupportedSubdomains());
    layerModel.set('tileURLTemplates', this._calculateTileURLTemplatesForTorqueLayers(windshaftMap));
    this._updateLegendModels(layerModel, windshaftMapLayerIndex, windshaftMap);

    layerModel.setOk();
  }, this);
};

ModelUpdater.prototype._calculateTileURLTemplatesForTorqueLayers = function (windshaftMap) {
  var urlTemplates = [];
  var indexesOfTorqueLayers = windshaftMap.getLayerIndexesByType('torque');
  if (indexesOfTorqueLayers.length > 0) {
    urlTemplates.push(this._generateTorqueTileURLTemplate(windshaftMap, indexesOfTorqueLayers));
  }
  return urlTemplates;
};

ModelUpdater.prototype._generateTorqueTileURLTemplate = function (windshaftMap, layerIndexes) {
  return windshaftMap.getBaseURL() + '/' + layerIndexes.join(',') + '/{z}/{x}/{y}.json.torque';
};

ModelUpdater.prototype._updateLegendModels = function (layerModel, remoteLayerIndex, windshaftMap) {
  var layerMetadata = windshaftMap.getLayerMetadata(remoteLayerIndex);
  _.each(this._getLayerLegends(layerModel), function (legendModel) {
    this._updateLegendModel(legendModel, layerMetadata);
  }, this);
};

ModelUpdater.prototype._updateLegendModel = function (legendModel, layerMetadata) {
  var cartoCSSRules = layerMetadata && layerMetadata.cartocss_meta && layerMetadata.cartocss_meta.rules;
  try {
    var newLegendAttrs = {
      state: 'success'
    };
    if (cartoCSSRules) {
      var adapter = RuleToLegendModelAdapters.getAdapterForLegend(legendModel);
      var ruleForLegend = _.find(cartoCSSRules, adapter.canAdapt);
      if (ruleForLegend) {
        newLegendAttrs = _.extend(newLegendAttrs, adapter.adapt(ruleForLegend));
      }
    }
    legendModel.set(newLegendAttrs);
  } catch (error) {
    legendModel.set({ state: 'error' });
    log.error("legend of type '" + legendModel.get('type') + "' couldn't be updated: " + error.message);
  }
};

ModelUpdater.prototype._updateDataviewModels = function (windshaftMap, sourceId, forceFetch) {
  this._dataviewsCollection.each(function (dataviewModel) {
    var dataviewMetadata = windshaftMap.getDataviewMetadata(dataviewModel.get('id'));
    if (dataviewMetadata) {
      dataviewModel.set({
        url: dataviewMetadata.url[this._getProtocol()]
      }, {
        sourceId: sourceId,
        forceFetch: forceFetch
      });
    }
  }, this);
};

ModelUpdater.prototype._updateAnalysisModels = function (windshaftMap) {
  this._analysisCollection.each(function (analysisNode) {
    var analysisMetadata = windshaftMap.getAnalysisNodeMetadata(analysisNode.get('id'));
    var attrs;
    if (analysisMetadata) {
      attrs = {
        status: analysisMetadata.status,
        url: analysisMetadata.url[this._getProtocol()],
        query: analysisMetadata.query
      };

      attrs = _.omit(attrs, analysisNode.getParamNames());

      if (analysisMetadata.error_message) {
        attrs = _.extend(attrs, {
          error: {
            message: analysisMetadata.error_message
          }
        });
        analysisNode.set(attrs);
      } else {
        analysisNode.set(attrs);
        analysisNode.setOk();
      }
    }
  }, this);
};

ModelUpdater.prototype._getProtocol = function () {
  // When running tests window.locationn.protocol using the jasmine test runner,
  // window.location.protocol returns 'file:'. This is a little hack to make tests happy.
  if (window.location.protocol === 'file:') {
    return 'http';
  }
  return window.location.protocol.replace(':', '');
};

ModelUpdater.prototype.setErrors = function (errors) {
  _.each(errors, this._setError, this);
  this._setLegendErrors();
};

ModelUpdater.prototype._setError = function (error) {
  if (error.isLayerError()) {
    var layerModel = this._layersCollection.get(error.layerId);
    layerModel && layerModel.setError(error);
  } else if (error.isAnalysisError()) {
    var analysisModel = this._analysisCollection.get(error.analysisId);
    analysisModel && analysisModel.setError(error);
  } else {
    this._visModel.setError(error);
  }
};

ModelUpdater.prototype._setLegendErrors = function () {
  var legendModels = this._layersCollection.chain()
    .map(this._getLayerLegends)
    .compact()
    .flatten()
    .value();

  _.each(legendModels, function (legendModel) {
    legendModel.set('state', 'error');
  });
};

ModelUpdater.prototype._getLayerLegends = function (layerModel) {
  return layerModel.legends && [
    layerModel.legends.bubble,
    layerModel.legends.category,
    layerModel.legends.choropleth
  ];
};
module.exports = ModelUpdater;
<|endoftext|>


<commit_before>var assert = require('assert');
var path = require('path');
var fs = require('fs');
var muk = require('muk');

var Index = require('../../lib/index.js');
var instance = new Index();
instance.load();

think.APP_PATH = path.dirname(__dirname) + '/testApp';

var Base = require('../../lib/adapter/base.js');
describe('adapter/base.js', function(){
  it('merge config, empty', function(){
    var instance = new Base();
    var data = instance.parseConfig();
    assert.deepEqual(data, {})
  })
  it('merge config', function(){
    var instance = new Base();
    var data = instance.parseConfig({
      type: 'test',
      name: '111',
      adapter: {
        test: {
          name: '222'
        }
      }
    });
    assert.equal(data.name, '222');
    assert.equal(data.adapter, undefined)
  })
  it('parse config, empty', function(){
    var instance = new Base();
    var data = instance.parseConfig();
    assert.deepEqual(data, {})
  })
  it('parse config, no parser', function(){
    var instance = new Base();
    var data = instance.parseConfig({name: 1});
    assert.deepEqual(data, {name: 1})
  })
  it('parse config, has parser', function(){
    var instance = new Base();
    var data = instance.parseConfig({name: 1, parser: function(options){
      return {name: 2}
    }});
    assert.deepEqual(data, {name: 2})
  })
  it('parse config, has parser, type', function(){
    var instance = new Base();
    var data = instance.parseConfig({name: 1, from: 'cache', parser: function(options){
      assert.equal(options.from, 'cache')
      return {name: 2}
    }});
    assert.deepEqual(data, {name: 2})
  })
  it('parse config, has parser, extra, type', function(){
    var instance = new Base();
    var data = instance.parseConfig({name: 1, parser: function(options, type){
      assert.equal(options.name, 3)
      return {name: 2}
    }}, {name: 3});
    assert.deepEqual(data, {name: 2})
  })
})<commit_msg>fix test error<commit_after>var assert = require('assert');
var path = require('path');
var fs = require('fs');
var muk = require('muk');

var Index = require('../../lib/index.js');
var instance = new Index();
instance.load();

think.APP_PATH = path.dirname(__dirname) + '/testApp';

var Base = require('../../lib/adapter/base.js');
describe('adapter/base.js', function(){
  it('merge config, empty', function(){
    var instance = new Base();
    var data = instance.parseConfig();
    assert.deepEqual(data, {})
  })
  it('merge config', function(){
    var instance = new Base();
    var data = instance.parseConfig({
      type: 'test',
      name: '111',
      adapter: {
        test: {
          name: '222'
        }
      }
    });
    assert.equal(data.name, '222');
    assert.equal(data.adapter, undefined)
  })
  it('parse config, empty', function(){
    var instance = new Base();
    var data = instance.parseConfig();
    assert.deepEqual(data, {})
  })
  it('parse config, no parser', function(){
    var instance = new Base();
    var data = instance.parseConfig({name: 1});
    assert.deepEqual(data, {name: 1})
  })
  it('parse config, has parser', function(){
    var instance = new Base();
    var data = instance.parseConfig({name: 1, parser: function(options){
      return {name: 2}
    }});
    assert.deepEqual(data, {name: 2})
  })
  it('parse config, has parser, type', function(){
    var instance = new Base();
    var data = instance.parseConfig({name: 1, from: 'cache', parser: function(options){
      assert.equal(options.from, 'cache')
      return {name: 2}
    }});
    assert.deepEqual(data, {name: 2, from: 'cache'});
  })
  it('parse config, has parser, extra, type', function(){
    var instance = new Base();
    var data = instance.parseConfig({name: 1, parser: function(options, type){
      assert.equal(options.name, 3)
      return {name: 2}
    }}, {name: 3});
    assert.deepEqual(data, {name: 2})
  })
})<|endoftext|>


<commit_before>var assert = require('assert');
var http = require('http');
var path = require('path');
var decache = require('decache');
var useragent = require('useragent');
var familyMapping = {
  canary: 'chrome',
  chromium: process.platform === 'darwin' ? 'chrome' : 'chromium',
  phantom: 'phantomjs',
  nodeWebkit: 'chrome'
};
var server = http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World\n');
}).listen(6785);

describe('Local browser launcher tests', function() {

  describe('Default env settings', function () {

    var local = require('../lib/local');

    it('does local browser and version discovery', function (done) {
      local(function (error, launcher) {
        launcher.browsers(function (error, browsers) {
          assert.ok(!error, 'No error discovering browsers');
          assert.ok(browsers.length, 'Found at least one browser');
          assert.ok(browsers[0].version, 'First browser has a version');
          assert.ok(browsers[0].path, 'First browser has a path');
          assert.ok(browsers[0].binPath, 'First browser has a binPath');
          done();
        });
      });
    });

    Object.keys(local.platform).forEach(function (name) {
      it('Launches ' + name + ' browser on ' + process.platform, function (done) {
        local(function (error, launcher) {
          launcher[name]('http://localhost:6785', function (error, instance) {
            if (error) {
              // That's the only error we should get
              assert.equal(error.message, 'Browser ' + name + ' not available.');
              return done();
            }

            server.once('request', function (req) {
              var userAgent = useragent.parse(req.headers['user-agent']);
              var expected = familyMapping[name] || name;
              assert.equal(userAgent.family.toLowerCase(), expected, 'Got expected browser family');
              instance.stop(done);
            });
          });
        });
      });
    });
  });

  describe('Custom env settings', function () {

    var node_modules = path.join(__dirname, '..', 'node_modules');
    var local;

    beforeEach(function () {
      decache(path.join(__dirname, '..', 'lib', 'local', 'index.js'));
    });

    after(function () {
      delete process.env.LAUNCHPAD_BROWSERS;
    });

    describe('Electron', function () {

      beforeEach(function () {
        process.env.LAUNCHPAD_BROWSERS = 'electron';
        process.env.LAUNCHPAD_ELECTRON = /^win/.test(process.platform) ?
            path.join(node_modules, 'electron', 'dist', 'electron.exe') : process.platform == 'darwin' ?
            path.join(node_modules, 'electron', 'dist', 'Electron.app', 'Contents', 'MacOS', 'Electron') :
            path.join(node_modules, 'electron', 'dist', 'electron');

        local = require('../lib/local');
      });

      after(function () {
        delete process.env.LAUNCHPAD_ELECTRON;
      });

      it('is detected only due to env settings', function (done) {
        local(function (error, launcher) {
          launcher.browsers(function (error, browsers) {
            assert.ok(!error, 'No error discovering browsers');
            assert.equal(browsers.length, 1, 'Found Electron browser');
            assert.equal(browsers[0].path, process.env.LAUNCHPAD_ELECTRON, 'Found Electron at selected location');
            done();
          });
        });
      });
    });

    describe('PhantomJS', function () {

      beforeEach(function () {
        process.env.LAUNCHPAD_BROWSERS = 'phantom';
        process.env.LAUNCHPAD_PHANTOM = /^win/.test(process.platform) ?
            path.join(node_modules, 'phantomjs-prebuilt', 'lib', 'phantom', 'phantomjs.exe') :
            path.join(node_modules, 'phantomjs-prebuilt', 'bin', 'phantomjs');

        local = require('../lib/local');
      });

      after(function () {
        delete process.env.LAUNCHPAD_PHANTOM;
      });

      it('is detected only due to env settings', function (done) {
        local(function (error, launcher) {
          launcher.browsers(function (error, browsers) {
            assert.ok(!error, 'No error discovering browsers');
            assert.equal(browsers.length, 1, 'Found PhantomJS browser');
            assert.equal(browsers[0].path, process.env.LAUNCHPAD_PHANTOM, 'Found PhantomJS at selected location');
            done();
          });
        });
      });
    });
  });

  describe('Clean option', function() {
    var local = require('../lib/local');
    var chrome;

    function makeTest(isHeadless, done) {
      local(function (error, launcher) {
        var args = isHeadless ? ['--headless'] : [];
        launcher.chrome('http://localhost:6785', {clean: true, args: args}, function(error, instance) {

          if (error) return done(error);

          server.once('request', function () {
            assert.ok(instance.options.clean);
            assert.ok(instance.options.tmpdir);
            assert.notEqual(chrome.id, instance.id);
            setTimeout(function () {
              instance.stop(done());
            }, 1000);
          });
        });
      });
    }

    beforeEach(function() {
      local(function (error, launcher) {
        launcher.chrome('http://localhost:6785', {clean: true}, function(error, instance) {
          chrome = instance;
        });
      });
    });

    afterEach(function() {
      chrome.stop();
    });

    it('Launches Chrome in clean mode', function(done) {
      makeTest(false, done);
    });

    it('Launches headless Chrome in clean mode', function(done) {
      makeTest(true, done);
    });
  });
});
<commit_msg>Run tests on Linux<commit_after>var assert = require('assert');
var http = require('http');
var path = require('path');
var decache = require('decache');
var useragent = require('useragent');
var familyMapping = {
  canary: 'chrome',
  chromium: process.platform === 'darwin' ? 'chrome' : 'chromium',
  phantom: 'phantomjs',
  nodeWebkit: 'chrome'
};
var server = http.createServer(function (req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('Hello World\n');
}).listen(6785);

describe('Local browser launcher tests', function() {

  describe('Default env settings', function () {

    var local = require('../lib/local');

    it('does local browser and version discovery', function (done) {
      local(function (error, launcher) {
        launcher.browsers(function (error, browsers) {
          assert.ok(!error, 'No error discovering browsers');
          assert.ok(browsers.length, 'Found at least one browser');
          assert.ok(browsers[0].version, 'First browser has a version');
          assert.ok(browsers[0].path, 'First browser has a path');
          assert.ok(browsers[0].binPath, 'First browser has a binPath');
          done();
        });
      });
    });

    Object.keys(local.platform).forEach(function (name) {
      it('Launches ' + name + ' browser on ' + process.platform, function (done) {
        local(function (error, launcher) {
          launcher[name]('http://localhost:6785', function (error, instance) {
            if (error) {
              // That's the only error we should get
              assert.equal(error.message, 'Browser ' + name + ' not available.');
              return done();
            }

            server.once('request', function (req) {
              var userAgent = useragent.parse(req.headers['user-agent']);
              var expected = familyMapping[name] || name;
              assert.equal(userAgent.family.toLowerCase(), expected, 'Got expected browser family');
              instance.stop(done);
            });
          });
        });
      });
    });
  });

  describe('Custom env settings', function () {

    var node_modules = path.join(__dirname, '..', 'node_modules');
    var local;

    beforeEach(function () {
      decache(path.join(__dirname, '..', 'lib', 'local', 'index.js'));
    });

    after(function () {
      delete process.env.LAUNCHPAD_BROWSERS;
    });

    describe('Electron', function () {

      beforeEach(function () {
        process.env.LAUNCHPAD_BROWSERS = 'electron';
        process.env.LAUNCHPAD_ELECTRON = /^win/.test(process.platform) ?
            path.join(node_modules, 'electron', 'dist', 'electron.exe') : process.platform == 'darwin' ?
            path.join(node_modules, 'electron', 'dist', 'Electron.app', 'Contents', 'MacOS', 'Electron') :
            path.join(node_modules, 'electron', 'dist', 'electron');

        local = require('../lib/local');
      });

      after(function () {
        delete process.env.LAUNCHPAD_ELECTRON;
      });

      it('is detected only due to env settings', function (done) {
        local(function (error, launcher) {
          launcher.browsers(function (error, browsers) {
            assert.ok(!error, 'No error discovering browsers');
            assert.equal(browsers.length, 1, 'Found Electron browser');
            assert.equal(browsers[0].path, process.env.LAUNCHPAD_ELECTRON, 'Found Electron at selected location');
            done();
          });
        });
      });
    });

    describe('PhantomJS', function () {

      beforeEach(function () {
        process.env.LAUNCHPAD_BROWSERS = 'phantom';
        process.env.LAUNCHPAD_PHANTOM = /^win/.test(process.platform) ?
            path.join(node_modules, 'phantomjs-prebuilt', 'lib', 'phantom', 'phantomjs.exe') :
            path.join(node_modules, 'phantomjs-prebuilt', 'bin', 'phantomjs');

        local = require('../lib/local');
      });

      after(function () {
        delete process.env.LAUNCHPAD_PHANTOM;
      });

      it('is detected only due to env settings', function (done) {
        local(function (error, launcher) {
          launcher.browsers(function (error, browsers) {
            assert.ok(!error, 'No error discovering browsers');
            assert.equal(browsers.length, 1, 'Found PhantomJS browser');
            assert.equal(browsers[0].path, process.env.LAUNCHPAD_PHANTOM, 'Found PhantomJS at selected location');
            done();
          });
        });
      });
    });
  });
  if (process.platform === 'linux') {
    describe('Clean option', function() {
      var local = require('../lib/local');
      var chrome;

      function makeTest(isHeadless, done) {
        local(function (error, launcher) {
          var args = isHeadless ? ['--headless'] : [];
          launcher.chrome('http://localhost:6785', {clean: true, args: args}, function(error, instance) {

            if (error) return done(error);

            server.once('request', function () {
              assert.ok(instance.options.clean);
              assert.ok(instance.options.tmpdir);
              assert.notEqual(chrome.id, instance.id);
              setTimeout(function () {
                instance.stop(done());
              }, 1000);
            });
          });
        });
      }

      beforeEach(function() {
        local(function (error, launcher) {
          launcher.chrome('http://localhost:6785', {clean: true}, function(error, instance) {
            chrome = instance;
          });
        });
      });

      afterEach(function() {
        chrome.stop();
      });

      it('Launches Chrome in clean mode', function(done) {
        makeTest(false, done);
      });

      it('Launches headless Chrome in clean mode', function(done) {
        makeTest(true, done);
      });
    });
  }
});
<|endoftext|>


<commit_before>'use strict';

const api = require('../../'),
  chai = require("chai"),
  chaiAsPromised = require("chai-as-promised");

chai.use(chaiAsPromised);
chai.should();

const LOCAL_SERVER = 'http://0.0.0.0:8282/info/';

const BROWSERS = ['chrome', 'firefox'];

describe('DOM apis:', function() {

  describe('getAdviceScript', function() {
    it('should return a script', function() {
      return api.dom.getAdviceScript().should.eventually.not.be.empty;
    });

  });

  BROWSERS.forEach(function(browser) {

    describe('runAdvice: ' + browser, function() {
      this.timeout(30000);

      it('should run simple script', function() {
        return api.dom.runAdvice(LOCAL_SERVER, {
          browser,
          iterations: 1,
          pageCompleteCheck: 'return window.performance.timing.loadEventEnd>0'
        }).should.eventually.have.property("browsertimeData");
      });
    });
  });
});
<commit_msg>todo: lets test more<commit_after>'use strict';

const api = require('../../'),
  chai = require("chai"),
  chaiAsPromised = require("chai-as-promised");

chai.use(chaiAsPromised);
chai.should();

const LOCAL_SERVER = 'http://0.0.0.0:8282/info/';

const BROWSERS = ['chrome', 'firefox'];

describe('DOM apis:', function() {

  describe('getAdviceScript', function() {
    it('should return a script', function() {
      return api.dom.getAdviceScript().should.eventually.not.be.empty;
    });

  });

  BROWSERS.forEach(function(browser) {

    describe('runAdvice: ' + browser, function() {
      this.timeout(30000);

      it('should run simple script', function() {
        return api.dom.runAdvice(LOCAL_SERVER, {
          browser,
          iterations: 1,
          pageCompleteCheck: 'return window.performance.timing.loadEventEnd>0'
          // TODO test that we actually have DOM data and valid HAR
        }).should.eventually.have.property("browsertimeData");
      });
    });
  });
});
<|endoftext|>