<commit_before>import { expect } from 'chai';
import { describe, it } from 'mocha';

import { parse } from '../../language/parser';

import type { GraphQLOutputType } from '../../type/definition';
import { GraphQLSchema } from '../../type/schema';
import { GraphQLString, GraphQLInt } from '../../type/scalars';
import {
  GraphQLList,
  GraphQLNonNull,
  GraphQLObjectType,
} from '../../type/definition';

import { execute } from '../execute';

// resolved() is shorthand for Promise.resolve()
const resolved = Promise.resolve.bind(Promise);

// rejected() is shorthand for Promise.reject()
const rejected = Promise.reject.bind(Promise);

/**
 * This function creates a test case passed to "it", there's a time delay
 * between when the test is created and when the test is run, so if testData
 * contains a rejection, testData should be a function that returns that
 * rejection so as not to trigger the "unhandled rejection" error watcher.
 */
function check(testType: GraphQLOutputType, testData: mixed, expected: mixed) {
  return async () => {
    const schema = new GraphQLSchema({
      query: new GraphQLObjectType({
        name: 'Query',
        fields: () => ({
          listField: { type: testType },
        }),
      }),
    });

    const response = await execute({
      schema,
      document: parse('{ listField }'),
      rootValue: { listField: testData },
    });
    expect(response).to.deep.equal(expected);
  };
}

describe('Execute: Accepts any iterable as list value', () => {
  it(
    'Accepts a Set as a List value',
    check(
      GraphQLList(GraphQLString),
      new Set(['apple', 'banana', 'apple', 'coconut']),
      { data: { listField: ['apple', 'banana', 'coconut'] } },
    ),
  );

  function* yieldItems() {
    yield 'one';
    yield 2;
    yield true;
  }

  it(
    'Accepts an Generator function as a List value',
    check(GraphQLList(GraphQLString), yieldItems(), {
      data: { listField: ['one', '2', 'true'] },
    }),
  );

  function getArgs(...args: Array<string>) {
    return args;
  }

  it(
    'Accepts function arguments as a List value',
    check(GraphQLList(GraphQLString), getArgs('one', 'two'), {
      data: { listField: ['one', 'two'] },
    }),
  );

  it(
    'Does not accept (Iterable) String-literal as a List value',
    check(GraphQLList(GraphQLString), 'Singular', {
      data: { listField: null },
      errors: [
        {
          message:
            'Expected Iterable, but did not find one for field "Query.listField".',
          locations: [{ line: 1, column: 3 }],
          path: ['listField'],
        },
      ],
    }),
  );
});

