<commit_before>const { expect } = require('chai');

const weiPixelPrice = ethers.utils.parseUnits("0.001", "ether");
const pixelsPerCell = ethers.BigNumber.from(100);
const oneHundredCellPrice = pixelsPerCell.mul(weiPixelPrice).mul(100);

describe('KetherNFT', function() {
  let KetherHomepage, KetherNFT, KetherNFTRender, FlashEscrow;
  let accounts, KH, KNFT, KNFTrender;

  beforeEach(async() => {
    // NOTE: We're using V2 here because it's ported to newer solidity so we can debug more easily. It should also work with V1.
    KetherHomepage = await ethers.getContractFactory("KetherHomepageV2");
    KetherNFT = await ethers.getContractFactory("KetherNFT");
    KetherNFTRender = await ethers.getContractFactory("KetherNFTRender");
    FlashEscrow = await ethers.getContractFactory("FlashEscrow");

    const [owner, withdrawWallet, metadataSigner, account1, account2, account3] = await ethers.getSigners();
    accounts = {owner, withdrawWallet, metadataSigner, account1, account2, account3};

    KH = await KetherHomepage.deploy(await owner.getAddress(), await withdrawWallet.getAddress());
    KNFTrender = await KetherNFTRender.deploy();
    KNFT = await KetherNFT.deploy(KH.address, KNFTrender.address);
  });

  const buyAd = async function(account, x=0, y=0, width=10, height=10, link="link", image="image", title="title", NSFW=false, value=oneHundredCellPrice) {
    const txn = await KH.connect(account).buy(x, y, width, height, { value: value });
    const receipt = await txn.wait();
    const event = receipt.events.pop();
    const [idx] = event.args;
    await KH.connect(account).publish(idx, link, image, title, false);
    return idx;
  }

  it("should wrap ad with KetherNFT", async function() {
    const {account1} = accounts;

    // Buy an ad
    const idx = await buyAd(account1);
    expect(idx).to.equal(0);

    const [salt, precomputeAddress] = await KNFT.connect(account1).precompute(idx, await account1.getAddress());

    // Set owner to precommitted wrap address
    await KH.connect(account1).setAdOwner(idx, precomputeAddress);

    // Wrap ad
    await KNFT.connect(account1).wrap(idx, await account1.getAddress());

    // Confirm owner can't publish directly anymore
    await expect(
      KH.connect(account1).publish(idx, "foo", "bar", "baaz", false)
    ).to.be.reverted;

    {
      const [addr,,,,,link,image,title] = await KH.ads(idx);
      expect(addr).to.equal(KNFT.address);
      expect(link).to.equal("link");
      expect(image).to.equal("image");
      expect(title).to.equal("title");
    }

    // Confirm NFT owner can publish through the NFT
    await KNFT.connect(account1).publish(idx, "foo2", "baar2", "baaz2", false);

    {
      const [addr,,,,,link,image,title] = await KH.ads(idx);
      expect(addr).to.equal(KNFT.address);
      expect(link).to.equal("foo2");
      expect(image).to.equal("baar2");
      expect(title).to.equal("baaz2");
    }

  });

  it('should wrap to non-owner', async function() {
    const {account1, account2, account3} = accounts;
    const idx = await buyAd(account1);
    {
      const otherIdx = await buyAd(account2, x=20, y=20);
      expect(otherIdx).to.not.equal(idx);
    }

    // Generate precommit to wrap idx (owned by account1) to ownership of account2.
    // Note that account2 can generate this precommit, as in this case.
    const [salt, precomputeAddress] = await KNFT.connect(account2).precompute(idx, await account2.getAddress());

    // Non-owner cannot change the ad ownership to precomputed address.
    await expect(
      KH.connect(account2).setAdOwner(idx, precomputeAddress)
    ).to.be.reverted;

    // Non-owner cannot wrap, either
    await expect(
      KNFT.connect(account2).wrap(idx, await account2.getAddress())
    ).to.be.revertedWith("KetherNFT: owner needs to be the correct precommitted address");

    // Same precomputed transaction is fine for the owner to run (precommit wrap to account2)
    await KH.connect(account1).setAdOwner(idx, precomputeAddress);

    // Rando can't wrap to themselves
    await expect(
      KNFT.connect(account3).wrap(idx, await account3.getAddress())
    ).to.be.revertedWith("KetherNFT: owner needs to be the correct precommitted address");

    // Rando can't wrap to owner (since the precommit is for account2)
    await expect(
      KNFT.connect(account3).wrap(idx, await account1.getAddress())
    ).to.be.revertedWith("KetherNFT: owner needs to be the correct precommitted address");

    // Rando *can* wrap to the precommitted account2
    // FIXME: Is this desirable? It allows non-owner to pay for the wrap, which is nice.
    await KNFT.connect(account3).wrap(idx, await account2.getAddress())

  });

  it("should generate tokenURI based on wrapped ad", async function() {
    const {account1, account2} = accounts;
    const idx = await buyAd(account1, 1, 2, 3, 4);
    const [salt, precomputeAddress] = await KNFT.connect(account1).precompute(idx, await account1.getAddress());

    await KH.connect(account1).publish(idx, "link", "image", "title", false);
    await KH.connect(account1).setAdOwner(idx, precomputeAddress);

    await KNFT.connect(account1).wrap(idx, await account1.getAddress());

    {
      const expected = {
        "name": "Thousand Ether Homepage Ad: 30x40 at [10,20]",
        "description": "This NFT represents an ad unit on thousandetherhomepage.com, the owner of the NFT controls the content of this ad unit.",
        "external_url": "https://thousandetherhomepage.com",
        "image": "omitted for testing", // TODO: Test image elsewhere
        "properties": {"width": 30, "height": 40},
      };
      const r = await KNFT.connect(account1).tokenURI(idx);
      const prefix = 'data:application/json;base64';
      expect(r).to.to.have.string(prefix);

      const got = Buffer.from(r.slice(prefix.length), 'base64').toString();

      let parsed;
      try {
        parsed = JSON.parse(got);
      } catch (e) {
        console.log("Failed to parse:", got);
        throw e;
      }
      expect(parsed['image']).to.have.string('data:image/svg+xml;base64,');

      parsed['image'] = expected['image'];
      expect(parsed).to.deep.equal(expected);
    }
  });

  it("should unwrap", async function() {
    const {account1, account2} = accounts;
    const idx = await buyAd(account1);

    {
      // Wrap from account1 to account2
      const [salt, precomputeAddress] = await KNFT.connect(account1).precompute(idx, await account1.getAddress());
      await KH.connect(account1).setAdOwner(idx, precomputeAddress);
      await KNFT.connect(account1).wrap(idx, await account1.getAddress());

      expect(await KNFT.connect(account2).balanceOf(await account1.getAddress())).to.equal(1);
      expect(await KNFT.connect(account2).tokenURI(idx)).to.not.equal("");
    }

    await expect(
      KNFT.connect(account2).unwrap(idx, await account2.getAddress())
    ).to.be.revertedWith("KetherNFT: unwrap for sender that is not owner")

    await KNFT.connect(account1).unwrap(idx, await account2.getAddress());

    {
      const [addr] = await KH.ads(idx);
      expect(addr).to.equal(await account2.getAddress());
    }

    // Confirm that the NFT is gone
    expect(await KNFT.connect(account2).balanceOf(await account2.getAddress())).to.equal(0);
    await expect(
      KNFT.connect(account2).tokenURI(idx)
    ).to.be.revertedWith("KetherNFT: tokenId does not exist");

    {
      // Wrap again, from account2 to account2
      const [salt, precomputeAddress] = await KNFT.connect(account2).precompute(idx, await account2.getAddress());
      await KH.connect(account2).setAdOwner(idx, precomputeAddress);
      await KNFT.connect(account2).wrap(idx, await account2.getAddress());

      expect(await KNFT.connect(account2).balanceOf(await account2.getAddress())).to.equal(1);
      expect(await KNFT.connect(account2).tokenURI(idx)).to.not.equal("");
    }
  });

  it("should generate correct precompute address and salt", async function() {
    const idx = 42;
    const account = accounts.account1;

    const [salt, precomputeAddress] = await KNFT.connect(account).precompute(42, await account.getAddress());

    {
      // Validate salt generation
      const expected = ethers.utils.sha256(await account.getAddress());
      expect(salt).to.equal(expected);
    }

    const wrappedPayload = KH.interface.encodeFunctionData("setAdOwner", [idx, KNFT.address]); // Confirmed this matches KetherNFT._wrapPayload

    {
      // Validate wrapped payload encoding
      const expected = KH.interface.encodeFunctionData('setAdOwner', [idx, KNFT.address]);
      expect(wrappedPayload).to.equal(expected);
    }

    const bytecode = ethers.utils.hexlify(
      ethers.utils.concat([
        FlashEscrow.bytecode,
        FlashEscrow.interface.encodeDeploy([KH.address, wrappedPayload]),
        // Same as: ethers.utils.defaultAbiCoder.encode(['address', 'bytes'], [KH.address, wrappedPayload]),
      ]));

    {
      // Validate full create2 address precompute
      const expected = ethers.utils.getCreate2Address(KNFT.address, salt, ethers.utils.keccak256(bytecode));
      expect(precomputeAddress).to.equal(expected);
    }
  });

  it("should not allow eth receive", async function() {
    const {account1} = accounts;

    expect(
      account1.sendTransaction({
        to: KNFT.address,
        value: ethers.utils.parseEther("42.0")
      })
    ).to.be.reverted;
  });

  it('should be buyable as NFT', async function() {
    const {account1} = accounts;

    const txn = await KNFT.connect(account1).buy(42, 69, 10, 10, {value: oneHundredCellPrice });
    const receipt = await txn.wait();
    const event = receipt.events.pop();
    const [idx] = event.args;

    await KNFT.connect(account1).publish(idx, "link", "image", "title", false);

    {
      const [addr,,,,,link,image,title] = await KH.ads(idx);
      expect(addr).to.equal(KNFT.address);
      expect(link).to.equal("link");
      expect(image).to.equal("image");
      expect(title).to.equal("title");
    }

    // Unwrap
    await KNFT.connect(account1).unwrap(idx, await account1.getAddress());
    {
      const [addr] = await KH.ads(idx);
      expect(addr).to.equal(await account1.getAddress());
    }
  });

  it("should be recoverable if ownership is transferred without minting", async function() {
    const {owner, account1} = accounts;

    // Buy an ad
    const idx = await buyAd(account1);
    expect(idx).to.equal(0);

    // One more
    const idx2 = await buyAd(account1, x=20, y=20);
    expect(idx2).to.equal(1);

    // Oopsie, transferred the ad to the KetherNFT contract rather than the commitment address (don't do this!)
    await KH.connect(account1).setAdOwner(idx, KNFT.address);
    {
      const [addr,..._] = await KH.ads(idx);
      expect(addr).to.equal(KNFT.address);
    }

    // Admin can't transfer non-stuck ads
    expect(
      KNFT.connect(owner).adminRecoverTrapped(idx2, await owner.getAddress())
    ).to.be.revertedWith("KetherNFT: ad not held by contract");

    // Benevolent admin steps in and transfers trapped ad back
    await KNFT.connect(owner).adminRecoverTrapped(idx, await account1.getAddress());

    {
      const [addr,..._] = await KH.ads(idx);
      expect(addr).to.equal(await account1.getAddress());
    }
  });
});