describe('Execute: Handles list nullability', () => {
  describe('[T]', () => {
    const type = GraphQLList(GraphQLInt);

    describe('Array<T>', () => {
      it(
        'Contains values',
        check(type, [1, 2], { data: { listField: [1, 2] } }),
      );

      it(
        'Contains null',
        check(type, [1, null, 2], { data: { listField: [1, null, 2] } }),
      );

      it('Returns null', check(type, null, { data: { listField: null } }));
    });

    describe('Promise<Array<T>>', () => {
      it(
        'Contains values',
        check(type, resolved([1, 2]), { data: { listField: [1, 2] } }),
      );

      it(
        'Contains null',
        check(type, resolved([1, null, 2]), {
          data: { listField: [1, null, 2] },
        }),
      );

      it(
        'Returns null',
        check(type, resolved(null), { data: { listField: null } }),
      );

      it(
        'Rejected',
        check(type, () => rejected(new Error('bad')), {
          data: { listField: null },
          errors: [
            {
              message: 'bad',
              locations: [{ line: 1, column: 3 }],
              path: ['listField'],
            },
          ],
        }),
      );
    });

    describe('Array<Promise<T>>', () => {
      it(
        'Contains values',
        check(type, [resolved(1), resolved(2)], {
          data: { listField: [1, 2] },
        }),
      );

      it(
        'Contains null',
        check(type, [resolved(1), resolved(null), resolved(2)], {
          data: { listField: [1, null, 2] },
        }),
      );

      it(
        'Contains reject',
        check(
          type,
          () => [resolved(1), rejected(new Error('bad')), resolved(2)],
          {
            data: { listField: [1, null, 2] },
            errors: [
              {
                message: 'bad',
                locations: [{ line: 1, column: 3 }],
                path: ['listField', 1],
              },
            ],
          },
        ),
      );
    });
  });

  describe('[T]!', () => {
    const type = GraphQLNonNull(GraphQLList(GraphQLInt));

    describe('Array<T>', () => {
      it(
        'Contains values',
        check(type, [1, 2], { data: { listField: [1, 2] } }),
      );

      it(
        'Contains null',
        check(type, [1, null, 2], { data: { listField: [1, null, 2] } }),
      );

      it(
        'Returns null',
        check(type, null, {
          data: null,
          errors: [
            {
              message:
                'Cannot return null for non-nullable field Query.listField.',
              locations: [{ line: 1, column: 3 }],
              path: ['listField'],
            },
          ],
        }),
      );
    });

    describe('Promise<Array<T>>', () => {
      it(
        'Contains values',
        check(type, resolved([1, 2]), { data: { listField: [1, 2] } }),
      );

      it(
        'Contains null',
        check(type, resolved([1, null, 2]), {
          data: { listField: [1, null, 2] },
        }),
      );

      it(
        'Returns null',
        check(type, resolved(null), {
          data: null,
          errors: [
            {
              message:
                'Cannot return null for non-nullable field Query.listField.',
              locations: [{ line: 1, column: 3 }],
              path: ['listField'],
            },
          ],
        }),
      );

      it(
        'Rejected',
        check(type, () => rejected(new Error('bad')), {
          data: null,
          errors: [
            {
              message: 'bad',
              locations: [{ line: 1, column: 3 }],
              path: ['listField'],
            },
          ],
        }),
      );
    });

    describe('Array<Promise<T>>', () => {
      it(
        'Contains values',
        check(type, [resolved(1), resolved(2)], {
          data: { listField: [1, 2] },
        }),
      );

      it(
        'Contains null',
        check(type, [resolved(1), resolved(null), resolved(2)], {
          data: { listField: [1, null, 2] },
        }),
      );

      it(
        'Contains reject',
        check(
          type,
          () => [resolved(1), rejected(new Error('bad')), resolved(2)],
          {
            data: { listField: [1, null, 2] },
            errors: [
              {
                message: 'bad',
                locations: [{ line: 1, column: 3 }],
                path: ['listField', 1],
              },
            ],
          },
        ),
      );
    });
  });

  describe('[T!]', () => {
    const type = GraphQLList(GraphQLNonNull(GraphQLInt));

    describe('Array<T>', () => {
      it(
        'Contains values',
        check(type, [1, 2], { data: { listField: [1, 2] } }),
      );

      it(
        'Contains null',
        check(type, [1, null, 2], {
          data: { listField: null },
          errors: [
            {
              message:
                'Cannot return null for non-nullable field Query.listField.',
              locations: [{ line: 1, column: 3 }],
              path: ['listField', 1],
            },
          ],
        }),
      );

      it('Returns null', check(type, null, { data: { listField: null } }));
    });

    describe('Promise<Array<T>>', () => {
      it(
        'Contains values',
        check(type, resolved([1, 2]), { data: { listField: [1, 2] } }),
      );

      it(
        'Contains null',
        check(type, resolved([1, null, 2]), {
          data: { listField: null },
          errors: [
            {
              message:
                'Cannot return null for non-nullable field Query.listField.',
              locations: [{ line: 1, column: 3 }],
              path: ['listField', 1],
            },
          ],
        }),
      );

      it(
        'Returns null',
        check(type, resolved(null), { data: { listField: null } }),
      );

      it(
        'Rejected',
        check(type, () => rejected(new Error('bad')), {
          data: { listField: null },
          errors: [
            {
              message: 'bad',
              locations: [{ line: 1, column: 3 }],
              path: ['listField'],
            },
          ],
        }),
      );
    });

    describe('Array<Promise<T>>', () => {
      it(
        'Contains values',
        check(type, [resolved(1), resolved(2)], {
          data: { listField: [1, 2] },
        }),
      );

      it(
        'Contains null',
        check(type, [resolved(1), resolved(null), resolved(2)], {
          data: { listField: null },
          errors: [
            {
              message:
                'Cannot return null for non-nullable field Query.listField.',
              locations: [{ line: 1, column: 3 }],
              path: ['listField', 1],
            },
          ],
        }),
      );

      it(
        'Contains reject',
        check(
          type,
          () => [resolved(1), rejected(new Error('bad')), resolved(2)],
          {
            data: { listField: null },
            errors: [
              {
                message: 'bad',
                locations: [{ line: 1, column: 3 }],
                path: ['listField', 1],
              },
            ],
          },
        ),
      );
    });
  });

  describe('[T!]!', () => {
    const type = GraphQLNonNull(GraphQLList(GraphQLNonNull(GraphQLInt)));

    describe('Array<T>', () => {
      it(
        'Contains values',
        check(type, [1, 2], { data: { listField: [1, 2] } }),
      );

      it(
        'Contains null',
        check(type, [1, null, 2], {
          data: null,
          errors: [
            {
              message:
                'Cannot return null for non-nullable field Query.listField.',
              locations: [{ line: 1, column: 3 }],
              path: ['listField', 1],
            },
          ],
        }),
      );

      it(
        'Returns null',
        check(type, null, {
          data: null,
          errors: [
            {
              message:
                'Cannot return null for non-nullable field Query.listField.',
              locations: [{ line: 1, column: 3 }],
              path: ['listField'],
            },
          ],
        }),
      );
    });

    describe('Promise<Array<T>>', () => {
      it(
        'Contains values',
        check(type, resolved([1, 2]), { data: { listField: [1, 2] } }),
      );

      it(
        'Contains null',
        check(type, resolved([1, null, 2]), {
          data: null,
          errors: [
            {
              message:
                'Cannot return null for non-nullable field Query.listField.',
              locations: [{ line: 1, column: 3 }],
              path: ['listField', 1],
            },
          ],
        }),
      );

      it(
        'Returns null',
        check(type, resolved(null), {
          data: null,
          errors: [
            {
              message:
                'Cannot return null for non-nullable field Query.listField.',
              locations: [{ line: 1, column: 3 }],
              path: ['listField'],
            },
          ],
        }),
      );

      it(
        'Rejected',
        check(type, () => rejected(new Error('bad')), {
          data: null,
          errors: [
            {
              message: 'bad',
              locations: [{ line: 1, column: 3 }],
              path: ['listField'],
            },
          ],
        }),
      );
    });

    describe('Array<Promise<T>>', () => {
      it(
        'Contains values',
        check(type, [resolved(1), resolved(2)], {
          data: { listField: [1, 2] },
        }),
      );

      it(
        'Contains null',
        check(type, [resolved(1), resolved(null), resolved(2)], {
          data: null,
          errors: [
            {
              message:
                'Cannot return null for non-nullable field Query.listField.',
              locations: [{ line: 1, column: 3 }],
              path: ['listField', 1],
            },
          ],
        }),
      );

      it(
        'Contains reject',
        check(
          type,
          () => [resolved(1), rejected(new Error('bad')), resolved(2)],
          {
            data: null,
            errors: [
              {
                message: 'bad',
                locations: [{ line: 1, column: 3 }],
                path: ['listField', 1],
              },
            ],
          },
        ),
      );
    });
  });
});
<commit_msg>list-test: improve test readability (part 2) (#2737)<commit_after>import { expect } from 'chai';
import { describe, it } from 'mocha';

import { parse } from '../../language/parser';

import { execute } from '../execute';

import { buildSchema } from '../../utilities/buildASTSchema';

describe('Execute: Accepts any iterable as list value', () => {
  function complete(rootValue: mixed) {
    return execute({
      schema: buildSchema('type Query { listField: [String] }'),
      document: parse('{ listField }'),
      rootValue,
    });
  }

  it('Accepts a Set as a List value', async () => {
    const listField = new Set(['apple', 'banana', 'apple', 'coconut']);

    expect(await complete({ listField })).to.deep.equal({
      data: { listField: ['apple', 'banana', 'coconut'] },
    });
  });

  it('Accepts an Generator function as a List value', async () => {
    function* yieldItems() {
      yield 'one';
      yield 2;
      yield true;
    }
    const listField = yieldItems();

    expect(await complete({ listField })).to.deep.equal({
      data: { listField: ['one', '2', 'true'] },
    });
  });

  it('Accepts function arguments as a List value', async () => {
    function getArgs(...args: Array<string>) {
      return args;
    }
    const listField = getArgs('one', 'two');

    expect(await complete({ listField })).to.deep.equal({
      data: { listField: ['one', 'two'] },
    });
  });

  it('Does not accept (Iterable) String-literal as a List value', async () => {
    const listField = 'Singular';

    expect(await complete({ listField })).to.deep.equal({
      data: { listField: null },
      errors: [
        {
          message:
            'Expected Iterable, but did not find one for field "Query.listField".',
          locations: [{ line: 1, column: 3 }],
          path: ['listField'],
        },
      ],
    });
  });
});

describe('Execute: Handles list nullability', () => {
  describe('[T]', () => {
    function complete(rootValue: mixed) {
      return execute({
        schema: buildSchema('type Query { listField: [Int] }'),
        document: parse('{ listField }'),
        rootValue,
      });
    }

    describe('Array<T>', () => {
      it('Contains values', async () => {
        const listField = [1, 2];

        expect(await complete({ listField })).to.deep.equal({
          data: { listField: [1, 2] },
        });
      });

      it('Contains null', async () => {
        const listField = [1, null, 2];

        expect(await complete({ listField })).to.deep.equal({
          data: { listField: [1, null, 2] },
        });
      });

      it('Returns null', async () => {
        const listField = null;

        expect(await complete({ listField })).to.deep.equal({
          data: { listField: null },
        });
      });
    });

    describe('Promise<Array<T>>', () => {
      it('Contains values', async () => {
        const listField = Promise.resolve([1, 2]);

        expect(await complete({ listField })).to.deep.equal({
          data: { listField: [1, 2] },
        });
      });

      it('Contains null', async () => {
        const listField = Promise.resolve([1, null, 2]);

        expect(await complete({ listField })).to.deep.equal({
          data: { listField: [1, null, 2] },
        });
      });

      it('Returns null', async () => {
        const listField = Promise.resolve(null);

        expect(await complete({ listField })).to.deep.equal({
          data: { listField: null },
        });
      });

      it('Rejected', async () => {
        const listField = Promise.reject(new Error('bad'));

        expect(await complete({ listField })).to.deep.equal({
          data: { listField: null },
          errors: [
            {
              message: 'bad',
              locations: [{ line: 1, column: 3 }],
              path: ['listField'],
            },
          ],
        });
      });
    });

    describe('Array<Promise<T>>', () => {
      it('Contains values', async () => {
        const listField = [Promise.resolve(1), Promise.resolve(2)];

        expect(await complete({ listField })).to.deep.equal({
          data: { listField: [1, 2] },
        });
      });

      it('Contains null', async () => {
        const listField = [
          Promise.resolve(1),
          Promise.resolve(null),
          Promise.resolve(2),
        ];

        expect(await complete({ listField })).to.deep.equal({
          data: { listField: [1, null, 2] },
        });
      });

      it('Contains reject', async () => {
        const listField = [
          Promise.resolve(1),
          Promise.reject(new Error('bad')),
          Promise.resolve(2),
        ];

        expect(await complete({ listField })).to.deep.equal({
          data: { listField: [1, null, 2] },
          errors: [
            {
              message: 'bad',
              locations: [{ line: 1, column: 3 }],
              path: ['listField', 1],
            },
          ],
        });
      });
    });
  });

  describe('[T]!', () => {
    function complete(rootValue: mixed) {
      return execute({
        schema: buildSchema('type Query { listField: [Int]! }'),
        document: parse('{ listField }'),
        rootValue,
      });
    }

    describe('Array<T>', () => {
      it('Contains values', async () => {
        const listField = [1, 2];

        expect(await complete({ listField })).to.deep.equal({
          data: { listField: [1, 2] },
        });
      });

      it('Contains null', async () => {
        const listField = [1, null, 2];

        expect(await complete({ listField })).to.deep.equal({
          data: { listField: [1, null, 2] },
        });
      });

      it('Returns null', async () => {
        const listField = null;

        expect(await complete({ listField })).to.deep.equal({
          data: null,
          errors: [
            {
              message:
                'Cannot return null for non-nullable field Query.listField.',
              locations: [{ line: 1, column: 3 }],
              path: ['listField'],
            },
          ],
        });
      });
    });

    describe('Promise<Array<T>>', () => {
      it('Contains values', async () => {
        const listField = Promise.resolve([1, 2]);

        expect(await complete({ listField })).to.deep.equal({
          data: { listField: [1, 2] },
        });
      });

      it('Contains null', async () => {
        const listField = Promise.resolve([1, null, 2]);

        expect(await complete({ listField })).to.deep.equal({
          data: { listField: [1, null, 2] },
        });
      });

      it('Returns null', async () => {
        const listField = Promise.resolve(null);

        expect(await complete({ listField })).to.deep.equal({
          data: null,
          errors: [
            {
              message:
                'Cannot return null for non-nullable field Query.listField.',
              locations: [{ line: 1, column: 3 }],
              path: ['listField'],
            },
          ],
        });
      });

      it('Rejected', async () => {
        const listField = Promise.reject(new Error('bad'));

        expect(await complete({ listField })).to.deep.equal({
          data: null,
          errors: [
            {
              message: 'bad',
              locations: [{ line: 1, column: 3 }],
              path: ['listField'],
            },
          ],
        });
      });
    });

    describe('Array<Promise<T>>', () => {
      it('Contains values', async () => {
        const listField = [Promise.resolve(1), Promise.resolve(2)];

        expect(await complete({ listField })).to.deep.equal({
          data: { listField: [1, 2] },
        });
      });

      it('Contains null', async () => {
        const listField = [
          Promise.resolve(1),
          Promise.resolve(null),
          Promise.resolve(2),
        ];

        expect(await complete({ listField })).to.deep.equal({
          data: { listField: [1, null, 2] },
        });
      });

      it('Contains reject', async () => {
        const listField = [
          Promise.resolve(1),
          Promise.reject(new Error('bad')),
          Promise.resolve(2),
        ];

        expect(await complete({ listField })).to.deep.equal({
          data: { listField: [1, null, 2] },
          errors: [
            {
              message: 'bad',
              locations: [{ line: 1, column: 3 }],
              path: ['listField', 1],
            },
          ],
        });
      });
    });
  });

  describe('[T!]', () => {
    function complete(rootValue: mixed) {
      return execute({
        schema: buildSchema('type Query { listField: [Int!] }'),
        document: parse('{ listField }'),
        rootValue,
      });
    }

    describe('Array<T>', () => {
      it('Contains values', async () => {
        const listField = [1, 2];

        expect(await complete({ listField })).to.deep.equal({
          data: { listField: [1, 2] },
        });
      });

      it('Contains null', async () => {
        const listField = [1, null, 2];

        expect(await complete({ listField })).to.deep.equal({
          data: { listField: null },
          errors: [
            {
              message:
                'Cannot return null for non-nullable field Query.listField.',
              locations: [{ line: 1, column: 3 }],
              path: ['listField', 1],
            },
          ],
        });
      });

      it('Returns null', async () => {
        const listField = null;

        expect(await complete({ listField })).to.deep.equal({
          data: { listField: null },
        });
      });
    });

    describe('Promise<Array<T>>', () => {
      it('Contains values', async () => {
        const listField = Promise.resolve([1, 2]);

        expect(await complete({ listField })).to.deep.equal({
          data: { listField: [1, 2] },
        });
      });

      it('Contains null', async () => {
        const listField = Promise.resolve([1, null, 2]);

        expect(await complete({ listField })).to.deep.equal({
          data: { listField: null },
          errors: [
            {
              message:
                'Cannot return null for non-nullable field Query.listField.',
              locations: [{ line: 1, column: 3 }],
              path: ['listField', 1],
            },
          ],
        });
      });

      it('Returns null', async () => {
        const listField = Promise.resolve(null);

        expect(await complete({ listField })).to.deep.equal({
          data: { listField: null },
        });
      });

      it('Rejected', async () => {
        const listField = Promise.reject(new Error('bad'));

        expect(await complete({ listField })).to.deep.equal({
          data: { listField: null },
          errors: [
            {
              message: 'bad',
              locations: [{ line: 1, column: 3 }],
              path: ['listField'],
            },
          ],
        });
      });
    });

    describe('Array<Promise<T>>', () => {
      it('Contains values', async () => {
        const listField = [Promise.resolve(1), Promise.resolve(2)];

        expect(await complete({ listField })).to.deep.equal({
          data: { listField: [1, 2] },
        });
      });

      it('Contains null', async () => {
        const listField = [
          Promise.resolve(1),
          Promise.resolve(null),
          Promise.resolve(2),
        ];

        expect(await complete({ listField })).to.deep.equal({
          data: { listField: null },
          errors: [
            {
              message:
                'Cannot return null for non-nullable field Query.listField.',
              locations: [{ line: 1, column: 3 }],
              path: ['listField', 1],
            },
          ],
        });
      });

      it('Contains reject', async () => {
        const listField = [
          Promise.resolve(1),
          Promise.reject(new Error('bad')),
          Promise.resolve(2),
        ];

        expect(await complete({ listField })).to.deep.equal({
          data: { listField: null },
          errors: [
            {
              message: 'bad',
              locations: [{ line: 1, column: 3 }],
              path: ['listField', 1],
            },
          ],
        });
      });
    });
  });

  describe('[T!]!', () => {
    function complete(rootValue: mixed) {
      return execute({
        schema: buildSchema('type Query { listField: [Int!]! }'),
        document: parse('{ listField }'),
        rootValue,
      });
    }

    describe('Array<T>', () => {
      it('Contains values', async () => {
        const listField = [1, 2];

        expect(await complete({ listField })).to.deep.equal({
          data: { listField: [1, 2] },
        });
      });

      it('Contains null', async () => {
        const listField = [1, null, 2];

        expect(await complete({ listField })).to.deep.equal({
          data: null,
          errors: [
            {
              message:
                'Cannot return null for non-nullable field Query.listField.',
              locations: [{ line: 1, column: 3 }],
              path: ['listField', 1],
            },
          ],
        });
      });

      it('Returns null', async () => {
        const listField = null;

        expect(await complete({ listField })).to.deep.equal({
          data: null,
          errors: [
            {
              message:
                'Cannot return null for non-nullable field Query.listField.',
              locations: [{ line: 1, column: 3 }],
              path: ['listField'],
            },
          ],
        });
      });
    });

    describe('Promise<Array<T>>', () => {
      it('Contains values', async () => {
        const listField = Promise.resolve([1, 2]);

        expect(await complete({ listField })).to.deep.equal({
          data: { listField: [1, 2] },
        });
      });

      it('Contains null', async () => {
        const listField = Promise.resolve([1, null, 2]);

        expect(await complete({ listField })).to.deep.equal({
          data: null,
          errors: [
            {
              message:
                'Cannot return null for non-nullable field Query.listField.',
              locations: [{ line: 1, column: 3 }],
              path: ['listField', 1],
            },
          ],
        });
      });

      it('Returns null', async () => {
        const listField = Promise.resolve(null);

        expect(await complete({ listField })).to.deep.equal({
          data: null,
          errors: [
            {
              message:
                'Cannot return null for non-nullable field Query.listField.',
              locations: [{ line: 1, column: 3 }],
              path: ['listField'],
            },
          ],
        });
      });

      it('Rejected', async () => {
        const listField = Promise.reject(new Error('bad'));

        expect(await complete({ listField })).to.deep.equal({
          data: null,
          errors: [
            {
              message: 'bad',
              locations: [{ line: 1, column: 3 }],
              path: ['listField'],
            },
          ],
        });
      });
    });

    describe('Array<Promise<T>>', () => {
      it('Contains values', async () => {
        const listField = [Promise.resolve(1), Promise.resolve(2)];

        expect(await complete({ listField })).to.deep.equal({
          data: { listField: [1, 2] },
        });
      });

      it('Contains null', async () => {
        const listField = [
          Promise.resolve(1),
          Promise.resolve(null),
          Promise.resolve(2),
        ];

        expect(await complete({ listField })).to.deep.equal({
          data: null,
          errors: [
            {
              message:
                'Cannot return null for non-nullable field Query.listField.',
              locations: [{ line: 1, column: 3 }],
              path: ['listField', 1],
            },
          ],
        });
      });

      it('Contains reject', async () => {
        const listField = [
          Promise.resolve(1),
          Promise.reject(new Error('bad')),
          Promise.resolve(2),
        ];

        expect(await complete({ listField })).to.deep.equal({
          data: null,
          errors: [
            {
              message: 'bad',
              locations: [{ line: 1, column: 3 }],
              path: ['listField', 1],
            },
          ],
        });
      });
    });
  });
});
<|endoftext|>


<commit_before>define(['knockout', 'jquery', 'lodash'],
    function(ko, $, _) {
        'use strict';

        //TODO: https://github.com/Knockout-Contrib/Knockout-Validation/issues/145#issuecomment-73754720
        ko.extenders.bootstrapValidation = function(target) {
            extendProperties(target);

            target.isValidating = ko.computed(function() {
                if (target.errors) {
                    return !!target.errors.find(function(obsv) {
                        return ko.validation.utils.isValidatable(obsv) && obsv.isValidating();
                    });
                } else if (target.error) {
                    return ko.validation.utils.isValidatable(target) && target.isValidating();
                }

                return false;
            });

            target.isValidAsync = function() {
                return new $.Deferred(function(dfd) {
                    try {
                        traverse(target(), function(key, value) {
                            if (ko.validation.utils.isValidatable(value)) {
                                value.validate();
                            }
                        });

                        if (!target.isValidating()) {
                            dfd.resolve(target.isValid());
                        } else {
                            var subscription = target.isValidating.subscribe(function() {
                                dfd.resolve(target.isValid());
                                subscription.dispose();
                            });
                        }
                    } catch (err) {
                        dfd.reject(err);
                    }
                }).promise();
            };

            target.subscribe(function() {
                extendProperties(target);
            });

            //return the original observable
            return target;
        };

        function extendProperties(target) {
            traverse(target(), function(key, value) {
                if (ko.validation.utils.isValidatable(value)) {
                    extendProperty(value);
                }
            });
        }

        function traverse(o, func) {
            var self = this;

            _.forIn(o, function(value, key) {
                if (key !== '__ko_mapping__') {
                    func.apply(self, [key, value]);

                    if (value !== null && typeof(value) === 'object') {
                        traverse(value, func);
                    }
                }
            });
        }

        function extendProperty(target) {
            if (target.validate) {
                return;
            }

            //add some sub-observables to our observable
            target.formGroupValidationCssClass = ko.observable('');
            target.helpBlockValidationMessage = ko.observable('');

            //define a function to do validation
            target.validate = function() {
                var cssClass = '';
                var message = '';

                var isValidating = target.isValidating();
                if (isValidating) {
                    if (isValidating === true && target.validatingMessage) {
                        //cssClass = 'has-warning';
                        message = target.validatingMessage;
                    }
                } else {
                    if (target.isValid()) {
                        cssClass = 'has-success';

                        if (target.validMessage) {
                            message = target.validMessage;
                        }
                    } else {
                        if (target.error) {
                            cssClass = 'has-error';
                            message = target.error();
                        }
                    }
                }

                target.formGroupValidationCssClass(cssClass);
                target.helpBlockValidationMessage(message);
            };

            target.isValidating.subscribe(function() {
                target.validate();
            });

            //validate whenever the value changes
            target.subscribe(function() {
                target.validate();
            });
        }
    });
<commit_msg>bug fix - calling validate on added properties is wrong<commit_after>define(['knockout', 'jquery', 'lodash'],
    function(ko, $, _) {
        'use strict';

        //TODO: https://github.com/Knockout-Contrib/Knockout-Validation/issues/145#issuecomment-73754720
        ko.extenders.bootstrapValidation = function(target) {
            extendProperties(target);

            target.isValidating = ko.computed(function() {
                if (target.errors) {
                    return !!target.errors.find(function(obsv) {
                        return ko.validation.utils.isValidatable(obsv) && obsv.isValidating();
                    });
                } else if (target.error) {
                    return ko.validation.utils.isValidatable(target) && target.isValidating();
                }

                return false;
            });

            target.isValidAsync = function() {
                return new $.Deferred(function(dfd) {
                    try {
                        traverse(target(), function(key, value) {
                            if (ko.validation.utils.isValidatable(value) && value.validate) {
                                value.validate();
                            }
                        });

                        if (!target.isValidating()) {
                            dfd.resolve(target.isValid());
                        } else {
                            var subscription = target.isValidating.subscribe(function() {
                                dfd.resolve(target.isValid());
                                subscription.dispose();
                            });
                        }
                    } catch (err) {
                        dfd.reject(err);
                    }
                }).promise();
            };

            target.subscribe(function() {
                extendProperties(target);
            });

            //return the original observable
            return target;
        };

        function extendProperties(target) {
            traverse(target(), function(key, value) {
                if (ko.validation.utils.isValidatable(value)) {
                    extendProperty(value);
                }
            });
        }

        function traverse(o, func) {
            var self = this;

            _.forIn(o, function(value, key) {
                if (key !== '__ko_mapping__') {
                    func.apply(self, [key, value]);

                    if (value !== null && typeof(value) === 'object') {
                        traverse(value, func);
                    }
                }
            });
        }

        function extendProperty(target) {
            if (target.validate) {
                return;
            }

            //add some sub-observables to our observable
            target.formGroupValidationCssClass = ko.observable('');
            target.helpBlockValidationMessage = ko.observable('');

            //define a function to do validation
            target.validate = function() {
                var cssClass = '';
                var message = '';

                var isValidating = target.isValidating();
                if (isValidating) {
                    if (isValidating === true && target.validatingMessage) {
                        //cssClass = 'has-warning';
                        message = target.validatingMessage;
                    }
                } else {
                    if (target.isValid()) {
                        cssClass = 'has-success';

                        if (target.validMessage) {
                            message = target.validMessage;
                        }
                    } else {
                        if (target.error) {
                            cssClass = 'has-error';
                            message = target.error();
                        }
                    }
                }

                target.formGroupValidationCssClass(cssClass);
                target.helpBlockValidationMessage(message);
            };

            target.isValidating.subscribe(function() {
                target.validate();
            });

            //validate whenever the value changes
            target.subscribe(function() {
                target.validate();
            });
        }
    });
<|endoftext|>


<commit_before>
angular.module('flashpoint')
.factory('ListenerSet', function() {

  function ListenerSet(root, scope) {

    var self = this,
      scrubbingListeners = false;

    self.watchers = {};
    self.liveWatchers = {};
    self.values = {};
    self.priorities = {};
    self.errors = {};

    self.root = root;
    self.scope = scope;

    function scrubListeners() {

      for (var path in self.watchers) {

        if (self.watchers[path] && !self.liveWatchers[path]) {
          self.remove(path);
        }

      }

      // as of now, nothing is alive.
      self.liveWatchers = {};
      scrubbingListeners = false;

    }

    self.scope.$watch(function() {

      // after each scope cycle, sweep out any "orphaned" listeners, i.e.,
      // ones we previously connected but don't need anymore.

      if (!scrubbingListeners) {

        scrubbingListeners = true;

        scope.$$postDigest(scrubListeners);

      }

    });

  }

  ListenerSet.prototype.add = function(path, watcher) {

    var self = this;

    self.liveWatchers[path] = true;

    if (!self.watchers[path]) {

      if (watcher) {
        self.watchers[path] = watcher;
      } else {

        self.watchers[path] = self.root.child(path)
        .on('value', function(snap) {

          self.errors[path] = null;
          self.values[path] = snap.val();
          self.priorities[path] = snap.getPriority();
          self.scope.$evalAsync();

        }, function(err) {

          self.liveWatchers[path] = false;
          self.watchers[path] = null;
          self.errors[path] = err;
          self.values[path] = null;
          self.priorities[path] = null;
          self.scope.$evalAsync();

        });

      }

    }

  };

  ListenerSet.prototype.has = function(path) {
    return this.watchers.hasOwnProperty(path);
  };

  ListenerSet.prototype.remove = function(path) {

    // disconnect this watcher, it doesn't exist anymore.
    if (this.watchers[path].disconnect) {
      this.watchers[path].disconnect();
    } else {
      this.root.child(path).off('value', this.watchers[path]);
    }

    // clear all values associated with the watcher
    this.values[path] = null;
    this.errors[path] = null;
    this.priorities[path] = null;
    this.watchers[path] = null;

  };

  ListenerSet.prototype.clear = function() {

    for (var path in this.watchers) {
      this.remove(path);
    }

  };

  return ListenerSet;

});
<commit_msg>ListenerSet handles watcher scrubbing more cleanly now<commit_after>
angular.module('flashpoint')
.factory('ListenerSet', function() {

  function ListenerSet(root, scope) {

    var self = this,
      scrubbingListeners = false;

    self.watchers = {};
    self.liveWatchers = {};
    self.values = {};
    self.priorities = {};
    self.errors = {};

    self.root = root;
    self.scope = scope;

    function scrubListeners() {

      var newWatchers = {};

      for (var path in self.watchers) {

        if (self.watchers[path] && self.liveWatchers[path]) {
          newWatchers[path] = self.watchers[path];
        } else {
          self.remove(path);
        }

      }

      // as of now, nothing is alive.
      self.watchers = newWatchers;
      self.liveWatchers = {};
      scrubbingListeners = false;

    }

    self.scope.$watch(function() {

      // after each scope cycle, sweep out any "orphaned" listeners, i.e.,
      // ones we previously connected but don't need anymore.

      if (!scrubbingListeners) {

        scrubbingListeners = true;

        scope.$$postDigest(scrubListeners);

      }

    });

  }

  ListenerSet.prototype.add = function(path, watcher) {

    var self = this;

    self.liveWatchers[path] = true;

    if (!self.watchers[path]) {

      if (watcher) {
        self.watchers[path] = watcher;
      } else {

        self.watchers[path] = self.root.child(path)
        .on('value', function(snap) {

          self.errors[path] = null;
          self.values[path] = snap.val();
          self.priorities[path] = snap.getPriority();
          self.scope.$evalAsync();

        }, function(err) {

          self.liveWatchers[path] = false;
          self.watchers[path] = null;
          self.errors[path] = err;
          self.values[path] = null;
          self.priorities[path] = null;
          self.scope.$evalAsync();

        });

      }

    }

  };

  ListenerSet.prototype.has = function(path) {
    return this.watchers.hasOwnProperty(path);
  };

  ListenerSet.prototype.remove = function(path) {

    if (this.watchers[path]) {

      // disconnect this watcher, it doesn't exist anymore.
      if (this.watchers[path].disconnect) {
        this.watchers[path].disconnect();
      } else {
        this.root.child(path).off('value', this.watchers[path]);
      }

      // clear all values associated with the watcher
      this.values[path] = null;
      this.errors[path] = null;
      this.priorities[path] = null;
      this.watchers[path] = null;

    }

  };

  ListenerSet.prototype.clear = function() {

    for (var path in this.watchers) {
      this.remove(path);
    }

    this.watchers = {};

  };

  return ListenerSet;

});
<|endoftext|>


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

foam.CLASS({
  package: 'foam.box',
  name: 'ClassWhitelistContext',
  exports: [
    'lookup'
  ],
  properties: [
    {
      class: 'StringArray',
      name: 'whitelist'
    },
    {
      class: 'Map',
      name: 'whitelist_',
      expression: function(whitelist) {
        var w = {};
        for ( var i = 0 ; i < whitelist.length ; i++ ) {
          w[whitelist[i]] = true;
        }
        return w;
      },
      swiftExpressionArgs: ['whitelist'],
      swiftExpression: function() {/*
var w = [String:Bool]()
for i in whitelist {
  w[i] = true
}
return w
      */}
    }
  ],
  methods: [
    {
      class: 'ContextMethod',
      name: 'lookup',
      swiftReturns: 'ClassInfo?',
      swiftThrows: true,
      args: [
        {
          swiftType: 'Context',
          name: 'X',
        },
        {
          swiftType: 'String',
          name: 'id',
        },
      ],
      code: function(X, id) {
        // TODO: This should be:
        // if ( ! this.whitelist_[id] ) { ... }
        // Change it back once #777 is closed.
        if ( this.whitelist.indexOf(id) === -1 ) {
          throw new Error('Class "' + id + '" is not whitelisted.');
        }
        return this.__context__.lookup.call(X, id);
      },
      swiftCode: function() {/*
if whitelist_[id] as? Bool ?? false {
  throw FoamError("Class " + id + " is not whitelisted.")
}
return X.lookup(id)
      */}
    }
  ]
});
<commit_msg>Follow-up from #779 with simpler solution (#784)<commit_after>/**
 * @license
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

foam.CLASS({
  package: 'foam.box',
  name: 'ClassWhitelistContext',
  exports: [
    'lookup'
  ],
  properties: [
    {
      class: 'StringArray',
      name: 'whitelist'
    },
    {
      name: 'whitelist_',
      expression: function(whitelist) {
        var w = {};
        for ( var i = 0 ; i < whitelist.length ; i++ ) {
          w[whitelist[i]] = true;
        }
        return w;
      },
      swiftExpressionArgs: ['whitelist'],
      swiftExpression: function() {/*
var w = [String:Bool]()
for i in whitelist {
  w[i] = true
}
return w
      */}
    }
  ],
  methods: [
    {
      class: 'ContextMethod',
      name: 'lookup',
      swiftReturns: 'ClassInfo?',
      swiftThrows: true,
      args: [
        {
          swiftType: 'Context',
          name: 'X',
        },
        {
          swiftType: 'String',
          name: 'id',
        },
      ],
      code: function(X, id) {
        if ( ! this.whitelist_[id] ) {
          throw new Error('Class "' + id + '" is not whitelisted.');
        }
        return this.__context__.lookup.call(X, id);
      },
      swiftCode: function() {/*
if whitelist_[id] as? Bool ?? false {
  throw FoamError("Class " + id + " is not whitelisted.")
}
return X.lookup(id)
      */}
    }
  ]
});
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2018 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.u2.view',
  name: 'UnstyledTableView',
  extends: 'foam.u2.Element',

  implements: [
    'foam.mlang.Expressions'
  ],

  requires: [
    'foam.u2.md.OverlayDropdown',
    'foam.u2.view.EditColumnsView'
  ],

  exports: [
    'columns',
    'selection',
    'hoverSelection'
  ],

  imports: [
    'dblclick?',
    'editRecord?',
    'selection? as importSelection'
  ],

  properties: [
    {
      class: 'Class',
      name: 'of'
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'data',
      postSet: function(_, data) {
        if ( ! this.of && data ) this.of = data.of;
      }
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'orderedDAO',
      expression: function(data, order) {
        return data ? data.orderBy(order) : foam.dao.NullDAO.create();
      }
    },
    {
      name: 'order'
    },
    {
      name: 'columns_',
      expression: function(columns, of) {
        var of = this.of;
        if ( ! of ) return [];

        return columns.map(function(p) {
          var c = typeof p == 'string' ?
            of.getAxiomByName(p) :
            p;

           if ( ! c ) {
             console.error('Unknown table column: ', p);
           }

          return c;
        }).filter(function(c) { return c; });
      }
    },
    {
      name: 'columns',
      expression: function(of) {
        var of = this.of;
        if ( ! of ) return [];

        var tableColumns = of.getAxiomByName('tableColumns');

        if ( tableColumns ) return tableColumns.columns;

        return of.getAxiomsByClass(foam.core.Property).
            filter(function(p) { return p.tableCellFormatter && ! p.hidden; }).
            map(foam.core.Property.NAME.f);
      }
    },
    {
      class: 'Boolean',
      name: 'editColumnsEnabled',
      value: true,
      documentation: 'Set this to true to let the user select columns.'
    },
    {
      class: 'Boolean',
      name: 'disableUserSelection',
      value: false,
      documentation: 'Ignores selection by user.'
    },
    {
      name: 'ascIcon',
      documentation: 'HTML entity representing unicode Up-Pointing Triangle',
      factory: function() {
        return this.Entity.create({ name: '#9650' });
      }
    },
    {
      name: 'descIcon',
      documentation: 'HTML entity representing unicode Down-Pointing Triangle',
      factory: function() {
        return this.Entity.create({ name: '#9660' });
      }
    },
    {
      name: 'vertMenuIcon',
      documentation: 'HTML entity representing unicode Vertical Ellipsis',
      factory: function() {
        return this.Entity.create({ name: '#8942' });
      }
    },
    {
      name: 'selection',
      expression: function(importSelection) { return importSelection || null; },
    },
    'hoverSelection',
    'dropdownOrigin',
    'overlayOrigin'
  ],

  methods: [
    function sortBy(column) {
      this.order = this.order === column ?
        this.DESC(column) :
        column;
    },

    function createColumnSelection() {
      var editor = this.EditColumnsView.create({
        columns: this.columns,
        columns_$: this.columns_$,
        table: this.of
      });

      return this.OverlayDropdown.create().add(editor);
    },

    /** Adds offset for edit columns overlay dropdown
     * OverlayDropdown adds element to top right of parent container.
     * We want the table dropdown to appear below the dropdown icon.
     */
    function positionOverlayDropdown(overlay) {
      // Dynamic position calculation
      var origin  = this.dropdownOrigin.el();
      var current = this.overlayOrigin.el();

      var boundingBox = origin.getBoundingClientRect();
      var dropdownMenu = current.getBoundingClientRect();

      overlay.style({ top: boundingBox.top - dropdownMenu.top + 'px' });
    },

    function initE() {
      var view = this;
      var columnSelectionE;

      this.start('div', null, this.overlayOrigin$)
        .callIf(view.editColumnsEnabled, function() {
          columnSelectionE = view.createColumnSelection();
          this.add(columnSelectionE);
        })
      .end();

      this.
        addClass(this.myClass()).
        addClass(this.myClass(this.of.id.replace(/\./g, '-'))).
        setNodeName('table').
        start('thead').
          add(this.slot(function(columns_) {
            return this.E('tr').
              forEach(columns_, function(column) {
                this.start('th').
                  addClass(view.myClass('th-' + column.name)).
                  callIf(column.tableWidth, function() {
                    this.style({ width: column.tableWidth });
                  }).
                  on('click', function(e) { view.sortBy(column); }).
                  call(column.tableHeaderFormatter, [column]).
                  add(' ', this.slot(function(order) {
                    return column === order ? view.ascIcon :
                        (view.Desc.isInstance(order) && order.arg1 === column) ? view.descIcon : ''
                  }, view.order$)).
                end();
              }).
              call(function() {
                this.start('th').
                  callIf(view.editColumnsEnabled, function() {
                    this.addClass(view.myClass('th-editColumns')).
                    on('click', function(e) {
                      view.positionOverlayDropdown(columnSelectionE);
                      columnSelectionE.open();
                    }).
                    add(' ', view.vertMenuIcon).
                    addClass(view.myClass('vertDots')).
                    addClass(view.myClass('noselect'));
                  }).
                  tag('div', null, view.dropdownOrigin$).
                end();
              });
          })).
          add(this.slot(function(columns_) {
            return this.
              E('tbody').
              select(this.orderedDAO$proxy, function(obj) {
                return this.E('tr').
                  on('mouseover', function() { view.hoverSelection = obj; }).
                  callIf(view.dblclick && ! view.disableUserSelection, function() {
                    this.on('dblclick', function() {
                      view.dblclick && view.dblclick(obj);
                    });
                  }).
                  callIf( ! view.disableUserSelection, function() {
                    this.on('click', function(evt) {
                      // If we're clicking somewhere to close the context menu,
                      // don't do anything.
                      if (
                        evt.target.nodeName === 'DROPDOWN-OVERLAY' ||
                        evt.target.classList.contains(view.myClass('vertDots'))
                      ) return;

                      view.selection = obj;
                      if ( view.importSelection$ ) view.importSelection = obj;
                      if ( view.editRecord$ ) view.editRecord(obj);
                    });
                  }).
                  addClass(view.slot(function(selection) {
                    return selection && foam.util.equals(obj.id, selection.id) ?
                        view.myClass('selected') : '';
                  })).
                  addClass(view.myClass('row')).
                  forEach(columns_, function(column) {
                    this.
                      start('td').
                        callOn(column.tableCellFormatter, 'format', [
                          column.f ? column.f(obj) : null, obj, column
                        ]).
                      end();
                  }).
                  call(function() {
                    var model = view.of;
                    var actions = model.getAxiomsByClass(foam.core.Action);
                    var overlay = view.OverlayDropdown.create();
                    var availableActions = actions.filter(function(action) {
                      return action.isAvailableFor(obj);
                    });

                    // Don't show the context menu if there are no available
                    // actions defined on the model.
                    if ( availableActions.length === 0 ) {
                      if ( view.editColumnsEnabled ) {
                        return this.tag('td');
                      }
                      return;
                    }

                    overlay.forEach(availableActions, function(action) {
                      var elm = this.start()
                        .addClass(view.myClass('context-menu-item'))
                        .add(action.label);

                      if ( action.isEnabledFor(obj) ) {
                        elm = elm.on('click', function(evt) {
                          action.maybeCall(view.__subContext__, obj);
                        });
                      } else {
                        elm.addClass('disabled');
                      }

                      elm.end();
                    });

                    return this.start('td').
                      add(overlay).
                      style({ 'text-align': 'right' }).
                      start('span').
                        addClass(view.myClass('vertDots')).
                        addClass(view.myClass('noselect')).
                        add(view.vertMenuIcon).
                        on('click', function(evt) {
                          view.positionOverlayDropdown(overlay);
                          overlay.open();
                        }).
                      end().
                    end();
                  });
              });
          }));
    }
  ]
});
<commit_msg>Pull actions from the context as well as the model<commit_after>/**
 * @license
 * Copyright 2018 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.u2.view',
  name: 'UnstyledTableView',
  extends: 'foam.u2.Element',

  implements: [
    'foam.mlang.Expressions'
  ],

  requires: [
    'foam.u2.md.OverlayDropdown',
    'foam.u2.view.EditColumnsView'
  ],

  exports: [
    'columns',
    'selection',
    'hoverSelection'
  ],

  imports: [
    // Each table row has a context menu that contains actions you can perform
    // on the object in that row. The actions used to populate that menu come
    // from two different sources. The first source is the 'contextMenuActions'
    // import. If you want a context menu action to do something in the view,
    // then you should write the code for that action on the view and export it.
    // The second source of actions is from the model of the object being shown
    // in the table.
    'contextMenuActions',
    'dblclick?',
    'editRecord?',
    'selection? as importSelection'
  ],

  properties: [
    {
      class: 'Class',
      name: 'of'
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'data',
      postSet: function(_, data) {
        if ( ! this.of && data ) this.of = data.of;
      }
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'orderedDAO',
      expression: function(data, order) {
        return data ? data.orderBy(order) : foam.dao.NullDAO.create();
      }
    },
    {
      name: 'order'
    },
    {
      name: 'columns_',
      expression: function(columns, of) {
        var of = this.of;
        if ( ! of ) return [];

        return columns.map(function(p) {
          var c = typeof p == 'string' ?
            of.getAxiomByName(p) :
            p;

           if ( ! c ) {
             console.error('Unknown table column: ', p);
           }

          return c;
        }).filter(function(c) { return c; });
      }
    },
    {
      name: 'columns',
      expression: function(of) {
        var of = this.of;
        if ( ! of ) return [];

        var tableColumns = of.getAxiomByName('tableColumns');

        if ( tableColumns ) return tableColumns.columns;

        return of.getAxiomsByClass(foam.core.Property).
            filter(function(p) { return p.tableCellFormatter && ! p.hidden; }).
            map(foam.core.Property.NAME.f);
      }
    },
    {
      class: 'Boolean',
      name: 'editColumnsEnabled',
      value: true,
      documentation: 'Set this to true to let the user select columns.'
    },
    {
      class: 'Boolean',
      name: 'disableUserSelection',
      value: false,
      documentation: 'Ignores selection by user.'
    },
    {
      name: 'ascIcon',
      documentation: 'HTML entity representing unicode Up-Pointing Triangle',
      factory: function() {
        return this.Entity.create({ name: '#9650' });
      }
    },
    {
      name: 'descIcon',
      documentation: 'HTML entity representing unicode Down-Pointing Triangle',
      factory: function() {
        return this.Entity.create({ name: '#9660' });
      }
    },
    {
      name: 'vertMenuIcon',
      documentation: 'HTML entity representing unicode Vertical Ellipsis',
      factory: function() {
        return this.Entity.create({ name: '#8942' });
      }
    },
    {
      name: 'selection',
      expression: function(importSelection) { return importSelection || null; },
    },
    'hoverSelection',
    'dropdownOrigin',
    'overlayOrigin'
  ],

  methods: [
    function sortBy(column) {
      this.order = this.order === column ?
        this.DESC(column) :
        column;
    },

    function createColumnSelection() {
      var editor = this.EditColumnsView.create({
        columns: this.columns,
        columns_$: this.columns_$,
        table: this.of
      });

      return this.OverlayDropdown.create().add(editor);
    },

    /** Adds offset for edit columns overlay dropdown
     * OverlayDropdown adds element to top right of parent container.
     * We want the table dropdown to appear below the dropdown icon.
     */
    function positionOverlayDropdown(overlay) {
      // Dynamic position calculation
      var origin  = this.dropdownOrigin.el();
      var current = this.overlayOrigin.el();

      var boundingBox = origin.getBoundingClientRect();
      var dropdownMenu = current.getBoundingClientRect();

      overlay.style({ top: boundingBox.top - dropdownMenu.top + 'px' });
    },

    function initE() {
      var view = this;
      var columnSelectionE;

      this.start('div', null, this.overlayOrigin$)
        .callIf(view.editColumnsEnabled, function() {
          columnSelectionE = view.createColumnSelection();
          this.add(columnSelectionE);
        })
      .end();

      this.
        addClass(this.myClass()).
        addClass(this.myClass(this.of.id.replace(/\./g, '-'))).
        setNodeName('table').
        start('thead').
          add(this.slot(function(columns_) {
            return this.E('tr').
              forEach(columns_, function(column) {
                this.start('th').
                  addClass(view.myClass('th-' + column.name)).
                  callIf(column.tableWidth, function() {
                    this.style({ width: column.tableWidth });
                  }).
                  on('click', function(e) { view.sortBy(column); }).
                  call(column.tableHeaderFormatter, [column]).
                  add(' ', this.slot(function(order) {
                    return column === order ? view.ascIcon :
                        (view.Desc.isInstance(order) && order.arg1 === column) ? view.descIcon : ''
                  }, view.order$)).
                end();
              }).
              call(function() {
                this.start('th').
                  callIf(view.editColumnsEnabled, function() {
                    this.addClass(view.myClass('th-editColumns')).
                    on('click', function(e) {
                      view.positionOverlayDropdown(columnSelectionE);
                      columnSelectionE.open();
                    }).
                    add(' ', view.vertMenuIcon).
                    addClass(view.myClass('vertDots')).
                    addClass(view.myClass('noselect'));
                  }).
                  tag('div', null, view.dropdownOrigin$).
                end();
              });
          })).
          add(this.slot(function(columns_) {
            return this.
              E('tbody').
              select(this.orderedDAO$proxy, function(obj) {
                return this.E('tr').
                  on('mouseover', function() { view.hoverSelection = obj; }).
                  callIf(view.dblclick && ! view.disableUserSelection, function() {
                    this.on('dblclick', function() {
                      view.dblclick && view.dblclick(obj);
                    });
                  }).
                  callIf( ! view.disableUserSelection, function() {
                    this.on('click', function(evt) {
                      // If we're clicking somewhere to close the context menu,
                      // don't do anything.
                      if (
                        evt.target.nodeName === 'DROPDOWN-OVERLAY' ||
                        evt.target.classList.contains(view.myClass('vertDots'))
                      ) return;

                      view.selection = obj;
                      if ( view.importSelection$ ) view.importSelection = obj;
                      if ( view.editRecord$ ) view.editRecord(obj);
                    });
                  }).
                  addClass(view.slot(function(selection) {
                    return selection && foam.util.equals(obj.id, selection.id) ?
                        view.myClass('selected') : '';
                  })).
                  addClass(view.myClass('row')).
                  forEach(columns_, function(column) {
                    this.
                      start('td').
                        callOn(column.tableCellFormatter, 'format', [
                          column.f ? column.f(obj) : null, obj, column
                        ]).
                      end();
                  }).
                  call(function() {
                    var modelActions = view.of.getAxiomsByClass(foam.core.Action);
                    var allActions = Array.isArray(view.contextMenuActions) ?
                      view.contextMenuActions.concat(modelActions) :
                      modelActions;
                    var actions = allActions.filter(function(action) {
                      return action.isAvailableFor(obj);
                    });

                    // Don't show the context menu if there are no available
                    // actions defined on the model.
                    if ( actions.length === 0 ) {
                      if ( view.editColumnsEnabled ) {
                        return this.tag('td');
                      }
                      return;
                    }

                    var overlay = view.OverlayDropdown.create();
                    overlay.forEach(actions, function(action) {
                      var elm = this.start()
                        .addClass(view.myClass('context-menu-item'))
                        .add(action.label);

                      if ( action.isEnabledFor(obj) ) {
                        elm = elm.on('click', function(evt) {
                          action.maybeCall(view.__subContext__, obj);
                        });
                      } else {
                        elm.addClass('disabled');
                      }

                      elm.end();
                    });

                    return this.start('td').
                      add(overlay).
                      style({ 'text-align': 'right' }).
                      start('span').
                        addClass(view.myClass('vertDots')).
                        addClass(view.myClass('noselect')).
                        add(view.vertMenuIcon).
                        on('click', function(evt) {
                          view.positionOverlayDropdown(overlay);
                          overlay.open();
                        }).
                      end().
                    end();
                  });
              });
          }));
    }
  ]
});
<|endoftext|>


<commit_before><commit_msg>Update ogario.v4.js<commit_after><|endoftext|>


<commit_before>import KEYBOARD_MAP from '../constants/keyboardMap.js';

const playerControlsHandler = {
    handle (pressedKeys, player) {
        if (pressedKeys[player.controls.left]) {
            if (player.velocity.x >= -player.maxSpeed) {
                player.velocity.x -= player.acceleration;
            }
        }

        if (pressedKeys[player.controls.right]) {
            if (player.velocity.x <= player.maxSpeed) {
                player.velocity.x += player.acceleration;
            }
        }

        if (pressedKeys[player.controls.up]) {
            if (player.velocity.y <= player.maxSpeed) {
                player.velocity.y += player.acceleration;
            }
        }

        if (pressedKeys[player.controls.down]) {
            if (player.velocity.y >= -player.maxSpeed) {
                player.velocity.y -= player.acceleration;
            }
        }

        if (!pressedKeys[player.controls.left] && !pressedKeys[player.controls.right]) {
            if (player.velocity.x > 0) {
                if (player.velocity.x < player.acceleration) {
                    player.velocity.x = 0;
                } else {
                    player.velocity.x -= player.acceleration;
                }
            }
            if (player.velocity.x < 0) {
                if (player.velocity.x > -player.acceleration) {
                    player.velocity.x = 0;
                } else {
                    player.velocity.x += player.acceleration;
                }
            }
        }

        if (!pressedKeys[player.controls.up] && !pressedKeys[player.controls.down]) {
            if (player.velocity.y > 0) {
                if (player.velocity.y < player.acceleration) {
                    player.velocity.y = 0;
                } else {
                    player.velocity.y -= player.acceleration;
                }
            }
            if (player.velocity.y < 0) {
                if (player.velocity.y > -player.acceleration) {
                    player.velocity.y = 0;
                } else {
                    player.velocity.y += player.acceleration;
                }
            }
        }

        player.sprite.worldPosition.x += player.velocity.x;
        player.sprite.worldPosition.y += player.velocity.y;

        if (player.sprite.worldPosition.x < 0) {
            player.sprite.worldPosition.x = 0;
            player.velocity.x = 0;
        }

        if (player.sprite.worldPosition.x + player.sprite.width > 40 * 32) {
            player.sprite.worldPosition.x = 40 * 32 - player.sprite.width;
            player.velocity.x = 0;
        }
    }
};