<commit_msg>tests: KetherNFT test rando trying adminRecoveryTrapped<commit_after>const { expect } = require('chai');

const weiPixelPrice = ethers.utils.parseUnits("0.001", "ether");
const pixelsPerCell = ethers.BigNumber.from(100);
const oneHundredCellPrice = pixelsPerCell.mul(weiPixelPrice).mul(100);

describe('KetherNFT', function() {
  let KetherHomepage, KetherNFT, KetherNFTRender, FlashEscrow;
  let accounts, KH, KNFT, KNFTrender;

  beforeEach(async() => {
    // NOTE: We're using V2 here because it's ported to newer solidity so we can debug more easily. It should also work with V1.
    KetherHomepage = await ethers.getContractFactory("KetherHomepageV2");
    KetherNFT = await ethers.getContractFactory("KetherNFT");
    KetherNFTRender = await ethers.getContractFactory("KetherNFTRender");
    FlashEscrow = await ethers.getContractFactory("FlashEscrow");

    const [owner, withdrawWallet, metadataSigner, account1, account2, account3] = await ethers.getSigners();
    accounts = {owner, withdrawWallet, metadataSigner, account1, account2, account3};

    KH = await KetherHomepage.deploy(await owner.getAddress(), await withdrawWallet.getAddress());
    KNFTrender = await KetherNFTRender.deploy();
    KNFT = await KetherNFT.deploy(KH.address, KNFTrender.address);
  });

  const buyAd = async function(account, x=0, y=0, width=10, height=10, link="link", image="image", title="title", NSFW=false, value=oneHundredCellPrice) {
    const txn = await KH.connect(account).buy(x, y, width, height, { value: value });
    const receipt = await txn.wait();
    const event = receipt.events.pop();
    const [idx] = event.args;
    await KH.connect(account).publish(idx, link, image, title, false);
    return idx;
  }

  it("should wrap ad with KetherNFT", async function() {
    const {account1} = accounts;

    // Buy an ad
    const idx = await buyAd(account1);
    expect(idx).to.equal(0);

    const [salt, precomputeAddress] = await KNFT.connect(account1).precompute(idx, await account1.getAddress());

    // Set owner to precommitted wrap address
    await KH.connect(account1).setAdOwner(idx, precomputeAddress);

    // Wrap ad
    await KNFT.connect(account1).wrap(idx, await account1.getAddress());

    // Confirm owner can't publish directly anymore
    await expect(
      KH.connect(account1).publish(idx, "foo", "bar", "baaz", false)
    ).to.be.reverted;

    {
      const [addr,,,,,link,image,title] = await KH.ads(idx);
      expect(addr).to.equal(KNFT.address);
      expect(link).to.equal("link");
      expect(image).to.equal("image");
      expect(title).to.equal("title");
    }

    // Confirm NFT owner can publish through the NFT
    await KNFT.connect(account1).publish(idx, "foo2", "baar2", "baaz2", false);

    {
      const [addr,,,,,link,image,title] = await KH.ads(idx);
      expect(addr).to.equal(KNFT.address);
      expect(link).to.equal("foo2");
      expect(image).to.equal("baar2");
      expect(title).to.equal("baaz2");
    }

  });

  it('should wrap to non-owner', async function() {
    const {account1, account2, account3} = accounts;
    const idx = await buyAd(account1);
    {
      const otherIdx = await buyAd(account2, x=20, y=20);
      expect(otherIdx).to.not.equal(idx);
    }

    // Generate precommit to wrap idx (owned by account1) to ownership of account2.
    // Note that account2 can generate this precommit, as in this case.
    const [salt, precomputeAddress] = await KNFT.connect(account2).precompute(idx, await account2.getAddress());

    // Non-owner cannot change the ad ownership to precomputed address.
    await expect(
      KH.connect(account2).setAdOwner(idx, precomputeAddress)
    ).to.be.reverted;

    // Non-owner cannot wrap, either
    await expect(
      KNFT.connect(account2).wrap(idx, await account2.getAddress())
    ).to.be.revertedWith("KetherNFT: owner needs to be the correct precommitted address");

    // Same precomputed transaction is fine for the owner to run (precommit wrap to account2)
    await KH.connect(account1).setAdOwner(idx, precomputeAddress);

    // Rando can't wrap to themselves
    await expect(
      KNFT.connect(account3).wrap(idx, await account3.getAddress())
    ).to.be.revertedWith("KetherNFT: owner needs to be the correct precommitted address");

    // Rando can't wrap to owner (since the precommit is for account2)
    await expect(
      KNFT.connect(account3).wrap(idx, await account1.getAddress())
    ).to.be.revertedWith("KetherNFT: owner needs to be the correct precommitted address");

    // Rando *can* wrap to the precommitted account2
    // FIXME: Is this desirable? It allows non-owner to pay for the wrap, which is nice.
    await KNFT.connect(account3).wrap(idx, await account2.getAddress())

  });

  it("should generate tokenURI based on wrapped ad", async function() {
    const {account1, account2} = accounts;
    const idx = await buyAd(account1, 1, 2, 3, 4);
    const [salt, precomputeAddress] = await KNFT.connect(account1).precompute(idx, await account1.getAddress());

    await KH.connect(account1).publish(idx, "link", "image", "title", false);
    await KH.connect(account1).setAdOwner(idx, precomputeAddress);

    await KNFT.connect(account1).wrap(idx, await account1.getAddress());

    {
      const expected = {
        "name": "Thousand Ether Homepage Ad: 30x40 at [10,20]",
        "description": "This NFT represents an ad unit on thousandetherhomepage.com, the owner of the NFT controls the content of this ad unit.",
        "external_url": "https://thousandetherhomepage.com",
        "image": "omitted for testing", // TODO: Test image elsewhere
        "properties": {"width": 30, "height": 40},
      };
      const r = await KNFT.connect(account1).tokenURI(idx);
      const prefix = 'data:application/json;base64';
      expect(r).to.to.have.string(prefix);

      const got = Buffer.from(r.slice(prefix.length), 'base64').toString();

      let parsed;
      try {
        parsed = JSON.parse(got);
      } catch (e) {
        console.log("Failed to parse:", got);
        throw e;
      }
      expect(parsed['image']).to.have.string('data:image/svg+xml;base64,');

      parsed['image'] = expected['image'];
      expect(parsed).to.deep.equal(expected);
    }
  });

  it("should unwrap", async function() {
    const {account1, account2} = accounts;
    const idx = await buyAd(account1);

    {
      // Wrap from account1 to account2
      const [salt, precomputeAddress] = await KNFT.connect(account1).precompute(idx, await account1.getAddress());
      await KH.connect(account1).setAdOwner(idx, precomputeAddress);
      await KNFT.connect(account1).wrap(idx, await account1.getAddress());

      expect(await KNFT.connect(account2).balanceOf(await account1.getAddress())).to.equal(1);
      expect(await KNFT.connect(account2).tokenURI(idx)).to.not.equal("");
    }

    await expect(
      KNFT.connect(account2).unwrap(idx, await account2.getAddress())
    ).to.be.revertedWith("KetherNFT: unwrap for sender that is not owner")

    await KNFT.connect(account1).unwrap(idx, await account2.getAddress());

    {
      const [addr] = await KH.ads(idx);
      expect(addr).to.equal(await account2.getAddress());
    }

    // Confirm that the NFT is gone
    expect(await KNFT.connect(account2).balanceOf(await account2.getAddress())).to.equal(0);
    await expect(
      KNFT.connect(account2).tokenURI(idx)
    ).to.be.revertedWith("KetherNFT: tokenId does not exist");

    {
      // Wrap again, from account2 to account2
      const [salt, precomputeAddress] = await KNFT.connect(account2).precompute(idx, await account2.getAddress());
      await KH.connect(account2).setAdOwner(idx, precomputeAddress);
      await KNFT.connect(account2).wrap(idx, await account2.getAddress());

      expect(await KNFT.connect(account2).balanceOf(await account2.getAddress())).to.equal(1);
      expect(await KNFT.connect(account2).tokenURI(idx)).to.not.equal("");
    }
  });

  it("should generate correct precompute address and salt", async function() {
    const idx = 42;
    const account = accounts.account1;

    const [salt, precomputeAddress] = await KNFT.connect(account).precompute(42, await account.getAddress());

    {
      // Validate salt generation
      const expected = ethers.utils.sha256(await account.getAddress());
      expect(salt).to.equal(expected);
    }

    const wrappedPayload = KH.interface.encodeFunctionData("setAdOwner", [idx, KNFT.address]); // Confirmed this matches KetherNFT._wrapPayload

    {
      // Validate wrapped payload encoding
      const expected = KH.interface.encodeFunctionData('setAdOwner', [idx, KNFT.address]);
      expect(wrappedPayload).to.equal(expected);
    }

    const bytecode = ethers.utils.hexlify(
      ethers.utils.concat([
        FlashEscrow.bytecode,
        FlashEscrow.interface.encodeDeploy([KH.address, wrappedPayload]),
        // Same as: ethers.utils.defaultAbiCoder.encode(['address', 'bytes'], [KH.address, wrappedPayload]),
      ]));

    {
      // Validate full create2 address precompute
      const expected = ethers.utils.getCreate2Address(KNFT.address, salt, ethers.utils.keccak256(bytecode));
      expect(precomputeAddress).to.equal(expected);
    }
  });

  it("should not allow eth receive", async function() {
    const {account1} = accounts;

    expect(
      account1.sendTransaction({
        to: KNFT.address,
        value: ethers.utils.parseEther("42.0")
      })
    ).to.be.reverted;
  });

  it('should be buyable as NFT', async function() {
    const {account1} = accounts;

    const txn = await KNFT.connect(account1).buy(42, 69, 10, 10, {value: oneHundredCellPrice });
    const receipt = await txn.wait();
    const event = receipt.events.pop();
    const [idx] = event.args;

    await KNFT.connect(account1).publish(idx, "link", "image", "title", false);

    {
      const [addr,,,,,link,image,title] = await KH.ads(idx);
      expect(addr).to.equal(KNFT.address);
      expect(link).to.equal("link");
      expect(image).to.equal("image");
      expect(title).to.equal("title");
    }

    // Unwrap
    await KNFT.connect(account1).unwrap(idx, await account1.getAddress());
    {
      const [addr] = await KH.ads(idx);
      expect(addr).to.equal(await account1.getAddress());
    }
  });

  it("should be recoverable if ownership is transferred without minting", async function() {
    const {owner, account1} = accounts;

    // Buy an ad
    const idx = await buyAd(account1);
    expect(idx).to.equal(0);

    // One more
    const idx2 = await buyAd(account1, x=20, y=20);
    expect(idx2).to.equal(1);

    // Oopsie, transferred the ad to the KetherNFT contract rather than the commitment address (don't do this!)
    await KH.connect(account1).setAdOwner(idx, KNFT.address);
    {
      const [addr,..._] = await KH.ads(idx);
      expect(addr).to.equal(KNFT.address);
    }

    // Admin can't transfer non-stuck ads
    expect(
      KNFT.connect(owner).adminRecoverTrapped(idx2, await owner.getAddress())
    ).to.be.revertedWith("KetherNFT: ad not held by contract");

    // Rando can't call admin recover
    expect(
      KNFT.connect(account1).adminRecoverTrapped(idx, await account1.getAddress())
    ).to.be.revertedWith("Ownable: caller is not the owner");

    // Benevolent admin steps in and transfers trapped ad back
    await KNFT.connect(owner).adminRecoverTrapped(idx, await account1.getAddress());

    {
      const [addr,..._] = await KH.ads(idx);
      expect(addr).to.equal(await account1.getAddress());
    }
  });
});