export default playerControlsHandler;
<commit_msg>Removed KEYBOARD_MAP from playerControlsHandler.js<commit_after>const playerControlsHandler = {
    handle (pressedKeys, player) {
        if (pressedKeys[player.controls.left]) {
            if (player.velocity.x >= -player.maxSpeed) {
                player.velocity.x -= player.acceleration;
            }
        }

        if (pressedKeys[player.controls.right]) {
            if (player.velocity.x <= player.maxSpeed) {
                player.velocity.x += player.acceleration;
            }
        }

        if (pressedKeys[player.controls.up]) {
            if (player.velocity.y <= player.maxSpeed) {
                player.velocity.y += player.acceleration;
            }
        }

        if (pressedKeys[player.controls.down]) {
            if (player.velocity.y >= -player.maxSpeed) {
                player.velocity.y -= player.acceleration;
            }
        }

        if (!pressedKeys[player.controls.left] && !pressedKeys[player.controls.right]) {
            if (player.velocity.x > 0) {
                if (player.velocity.x < player.acceleration) {
                    player.velocity.x = 0;
                } else {
                    player.velocity.x -= player.acceleration;
                }
            }
            if (player.velocity.x < 0) {
                if (player.velocity.x > -player.acceleration) {
                    player.velocity.x = 0;
                } else {
                    player.velocity.x += player.acceleration;
                }
            }
        }

        if (!pressedKeys[player.controls.up] && !pressedKeys[player.controls.down]) {
            if (player.velocity.y > 0) {
                if (player.velocity.y < player.acceleration) {
                    player.velocity.y = 0;
                } else {
                    player.velocity.y -= player.acceleration;
                }
            }
            if (player.velocity.y < 0) {
                if (player.velocity.y > -player.acceleration) {
                    player.velocity.y = 0;
                } else {
                    player.velocity.y += player.acceleration;
                }
            }
        }

        player.sprite.worldPosition.x += player.velocity.x;
        player.sprite.worldPosition.y += player.velocity.y;

        if (player.sprite.worldPosition.x < 0) {
            player.sprite.worldPosition.x = 0;
            player.velocity.x = 0;
        }

        if (player.sprite.worldPosition.x + player.sprite.width > 40 * 32) {
            player.sprite.worldPosition.x = 40 * 32 - player.sprite.width;
            player.velocity.x = 0;
        }
    }
};

export default playerControlsHandler;
<|endoftext|>


<commit_before>/**
 * Created by Gaplo917 on 11/1/2016.
 */
import * as HKEPC from '../../data/config/hkepc'
import * as URLUtils from '../../utils/url'
import {GeneralHtml} from '../model/general-html'
import {CommonInfoExtractRequest} from "../model/CommonInfoExtractRequest"

const cheerio = require('cheerio')

export class TopicListController {
  static get STATE() { return 'tab.topics'}
  static get NAME() { return 'TopicListController'}
  static get CONFIG() { return {
    url: '/topics',
    views: {
      'tab-topics': {
        templateUrl: 'templates/tab-topics.html',
        controller: TopicListController.NAME,
        controllerAs: 'vm'
      }
    }
  }}

  constructor($scope,$http,LocalStorageService,AuthService,ngToast, apiService,rx,observeOnScope,$q) {

    this.scope = $scope
    this.http = $http
    this.rx = rx
    this.localStorageService = LocalStorageService
    this.authService = AuthService
    this.topics = []
    this.apiService = apiService
    this.firstLogin = true
    this.q = $q

    rx.Observable.interval(30000)
      .startWith(0)
      .flatMap(() => this.localStorageService.get('topics-cache-timestamp'))
      .safeApply($scope, data => {
        if(data){
          this.cacheTimestamp = moment(data * 1000).fromNow()
          console.log(this.cacheTimestamp)

        }
      }).subscribe()

    observeOnScope($scope, 'vm.topics')
      .delay(1000) // delay for saving topics
      .subscribe(({newValue, oldValue}) => {
        if (newValue.length > 0) {
          this.localStorageService.setObject('topics', newValue)
        }
      })

    $scope.$on('$ionicView.loaded', (e) => {
      console.log("loaded")

      this.topicsSubscription = this.localStorageService.getObject('topics')
        .do(topics => {
          if (topics) {
            console.log('[TopicListController]', 'using cache')
          }
        })
        .flatMap(topics => {
          return topics
            ? rx.Observable.just(topics)
            : this.apiService.topicList()
              .do(() => this.localStorageService.set('topics-cache-timestamp', moment().unix()))
        })
        .safeApply($scope, topics => {
          this.topics = topics
        })
        .subscribe()
    })

    $scope.$on('$ionicView.enter', (e) => {

      this.authService.isLoggedIn().subscribe(isLoggedIn => {
        this.isLoggedIn = isLoggedIn

        if(isLoggedIn && this.firstLogin) {
          this.loadList()

          // unset to false to prevent next loading
          this.firstLogin = false
        }

      })

      this.authService.getUsername().subscribe(username => {
        this.username = username
      })

    })

    $scope.$on('$ionicView.beforeLeave', (e) => {
    })

  }