<|endoftext|>


<commit_before>Ext.define("DARPA.EntityGraphPanel", {
	extend: "DARPA.AbstractGraphPanel",

	constructor: function(config) {
		var self = this;

		this.GraphVis = new CytoscapeGraphVis(config.id + "-ENTcygraph");

		var graphSettings = Ext.create("DARPA.GraphSettings", {
			id: config.id + "-Settings"
		});

		graphSettings.setGraph(self);

		var filterSettings = Ext.create("DARPA.FilterSettings", {
			id: config.id + "-Filter"
		});

		filterSettings.setAdditionalFields([{
			dispFieldName: "Node Color", 		dispFieldType: "dropdown", 	dispFieldWidth: 100,
			dispFieldChoices: "getfromnode",	dataSourceType: "nodes", 	dataSourceField: "color"
		}, {
			dispFieldName: "Node Name", 		dispFieldType: "dropdown", 	dispFieldWidth: 100,
			dispFieldChoices: "getfromnode", 	dataSourceType: "nodes", 	dataSourceField: "name"
		}, {
			dispFieldName: "Identifier Type", 	dispFieldType: "dropdown", 	dispFieldWidth: 100,
			dispFieldChoices: "getfromnode", 	dataSourceType: "nodes", 	dataSourceField: "idType"
		}, {
			dispFieldName: "getfromkey", 	dispFieldType: "text", 		dispFieldWidth: 100,
			dispFieldChoices: "", 			dataSourceType: "nodes", 	dataSourceField: "attrs"
		}, {
			dispFieldName: "Amount", 	dispFieldType: "text", 		dispFieldWidth: 100,
			dispFieldChoices: "", 		dataSourceType: "edges", 	dataSourceField: "amount"
		}, {
			dispFieldName: "getfromkey", dispFieldType: "text", dispFieldWidth: 100,
			dispFieldChoices: "", dataSourceType: "edges", dataSourceField: "attrs"
		}]);

		filterSettings.setGraph(self);
		filterSettings.enableTimeFilter(false); // TODO renable based on graph type or if data is temporally-based
		filterSettings.setSearchFieldLabel("Identifiers(s)");

		var graphContainer = Ext.create("Ext.Container", {
			width: 'auto',
			height: 'auto',
			id: config.id + "-ENTcygraph",
			flex: 1
		});

		var settingsPanel = Ext.create("Ext.tab.Panel", {
			width: 320,
			height: 'auto',
			collapsible: true,
			collapseDirection: 'right',
			listeners: {
				collapse: function(e) {
					self.GraphVis.resize();
				},
				expand: function(e) {
					self.GraphVis.resize();
				}
			},
			items: [
				Ext.create("Ext.panel.Panel", {
					title: "DETAILS/ACTIONS",
					layout: 'fit',
					items: [
						Ext.create("DARPA.EntityNodeDisplay", {
							id: config.id + "-NodeDisplay",
							height: "auto"
						})
					]
				}),
				Ext.create("Ext.panel.Panel", {
					title: "SETTINGS/FILTER",
					items: [
						graphSettings,
						Ext.create("Ext.panel.Panel", {
							title: "FILTER",
							collapsible: false,
							items: filterSettings
						})
					] // END settings/filter items
				}),
				Ext.create("DARPA.AdvancedQueryPanel", {
					title: "ADV. QUERY",
					id: config.id + "-AdvQuery",
					graphRef: self.GraphVis
				})
			] // END settings panel items
		});

		this.items = [graphContainer, settingsPanel];
		this.callParent(arguments);
	},

	getNodeDisplay : function() {
		var tabPanel = this.items.items[1];
		var tabScreen = tabPanel.items.items[0];
		return tabScreen.items.items[0];
	},

	getSettings : function() {
		var tabPanel = this.items.items[1];
		var settingsPanel = tabPanel.items.items[1];
		return settingsPanel.items.items[0];
	},

	afterLayout: function() {
		var self = this;
		if (self.GraphVis.getGv() == null) {
			var config = {/* configs go here */};
			self.GraphVis.init(config, self, function() {
				//self.showjson(self.prevLoadParams.value);
			}, true);
		}
		this.callParent(arguments); // ?
	},

	load: function(custno, useSaved) {
		var self = this;

		// TODO write catch to make sure variable useSaved is a boolean

		var graphStore = self.graphStore;
		var hops = self.getSettings().getMaxHops();
		var degree = parseInt(hops);// + 1; //djue

		self.json = null;
		self.prevLoadParams.searchValue = self.prevLoadParams.value = self.prevLoadParams.prevValue = custno;

		self.getProgressBar().wait({
        	interval: 1000,
        	duration: 90000,
        	increment: 10,
        	text: "Searching..."
        });

		graphStore.proxy.extraParams.degree = degree;
		graphStore.proxy.extraParams.useSaved = useSaved;
		graphStore.proxy.url = Config.entityGraphCSUrl + 'customer/' + custno;

		graphStore.load({
			scope: this, // ?
			callback: function(records, operation, success) {

				self.getProgressBar().reset();

				if (success == false || records == null || records.length == 0) {
					if (success == false) self.setStatus("SERVER ERROR REQUESTING GRAPH", 0);
					else if (records == null) self.setStatus("SERVER RETURNED NULL GRAPH", 0);
					else if (records.length == 0) self.setStatus("SERVER RETURNED EMPTY GRAPH", 0);
					self.clear();
					return;
				}

				var retJSON = records[0].raw;

				self.json = retJSON;
				self.GraphVis.setUserName(retJSON.userName);
				self.setStatus(retJSON.strStatus, 1);

				self.legendJSON = retJSON.legend;
				if (typeof self.legendJSON == "string") {
					self.legendJSON = Ext.decode(self.legendJSON);
				} // else assume JSON

				var loadGraph = function(scope, useSaved) {
					if (scope.GraphVis.getGv() != null) {
						//scope.clear();
						scope.showjson(scope.prevLoadParams.value, useSaved);
						scope.getNodeDisplay().updateLegend(scope.legendJSON, "EntityGraph");
					}
				};

				if (typeof self.json == "undefined") {
					//TODO error notice
					return;
				}

				var THRESHOLD = 300;
				var useSaved = false;

				if (self.json.nodes.length < THRESHOLD) {
					useSaved = self.json.nodes[0].position != null;
					loadGraph(self, useSaved);
				} else {
					useSaved = self.json.nodes[0].position != null;
					Ext.Msg.confirm(
						"Loading a Large Graph",
						"The expected graph contains over " + THRESHOLD + " nodes.  This may take a moment to render.  Do you wish to wait?\n" +
						"If not, the graph will not render.",
						function(ans) {
							if (ans == 'yes') {
								loadGraph(self, useSaved);
							}
						}
					);
				}
			}
		});
	},

	loadOneHop: function(node, intype) {
		var self = this;
		var graphStore = self.graphStore;
		var s = self.getSettings();
		var maxNewCallsAlertThresh = 30; // Adjust as needed

		graphStore.proxy.extraParams.degree = 1; // labelled hops. only 1 hop out from this node
		graphStore.proxy.extraParams.maxEdgesPerNode = s.getMaxEdgesPerNode();
		graphStore.proxy.extraParams.maxNodes = s.getMaxNodes();
		if (graphStore.proxy.extraParams.maxNodes > 200) {
			graphStore.proxy.extraParams.maxNodes = 200; // hard limit for this case
		}
		if (maxNewCallsAlertThresh > graphStore.proxy.extraParams.maxEdgesPerNode) {
			maxNewCallsAlertThresh = graphStore.proxy.extraParams.maxEdgesPerNode;
		}
		graphStore.proxy.extraParams.minWeight = s.getMinWeight();

		if (intype == null || intype.length == 0) {
			intype = "customer";
		}
		graphStore.proxy.extraParams.Type = intype;

		// FIXME: REST services don't seem to like properly encoded symbols. Re-address when time permits
		// In the meantime, use regex to replace all / characters with a space
		/* graphStore.proxy.url = Config.entityGraphUrl + intype + "/" + encodeURIComponent(node.data("name")); */
		graphStore.proxy.url = Config.entityGraphUrl + intype + "/" + node.data("name").replace(/\//g, " ");

		self.json1Hop = null; // prevents us from trying to display the previous graph if we switch to this tab before we have fully loaded the new graph
		self.json1HopNode = node;

		self.getProgressBar().wait({
        	interval: 1000,
        	duration: 90000,
        	increment: 10,
        	text: "Expanding..."
        });

		graphStore.load({
			scope : this, // ?
			callback : function(records, operation, success) {

				self.getProgressBar().reset();

				if (success == false || records == null || records.length == 0) {
					if (success == false) self.setStatus("SERVER ERROR REQUESTING GRAPH", 0);
					else if (records == null) self.setStatus("SERVER RETURNED NULL GRAPH", 0);
					else if (records.length == 0) self.setStatus("SERVER RETURNED EMPTY GRAPH", 0);
					self.clear();
					return;
				}

				var retJSON = records[0].raw;

				self.json = retJSON;
				self.setStatus(retJSON.strStatus, 1);

				self.legendJSON = retJSON.legend;
				if (typeof self.legendJSON == "string") {
					self.legendJSON = Ext.decode(self.legendJSON);
				} // else assume JSON

				// should be self.json.nodes.length
				if (self.json.length > maxNewCallsAlertThresh) {
					Ext.Msg.confirm(
						'Confirm',
						'This value has more than ' + maxNewCallsAlertThresh + ' items and may clutter the display. Do you want to continue displaying it?',
						function(ans) {
							if (ans == 'yes') {
								self.GraphVis.expand(self.json, node);
								self.getNodeDisplay().updateLegend(self.legendJSON, "EntityGraph");
							}
						}
					);
				} else {
					self.GraphVis.expand(self.json, node);
					self.getNodeDisplay().updateLegend(self.legendJSON, "EntityGraph");
				}
			}
		});
	},

	nodeClick: function(node) {
		var self = this;
		self.currentNode = node;
		var nodeDisp = self.getNodeDisplay();
		nodeDisp.showNodeAttrs(node);
		var type = node.data().idType;
		var isEntity = false;
		if (typeof isPivotableType == "function") {
			// isPivotableType is a global function found in .html
			isEntity = isPivotableType(type);
		} else {
			isEntity = (type == 'customer' || type == 'LENDER' || type == 'BORROWER');
		}

		var len = this.GraphVis.getSelectedNodes().length;
		var msg = "" + len + " selected node" + ((len > 1) ? "s" : "");
		this.setStatus(msg, 1);

		nodeDisp.enablePivot(isEntity && len == 1);
		nodeDisp.enableShow(type == 'account' || isEntity);
		nodeDisp.enableHide(true);
	},

	edgeClick: function(edge) {
		var nodeDisp = this.getNodeDisplay();
		var d = edge.data();
		if (d && d.attrs) {
			nodeDisp.showEdgeAttrs(edge);
		}
	},

	edgeRightClick: function(edge) {
		// do nothing
	}
});<commit_msg>Hide the Advanced Query Panel until it is completely implemented<commit_after>Ext.define("DARPA.EntityGraphPanel", {
	extend: "DARPA.AbstractGraphPanel",

	constructor: function(config) {
		var self = this;

		this.GraphVis = new CytoscapeGraphVis(config.id + "-ENTcygraph");

		var graphSettings = Ext.create("DARPA.GraphSettings", {
			id: config.id + "-Settings"
		});

		graphSettings.setGraph(self);

		var filterSettings = Ext.create("DARPA.FilterSettings", {
			id: config.id + "-Filter"
		});

		filterSettings.setAdditionalFields([{
			dispFieldName: "Node Color", 		dispFieldType: "dropdown", 	dispFieldWidth: 100,
			dispFieldChoices: "getfromnode",	dataSourceType: "nodes", 	dataSourceField: "color"
		}, {
			dispFieldName: "Node Name", 		dispFieldType: "dropdown", 	dispFieldWidth: 100,
			dispFieldChoices: "getfromnode", 	dataSourceType: "nodes", 	dataSourceField: "name"
		}, {
			dispFieldName: "Identifier Type", 	dispFieldType: "dropdown", 	dispFieldWidth: 100,
			dispFieldChoices: "getfromnode", 	dataSourceType: "nodes", 	dataSourceField: "idType"
		}, {
			dispFieldName: "getfromkey", 	dispFieldType: "text", 		dispFieldWidth: 100,
			dispFieldChoices: "", 			dataSourceType: "nodes", 	dataSourceField: "attrs"
		}, {
			dispFieldName: "Amount", 	dispFieldType: "text", 		dispFieldWidth: 100,
			dispFieldChoices: "", 		dataSourceType: "edges", 	dataSourceField: "amount"
		}, {
			dispFieldName: "getfromkey", dispFieldType: "text", dispFieldWidth: 100,
			dispFieldChoices: "", dataSourceType: "edges", dataSourceField: "attrs"
		}]);

		filterSettings.setGraph(self);
		filterSettings.enableTimeFilter(false); // TODO renable based on graph type or if data is temporally-based
		filterSettings.setSearchFieldLabel("Identifiers(s)");

		var graphContainer = Ext.create("Ext.Container", {
			width: 'auto',
			height: 'auto',
			id: config.id + "-ENTcygraph",
			flex: 1
		});

		var settingsPanel = Ext.create("Ext.tab.Panel", {
			width: 320,
			height: 'auto',
			collapsible: true,
			collapseDirection: 'right',
			listeners: {
				collapse: function(e) {
					self.GraphVis.resize();
				},
				expand: function(e) {
					self.GraphVis.resize();
				}
			},
			items: [
				Ext.create("Ext.panel.Panel", {
					title: "DETAILS/ACTIONS",
					layout: 'fit',
					items: [
						Ext.create("DARPA.EntityNodeDisplay", {
							id: config.id + "-NodeDisplay",
							height: "auto"
						})
					]
				}),
				Ext.create("Ext.panel.Panel", {
					title: "SETTINGS/FILTER",
					items: [
						graphSettings,
						Ext.create("Ext.panel.Panel", {
							title: "FILTER",
							collapsible: false,
							items: filterSettings
						})
					] // END settings/filter items
				}),
				/* Hide until it is complete *//*
				Ext.create("DARPA.AdvancedQueryPanel", {
					title: "ADV. QUERY",
					id: config.id + "-AdvQuery",
					graphRef: self.GraphVis
				})
				*/
			] // END settings panel items
		});

		this.items = [graphContainer, settingsPanel];
		this.callParent(arguments);
	},

	getNodeDisplay : function() {
		var tabPanel = this.items.items[1];
		var tabScreen = tabPanel.items.items[0];
		return tabScreen.items.items[0];
	},

	getSettings : function() {
		var tabPanel = this.items.items[1];
		var settingsPanel = tabPanel.items.items[1];
		return settingsPanel.items.items[0];
	},

	afterLayout: function() {
		var self = this;
		if (self.GraphVis.getGv() == null) {
			var config = {/* configs go here */};
			self.GraphVis.init(config, self, function() {
				//self.showjson(self.prevLoadParams.value);
			}, true);
		}
		this.callParent(arguments); // ?
	},

	load: function(custno, useSaved) {
		var self = this;

		// TODO write catch to make sure variable useSaved is a boolean

		var graphStore = self.graphStore;
		var hops = self.getSettings().getMaxHops();
		var degree = parseInt(hops);// + 1; //djue

		self.json = null;
		self.prevLoadParams.searchValue = self.prevLoadParams.value = self.prevLoadParams.prevValue = custno;

		self.getProgressBar().wait({
        	interval: 1000,
        	duration: 90000,
        	increment: 10,
        	text: "Searching..."
        });

		graphStore.proxy.extraParams.degree = degree;
		graphStore.proxy.extraParams.useSaved = useSaved;
		graphStore.proxy.url = Config.entityGraphCSUrl + 'customer/' + custno;

		graphStore.load({
			scope: this, // ?
			callback: function(records, operation, success) {

				self.getProgressBar().reset();

				if (success == false || records == null || records.length == 0) {
					if (success == false) self.setStatus("SERVER ERROR REQUESTING GRAPH", 0);
					else if (records == null) self.setStatus("SERVER RETURNED NULL GRAPH", 0);
					else if (records.length == 0) self.setStatus("SERVER RETURNED EMPTY GRAPH", 0);
					self.clear();
					return;
				}

				var retJSON = records[0].raw;

				self.json = retJSON;
				self.GraphVis.setUserName(retJSON.userName);
				self.setStatus(retJSON.strStatus, 1);

				self.legendJSON = retJSON.legend;
				if (typeof self.legendJSON == "string") {
					self.legendJSON = Ext.decode(self.legendJSON);
				} // else assume JSON

				var loadGraph = function(scope, useSaved) {
					if (scope.GraphVis.getGv() != null) {
						//scope.clear();
						scope.showjson(scope.prevLoadParams.value, useSaved);
						scope.getNodeDisplay().updateLegend(scope.legendJSON, "EntityGraph");
					}
				};

				if (typeof self.json == "undefined") {
					//TODO error notice
					return;
				}

				var THRESHOLD = 300;
				var useSaved = false;

				if (self.json.nodes.length < THRESHOLD) {
					useSaved = self.json.nodes[0].position != null;
					loadGraph(self, useSaved);
				} else {
					useSaved = self.json.nodes[0].position != null;
					Ext.Msg.confirm(
						"Loading a Large Graph",
						"The expected graph contains over " + THRESHOLD + " nodes.  This may take a moment to render.  Do you wish to wait?\n" +
						"If not, the graph will not render.",
						function(ans) {
							if (ans == 'yes') {
								loadGraph(self, useSaved);
							}
						}
					);
				}
			}
		});
	},

	loadOneHop: function(node, intype) {
		var self = this;
		var graphStore = self.graphStore;
		var s = self.getSettings();
		var maxNewCallsAlertThresh = 30; // Adjust as needed

		graphStore.proxy.extraParams.degree = 1; // labelled hops. only 1 hop out from this node
		graphStore.proxy.extraParams.maxEdgesPerNode = s.getMaxEdgesPerNode();
		graphStore.proxy.extraParams.maxNodes = s.getMaxNodes();
		if (graphStore.proxy.extraParams.maxNodes > 200) {
			graphStore.proxy.extraParams.maxNodes = 200; // hard limit for this case
		}
		if (maxNewCallsAlertThresh > graphStore.proxy.extraParams.maxEdgesPerNode) {
			maxNewCallsAlertThresh = graphStore.proxy.extraParams.maxEdgesPerNode;
		}
		graphStore.proxy.extraParams.minWeight = s.getMinWeight();

		if (intype == null || intype.length == 0) {
			intype = "customer";
		}
		graphStore.proxy.extraParams.Type = intype;

		// FIXME: REST services don't seem to like properly encoded symbols. Re-address when time permits
		// In the meantime, use regex to replace all / characters with a space
		/* graphStore.proxy.url = Config.entityGraphUrl + intype + "/" + encodeURIComponent(node.data("name")); */
		graphStore.proxy.url = Config.entityGraphUrl + intype + "/" + node.data("name").replace(/\//g, " ");

		self.json1Hop = null; // prevents us from trying to display the previous graph if we switch to this tab before we have fully loaded the new graph
		self.json1HopNode = node;

		self.getProgressBar().wait({
        	interval: 1000,
        	duration: 90000,
        	increment: 10,
        	text: "Expanding..."
        });

		graphStore.load({
			scope : this, // ?
			callback : function(records, operation, success) {

				self.getProgressBar().reset();

				if (success == false || records == null || records.length == 0) {
					if (success == false) self.setStatus("SERVER ERROR REQUESTING GRAPH", 0);
					else if (records == null) self.setStatus("SERVER RETURNED NULL GRAPH", 0);
					else if (records.length == 0) self.setStatus("SERVER RETURNED EMPTY GRAPH", 0);
					self.clear();
					return;
				}

				var retJSON = records[0].raw;

				self.json = retJSON;
				self.setStatus(retJSON.strStatus, 1);

				self.legendJSON = retJSON.legend;
				if (typeof self.legendJSON == "string") {
					self.legendJSON = Ext.decode(self.legendJSON);
				} // else assume JSON

				// should be self.json.nodes.length
				if (self.json.length > maxNewCallsAlertThresh) {
					Ext.Msg.confirm(
						'Confirm',
						'This value has more than ' + maxNewCallsAlertThresh + ' items and may clutter the display. Do you want to continue displaying it?',
						function(ans) {
							if (ans == 'yes') {
								self.GraphVis.expand(self.json, node);
								self.getNodeDisplay().updateLegend(self.legendJSON, "EntityGraph");
							}
						}
					);
				} else {
					self.GraphVis.expand(self.json, node);
					self.getNodeDisplay().updateLegend(self.legendJSON, "EntityGraph");
				}
			}
		});
	},

	nodeClick: function(node) {
		var self = this;
		self.currentNode = node;
		var nodeDisp = self.getNodeDisplay();
		nodeDisp.showNodeAttrs(node);
		var type = node.data().idType;
		var isEntity = false;
		if (typeof isPivotableType == "function") {
			// isPivotableType is a global function found in .html
			isEntity = isPivotableType(type);
		} else {
			isEntity = (type == 'customer' || type == 'LENDER' || type == 'BORROWER');
		}

		var len = this.GraphVis.getSelectedNodes().length;
		var msg = "" + len + " selected node" + ((len > 1) ? "s" : "");
		this.setStatus(msg, 1);

		nodeDisp.enablePivot(isEntity && len == 1);
		nodeDisp.enableShow(type == 'account' || isEntity);
		nodeDisp.enableHide(true);
	},

	edgeClick: function(edge) {
		var nodeDisp = this.getNodeDisplay();
		var d = edge.data();
		if (d && d.attrs) {
			nodeDisp.showEdgeAttrs(edge);
		}
	},

	edgeRightClick: function(edge) {
		// do nothing
	}
});<|endoftext|>


<commit_before>exports.validationError = function ( message ) {
    return {
        err: null,
        type: 'validation',
        message: message
    };
};

exports.systemError = function ( err ) {
    return {
        err: err,
        type: 'system',
        message: "A system error occurred"
    };
};

exports.handleRouteError = function ( err, res ) {
    if ( err.type === 'validation' ) {
        return res.json( err, 400 );
    }
    if ( err.type === 'system' ) {
        return res.json( err, 500 );
    }
};
<commit_msg>Add authorization error utility<commit_after>exports.validationError = function ( message ) {
    return {
        err: null,
        type: 'validation',
        message: message
    };
};

exports.systemError = function ( err ) {
    return {
        err: err,
        type: 'system',
        message: "A system error occurred"
    };
};

exports.authorizationError = function ( message ) {
    return {
        err: null,
        type: 'authorization',
        message: message
    };
};

exports.handleRouteError = function ( err, res ) {
    if ( err.type === 'validation' ) {
        return res.json( err, 400 );
    }
    if ( err.type === 'system' ) {
        return res.json( err, 500 );
    }
    if ( err.type === 'authorization' ) {
        return res.json( err, 401 );
    }
};
<|endoftext|>


<commit_before>/*

 The MIT License (MIT)

 Copyright (c) 2014 SolDevelo

 Permission is hereby granted, free of charge, to any person obtaining a copy of
 this software and associated documentation files (the "Software"), to deal in
 the Software without restriction, including without limitation the rights to
 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 the Software, and to permit persons to whom the Software is furnished to do so,
 subject to the following conditions:

 The above copyright notice and this permission notice shall be included in all
 copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

 */

(function($) {
    "use strict";

    $.fn.multiselect = function(options) {
        // currently selected element === this
        // assigning it to a variable for use in inline functions
        var currentElement = this;
        var defaultOptions = {
            width: 500,
            minResultsHeight: 300,
            itemDisplayLimit: 25,
            objectAdded: null,
            objectRemoved: null
        };

        var constants = {
            groupingNames: {
                contacts: 'contacts',
                groups: 'groups',
                smartgroups: 'smartgroups'
            }
        };

        var helpers = {
            parseOptions: function(options, defaultOptions) {
                if (!options) {
                    return defaultOptions;
                }

                if (options.itemDisplayLimit === undefined) {
                    options.itemDisplayLimit = defaultOptions.itemDisplayLimit;
                }
                if (options.width === undefined) {
                    options.width = defaultOptions.width;
                }
                if (options.minResultsHeight === undefined) {
                    options.minResultsHeight = defaultOptions.minResultsHeight;
                }
                if (options.objectAdded === undefined) {
                    options.objectAdded = defaultOptions.objectAdded;
                }
                if (options.objectRemoved === undefined) {
                    options.objectRemoved = defaultOptions.objectRemoved;
                }

                return options;
            },
            getGroupingByName: function(name) {
                var grouping = null;
                if (multiSelector.results.length) {
                    for (var i = 0; i < multiSelector.results.length; i++) {
                        if (multiSelector.results[i].displayName === name) {
                            return multiSelector.results[i];
                        }
                    }
                }
                return grouping;
            },
            createGroupChildElement: function(group, child) {
                if (child === undefined || child === null) {
                    return null;
                }

                return $(document.createElement("li"))
                    .addClass("multiselector-list-item")
                    .text(child.name);
            },
            createGroupElement: function(group) {
                if (group === undefined || group === null) {
                    return null;
                }

                var groupElement = $(document.createElement("ul"));
                var groupNameElement = $(document.createElement("span")).text(group.displayName);
                var listItem = $(document.createElement("li"))
                    .append(groupNameElement)
                    .append(groupElement);

                if (group.customCssClass !== undefined) {
                    listItem.addClass(group.customCssClass);
                }

                for (var i = 0; i < group.members.length && i < multiSelector.options.itemDisplayLimit; i++) {
                    groupElement.append(helpers.createGroupChildElement(group, group.members[i]));
                }

                return listItem;
            },
            createInput: function() {
                return $(document.createElement("input"))
                    .addClass("multiselector-input")
                    .width(multiSelector.options.width);
            },
            createResultsDiv: function() {
                var ul = document.createElement("ul");

                return $(document.createElement("div"))
                    .addClass("multiselector-results")
                    .width(multiSelector.options.width)
                    .height(multiSelector.options.minResultsHeight)
                    .append(ul);
            },
            clearList: function() {
                multiSelector.results.length = 0;
                $(".multiselector-results").find("ul").empty();
            },
            addItemLimitInfoElement: function(count, max) {
                var message = "Showing " + count + " out of " + max + " matches";
                var infoElement = $(document.createElement("li"))
                    .addClass("multiselector-item-limit-info")
                    .text(message);

                $(".multiselector-results").find("ul").append(infoElement);
            }
        };

        var multiSelector = {
            version: "0.1-SNAPSHOT",
            targetElement: currentElement,
            options: helpers.parseOptions(options, defaultOptions),
            selected: {},
            results: {},
            previousText: ""
        };

        multiSelector.addObject = function(objectJson) {
            var newObject = JSON.parse(objectJson);

            if (objectAdded === "function") {
                objectAdded(newObject.id);
            }
        };

        multiSelector.removeObject = function(objectId) {
            if (objectRemoved === "function") {
                objectRemoved(objectId);
            }
        };

        multiSelector.toggleEnabled = function(objectId) {

        };

        multiSelector.getSelectedCount = function() {
            return this.selected.length;
        };

        multiSelector.returnSelectedObjects = function() {
            return this.selected;
        };

        //Make public getGroupingByName()
        multiSelector.getHelperFunctions = function() {
            return helpers;
        };

        // Function transforming the currently selected (with jQuery) element into the dropdown
        var transformElement = function() {

            var populateList = function() {
                if (multiSelector.results.length) {
                    var contacts = helpers.getGroupingByName(constants.groupingNames.contacts);
                    var groups = helpers.getGroupingByName(constants.groupingNames.groups);
                    var smartgroups = helpers.getGroupingByName(constants.groupingNames.smartgroups);

                    var contactsElement = resultsUl.append(helpers.createGroupElement(contacts)).find("ul");
                    if (contacts !== null && contacts.members.length > 0 &&
                        contactsElement.children().length >= multiSelector.options.itemDisplayLimit) {

                        helpers.addItemLimitInfoElement(contactsElement.children().length, contacts.members.length);
                        return;
                    }

                    resultsUl.append(helpers.createGroupElement(groups));
                    resultsUl.append(helpers.createGroupElement(smartgroups));
                }
            };

            var input = helpers.createInput();
            var results = helpers.createResultsDiv();
            var resultsUl = results.find("ul");

            var handleKeys = function(e) {
                var keyId = e.keyCode;
                var text = input.val();

                if (keyId === 13) {
                    helpers.clearList();
                    // Enter
                }
                else if (keyId === 188) {
                    helpers.clearList();
                    // Comma
                }

                if (text !== multiSelector.previousText)
                {
                    helpers.clearList();
                    if (text.length > 0 || multiSelector.previousText !== text) {
                        multiSelector.results = contactService.getFilteredMatches("", text);
                    }
                    populateList();
                }

                multiSelector.previousText = text;
            };

            currentElement.html(input);
            input.after(results);
            //Handling input
            input.keyup(handleKeys);
        };
        transformElement();

        // Return the selector object for public function access
        return multiSelector;
    };
}(jQuery));
<commit_msg>Fixed list not clearing when input is empty<commit_after>/*

 The MIT License (MIT)

 Copyright (c) 2014 SolDevelo

 Permission is hereby granted, free of charge, to any person obtaining a copy of
 this software and associated documentation files (the "Software"), to deal in
 the Software without restriction, including without limitation the rights to
 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 the Software, and to permit persons to whom the Software is furnished to do so,
 subject to the following conditions:

 The above copyright notice and this permission notice shall be included in all
 copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

 */

(function($) {
    "use strict";

    $.fn.multiselect = function(options) {
        // currently selected element === this
        // assigning it to a variable for use in inline functions
        var currentElement = this;
        var defaultOptions = {
            width: 500,
            minResultsHeight: 300,
            itemDisplayLimit: 25,
            objectAdded: null,
            objectRemoved: null
        };

        var constants = {
            groupingNames: {
                contacts: 'contacts',
                groups: 'groups',
                smartgroups: 'smartgroups'
            }
        };

        var helpers = {
            parseOptions: function(options, defaultOptions) {
                if (!options) {
                    return defaultOptions;
                }

                if (options.itemDisplayLimit === undefined) {
                    options.itemDisplayLimit = defaultOptions.itemDisplayLimit;
                }
                if (options.width === undefined) {
                    options.width = defaultOptions.width;
                }
                if (options.minResultsHeight === undefined) {
                    options.minResultsHeight = defaultOptions.minResultsHeight;
                }
                if (options.objectAdded === undefined) {
                    options.objectAdded = defaultOptions.objectAdded;
                }
                if (options.objectRemoved === undefined) {
                    options.objectRemoved = defaultOptions.objectRemoved;
                }

                return options;
            },
            getGroupingByName: function(name) {
                var grouping = null;
                if (multiSelector.results.length) {
                    for (var i = 0; i < multiSelector.results.length; i++) {
                        if (multiSelector.results[i].displayName === name) {
                            return multiSelector.results[i];
                        }
                    }
                }
                return grouping;
            },
            createGroupChildElement: function(group, child) {
                if (child === undefined || child === null) {
                    return null;
                }

                return $(document.createElement("li"))
                    .addClass("multiselector-list-item")
                    .text(child.name);
            },
            createGroupElement: function(group) {
                if (group === undefined || group === null) {
                    return null;
                }

                var groupElement = $(document.createElement("ul"));
                var groupNameElement = $(document.createElement("span")).text(group.displayName);
                var listItem = $(document.createElement("li"))
                    .append(groupNameElement)
                    .append(groupElement);

                if (group.customCssClass !== undefined) {
                    listItem.addClass(group.customCssClass);
                }

                for (var i = 0; i < group.members.length && i < multiSelector.options.itemDisplayLimit; i++) {
                    groupElement.append(helpers.createGroupChildElement(group, group.members[i]));
                }

                return listItem;
            },
            createInput: function() {
                return $(document.createElement("input"))
                    .addClass("multiselector-input")
                    .width(multiSelector.options.width);
            },
            createResultsDiv: function() {
                var ul = document.createElement("ul");

                return $(document.createElement("div"))
                    .addClass("multiselector-results")
                    .width(multiSelector.options.width)
                    .height(multiSelector.options.minResultsHeight)
                    .append(ul);
            },
            clearList: function() {
                multiSelector.results.length = 0;
                $(".multiselector-results").find("ul").empty();
            },
            addItemLimitInfoElement: function(count, max) {
                var message = "Showing " + count + " out of " + max + " matches";
                var infoElement = $(document.createElement("li"))
                    .addClass("multiselector-item-limit-info")
                    .text(message);

                $(".multiselector-results").find("ul").append(infoElement);
            }
        };

        var multiSelector = {
            version: "0.1-SNAPSHOT",
            targetElement: currentElement,
            options: helpers.parseOptions(options, defaultOptions),
            selected: {},
            results: {},
            previousText: ""
        };

        multiSelector.addObject = function(objectJson) {
            var newObject = JSON.parse(objectJson);

            if (objectAdded === "function") {
                objectAdded(newObject.id);
            }
        };

        multiSelector.removeObject = function(objectId) {
            if (objectRemoved === "function") {
                objectRemoved(objectId);
            }
        };

        multiSelector.toggleEnabled = function(objectId) {

        };

        multiSelector.getSelectedCount = function() {
            return this.selected.length;
        };

        multiSelector.returnSelectedObjects = function() {
            return this.selected;
        };

        //Make public getGroupingByName()
        multiSelector.getHelperFunctions = function() {
            return helpers;
        };

        // Function transforming the currently selected (with jQuery) element into the dropdown
        var transformElement = function() {

            var populateList = function() {
                if (multiSelector.results.length) {
                    var contacts = helpers.getGroupingByName(constants.groupingNames.contacts);
                    var groups = helpers.getGroupingByName(constants.groupingNames.groups);
                    var smartgroups = helpers.getGroupingByName(constants.groupingNames.smartgroups);

                    var contactsElement = resultsUl.append(helpers.createGroupElement(contacts)).find("ul");
                    if (contacts !== null && contacts.members.length > 0 &&
                        contactsElement.children().length >= multiSelector.options.itemDisplayLimit) {

                        helpers.addItemLimitInfoElement(contactsElement.children().length, contacts.members.length);
                        return;
                    }

                    resultsUl.append(helpers.createGroupElement(groups));
                    resultsUl.append(helpers.createGroupElement(smartgroups));
                }
            };

            var input = helpers.createInput();
            var results = helpers.createResultsDiv();
            var resultsUl = results.find("ul");

            var handleKeys = function(e) {
                var keyId = e.keyCode;
                var text = input.val();

                if (keyId === 13) {
                    helpers.clearList();
                    // Enter
                }
                else if (keyId === 188) {
                    helpers.clearList();
                    // Comma
                }

                if (text !== multiSelector.previousText)
                {
                    helpers.clearList();
                    if (text.length > 0) {
                        multiSelector.results = contactService.getFilteredMatches("", text);
                    }
                    populateList();
                }

                multiSelector.previousText = text;
            };

            currentElement.html(input);
            input.after(results);
            //Handling input
            input.keyup(handleKeys);
        };
        transformElement();

        // Return the selector object for public function access
        return multiSelector;
    };
}(jQuery));
<|endoftext|>


<commit_before>const webviews = require('webviews.js')
const focusMode = require('focusMode.js')
const urlParser = require('util/urlParser.js')
const readerView = require('readerView.js')
const dragula = require('dragula')

const tabEditor = require('navbar/tabEditor.js')
const progressBar = require('navbar/progressBar.js')
const permissionRequests = require('navbar/permissionRequests.js')

var lastTabDeletion = 0 // TODO get rid of this

const tabBar = {
  container: document.getElementById('tabs'),
  containerInner: document.getElementById('tabs-inner'),
  tabElementMap: {}, // tabId: tab element
  events: new EventEmitter(),
  dragulaInstance: dragula([document.getElementById('tabs-inner')], {
    direction: 'horizontal'
  }),
  getTab: function (tabId) {
    return tabBar.tabElementMap[tabId]
  },
  getTabInput: function (tabId) {
    return tabBar.getTab(tabId).querySelector('.tab-input')
  },
  setActiveTab: function (tabId) {
    var activeTab = document.querySelector('.tab-item.active')

    if (activeTab) {
      activeTab.classList.remove('active')
      activeTab.removeAttribute('aria-selected')
    }

    var el = tabBar.getTab(tabId)
    el.classList.add('active')
    el.setAttribute('aria-selected', 'true')

    requestAnimationFrame(function () {
      el.scrollIntoView()
    })
  },
  createTab: function (data) {
    var tabEl = document.createElement('div')
    tabEl.className = 'tab-item'
    tabEl.setAttribute('data-tab', data.id)
    tabEl.setAttribute('role', 'tab')

    tabEl.appendChild(readerView.getButton(data.id))
    tabEl.appendChild(progressBar.create())

    // icons

    var iconArea = document.createElement('span')
    iconArea.className = 'tab-icon-area'

    if (data.private) {
      var pbIcon = document.createElement('i')
      pbIcon.className = 'icon-tab-is-private tab-icon tab-info-icon i carbon:view-off'
      iconArea.appendChild(pbIcon)
    }

    var secIcon = document.createElement('i')
    secIcon.className = 'icon-tab-not-secure tab-icon tab-info-icon i carbon:unlocked'
    secIcon.title = l('connectionNotSecure')
    iconArea.appendChild(secIcon)

    var closeTabButton = document.createElement('button')
    closeTabButton.className = 'tab-icon tab-close-button i carbon:close'

    closeTabButton.addEventListener('click', function (e) {
      tabBar.events.emit('tab-closed', data.id)
      // prevent the searchbar from being opened
      e.stopPropagation()
    })

    iconArea.appendChild(closeTabButton)

    tabEl.appendChild(iconArea)

    // title

    var title = document.createElement('span')
    title.className = 'title'

    tabEl.appendChild(title)

    // click to enter edit mode or switch to a tab
    tabEl.addEventListener('click', function (e) {
      if (tabs.getSelected() !== data.id) { // else switch to tab if it isn't focused
        tabBar.events.emit('tab-selected', data.id)
      } else { // the tab is focused, edit tab instead
        tabEditor.show(data.id)
      }
    })

    tabEl.addEventListener('auxclick', function (e) {
      if (e.which === 2) { // if mouse middle click -> close tab
        tabBar.events.emit('tab-closed', data.id)
      }
    })

    tabEl.addEventListener('wheel', function (e) {
      if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) {
        // https://github.com/minbrowser/min/issues/698
        return
      }
      if (e.deltaY > 65 && e.deltaX < 10 && Date.now() - lastTabDeletion > 900) { // swipe up to delete tabs
        lastTabDeletion = Date.now()

        /* tab deletion is disabled in focus mode */
        if (focusMode.enabled()) {
          focusMode.warn()
          return
        }

        this.style.transform = 'translateY(-100%)'

        setTimeout(function () {
          tabBar.events.emit('tab-closed', data.id)
        }, 150) // wait until the animation has completed
      }
    })

    tabBar.updateTab(data.id, tabEl)

    return tabEl
  },
  updateTab: function (tabId, tabEl = tabBar.getTab(tabId)) {
    var tabData = tabs.get(tabId)

    var tabTitle = (tabData.title || l('newTabLabel')).substring(0, 500)
    var titleEl = tabEl.querySelector('.title')
    titleEl.textContent = tabTitle

    tabEl.title = tabTitle
    if (tabData.private) {
      tabEl.title += ' (' + l('privateTab') + ')'
    }

    tabEl.querySelectorAll('.permission-request-icon').forEach(el => el.remove())

    permissionRequests.getButtons(tabId).reverse().forEach(function (button) {
      tabEl.insertBefore(button, tabEl.children[0])
    })

    var secIcon = tabEl.getElementsByClassName('icon-tab-not-secure')[0]
    if (tabData.secure === false) {
      secIcon.hidden = false
    } else {
      secIcon.hidden = true
    }
  },
  updateAll: function () {
    empty(tabBar.containerInner)
    tabBar.tabElementMap = {}

    tabs.get().forEach(function (tab) {
      var el = tabBar.createTab(tab)
      tabBar.containerInner.appendChild(el)
      tabBar.tabElementMap[tab.id] = el
    })

    if (tabs.getSelected()) {
      tabBar.setActiveTab(tabs.getSelected())
    }
  },
  addTab: function (tabId) {
    var tab = tabs.get(tabId)
    var index = tabs.getIndex(tabId)

    var tabEl = tabBar.createTab(tab)
    tabBar.containerInner.insertBefore(tabEl, tabBar.containerInner.childNodes[index])
    tabBar.tabElementMap[tabId] = tabEl
  },
  removeTab: function (tabId) {
    var tabEl = tabBar.getTab(tabId)
    if (tabEl) {
      // The tab does not have a corresponding .tab-item element.
      // This happens when destroying tabs from other task where this .tab-item is not present
      tabBar.containerInner.removeChild(tabEl)
      delete tabBar.tabElementMap[tabId]
    }
  }
}

/* progress bar events */

webviews.bindEvent('did-start-loading', function (tabId) {
  progressBar.update(tabBar.getTab(tabId).querySelector('.progress-bar'), 'start')
})

webviews.bindEvent('did-stop-loading', function (tabId) {
  progressBar.update(tabBar.getTab(tabId).querySelector('.progress-bar'), 'finish')
})

tasks.on('tab-updated', function (id, key) {
  if (key === 'title' || key === 'secure' || key === 'url') {
    tabBar.updateTab(id)
  }
})

permissionRequests.onChange(function (tabId) {
  tabBar.updateTab(tabId)
})

if (window.platformType === 'mac') {
  tabBar.dragulaInstance.containers = []
} else {
  tabBar.dragulaInstance.on('drop', function (el, target, source, sibling) {
    var tabId = el.getAttribute('data-tab')
    if (sibling) {
      var adjacentTabId = sibling.getAttribute('data-tab')
    }

    var oldTab = tabs.splice(tabs.getIndex(tabId), 1)[0]

    if (adjacentTabId) {
      var newIdx = tabs.getIndex(adjacentTabId)
    } else {
    // tab was inserted at end
      var newIdx = tabs.count()
    }
    tabs.splice(newIdx, 0, oldTab)
  })
}

module.exports = tabBar
<commit_msg>Add drag and drop support to tab bar (#1185)<commit_after>const webviews = require('webviews.js')
const focusMode = require('focusMode.js')
const urlParser = require('util/urlParser.js')
const readerView = require('readerView.js')
const dragula = require('dragula')
const settings = require('util/settings/settings.js')

const tabEditor = require('navbar/tabEditor.js')
const progressBar = require('navbar/progressBar.js')
const permissionRequests = require('navbar/permissionRequests.js')

var lastTabDeletion = 0 // TODO get rid of this

const tabBar = {
  container: document.getElementById('tabs'),
  containerInner: document.getElementById('tabs-inner'),
  tabElementMap: {}, // tabId: tab element
  events: new EventEmitter(),
  dragulaInstance: dragula([document.getElementById('tabs-inner')], {
    direction: 'horizontal'
  }),
  getTab: function (tabId) {
    return tabBar.tabElementMap[tabId]
  },
  getTabInput: function (tabId) {
    return tabBar.getTab(tabId).querySelector('.tab-input')
  },
  setActiveTab: function (tabId) {
    var activeTab = document.querySelector('.tab-item.active')

    if (activeTab) {
      activeTab.classList.remove('active')
      activeTab.removeAttribute('aria-selected')
    }

    var el = tabBar.getTab(tabId)
    el.classList.add('active')
    el.setAttribute('aria-selected', 'true')

    requestAnimationFrame(function () {
      el.scrollIntoView()
    })
  },
  createTab: function (data) {
    var tabEl = document.createElement('div')
    tabEl.className = 'tab-item'
    tabEl.setAttribute('data-tab', data.id)
    tabEl.setAttribute('role', 'tab')

    tabEl.appendChild(readerView.getButton(data.id))
    tabEl.appendChild(progressBar.create())

    // icons

    var iconArea = document.createElement('span')
    iconArea.className = 'tab-icon-area'

    if (data.private) {
      var pbIcon = document.createElement('i')
      pbIcon.className = 'icon-tab-is-private tab-icon tab-info-icon i carbon:view-off'
      iconArea.appendChild(pbIcon)
    }

    var secIcon = document.createElement('i')
    secIcon.className = 'icon-tab-not-secure tab-icon tab-info-icon i carbon:unlocked'
    secIcon.title = l('connectionNotSecure')
    iconArea.appendChild(secIcon)

    var closeTabButton = document.createElement('button')
    closeTabButton.className = 'tab-icon tab-close-button i carbon:close'

    closeTabButton.addEventListener('click', function (e) {
      tabBar.events.emit('tab-closed', data.id)
      // prevent the searchbar from being opened
      e.stopPropagation()
    })

    iconArea.appendChild(closeTabButton)

    tabEl.appendChild(iconArea)

    // title

    var title = document.createElement('span')
    title.className = 'title'

    tabEl.appendChild(title)

    // click to enter edit mode or switch to a tab
    tabEl.addEventListener('click', function (e) {
      if (tabs.getSelected() !== data.id) { // else switch to tab if it isn't focused
        tabBar.events.emit('tab-selected', data.id)
      } else { // the tab is focused, edit tab instead
        tabEditor.show(data.id)
      }
    })

    tabEl.addEventListener('auxclick', function (e) {
      if (e.which === 2) { // if mouse middle click -> close tab
        tabBar.events.emit('tab-closed', data.id)
      }
    })

    tabEl.addEventListener('wheel', function (e) {
      if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) {
        // https://github.com/minbrowser/min/issues/698
        return
      }
      if (e.deltaY > 65 && e.deltaX < 10 && Date.now() - lastTabDeletion > 900) { // swipe up to delete tabs
        lastTabDeletion = Date.now()

        /* tab deletion is disabled in focus mode */
        if (focusMode.enabled()) {
          focusMode.warn()
          return
        }

        this.style.transform = 'translateY(-100%)'

        setTimeout(function () {
          tabBar.events.emit('tab-closed', data.id)
        }, 150) // wait until the animation has completed
      }
    })

    tabBar.updateTab(data.id, tabEl)

    return tabEl
  },
  updateTab: function (tabId, tabEl = tabBar.getTab(tabId)) {
    var tabData = tabs.get(tabId)

    var tabTitle = (tabData.title || l('newTabLabel')).substring(0, 500)
    var titleEl = tabEl.querySelector('.title')
    titleEl.textContent = tabTitle

    tabEl.title = tabTitle
    if (tabData.private) {
      tabEl.title += ' (' + l('privateTab') + ')'
    }

    tabEl.querySelectorAll('.permission-request-icon').forEach(el => el.remove())

    permissionRequests.getButtons(tabId).reverse().forEach(function (button) {
      tabEl.insertBefore(button, tabEl.children[0])
    })

    var secIcon = tabEl.getElementsByClassName('icon-tab-not-secure')[0]
    if (tabData.secure === false) {
      secIcon.hidden = false
    } else {
      secIcon.hidden = true
    }
  },
  updateAll: function () {
    empty(tabBar.containerInner)
    tabBar.tabElementMap = {}

    tabs.get().forEach(function (tab) {
      var el = tabBar.createTab(tab)
      tabBar.containerInner.appendChild(el)
      tabBar.tabElementMap[tab.id] = el
    })

    if (tabs.getSelected()) {
      tabBar.setActiveTab(tabs.getSelected())
    }
  },
  addTab: function (tabId) {
    var tab = tabs.get(tabId)
    var index = tabs.getIndex(tabId)

    var tabEl = tabBar.createTab(tab)
    tabBar.containerInner.insertBefore(tabEl, tabBar.containerInner.childNodes[index])
    tabBar.tabElementMap[tabId] = tabEl
  },
  removeTab: function (tabId) {
    var tabEl = tabBar.getTab(tabId)
    if (tabEl) {
      // The tab does not have a corresponding .tab-item element.
      // This happens when destroying tabs from other task where this .tab-item is not present
      tabBar.containerInner.removeChild(tabEl)
      delete tabBar.tabElementMap[tabId]
    }
  }
}

/* progress bar events */

webviews.bindEvent('did-start-loading', function (tabId) {
  progressBar.update(tabBar.getTab(tabId).querySelector('.progress-bar'), 'start')
})

webviews.bindEvent('did-stop-loading', function (tabId) {
  progressBar.update(tabBar.getTab(tabId).querySelector('.progress-bar'), 'finish')
})

tasks.on('tab-updated', function (id, key) {
  if (key === 'title' || key === 'secure' || key === 'url') {
    tabBar.updateTab(id)
  }
})

permissionRequests.onChange(function (tabId) {
  tabBar.updateTab(tabId)
})

if (window.platformType === 'mac') {
  tabBar.dragulaInstance.containers = []
} else {
  tabBar.dragulaInstance.on('drop', function (el, target, source, sibling) {
    var tabId = el.getAttribute('data-tab')
    if (sibling) {
      var adjacentTabId = sibling.getAttribute('data-tab')
    }

    var oldTab = tabs.splice(tabs.getIndex(tabId), 1)[0]

    if (adjacentTabId) {
      var newIdx = tabs.getIndex(adjacentTabId)
    } else {
    // tab was inserted at end
      var newIdx = tabs.count()
    }
    tabs.splice(newIdx, 0, oldTab)
  })
}

tabBar.container.addEventListener('dragover', e => e.preventDefault())

tabBar.container.addEventListener('drop', e => {
  e.preventDefault()
  var data = e.dataTransfer
  require('browserUI.js').addTab(tabs.add({
    url: data.files[0] ? 'file://' + data.files[0].path : data.getData('text'),
    private: tabs.get(tabs.getSelected()).private
  }), { enterEditMode: false, openInBackground: !settings.get('openTabsInForeground') })
})

module.exports = tabBar
<|endoftext|>


<commit_before>/*jshint smarttabs:true */
(function (root, factory) {
	if (typeof exports === 'object') {
		// Node. Does not work with strict CommonJS, but
		// only CommonJS-like enviroments that support module.exports,
		// like Node.
		module.exports = factory(
			require('../node_modules/expect.js/expect.js'),
			require('../js/makeAsync.js'),
			require('../js/FakeLocalStorage.js')
		);
	} else if (typeof define === 'function' && define.amd) {
		// AMD. Register as an anonymous module.
		define(
			[
				'expect.js',
				'syncit/makeAsync',
				'syncit/FakeLocalStorage'
			],
			factory
		);
	} else {
		// Browser globals (root is window)
		root.returnExports = factory(
			root.expect,
			root.SyncIt_makeAsync,
			root.SyncIt_FakeLocalStorage
		);
	}
})(this, function (
	expect,
	makeAsync,
	FakeLocalStorage
) {

// describe('_cloneObj',function() {
//     var source = {color: 'blue', size: 'large'},
//         syncIt = new SyncIt();
//     it('should be able to clone',function() {
//         expect(syncIt._cloneObj(source).color).to.equal(source.color);
//     });

describe('manip',function() {
	it('can manipulate',function(done) {
		var fls = makeAsync(new FakeLocalStorage('aa'));
		fls.setItem('abc','def',function(r) {
		fls.getItem('abc',function(r) {
			expect(r).to.equal('def');
			done();
		});
		});
	});
});

});
<commit_msg>remove commented code, add license<commit_after>/*jshint smarttabs:true */
(function (root, factory) {
	if (typeof exports === 'object') {
		// Node. Does not work with strict CommonJS, but
		// only CommonJS-like enviroments that support module.exports,
		// like Node.
		module.exports = factory(
			require('../node_modules/expect.js/expect.js'),
			require('../js/makeAsync.js'),
			require('../js/FakeLocalStorage.js')
		);
	} else if (typeof define === 'function' && define.amd) {
		// AMD. Register as an anonymous module.
		define(
			[
				'expect.js',
				'syncit/makeAsync',
				'syncit/FakeLocalStorage'
			],
			factory
		);
	} else {
		// Browser globals (root is window)
 		root.returnExports = factory(
			root.expect,
			root.SyncIt_makeAsync,
			root.SyncIt_FakeLocalStorage
		);
	}
})(this, function (
	expect,
	makeAsync,
	FakeLocalStorage
) {

// Author: Matthew Forrester <matt_at_keyboardwritescode.com>
// Copyright: Matthew Forrester
// License: MIT/BSD-style

describe('manip',function() {
	it('can manipulate',function(done) {
		var fls = makeAsync(new FakeLocalStorage('aa'));
		fls.setItem('abc','def',function(r) {
		fls.getItem('abc',function(r) {
			expect(r).to.equal('def');
			done();
		});
		});
	});
});

});
<|endoftext|>


<commit_before>"use strict"

var maybe = require("../")
var assert = require("assert")
var Promise = global.Promise || require("promise")

describe("maybe", function () {
  it("should call the callback with result the promise is resolved to", function (done) {
    var f = function f (cb) {
      return maybe(cb, new Promise(function (resolve, reject) {
        process.nextTick(function () {
          return resolve("hi")
        })
      }))
    }

    f(function (err, result) {
      assert.ifError(err, "no error")
      assert.strictEqual(result, "hi")
      return done()
    })
  })

  it("should call the callback with the error the promise is rejected with", function (done) {
    var f = function f (cb) {
      return maybe(cb, new Promise(function (resolve, reject) {
        process.nextTick(function () {
          return reject(new Error("boom"))
        })
      }))
    }

    f(function (err, result) {
      assert(err, "we got an error")
      assert.strictEqual(result, undefined, "we got undefined result")
      assert(err instanceof Error, "error is an Error")
      assert.strictEqual(err.message, "boom", "error message is boom")
      return done()
    })
  })

  it("should return undefined when called with a callback", function () {
    var f = function f (cb) {
      return maybe(cb, new Promise(function (resolve, reject) {
        //...
      }))
    }

    var returnVal = f(function (err, result) {})
    assert.strictEqual(returnVal, undefined, "returned val is undefined")
  })

  it("should return the same promise when no callback is provided", function () {
    var p

    var f = function f (cb) {
      p = new Promise(function (resolve, reject) {
        process.nextTick(function () {
          return resolve("hi")
        })
      })
      return maybe(cb, p)
    }

    var returnVal = f()
    assert(p instanceof Promise, "returned val is a Promise")
    assert.strictEqual(returnVal, p, "returned val is same obj (not a new Promise)")
  })

  it("should allow errors thrown in the callback to be uncaught", function (done) {
    var mochaHandler

    // Temporarily remove Mocha's global error handling so we can
    // verify error is indeed uncaught by installing our own
    // global error handler.
    if (process.browser) {
      mochaHandler = global.onerror
      global.onerror = handleUncaughtException
    }
    else {
      mochaHandler = process.listeners("uncaughtException").pop()
      process.removeListener("uncaughtException", mochaHandler)
      process.once("uncaughtException", handleUncaughtException)
    }

    var f = function f (cb) {
      return maybe(cb, new Promise(function (resolve, reject) {
        process.nextTick(function () {
          return resolve("hi")
        })
      }))
    }

    f(function (err, result) {
      throw new Error("yep")
    })

    function handleUncaughtException (err) {
      // `err` is either an Error when running under Node, or a
      // string if running under a browser.
      var msg = err.message || err

      assert(msg.match(/\byep\b/), "got expected error")

      // Restore Mocha's global error handler.
      if (process.browser) {
        global.onerror = mochaHandler
      }
      else {
        process.on("uncaughtException", mochaHandler)
      }

      done()

      // Don't leak error to browser console
      return true
    }
  })

  it("should not let the callback be called more than once", function (done) {
    var f = function f (cb) {
      return maybe(cb, new Promise(function (resolve, reject) {
        process.nextTick(function () {
          resolve("foo")
        })
      }))
    }

    var called = 0
    f(function (err, result) {
      called++
      assert(called <= 1, "called only once")
      setTimeout(function () { done() }, 100)
      return Promise.reject(new Error("bah"))
    })
  })
})
<commit_msg>test: fix tests in new versions of node<commit_after>"use strict"

var maybe = require("../")
var assert = require("assert")
var Promise = global.Promise || require("promise")

describe("maybe", function () {
  it("should call the callback with result the promise is resolved to", function (done) {
    var f = function f (cb) {
      return maybe(cb, new Promise(function (resolve, reject) {
        process.nextTick(function () {
          return resolve("hi")
        })
      }))
    }

    f(function (err, result) {
      assert.ifError(err, "no error")
      assert.strictEqual(result, "hi")
      return done()
    })
  })

  it("should call the callback with the error the promise is rejected with", function (done) {
    var f = function f (cb) {
      return maybe(cb, new Promise(function (resolve, reject) {
        process.nextTick(function () {
          return reject(new Error("boom"))
        })
      }))
    }

    f(function (err, result) {
      assert(err, "we got an error")
      assert.strictEqual(result, undefined, "we got undefined result")
      assert(err instanceof Error, "error is an Error")
      assert.strictEqual(err.message, "boom", "error message is boom")
      return done()
    })
  })

  it("should return undefined when called with a callback", function () {
    var f = function f (cb) {
      return maybe(cb, new Promise(function (resolve, reject) {
        //...
      }))
    }

    var returnVal = f(function (err, result) {})
    assert.strictEqual(returnVal, undefined, "returned val is undefined")
  })

  it("should return the same promise when no callback is provided", function () {
    var p

    var f = function f (cb) {
      p = new Promise(function (resolve, reject) {
        process.nextTick(function () {
          return resolve("hi")
        })
      })
      return maybe(cb, p)
    }

    var returnVal = f()
    assert(p instanceof Promise, "returned val is a Promise")
    assert.strictEqual(returnVal, p, "returned val is same obj (not a new Promise)")
  })

  it("should allow errors thrown in the callback to be uncaught", function (done) {
    var mochaHandler

    // Temporarily remove Mocha's global error handling so we can
    // verify error is indeed uncaught by installing our own
    // global error handler.
    if (process.browser) {
      mochaHandler = global.onerror
      global.onerror = handleUncaughtException
    }
    else {
      mochaHandler = process.listeners("uncaughtException").pop()
      process.removeListener("uncaughtException", mochaHandler)
      process.once("uncaughtException", handleUncaughtException)
    }

    var f = function f (cb) {
      return maybe(cb, new Promise(function (resolve, reject) {
        process.nextTick(function () {
          return resolve("hi")
        })
      }))
    }

    f(function (err, result) {
      throw new Error("yep")
    })

    function handleUncaughtException (err) {
      // `err` is either an Error when running under Node, or a
      // string if running under a browser.
      var msg = err.message || err

      assert(msg.match(/\byep\b/), "got expected error")

      // Restore Mocha's global error handler.
      if (process.browser) {
        global.onerror = mochaHandler
      }
      else {
        process.on("uncaughtException", mochaHandler)
      }

      done()

      // Don't leak error to browser console
      return true
    }
  })

  describe("when callback returns rejected promise", function () {
    it("should not let the callback be called more than once", function (done) {
      var f = function f (cb) {
        return maybe(cb, new Promise(function (resolve, reject) {
          process.nextTick(function () {
            resolve("foo")
          })
        }))
      }

      var called = 0
      f(function (err, result) {
        called++
        assert(called <= 1, "called only once")
        setTimeout(function () { done() }, 100)
        process.once('unhandledRejection', function () { /* ignore unhandled rejection */ })
        return Promise.reject(new Error("bah"))
      })
    })
  })
})
<|endoftext|>


<commit_before>// XXX could use some tests

Session = _.extend({}, {
  keys: {},

  next_id: 1,
  key_callbacks: {}, // key -> id -> func
  key_value_callbacks: {}, // key -> value -> id -> func

  // XXX remove debugging method (or improve it, but anyway, don't
  // ship it in production)
  dump_state: function () {
    var self = this;
    console.log("=== Session state ===");
    for (var key in self.key_callbacks) {
      var ids = _.keys(self.key_callbacks[key]);
      if (!ids.length)
        continue;
      console.log(key + ": " + _.map(ids, function (x) {return x.substr(0,4);}).join(' '));
    }

    for (var key in self.key_value_callbacks) {
      for (var value in self.key_value_callbacks[key]) {
        var ids = _.keys(self.key_value_callbacks[key][value]);
        if (!ids.length)
          continue;
        console.log(key + "(" + value + "): " + _.map(ids, function (x) {return x.substr(0,4);}).join(' '));
      }
    }
  },

  set: function (key, value) {
    var self = this;

    var old_value = self.keys[key];
    if (value === old_value)
      return;
    self.keys[key] = value;
    self._ensureKey(key);

    // Old code did this:
    //   for (var id in self.key_callbacks[key])
    //     self.key_callbacks[key][id]();
    //
    // This works on all browsers except IE8. The problem is that the
    // callback we call will (most likely) add a new callback to
    // self.key_callbacks. On IE8 this results in an infinite
    // loop. Suprisingly, this does work on other browsers, they don't
    // include newly added keys in the iteration.
    //
    // Work around this on IE8 by iterating over a copy.

    _.each(_.keys(self.key_callbacks[key]), function (id) {
      self.key_callbacks[key][id]();
    });

    if (old_value in self.key_value_callbacks[key])
      _.each(_.keys(self.key_value_callbacks[key][old_value]), function (id) {
        self.key_value_callbacks[key][old_value][id]();
      });

    if (value in self.key_value_callbacks[key])
      _.each(_.keys(self.key_value_callbacks[key][value]), function (id) {
        self.key_value_callbacks[key][value][id]();
      });
  },

  get: function (key) {
    var self = this;

    if (Sky.deps.monitoring) {
      var id = self.next_id++;

      self._ensureKey(key);
      self.key_callbacks[key][id] = Sky.deps.getInvalidate();

      Sky.deps.cleanup(function () {
        delete self.key_callbacks[key][id];
      });
    }

    return self.keys[key];
  },

  equals: function (key, value) {
    var self = this;

    if (typeof(value) !== 'string' &&
        typeof(value) !== 'number' &&
        typeof(value) !== 'boolean' &&
        value !== null && value !== undefined)
      throw new Error("Session.equals: value can't be an object");

    if (Sky.deps.monitoring) {
      var id = self.next_id++;

      self._ensureKey(key);
      if (!(value in self.key_value_callbacks[key]))
        self.key_value_callbacks[key][value] = {};
      self.key_value_callbacks[key][value][id] = Sky.deps.getInvalidate();

      Sky.deps.cleanup(function () {
        delete self.key_value_callbacks[key][value][id];

        // clean up [key][value] if it's now empty, so we don't use
        // O(n) memory for n = values seen ever
        for (var x in self.key_value_callbacks[key][value])
          return;
        delete self.key_value_callbacks[key][value];
      });
    }

    return self.keys[key] === value;
  },

  _ensureKey: function (key) {
    var self = this;
    if (!(key in self.key_callbacks)) {
      self.key_callbacks[key] = {};
      self.key_value_callbacks[key] = {};
    }
  }
});
<commit_msg>fix bug introduced by IE8 iteration fix<commit_after>// XXX could use some tests

Session = _.extend({}, {
  keys: {},

  next_id: 1,
  key_callbacks: {}, // key -> id -> func
  key_value_callbacks: {}, // key -> value -> id -> func

  // XXX remove debugging method (or improve it, but anyway, don't
  // ship it in production)
  dump_state: function () {
    var self = this;
    console.log("=== Session state ===");
    for (var key in self.key_callbacks) {
      var ids = _.keys(self.key_callbacks[key]);
      if (!ids.length)
        continue;
      console.log(key + ": " + _.map(ids, function (x) {return x.substr(0,4);}).join(' '));
    }

    for (var key in self.key_value_callbacks) {
      for (var value in self.key_value_callbacks[key]) {
        var ids = _.keys(self.key_value_callbacks[key][value]);
        if (!ids.length)
          continue;
        console.log(key + "(" + value + "): " + _.map(ids, function (x) {return x.substr(0,4);}).join(' '));
      }
    }
  },

  set: function (key, value) {
    var self = this;

    var old_value = self.keys[key];
    if (value === old_value)
      return;
    self.keys[key] = value;
    self._ensureKey(key);

    // Old code did this:
    //   for (var id in self.key_callbacks[key])
    //     self.key_callbacks[key][id]();
    //
    // This works on all browsers except IE8. The problem is that the
    // callback we call will (most likely) add a new callback to
    // self.key_callbacks. On IE8 this results in an infinite
    // loop. Suprisingly, this does work on other browsers, they don't
    // include newly added keys in the iteration.
    //
    // Work around this on IE8 by iterating over a copy. We need to be
    // careful though and not try to call callbacks that have been
    // removed since we make our copy.

    _.each(_.keys(self.key_callbacks[key]), function (id) {
      var f = self.key_callbacks[key][id];
      f && f();
    });

    if (old_value in self.key_value_callbacks[key])
      _.each(_.keys(self.key_value_callbacks[key][old_value]), function (id) {
        var f = self.key_value_callbacks[key][old_value][id];
        f && f();
      });

    if (value in self.key_value_callbacks[key])
      _.each(_.keys(self.key_value_callbacks[key][value]), function (id) {
        var f = self.key_value_callbacks[key][value][id];
        f && f();
      });
  },

  get: function (key) {
    var self = this;

    if (Sky.deps.monitoring) {
      var id = self.next_id++;

      self._ensureKey(key);
      self.key_callbacks[key][id] = Sky.deps.getInvalidate();

      Sky.deps.cleanup(function () {
        delete self.key_callbacks[key][id];
      });
    }

    return self.keys[key];
  },

  equals: function (key, value) {
    var self = this;

    if (typeof(value) !== 'string' &&
        typeof(value) !== 'number' &&
        typeof(value) !== 'boolean' &&
        value !== null && value !== undefined)
      throw new Error("Session.equals: value can't be an object");

    if (Sky.deps.monitoring) {
      var id = self.next_id++;

      self._ensureKey(key);
      if (!(value in self.key_value_callbacks[key]))
        self.key_value_callbacks[key][value] = {};
      self.key_value_callbacks[key][value][id] = Sky.deps.getInvalidate();

      Sky.deps.cleanup(function () {
        delete self.key_value_callbacks[key][value][id];

        // clean up [key][value] if it's now empty, so we don't use
        // O(n) memory for n = values seen ever
        for (var x in self.key_value_callbacks[key][value])
          return;
        delete self.key_value_callbacks[key][value];
      });
    }

    return self.keys[key] === value;
  },

  _ensureKey: function (key) {
    var self = this;
    if (!(key in self.key_callbacks)) {
      self.key_callbacks[key] = {};
      self.key_value_callbacks[key] = {};
    }
  }
});
<|endoftext|>