  reset(){
    // reset the model
    // this.topics = []
    this.topicsSubscription && this.topicsSubscription.dispose()
  }

  loadList() {

    this.refreshing = true

    this.topicsSubscription = this.apiService.topicList()
      .safeApply(this.scope, topics => {
        this.cacheTimestamp = moment().fromNow()
        this.refreshing = false

        // save to local
        this.localStorageService.set('topics-cache-timestamp', moment().unix())

        this.topics = topics
      })
      .subscribe()
  }

  doRefresh(){

    this.reset()

    this.loadList()
  }

  canShowGroupNameIniOSReview(groupName){
    return !ionic.Platform.isIOS() || groupName != 'Mobile Phone'
  }

  canShowIniOSReview(topicId){
    const blackList = [121,123,202]
    return !ionic.Platform.isIOS() || (blackList.indexOf(parseInt(topicId)) < 0 || (this.isLoggedIn && this.username != 'logary917'))
  }

  canShowSectionInIOSReview(topicId){
    const blackList = [171,168,170,44,277,202,-1] // -1 is IR Zone
    return !ionic.Platform.isIOS() || (blackList.indexOf(parseInt(topicId)) < 0 || (this.isLoggedIn && this.username != 'logary917'))
  }

}<commit_msg>clear cache if the topic list is different<commit_after>/**
 * Created by Gaplo917 on 11/1/2016.
 */
import * as HKEPC from '../../data/config/hkepc'
import * as URLUtils from '../../utils/url'
import {GeneralHtml} from '../model/general-html'
import {CommonInfoExtractRequest} from "../model/CommonInfoExtractRequest"

const cheerio = require('cheerio')

export class TopicListController {
  static get STATE() { return 'tab.topics'}
  static get NAME() { return 'TopicListController'}
  static get CONFIG() { return {
    url: '/topics',
    views: {
      'tab-topics': {
        templateUrl: 'templates/tab-topics.html',
        controller: TopicListController.NAME,
        controllerAs: 'vm'
      }
    }
  }}

  constructor($scope,$http,LocalStorageService,AuthService,ngToast, apiService,rx,observeOnScope,$q,$state) {

    this.scope = $scope
    this.http = $http
    this.rx = rx
    this.localStorageService = LocalStorageService
    this.authService = AuthService
    this.topics = []
    this.apiService = apiService
    this.firstLogin = true
    this.q = $q
    this.state = $state

    rx.Observable.interval(30000)
      .startWith(0)
      .flatMap(() => this.localStorageService.get('topics-cache-timestamp'))
      .safeApply($scope, data => {
        if(data){
          this.cacheTimestamp = moment(data * 1000).fromNow()
          console.log(this.cacheTimestamp)

        }
      }).subscribe()

    observeOnScope($scope, 'vm.topics')
      .delay(1000) // delay for saving topics
      .subscribe(({newValue, oldValue}) => {
        if (newValue.length > 0) {
          this.localStorageService.setObject('topics', newValue)
        }
      })

    $scope.$on('$ionicView.loaded', (e) => {
      console.log("loaded")

      this.topicsSubscription = this.localStorageService.getObject('topics')
        .do(topics => {
          if (topics) {
            console.log('[TopicListController]', 'using cache')
          }
        })
        .flatMap(topics => {
          return topics
            ? rx.Observable.just(topics)
            : this.apiService.topicList()
              .do(() => this.localStorageService.set('topics-cache-timestamp', moment().unix()))
        })
        .safeApply($scope, topics => {
          this.topics = topics
        })
        .subscribe()
    })

    $scope.$on('$ionicView.enter', (e) => {

      this.authService.isLoggedIn().subscribe(isLoggedIn => {
        this.isLoggedIn = isLoggedIn

        if(isLoggedIn && this.firstLogin) {
          this.loadList()

          // unset to false to prevent next loading
          this.firstLogin = false
        }

      })

      this.authService.getUsername().subscribe(username => {
        this.username = username
      })

    })

    $scope.$on('$ionicView.beforeLeave', (e) => {
    })

  }

  reset(){
    // reset the model
    // this.topics = []
    this.topicsSubscription && this.topicsSubscription.dispose()
  }

  loadList() {

    this.refreshing = true

    this.topicsSubscription = this.apiService.topicList()
      .safeApply(this.scope, topics => {
        this.cacheTimestamp = moment().fromNow()
        this.refreshing = false

        // save to local
        this.localStorageService.set('topics-cache-timestamp', moment().unix())

        if(this.topics.length != topics.length){
          this.state.go(this.state.currentState, {}, {reload:true})
        }
        this.topics = topics
      })
      .subscribe()
  }

  doRefresh(){

    this.reset()

    this.loadList()
  }

  canShowGroupNameIniOSReview(groupName){
    return !ionic.Platform.isIOS() || groupName != 'Mobile Phone'
  }

  canShowIniOSReview(topicId){
    const blackList = [121,123,202]
    return !ionic.Platform.isIOS() || (blackList.indexOf(parseInt(topicId)) < 0 || (this.isLoggedIn && this.username != 'logary917'))
  }

  canShowSectionInIOSReview(topicId){
    const blackList = [171,168,170,44,277,202,-1] // -1 is IR Zone
    return !ionic.Platform.isIOS() || (blackList.indexOf(parseInt(topicId)) < 0 || (this.isLoggedIn && this.username != 'logary917'))
  }

}<|endoftext|>


<commit_before>const SearchCommand = require("../types/SearchCommand");
const CreatureModel = require("../models/Creature");
const Discord = require("discord.js");

module.exports = class Creature extends SearchCommand{
	constructor(client){
		super(client, {
			name: "creature",
			help: "Fetches a creature by name.",
			category: "5e",
			helpArgs: "<Creature Name>",
			elevation: 0,
			timeout: 15000
		});

		this._attachFieldToEmbed = function(name, data, embed){
			embed = embed.addField("\u200b", `__**${name}**__`);
			data.forEach((element) => {
				if(element.text.length + (element.attack ? element.attack.length : 0) > 1024){
					let text = element.text;
					text = text.concat(`${element.attack ? "\n**" + element.attack + "**" : ""}`);
					let stringBuilder = [];

					while(text.length > 900){	// TODO: figure out why this doesn't split correctly, factoring in the concat above
						let splitIndex = text.lastIndexOf("\n", 900);
						stringBuilder.push(text.substring(0, splitIndex));
						text = text.substring(splitIndex + 1);
					}
					stringBuilder.push(text);

					embed = embed.addField(element.name, stringBuilder.shift());
					stringBuilder.forEach((string) => {
						embed = embed.addField(`${element.name}, continued. . .`, `${string}${element.attack ? "\n**" + element.attack + "**" : ""}`);
					});
				}
				else if(element.text.length + (element.attack ? element.attack.length : 0) > 0){
					embed = embed.addField(element.name, `${element.text}${element.attack ? "\n**" + element.attack + "**" : ""}`);
				}
			});
			return embed;
		};

		this._getModifierFor = function(attribute){
			let modifier = Math.floor(parseInt(attribute) / 2) - 5;
			if(modifier >= 0){
				return `+${modifier}`;
			}
			else{
				return `${modifier}`; // Integer to string conversion
			}
		};
		this._crToExp = function(cr){
			switch(cr){
				case "0":
					return "10";
				case "1/8":
					return "25";
				case "1/4":
					return "50";
				case "1/2":
					return "100";
				case "1":
					return "200";
				case "2":
					return "450";
				case "3":
					return "700";
				case "4":
					return "1,100";
				case "5":
					return "1,800";
				case "6":
					return "2,300";
				case "7":
					return "2,900";
				case "8":
					return "3,900";
				case "9":
					return "5,000";
				case "10":
					return "5,900";
				case "11":
					return "7,200";
				case "12":
					return "8,400";
				case "13":
					return "10,000";
				case "14":
					return "11,500";
				case "15":
					return "13,000";
				case "16":
					return "15,000";
				case "17":
					return "18,000";
				case "18":
					return "20,000";
				case "19":
					return "22,000";
				case "20":
					return "25,000";
				case "21":
					return "33,000";
				case "22":
					return "41,000";
				case "23":
					return "50,000";
				case "24":
					return "62,000";
				case "30":
					return "155,000";
				default:
					return "";
			}
		};
	}
	async execute(msg, args){
		let prefix;
		if(msg.guild){
			prefix = await this.client.fetchPrefix(msg.guild.id);
		}
		else{
			prefix = "";
		}
		if(!args){
			return msg.reply(`invalid command; please specify a creature.\nProper usage: \`${prefix}${this.name} aboleth\``);
		}
		let toEdit = await msg.reply("fetching your creature. . .");
		let creatures = await CreatureModel.find({name: new RegExp(args.join(" "), "i")});
		if(!creatures || creatures.length === 0){
			return toEdit.edit("Unable to find your creature. Sorry!");
		}

		let result;
		if(creatures.length > 1){
			try{
				result = await super.disambiguate(toEdit, msg.author, "creature", creatures, "name");
			}
			catch(err){
				return toEdit.edit(err);
			}
		}
		else{
			result = creatures[0];
		}

		let description = [];
		description.push(`*${result.size} ${result.type} // ${result.alignment}*\n`);
		description.push(`**Armor Class** ${result.ac}\n**Hit Points** ${result.hp}\n**Speed** ${result.speed}\n`);
		let attributes = [];
		attributes.push(`**STR** ${result.str} (${this._getModifierFor(result.str)})`);
		attributes.push(`**DEX** ${result.dex} (${this._getModifierFor(result.dex)})`);
		attributes.push(`**CON** ${result.con} (${this._getModifierFor(result.con)})`);
		attributes.push(`**INT** ${result.int} (${this._getModifierFor(result.int)})`);
		attributes.push(`**WIS** ${result.wis} (${this._getModifierFor(result.wis)})`);
		attributes.push(`**CHA** ${result.cha} (${this._getModifierFor(result.cha)})`);
		description.push(attributes.join("; "));
		if(result.saves){
			description.push(`**Saving Throws** ${result.saves}`);
		}
		if(result.skill){
			description.push(`**Skills** ${result.skill}\n`);
		}
		if(result.vulnerable){
			description.push(`**Damage Vulnerabilities** ${result.vulnerable}`);
		}
		if(result.immune){
			description.push(`**Damage Immunities** ${result.immune}`);
		}
		if(result.conditionImmune){
			description.push(`**Condition Immunities** ${result.conditionImmune}`);
		}

		description.push(`**Senses** ${result.senses? result.senses + ", " : ""}passive Perception ${result.passive}`);

		if(result.languages){
			description.push(result.languages);
		}
		description.push(`**Challenge** ${result.cr} (${this._crToExp(result.cr)} XP)`);

		let embed = new Discord.RichEmbed().setTitle(`__**${result.name}**__`)
											.setColor(0x97ff43)
											.setDescription(description.join("\n"));

		// Attach trait fields
		if(result.traits && result.traits.length > 0){
			embed = this._attachFieldToEmbed("Traits", result.traits, embed);
		}

		if(result.spells){
			embed = embed.addField("Known Spells", result.spells);
		}

		console.log("Attaching actions");
		if(result.actions && result.actions.length > 0){
			embed = this._attachFieldToEmbed("Actions", result.actions, embed);
		}
		console.log("donezo actions");

		if(result.legendary && result.legendary.length > 0){
			embed = this._attachFieldToEmbed("Legendary Actions", result.legendary, embed);
		}

		return toEdit.edit("", {embed: embed});
	}
};
<commit_msg>Remove debug logging from Creature command<commit_after>const SearchCommand = require("../types/SearchCommand");
const CreatureModel = require("../models/Creature");
const Discord = require("discord.js");

module.exports = class Creature extends SearchCommand{
	constructor(client){
		super(client, {
			name: "creature",
			help: "Fetches a creature by name.",
			category: "5e",
			helpArgs: "<Creature Name>",
			elevation: 0,
			timeout: 15000
		});

		this._attachFieldToEmbed = function(name, data, embed){
			embed = embed.addField("\u200b", `__**${name}**__`);
			data.forEach((element) => {
				if(element.text.length + (element.attack ? element.attack.length : 0) > 1024){
					let text = element.text;
					text = text.concat(`${element.attack ? "\n**" + element.attack + "**" : ""}`);
					let stringBuilder = [];

					while(text.length > 900){	// TODO: figure out why this doesn't split correctly, factoring in the concat above
						let splitIndex = text.lastIndexOf("\n", 900);
						stringBuilder.push(text.substring(0, splitIndex));
						text = text.substring(splitIndex + 1);
					}
					stringBuilder.push(text);

					embed = embed.addField(element.name, stringBuilder.shift());
					stringBuilder.forEach((string) => {
						embed = embed.addField(`${element.name}, continued. . .`, `${string}${element.attack ? "\n**" + element.attack + "**" : ""}`);
					});
				}
				else if(element.text.length + (element.attack ? element.attack.length : 0) > 0){
					embed = embed.addField(element.name, `${element.text}${element.attack ? "\n**" + element.attack + "**" : ""}`);
				}
			});
			return embed;
		};

		this._getModifierFor = function(attribute){
			let modifier = Math.floor(parseInt(attribute) / 2) - 5;
			if(modifier >= 0){
				return `+${modifier}`;
			}
			else{
				return `${modifier}`; // Integer to string conversion
			}
		};
		this._crToExp = function(cr){
			switch(cr){
				case "0":
					return "10";
				case "1/8":
					return "25";
				case "1/4":
					return "50";
				case "1/2":
					return "100";
				case "1":
					return "200";
				case "2":
					return "450";
				case "3":
					return "700";
				case "4":
					return "1,100";
				case "5":
					return "1,800";
				case "6":
					return "2,300";
				case "7":
					return "2,900";
				case "8":
					return "3,900";
				case "9":
					return "5,000";
				case "10":
					return "5,900";
				case "11":
					return "7,200";
				case "12":
					return "8,400";
				case "13":
					return "10,000";
				case "14":
					return "11,500";
				case "15":
					return "13,000";
				case "16":
					return "15,000";
				case "17":
					return "18,000";
				case "18":
					return "20,000";
				case "19":
					return "22,000";
				case "20":
					return "25,000";
				case "21":
					return "33,000";
				case "22":
					return "41,000";
				case "23":
					return "50,000";
				case "24":
					return "62,000";
				case "30":
					return "155,000";
				default:
					return "";
			}
		};
	}
	async execute(msg, args){
		let prefix;
		if(msg.guild){
			prefix = await this.client.fetchPrefix(msg.guild.id);
		}
		else{
			prefix = "";
		}
		if(!args){
			return msg.reply(`invalid command; please specify a creature.\nProper usage: \`${prefix}${this.name} aboleth\``);
		}
		let toEdit = await msg.reply("fetching your creature. . .");
		let creatures = await CreatureModel.find({name: new RegExp(args.join(" "), "i")});
		if(!creatures || creatures.length === 0){
			return toEdit.edit("Unable to find your creature. Sorry!");
		}

		let result;
		if(creatures.length > 1){
			try{
				result = await super.disambiguate(toEdit, msg.author, "creature", creatures, "name");
			}
			catch(err){
				return toEdit.edit(err);
			}
		}
		else{
			result = creatures[0];
		}

		let description = [];
		description.push(`*${result.size} ${result.type} // ${result.alignment}*\n`);
		description.push(`**Armor Class** ${result.ac}\n**Hit Points** ${result.hp}\n**Speed** ${result.speed}\n`);
		let attributes = [];
		attributes.push(`**STR** ${result.str} (${this._getModifierFor(result.str)})`);
		attributes.push(`**DEX** ${result.dex} (${this._getModifierFor(result.dex)})`);
		attributes.push(`**CON** ${result.con} (${this._getModifierFor(result.con)})`);
		attributes.push(`**INT** ${result.int} (${this._getModifierFor(result.int)})`);
		attributes.push(`**WIS** ${result.wis} (${this._getModifierFor(result.wis)})`);
		attributes.push(`**CHA** ${result.cha} (${this._getModifierFor(result.cha)})`);
		description.push(attributes.join("; "));
		if(result.saves){
			description.push(`**Saving Throws** ${result.saves}`);
		}
		if(result.skill){
			description.push(`**Skills** ${result.skill}\n`);
		}
		if(result.vulnerable){
			description.push(`**Damage Vulnerabilities** ${result.vulnerable}`);
		}
		if(result.immune){
			description.push(`**Damage Immunities** ${result.immune}`);
		}
		if(result.conditionImmune){
			description.push(`**Condition Immunities** ${result.conditionImmune}`);
		}

		description.push(`**Senses** ${result.senses? result.senses + ", " : ""}passive Perception ${result.passive}`);

		if(result.languages){
			description.push(result.languages);
		}
		description.push(`**Challenge** ${result.cr} (${this._crToExp(result.cr)} XP)`);

		let embed = new Discord.RichEmbed().setTitle(`__**${result.name}**__`)
											.setColor(0x97ff43)
											.setDescription(description.join("\n"));

		// Attach trait fields
		if(result.traits && result.traits.length > 0){
			embed = this._attachFieldToEmbed("Traits", result.traits, embed);
		}

		if(result.spells){
			embed = embed.addField("Known Spells", result.spells);
		}

		if(result.actions && result.actions.length > 0){
			embed = this._attachFieldToEmbed("Actions", result.actions, embed);
		}

		if(result.legendary && result.legendary.length > 0){
			embed = this._attachFieldToEmbed("Legendary Actions", result.legendary, embed);
		}

		return toEdit.edit("", {embed: embed});
	}
};
<|endoftext|>