<commit_before>// mongodatabank.js
//
// Implementation of Databank interface for MongoDB
//
// Copyright 2011,2012 StatusNet Inc.
//
// 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.

var databank = require('databank'),
    Step = require('step'),
    Databank = databank.Databank,
    DatabankError = databank.DatabankError,
    AlreadyExistsError = databank.AlreadyExistsError,
    NoSuchThingError = databank.NoSuchThingError,
    NotImplementedError = databank.NotImplementedError,
    AlreadyConnectedError = databank.AlreadyConnectedError,
    NotConnectedError = databank.NotConnectedError;

var mongodb = require('mongodb'),
    Db = mongodb.Db,
    Server = mongodb.Server;

var MongoDatabank = function(params) {

    this.db = null;
    this.host = params.host || 'localhost';
    this.port = params.port || 27017;
    this.dbname = params.dbname || 'test';
    this.checkSchema = params.checkSchema || true;

    this.schema = params.schema || {};
    this.collections = {};
};

MongoDatabank.prototype = new Databank();
MongoDatabank.prototype.constructor = MongoDatabank;

// keep a cache of collections

MongoDatabank.prototype.getCollection = function(name, callback) {
    var bank = this;

    if (bank.collections[name]) {
        callback(null, bank.collections[name]);
    } else {
        bank.db.collection(name, function(err, coll) {
            if (err) {
                callback(err, null);
            } else {
                bank.collections[name] = coll;
                callback(null, coll);
            }
        });
    }
};

MongoDatabank.prototype.connect = function(params, callback) {

    var bank = this,
        server = new Server(bank.host, bank.port, {});

    if (bank.db) {
        callback(new AlreadyConnectedError());
        return;
    }

    bank.db = new Db(bank.dbname, server);

    bank.db.open(function(err, newDb) {
        if (err) {
            callback(err);
        } else {
            if (bank.checkSchema) {
                bank.checkBankSchema(callback);
            } else {
                callback(null);
            }
        }
    });
};

// Disconnect yourself.
// callback(err): function to call on completion

MongoDatabank.prototype.disconnect = function(callback) {
    var bank = this;

    if (!bank.db) {
        callback(new NotConnectedError());
        return;
    }
    bank.db.close(function() {
        bank.db     = null;
        bank.server = null;
        callback(null);
    });
};

MongoDatabank.prototype._valueToRec = function(type, id, value) {

    var pkey = this.getPrimaryKey(type);

    if (typeof value === 'object' && !(value instanceof Array)) {
        value._id = id;
        if (value.hasOwnProperty(pkey)) {
            delete value[pkey];
        }
    } else {
        value = {_v: value, _s: true, _id: id};
    }

    return value;
};

MongoDatabank.prototype._recToValue = function(type, rec) {

    var pkey = this.getPrimaryKey(type), value;

    if (rec._s) {
        value = rec._v;
    } else {
        value = rec;
        if (pkey !== '_id') {
            value[pkey] = rec._id;
            delete value._id;
        }
    }

    return value;
};

// Create a new thing
// type: string, type of thing, usually 'user' or 'activity'
// id: a unique ID, like a nickname or a UUID
// value: JavaScript value; will be JSONified
// callback(err, value): function to call on completion

MongoDatabank.prototype.create = function(type, id, value, callback) {

    var bank = this;

    if (!this.db) {
        callback(new NotConnectedError());
        return;
    }

    value = this._valueToRec(type, id, value);

    bank.getCollection(type, function(err, coll) {
        if (err) {
            callback(err, null);
        }
        coll.insert(value, {safe: true}, function(err, newValues) {
            if (err) {
                if (err.name && err.name == 'MongoError' && err.code && err.code == 11000) {
                    callback(new AlreadyExistsError(type, id), null);
                } else {
                    callback(err, null);
                }
            } else {
                // Mongo returns an array of values
                value = bank._recToValue(type, newValues[0]);
                callback(null, value);
            }
        });
    });
};

// Read an existing thing
// type: the type of thing; 'user', 'activity'
// id: a unique ID -- nickname or UUID or URI
// callback(err, value): function to call on completion

MongoDatabank.prototype.read = function(type, id, callback) {

    var bank = this;

    if (!this.db) {
        callback(new NotConnectedError());
        return;
    }

    bank.getCollection(type, function(err, coll) {
        var sel = {};
        if (err) {
            callback(err, null);
        }
        sel._id = id;
        coll.findOne(sel, function(err, value) {
            if (err) {
                // FIXME: find key-miss errors and return a NotExistsError
                callback(err, null);
            } else if (!value) {
                callback(new NoSuchThingError(type, id), null);
            } else {
                value = bank._recToValue(type, value);
                callback(null, value);
            }
        });
    });
};

// Update an existing thing
// type: the type of thing; 'user', 'activity'
// id: a unique ID -- nickname or UUID or URI
// value: the new value of the thing
// callback(err, value): function to call on completion

MongoDatabank.prototype.update = function(type, id, value, callback) {

    var bank = this;

    if (!this.db) {
        callback(new NotConnectedError());
        return;
    }

    value = this._valueToRec(type, id, value);

    bank.getCollection(type, function(err, coll) {
        var sel = {};
        if (err) {
            callback(err, null);
        }
        sel._id = id;
        coll.findAndModify(sel, [['_id', 'ascending']], value, {safe: true, 'new': true}, function(err, result) {
            if (err) {
                // FIXME: find key-miss errors and return a NotExistsError
                callback(err, null);
            } else {
                result = bank._recToValue(type, result);
                callback(null, result);
            }
        });
    });
};

MongoDatabank.prototype.save = function(type, id, value, callback) {

    var bank = this;

    if (!this.db) {
        callback(new NotConnectedError());
        return;
    }

    value = this._valueToRec(type, id, value);

    bank.getCollection(type, function(err, coll) {
        var sel = {};
        if (err) {
            callback(err, null);
        }
        sel._id = id;
        coll.update(sel, value, {upsert: true}, function(err) {
            if (err) {
                // FIXME: find key-miss errors and return a NotExistsError
                callback(err, null);
            } else {
                value = bank._recToValue(type, value);
                callback(null, value);
            }
        });
    });
};

// Delete an existing thing
// type: the type of thing; 'user', 'activity'
// id: a unique ID -- nickname or UUID or URI
// value: the new value of the thing
// callback(err): function to call on completion

MongoDatabank.prototype.del = function(type, id, callback) {

    var bank = this;

    if (!bank.db) {
        callback(new NotConnectedError());
        return;
    }

    bank.getCollection(type, function(err, coll) {

        var sel = {};

        if (err) {
            callback(err, null);
        }

        sel._id = id;
        coll.remove(sel, {}, callback);
    });
};

// Search for things
// type: type of thing
// criteria: map of criteria, with exact matches, like {'subject.id':'tag:example.org,2011:evan' }
// onResult(value): called once per result found
// callback(err): called once at the end of results

MongoDatabank.prototype.search = function(type, criteria, onResult, callback) {

    var pkey = this.getPrimaryKey(type), bank = this;

    if (!this.db) {
        callback(new NotConnectedError());
        return;
    }

    if (criteria.hasOwnProperty(pkey)) {
        criteria._id = criteria[pkey];
        delete criteria[pkey];
    }

    bank.getCollection(type, function(err, coll) {
        if (err) {
            callback(err, null);
        } else {
            coll.find(criteria, function(err, cursor) {
                if (err) {
                    callback(err);
                } else {
                    var lastErr = null;

                    cursor.each(function(err, value) {
                        if (err) {
                            lastErr = err;
                        } else if (value && !lastErr) {
                            value = bank._recToValue(type, value);
                            onResult(value);
                        } else if (value === null) { // called after last value
                            callback(lastErr);
                        }
                    });
                }
            });
        }
    });
};

MongoDatabank.prototype.incr = function(type, id, callback) {

    var bank = this;

    if (!this.db) {
        callback(new NotConnectedError());
        return;
    }

    bank.getCollection(type, function(err, coll) {
        if (err) {
            callback(err, null);
        } else {
            coll.update({_id: id}, {"$inc": {"_v": 1}, "$set": {"_s": true}}, {upsert: true, multi: false}, function(err) {
                if (err) {
                    callback(err, null);
                } else {
                    bank.read(type, id, callback);
                }
            });
        }
    });
};

MongoDatabank.prototype.decr = function(type, id, callback) {

    var bank = this;

    if (!this.db) {
        callback(new NotConnectedError());
        return;
    }

    bank.getCollection(type, function(err, coll) {
        if (err) {
            callback(err, null);
        } else {
            coll.update({_id: id}, {"$inc": {"_v": -1}, "$set": {"_s": true}}, {upsert: true, multi: false}, function(err) {
                if (err) {
                    callback(err, null);
                } else {
                    bank.read(type, id, callback);
                }
            });
        }
    });
};

MongoDatabank.prototype.append = function(type, id, toAppend, callback) {
    var bank = this;

    if (!this.db) {
        callback(new NotConnectedError());
        return;
    }

    bank.getCollection(type, function(err, coll) {
        if (err) {
            callback(err, null);
        } else {
            coll.update({_id: id}, {"$push": {"_v": toAppend}, "$set": {"_s": true}}, {upsert: true, multi: false}, function(err) {
                if (err) {
                    callback(err, null);
                } else {
                    bank.read(type, id, callback);
                }
            });
        }
    });
};

MongoDatabank.prototype.readAll = function(type, ids, callback) {

    var pkey = this.getPrimaryKey(type), bank = this;

    if (!this.db) {
        callback(new NotConnectedError());
        return;
    }

    bank.getCollection(type, function(err, coll) {
        if (err) {
            callback(err, null);
        } else {
            coll.find({'_id': {'$in': ids}}, function(err, cursor) {
                if (err) {
                    callback(err);
                } else {
                    var lastErr = null,
                        results = {}, i, id;

                    // Initialize with nulls

                    for (i in ids) {
                        results[ids[i]] = null;
                    }

                    cursor.each(function(err, value) {
                        if (err) {
                            callback(err, null);
                        } else if (value === null) {
                            callback(null, results);
                        } else {
                            id = value._id;
                            value = bank._recToValue(type, value);
                            results[id] = value;
                        }
                    });
                }
            });
        }
    });
};

MongoDatabank.prototype.getPrimaryKey = function(type) {
    return (this.schema && this.schema[type]) ? this.schema[type].pkey : '_id';
};

// XXX: this got weird. Not sure why.

MongoDatabank.prototype.checkBankSchema = function(callback) {

    var bank = this;

    Step(
        function() {
            var type, group = this.group();
            for (type in bank.schema) {
                bank.getCollection(type, group());
            }
        },
        function(err, colls) {
            var i, j, coll, type, field, keys, indices, group = this.group();
            if (err) throw err;

            for (i = 0; i < colls.length; i++) {
                coll = colls[i];
                type = coll.collectionName;
                if (bank.schema[type].hasOwnProperty('indices')) {
                    indices = bank.schema[type].indices;
                    for (j = 0; j < indices.length; j++) {
                        keys = {};
                        field = indices[j];
                        keys[field] = 1;
                        coll.ensureIndex(keys, {}, group());
                    }
                }
            }
        },
        callback
    );
};

module.exports = MongoDatabank;

<commit_msg>Trim results of callback<commit_after>// mongodatabank.js
//
// Implementation of Databank interface for MongoDB
//
// Copyright 2011,2012 StatusNet Inc.
//
// 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.

var databank = require('databank'),
    Step = require('step'),
    Databank = databank.Databank,
    DatabankError = databank.DatabankError,
    AlreadyExistsError = databank.AlreadyExistsError,
    NoSuchThingError = databank.NoSuchThingError,
    NotImplementedError = databank.NotImplementedError,
    AlreadyConnectedError = databank.AlreadyConnectedError,
    NotConnectedError = databank.NotConnectedError;

var mongodb = require('mongodb'),
    Db = mongodb.Db,
    Server = mongodb.Server;

var MongoDatabank = function(params) {

    this.db = null;
    this.host = params.host || 'localhost';
    this.port = params.port || 27017;
    this.dbname = params.dbname || 'test';
    this.checkSchema = params.checkSchema || true;

    this.schema = params.schema || {};
    this.collections = {};
};

MongoDatabank.prototype = new Databank();
MongoDatabank.prototype.constructor = MongoDatabank;

// keep a cache of collections

MongoDatabank.prototype.getCollection = function(name, callback) {
    var bank = this;

    if (bank.collections[name]) {
        callback(null, bank.collections[name]);
    } else {
        bank.db.collection(name, function(err, coll) {
            if (err) {
                callback(err, null);
            } else {
                bank.collections[name] = coll;
                callback(null, coll);
            }
        });
    }
};

MongoDatabank.prototype.connect = function(params, callback) {

    var bank = this,
        server = new Server(bank.host, bank.port, {});

    if (bank.db) {
        callback(new AlreadyConnectedError());
        return;
    }

    bank.db = new Db(bank.dbname, server);

    bank.db.open(function(err, newDb) {
        if (err) {
            callback(err);
        } else {
            if (bank.checkSchema) {
                bank.checkBankSchema(callback);
            } else {
                callback(null);
            }
        }
    });
};

// Disconnect yourself.
// callback(err): function to call on completion

MongoDatabank.prototype.disconnect = function(callback) {
    var bank = this;

    if (!bank.db) {
        callback(new NotConnectedError());
        return;
    }
    bank.db.close(function() {
        bank.db     = null;
        bank.server = null;
        callback(null);
    });
};

MongoDatabank.prototype._valueToRec = function(type, id, value) {

    var pkey = this.getPrimaryKey(type);

    if (typeof value === 'object' && !(value instanceof Array)) {
        value._id = id;
        if (value.hasOwnProperty(pkey)) {
            delete value[pkey];
        }
    } else {
        value = {_v: value, _s: true, _id: id};
    }

    return value;
};

MongoDatabank.prototype._recToValue = function(type, rec) {

    var pkey = this.getPrimaryKey(type), value;

    if (rec._s) {
        value = rec._v;
    } else {
        value = rec;
        if (pkey !== '_id') {
            value[pkey] = rec._id;
            delete value._id;
        }
    }

    return value;
};

// Create a new thing
// type: string, type of thing, usually 'user' or 'activity'
// id: a unique ID, like a nickname or a UUID
// value: JavaScript value; will be JSONified
// callback(err, value): function to call on completion

MongoDatabank.prototype.create = function(type, id, value, callback) {

    var bank = this;

    if (!this.db) {
        callback(new NotConnectedError());
        return;
    }

    value = this._valueToRec(type, id, value);

    bank.getCollection(type, function(err, coll) {
        if (err) {
            callback(err, null);
        }
        coll.insert(value, {safe: true}, function(err, newValues) {
            if (err) {
                if (err.name && err.name == 'MongoError' && err.code && err.code == 11000) {
                    callback(new AlreadyExistsError(type, id), null);
                } else {
                    callback(err, null);
                }
            } else {
                // Mongo returns an array of values
                value = bank._recToValue(type, newValues[0]);
                callback(null, value);
            }
        });
    });
};

// Read an existing thing
// type: the type of thing; 'user', 'activity'
// id: a unique ID -- nickname or UUID or URI
// callback(err, value): function to call on completion

MongoDatabank.prototype.read = function(type, id, callback) {

    var bank = this;

    if (!this.db) {
        callback(new NotConnectedError());
        return;
    }

    bank.getCollection(type, function(err, coll) {
        var sel = {};
        if (err) {
            callback(err, null);
        }
        sel._id = id;
        coll.findOne(sel, function(err, value) {
            if (err) {
                // FIXME: find key-miss errors and return a NotExistsError
                callback(err, null);
            } else if (!value) {
                callback(new NoSuchThingError(type, id), null);
            } else {
                value = bank._recToValue(type, value);
                callback(null, value);
            }
        });
    });
};

// Update an existing thing
// type: the type of thing; 'user', 'activity'
// id: a unique ID -- nickname or UUID or URI
// value: the new value of the thing
// callback(err, value): function to call on completion

MongoDatabank.prototype.update = function(type, id, value, callback) {

    var bank = this;

    if (!this.db) {
        callback(new NotConnectedError());
        return;
    }

    value = this._valueToRec(type, id, value);

    bank.getCollection(type, function(err, coll) {
        var sel = {};
        if (err) {
            callback(err, null);
        }
        sel._id = id;
        coll.findAndModify(sel, [['_id', 'ascending']], value, {safe: true, 'new': true}, function(err, result) {
            if (err) {
                // FIXME: find key-miss errors and return a NotExistsError
                callback(err, null);
            } else {
                result = bank._recToValue(type, result);
                callback(null, result);
            }
        });
    });
};

MongoDatabank.prototype.save = function(type, id, value, callback) {

    var bank = this;

    if (!this.db) {
        callback(new NotConnectedError());
        return;
    }

    value = this._valueToRec(type, id, value);

    bank.getCollection(type, function(err, coll) {
        var sel = {};
        if (err) {
            callback(err, null);
        }
        sel._id = id;
        coll.update(sel, value, {upsert: true}, function(err) {
            if (err) {
                // FIXME: find key-miss errors and return a NotExistsError
                callback(err, null);
            } else {
                value = bank._recToValue(type, value);
                callback(null, value);
            }
        });
    });
};

// Delete an existing thing
// type: the type of thing; 'user', 'activity'
// id: a unique ID -- nickname or UUID or URI
// value: the new value of the thing
// callback(err): function to call on completion

MongoDatabank.prototype.del = function(type, id, callback) {

    var bank = this;

    if (!bank.db) {
        callback(new NotConnectedError());
        return;
    }

    bank.getCollection(type, function(err, coll) {

        var sel = {};

        if (err) {
            callback(err, null);
        }

        sel._id = id;
        coll.remove(sel, {}, callback);
    });
};

// Search for things
// type: type of thing
// criteria: map of criteria, with exact matches, like {'subject.id':'tag:example.org,2011:evan' }
// onResult(value): called once per result found
// callback(err): called once at the end of results

MongoDatabank.prototype.search = function(type, criteria, onResult, callback) {

    var pkey = this.getPrimaryKey(type), bank = this;

    if (!this.db) {
        callback(new NotConnectedError());
        return;
    }

    if (criteria.hasOwnProperty(pkey)) {
        criteria._id = criteria[pkey];
        delete criteria[pkey];
    }

    bank.getCollection(type, function(err, coll) {
        if (err) {
            callback(err, null);
        } else {
            coll.find(criteria, function(err, cursor) {
                if (err) {
                    callback(err);
                } else {
                    var lastErr = null;

                    cursor.each(function(err, value) {
                        if (err) {
                            lastErr = err;
                        } else if (value && !lastErr) {
                            value = bank._recToValue(type, value);
                            onResult(value);
                        } else if (value === null) { // called after last value
                            callback(lastErr);
                        }
                    });
                }
            });
        }
    });
};

MongoDatabank.prototype.incr = function(type, id, callback) {

    var bank = this;

    if (!this.db) {
        callback(new NotConnectedError());
        return;
    }

    bank.getCollection(type, function(err, coll) {
        if (err) {
            callback(err, null);
        } else {
            coll.update({_id: id}, {"$inc": {"_v": 1}, "$set": {"_s": true}}, {upsert: true, multi: false}, function(err) {
                if (err) {
                    callback(err, null);
                } else {
                    bank.read(type, id, callback);
                }
            });
        }
    });
};

MongoDatabank.prototype.decr = function(type, id, callback) {

    var bank = this;

    if (!this.db) {
        callback(new NotConnectedError());
        return;
    }

    bank.getCollection(type, function(err, coll) {
        if (err) {
            callback(err, null);
        } else {
            coll.update({_id: id}, {"$inc": {"_v": -1}, "$set": {"_s": true}}, {upsert: true, multi: false}, function(err) {
                if (err) {
                    callback(err, null);
                } else {
                    bank.read(type, id, callback);
                }
            });
        }
    });
};

MongoDatabank.prototype.append = function(type, id, toAppend, callback) {
    var bank = this;

    if (!this.db) {
        callback(new NotConnectedError());
        return;
    }

    bank.getCollection(type, function(err, coll) {
        if (err) {
            callback(err, null);
        } else {
            coll.update({_id: id}, {"$push": {"_v": toAppend}, "$set": {"_s": true}}, {upsert: true, multi: false}, function(err) {
                if (err) {
                    callback(err, null);
                } else {
                    bank.read(type, id, callback);
                }
            });
        }
    });
};

MongoDatabank.prototype.readAll = function(type, ids, callback) {

    var pkey = this.getPrimaryKey(type), bank = this;

    if (!this.db) {
        callback(new NotConnectedError());
        return;
    }

    bank.getCollection(type, function(err, coll) {
        if (err) {
            callback(err, null);
        } else {
            coll.find({'_id': {'$in': ids}}, function(err, cursor) {
                if (err) {
                    callback(err);
                } else {
                    var lastErr = null,
                        results = {}, i, id;

                    // Initialize with nulls

                    for (i in ids) {
                        results[ids[i]] = null;
                    }

                    cursor.each(function(err, value) {
                        if (err) {
                            callback(err, null);
                        } else if (value === null) {
                            callback(null, results);
                        } else {
                            id = value._id;
                            value = bank._recToValue(type, value);
                            results[id] = value;
                        }
                    });
                }
            });
        }
    });
};

MongoDatabank.prototype.getPrimaryKey = function(type) {
    return (this.schema && this.schema[type]) ? this.schema[type].pkey : '_id';
};

// XXX: this got weird. Not sure why.

MongoDatabank.prototype.checkBankSchema = function(callback) {

    var bank = this;

    Step(
        function() {
            var type, group = this.group();
            for (type in bank.schema) {
                bank.getCollection(type, group());
            }
        },
        function(err, colls) {
            var i, j, coll, type, field, keys, indices, group = this.group();
            if (err) throw err;

            for (i = 0; i < colls.length; i++) {
                coll = colls[i];
                type = coll.collectionName;
                if (bank.schema[type].hasOwnProperty('indices')) {
                    indices = bank.schema[type].indices;
                    for (j = 0; j < indices.length; j++) {
                        keys = {};
                        field = indices[j];
                        keys[field] = 1;
                        coll.ensureIndex(keys, {}, group());
                    }
                }
            }
        },
        function(err, names) {
            if (err) {
                callback(err);
            } else {
                callback(null);
            }
        }
    );
};

module.exports = MongoDatabank;

<|endoftext|>


<commit_before>// @flow
/**
 * Part of GDL gdl-frontend.
 * Copyright (C) 2017 GDL
 *
 * See LICENSE
 */

import React, { Fragment, type Element } from 'react';
import styled from 'react-emotion';
import { Trans } from '@lingui/react';

import View from '../../elements/View';
import Text from '../../elements/Text';
import A from '../../elements/A';
import { spacing, fonts } from '../../style/theme/';
import type { Book } from '../../types';
import BookLink from '../BookLink';
import BrowseLink, { type Props as BrowseLinkProps } from '../BrowseLink';

type Props = {
  books: Array<Book>,
  heading: Element<typeof Trans>,
  browseLinkProps?: BrowseLinkProps
};

// Add a wrapper around each book list, so we can apply padding on the last element to get our wanted "overscroll effect" on mobile
const BookList = ({ books, heading, browseLinkProps }: Props) => (
  <Fragment>
    <View
      flexDirection="row"
      alignItems="center"
      justifyContent="space-between"
      mb={spacing.small}
    >
      <Text
        accessibilityRole="heading"
        fontSize="1.3rem"
        fontWeight={fonts.weight.medium}
      >
        {heading}
      </Text>
      {browseLinkProps && (
        <BrowseLink {...browseLinkProps}>

            <Trans>More</Trans>

        </BrowseLink>
      )}
    </View>
    <HorizontalScrollList>
      {books.map(book => (

          <BookLink key={book.id} book={book} />

      ))}
    </HorizontalScrollList>
  </Fragment>
);

const HorizontalScrollList = styled('div')`
  overflow-x: auto;
  /* Fixes Safari scrolling all over the place */
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  margin: 0 -${spacing.medium};
  padding: 0 ${spacing.medium};
  padding-bottom: 4px; /* because of box-shadow on booklink */

  & > :last-child  {
    padding-right: ${spacing.medium};
  }

  display: flex;

  @supports (display: grid) {
    display: grid;
    grid-auto-flow: column;
    grid-column-gap: 11px;
    justify-content: start;
  }
`;

BookList.defaultProps = {
  route: (book: Book) => `/${book.language.code}/books/${book.id}`
};

export default BookList;
<commit_msg>Way simpler CSS for scrollable booklists<commit_after>// @flow
/**
 * Part of GDL gdl-frontend.
 * Copyright (C) 2017 GDL
 *
 * See LICENSE
 */

import React, { Fragment, type Element } from 'react';
import styled from 'react-emotion';
import { Trans } from '@lingui/react';

import View from '../../elements/View';
import Text from '../../elements/Text';
import A from '../../elements/A';
import { spacing, fonts } from '../../style/theme/';
import type { Book } from '../../types';
import BookLink from '../BookLink';
import BrowseLink, { type Props as BrowseLinkProps } from '../BrowseLink';

type Props = {
  books: Array<Book>,
  heading: Element<typeof Trans>,
  browseLinkProps?: BrowseLinkProps
};

// Add a wrapper around each book list, so we can apply padding on the last element to get our wanted "overscroll effect" on mobile
const BookList = ({ books, heading, browseLinkProps }: Props) => (
  <Fragment>
    <View
      flexDirection="row"
      alignItems="center"
      justifyContent="space-between"
      mb={spacing.small}
    >
      <Text
        accessibilityRole="heading"
        fontSize="1.3rem"
        fontWeight={fonts.weight.medium}
      >
        {heading}
      </Text>
      {browseLinkProps && (
        <BrowseLink {...browseLinkProps}>

            <Trans>More</Trans>

        </BrowseLink>
      )}
    </View>
    <NegativeMarginDiv>
      <ScrollDiv>
        {books.map(book => (
          <CoverDiv key={book.id}>
            <BookLink key={book.id} book={book} />
          </CoverDiv>
        ))}
      </ScrollDiv>
    </NegativeMarginDiv>
  </Fragment>
);

const NegativeMarginDiv = styled('div')`
  margin: 0 -${spacing.medium};
`;

const ScrollDiv = styled('div')`
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  ::-webkit-scrollbar {
    display: none;
  }
  white-space: nowrap;
  padding: 0 ${spacing.medium};
  padding-bottom: 4px;
`;

const CoverDiv = styled('div')`
  display: inline-block;
  &:not(:last-child) {
    margin-right: 11px;
  }
`;

BookList.defaultProps = {
  route: (book: Book) => `/${book.language.code}/books/${book.id}`
};

export default BookList;
<|endoftext|>


<commit_before>/**
 * A mixin that helps handle the POST cycle of ajax forms in Vue components
 *
 *
 *   koohiiformGetErrors()    returns html for errors, or false (use with v-if, and v-html)
 *
 *   koohiiformHandleResponse(tron)    call once from an ajax response handler, to set up the form errors
 *
 */

export default {

  data: function() {
    return {
      koohiiformErrors: []
    }
  },

  computed: {
    koohiiformGetErrors() {
      return this.koohiiformErrors.length
        ? `<span>${ this.koohiiformErrors.join('</span><span>') }</span>`
        : false;
    },
  },

  methods: {

    koohiiformHandleResponse(tron) {
      this.koohiiformErrors = tron.getErrors()
    }
  }
}
<commit_msg>refactor(KoohiiForm): delete obsolete mixin<commit_after><|endoftext|>


<commit_before>/**
 * Runs a waterfall of promises: calls each task, passing the result
 * from the previous one as an argument. The first task is run with an empty array.
 *
 * @param {array} methods of Promises to run waterfall on
 * @return {Promise} of the final task
 */
function promiseWaterfall(methods) {
  const [resolvedPromise, ...tasks] = methods;
  const finalTaskPromise = tasks.reduce(function (prevTaskPromise, task) {
    return prevTaskPromise.then(task);
  }, resolvedPromise([]));  // initial value

  return finalTaskPromise;
}

/**
 * Toggles a class on a DOM element
 * This is how we roll $('.element').toggleClass in a non-jQuery world
 *
 * @param {String} el selector
 * @param {String} className to toggle
 * @return {String}
 */
function toggleClass(el, className) {
  if (el.classList) {
    el.classList.toggle(className);
  } else {
    const classes = el.className.split(' ');
    const existingIndex = classes.indexOf(className);

    if (existingIndex >= 0) {
      classes.splice(existingIndex, 1);
    } else {
      classes.push(className);
      el.className = classes.join(' ');
    }
  }
}

/**
 * Adds a class to a DOM element
 *
 * @param {String} el selector
 * @param {String} className to add
 * @return {String}
 */
function addClass(el, className) {
  if (el.classList) {
    el.classList.add(className);
  } else {
    el.className += ' ' + className;
  }
}

/**
 * Removes a class to a DOM element
 *
 * @param {String} el selector
 * @param {String} className to remove
 * @return {String}
 */
function removeClass(el, className) {
  if (el.classList) {
    el.classList.remove(className);
  } else {
    el.className = el.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
  }
}

/**
 * Adds multiple listeners to to a DOM element
 * Equvalent to jQuery’s `$form.on('drag dragstart dragend dragover dragenter dragleave drop')`.
 *
 * @param {String} el selector
 * @param {String} events to add, like `drag dragstart dragend dragover dragenter dragleave drop`
 * @param {requestCallback} cb
 * @return {String}
 */
function addListenerMulti(el, events, cb) {
  const eventsArray = events.split(' ');
  for (let event in eventsArray) {
    el.addEventListener(eventsArray[event], cb, false);
  }
}

export default {
  promiseWaterfall,
  toggleClass,
  addClass,
  removeClass,
  addListenerMulti
};
<commit_msg><100 char lines<commit_after>/**
 * Runs a waterfall of promises: calls each task, passing the result
 * from the previous one as an argument. The first task is run with an empty array.
 *
 * @param {array} methods of Promises to run waterfall on
 * @return {Promise} of the final task
 */
function promiseWaterfall(methods) {
  const [resolvedPromise, ...tasks] = methods;
  const finalTaskPromise = tasks.reduce(function (prevTaskPromise, task) {
    return prevTaskPromise.then(task);
  }, resolvedPromise([]));  // initial value

  return finalTaskPromise;
}

/**
 * Toggles a class on a DOM element
 * This is how we roll $('.element').toggleClass in a non-jQuery world
 *
 * @param {String} el selector
 * @param {String} className to toggle
 * @return {String}
 */
function toggleClass(el, className) {
  if (el.classList) {
    el.classList.toggle(className);
  } else {
    const classes = el.className.split(' ');
    const existingIndex = classes.indexOf(className);

    if (existingIndex >= 0) {
      classes.splice(existingIndex, 1);
    } else {
      classes.push(className);
      el.className = classes.join(' ');
    }
  }
}

/**
 * Adds a class to a DOM element
 *
 * @param {String} el selector
 * @param {String} className to add
 * @return {String}
 */
function addClass(el, className) {
  if (el.classList) {
    el.classList.add(className);
  } else {
    el.className += ' ' + className;
  }
}

/**
 * Removes a class to a DOM element
 *
 * @param {String} el selector
 * @param {String} className to remove
 * @return {String}
 */
function removeClass(el, className) {
  if (el.classList) {
    el.classList.remove(className);
  } else {
    const patClasses = className.split(' ').join('|');
    const pattern    = new RegExp('(^|\\b)' + patClasses + '(\\b|$)', 'gi');
    el.className     = el.className.replace(pat, ' ');
  }
}

/**
 * Adds multiple listeners to to a DOM element
 * Equvalent to jQuery’s `$form.on('drag dragstart dragend dragover dragenter dragleave drop')`.
 *
 * @param {String} el selector
 * @param {String} events to add, like `drag dragstart dragend dragover dragenter dragleave drop`
 * @param {requestCallback} cb
 * @return {String}
 */
function addListenerMulti(el, events, cb) {
  const eventsArray = events.split(' ');
  for (let event in eventsArray) {
    el.addEventListener(eventsArray[event], cb, false);
  }
}

export default {
  promiseWaterfall,
  toggleClass,
  addClass,
  removeClass,
  addListenerMulti
};
<|endoftext|>


<commit_before>describe("JSInput", function() {
    beforeEach(function () {
        loadFixtures('js/capa/fixtures/jsinput.html');
    });

    it('sets all data-processed attributes to true on first load', function() {
        var sections = $(document).find('section[id="inputtype_"]');
        JSInput.walkDOM();
        sections.each(function(index, section) {
            expect(section.attr('data-processed')).toEqual('true');
        });
    });

    it('sets the data-processed attribute to true on subsequent load', function() {
        var section1 = $(document).find('section[id="inputtype_1"]'),
            section2 = $(document).find('section[id="inputtype_2"]');
        section1.attr('data-processed', false);
        JSInput.walkDOM();
        expect(section1.attr('data-processed')).toEqual('true');
        expect(section2.attr('data-processed')).toEqual('true');
    });

    it('sets the waitfor attribute to its update function', function() {
        var inputFields = $(document).find('input[id="input_"]');
        JSInput.walkDOM();
        inputFields.each(function(index, inputField) {
            expect(inputField.data('waitfor')).toBeDefined();
        });
    });
});
<commit_msg>Ensure JSInput tests actually run<commit_after>describe("JSInput", function() {
    beforeEach(function () {
        loadFixtures('js/capa/fixtures/jsinput.html');
    });

    it('sets all data-processed attributes to true on first load', function() {
        var sections = $(document).find('section[id="inputtype_"]');
        JSInput.walkDOM();
        sections.each(function(index, section) {
            expect(section.attr('data-processed')).toEqual('true');
        });
    });

    it('sets the data-processed attribute to true on subsequent load', function() {
        var section1 = $(document).find('section[id="inputtype_1"]'),
            section2 = $(document).find('section[id="inputtype_2"]');
        section1.attr('data-processed', false);
        JSInput.walkDOM();
        expect(section1.attr('data-processed')).toEqual('true');
        expect(section2.attr('data-processed')).toEqual('true');
    });

    it('sets the waitfor attribute to its update function', function() {
        var inputFields = $(document).find('input[id="input_"]');
        JSInput.walkDOM();
        inputFields.each(function(index, inputField) {
            expect(inputField.data('waitfor')).toBeDefined();
        });
    });

    it('tests the correct number of sections', function () {
        var sections = $(document).find('section[id="inputtype_"]');
        var inputFields = $(document).find('input[id="input_"]');
        expect(sections.length).toEqual(2);
        expect(sections.length).toEqual(inputFields.length);
    });
});

<|endoftext|>


<commit_before>var am = angular.module('am', ['ui.bootstrap']);
am.controller('amCtl', function ($scope, $http, $uibModal, $log, $q) {
    var AM_FORM_DATA = "amFormData";
    $scope.title = "AM REST DB Client";
    $scope.formData = {
        server: "16.165.217.186:8081",
        context: "/AssetManagerWebService/rs/",
        "ref-link": "",     // "db/amLocation/126874",
        collection: "",     // "EmplDepts",
        param: {
            limit: "100",
            offset: "0",
            filter: "",
            orderby: "",
            fields: []
        },

        user: "admin",
        password: ""
    };
    $scope.pageSize = 10;
    $scope.fields = [];
    $scope.breadcrumb = [];
    $scope.toggleFilter = function toggleSelection(field) {
        var idx = $scope.fields.indexOf(field);
        if (idx > -1) {
            $scope.fields.splice(idx, 1);
        }
        else {
            $scope.fields.push(field);
        }
//        $scope.addFields();
    };
    $scope.addFields = function () {
        $scope.formData.param.fields = $scope.fields;
        $scope.query();
    };

    if (localStorage && localStorage[AM_FORM_DATA]) {
        $scope.formData = JSON.parse(localStorage.getItem(AM_FORM_DATA));
        $scope.formData.collection = "";
        $scope.formData.param.filter = "";
        $scope.formData.param.offset = 0;
        $scope.formData.param.orderby = "";
        $scope.formData.param.fields = [];
    }

    $scope.query = function (form) {
        $scope.loading = true;
        $scope.tableData = {};

        // if param is query form, use it
        var form = form ? form : clone($scope.formData);

        form.method = "get";
        $http.post('/am/rest', form).success(function (data) {
//            console.log("rest data: " + JSON.stringify(data));
            $scope.loading = false;
            if (data instanceof Object) {
                if (data.entities instanceof Array)
                    $scope.tableData = data;
                else if (data.type == 'Buffer') {
                    $scope.tableData.count = 0;
                } else {
                    $scope.tableData.count = 1;
                    $scope.tableData.entities = [];
                    $scope.tableData.entities.push(data);
                }
                $scope.tableName = $scope.tableData.entities[0]["ref-link"].split("/")[1];
            } else {
                $scope.message = data;
            }
        });
        $scope.store();
    };

    $scope.load = function (data) {
        var modalInstance = $uibModal.open({
            animation: true,
            templateUrl: 'am_modal.html',
            controller: 'amModalCtrl',
            size: "modal-lg",
            resolve: {
                data: function () {
                    return data;
                },
                form: function () {
                    return clone($scope.formData);
                }
            }
        });

        modalInstance.result.then(function () {
            $scope.query();
        }, function () {
            $log.info('Modal dismissed at: ' + new Date());
        });
    };

    $scope.store = function () {
        if (localStorage)
            localStorage.setItem(AM_FORM_DATA, JSON.stringify($scope.formData));
    };

    // list order by and pagination =============================
    $scope.predicate = '';
    $scope.reverse = true;
    $scope.order = function (predicate, reQuery) {
        console.log("order: " + predicate);
        $scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false;
        $scope.predicate = predicate;
//        $scope.formData.param.orderby = predicate + ($scope.reverse) ? " desc" : "";
        if (reQuery) {
            var form = clone($scope.formData);
            form.param.orderby = predicate;
            if ($scope.reverse)
                form.param.orderby = form.param.orderby + " desc";
            $scope.query(form);
        }
    }
    ;

    $scope.jump = function (i) {
        var pos = i;
        $scope.currentPage = pos;
    };

    $scope.showValue = function (value) {
        if (value instanceof Object)
            return value[Object.keys(value)[0]];
        else
            return value;
    };

    $scope.metadata = function (schema, link, callback) {
        var form = clone($scope.formData);
        var metadata = "";
        if (schema == 'all') {
            metadata = "metadata/tables";
        } else {
            metadata = "metadata/schema/" + schema;

            // click query table from tree
            if (!link && !callback) {
                $scope.formData['ref-link'] = "db/" + schema;
//                $scope.tableName = schema;
                $scope.formData.param.fields = [];
                $scope.fields = [];
                $scope.query();
            }

        }
        form['metadata'] = metadata;
        $http.post('/am/metadata', form).success(function (data) {

            if (callback instanceof Function) {
                callback(data);
            } else {
                if (data.Tables) {
                    $scope.metadata["tables"] = [];
                    for (var t in data.Tables.Table) {
                        $scope.metadata["tables"].push(data.Tables.Table[t]["$"]);
                    }

//                console.log("meta data: " + JSON.stringify($scope.metadata["tables"]));
                } else if (data.table) {
//                console.log("meta data table: " + JSON.stringify(data));
//                console.log("parent: " + JSON.stringify(parent));

                    if (link) {
                        link["table"] = data.table;

                        if (link["parent"])
                            link["table"].parent = link["parent"] + "." + link["$"]["sqlname"];
                        else
                            link["table"].parent = link["$"]["sqlname"];
//                    console.log("parent's reverse: " + parent["table"].parent);
                    }
                    else
                        $scope.metadata["table"] = data.table;
                }
            }

        });
    };

    $scope.foldChild = function (link) {
//        console.log("child: " + JSON.stringify(child));
        delete link["table"];
    };

    $scope.addBreadcrumb = function (refLink) {
        var bread = {
            label: refLink.split("/")[1] + "[" + refLink.split("/")[2] + "]",
            link: refLink
        };

        if ($scope.breadcrumb.map(function (e) {
            return e.label;
        }).indexOf(bread.label) < 0)
            $scope.breadcrumb.push(bread);
    };

    $scope.removeBreadcrumb = function (refLink) {
        if (!refLink)
            $scope.breadcrumb = [];
        else {
            var bread = {
                label: refLink.split("/")[1] + "[" + refLink.split("/")[2] + "]",
                link: refLink
            };
            var pos = $scope.breadcrumb.map(function (e) {
                return e.label;
            }).indexOf(bread.label);

            $scope.breadcrumb.splice(pos, 1);
        }
    };

    $scope.useBreadcrumb = function (bread) {
        var form = clone($scope.formData);
        form["ref-link"] = bread.link;
        $scope.query(form);
        $scope.hiddenRelations();
    };

    $scope.showRelations = function (record, parent) {
        if (!parent) {
            $scope.relations = [];
            $scope.relations.push({
                table: record["ref-link"].split("/")[1],
                active: true,
                records: [record],
                child: null
            });
        } else {
            parent.child = [];
            parent.child.push({
                table: parent.table,
                active: true,
                records: [record],
                child: null
            });
        }

        $scope.addBreadcrumb(record["ref-link"]);

        $scope.metadata(record["ref-link"].split("/")[1], null, function (data) {
            var links = data.table.link;
            for (var i in links) {
                var form = clone($scope.formData);
                var sqlname = links[i]['$']['sqlname'];

                // check 1v1
                if (links[i]['$']['card11']) {
                    form["ref-link"] = "db/" + links[i]['$']['desttable'];
                    form.param.filter = links[i]['$']['reverse'] + ".PK=" + record["ref-link"].split('/')[2];
                } else {
                    form["ref-link"] = record["ref-link"];
                    form["collection"] = "/" + sqlname;
                }

                form.method = "get";
                if (!parent) {
                    $scope.relations.push({
                        table: sqlname,
                        records: [],
                        form: form
                    });
                } else {
                    parent.child.push({
                        table: sqlname,
                        records: [],
                        form: form
                    });
                }

            }
        });

    };

    $scope.getRecords = function (record) {
        if (record.form) {
            $http.post('/am/rest', record.form).success(function (data) {
                record.records = data.entities;
            });
        }
    };

    $scope.showRecord = function (record) {
        record.active = (record.active) ? !record.active : true;
    };

    $scope.hiddenRelations = function (record) {
        if (!record) {
            delete $scope.relations;
            $scope.removeBreadcrumb();
        } else {
            $scope.removeBreadcrumb(record["ref-link"]);
            delete record.child;
        }
    };

    $scope.removeOneTable = function () {
        delete $scope.metadata["table"];
        $scope.formData.param.fields = [];
        $scope.fields = [];
        $scope.breadcrumb = [];
        $scope.hiddenRelations();
    };

    $scope.getMeta = function (ref) {
        var words = ref.split('/');
        for (var i in words) {
            if (words[i].indexOf("am") == 0)
                return 'metadata/schema/' + words[i];
        }
        return "metadata/tables";
    };

    $scope.clearMsg = function () {
        delete $scope.message;
    };

    $scope.toggleNavbar = function () {
        if ($scope.navbar) {
            $scope.navbar = !$scope.navbar;
        } else
            $scope.navbar = true;
    };

    $scope.toggleServer = function () {
        if ($scope.serverbar) {
            $scope.serverbar = !$scope.serverbar;
        } else
            $scope.serverbar = true;
    };

    $scope.metadata("all");
});

am.filter('startFrom', function () {
    return function (input, start) {
        if (input) {
            start = +start; //parse to int
            return input.slice(start);
        }
    }
});

am.filter('range', function () {
    return function (input, total) {
        total = Math.ceil(total);
        for (var i = 0; i < total; i++) {
            input.push(i);
        }
        return input;
    };
});

function clone(obj) {
    var o;
    if (typeof obj == "object") {
        if (obj === null) {
            o = null;
        } else {
            if (obj instanceof Array) {
                o = [];
                for (var i = 0, len = obj.length; i < len; i++) {
                    o.push(clone(obj[i]));
                }
            } else {
                o = {};
                for (var j in obj) {
                    o[j] = clone(obj[j]);
                }
            }
        }
    } else {
        o = obj;
    }
    return o;
}<commit_msg>fix remove bread bug<commit_after>var am = angular.module('am', ['ui.bootstrap']);
am.controller('amCtl', function ($scope, $http, $uibModal, $log, $q) {
    var AM_FORM_DATA = "amFormData";
    $scope.title = "AM REST DB Client";
    $scope.formData = {
        server: "16.165.217.186:8081",
        context: "/AssetManagerWebService/rs/",
        "ref-link": "",     // "db/amLocation/126874",
        collection: "",     // "EmplDepts",
        param: {
            limit: "100",
            offset: "0",
            filter: "",
            orderby: "",
            fields: []
        },

        user: "admin",
        password: ""
    };
    $scope.pageSize = 10;
    $scope.fields = [];
    $scope.breadcrumb = [];
    $scope.toggleFilter = function toggleSelection(field) {
        var idx = $scope.fields.indexOf(field);
        if (idx > -1) {
            $scope.fields.splice(idx, 1);
        }
        else {
            $scope.fields.push(field);
        }
//        $scope.addFields();
    };
    $scope.addFields = function () {
        $scope.formData.param.fields = $scope.fields;
        $scope.query();
    };

    if (localStorage && localStorage[AM_FORM_DATA]) {
        $scope.formData = JSON.parse(localStorage.getItem(AM_FORM_DATA));
        $scope.formData.collection = "";
        $scope.formData.param.filter = "";
        $scope.formData.param.offset = 0;
        $scope.formData.param.orderby = "";
        $scope.formData.param.fields = [];
    }

    $scope.query = function (form) {
        $scope.loading = true;
        $scope.tableData = {};

        // if param is query form, use it
        var form = form ? form : clone($scope.formData);
        $scope.tableName = form["ref-link"].split("/")[1];

        form.method = "get";
        $http.post('/am/rest', form).success(function (data) {
//            console.log("rest data: " + JSON.stringify(data));
            $scope.loading = false;
            if (data instanceof Object) {
//                console.log("query data:" + JSON.stringify(data));
                if (data.entities instanceof Array)
                    $scope.tableData = data;
            } else {
                $scope.message = data;
            }
        });
        $scope.store();
    };

    $scope.load = function (data) {
        var modalInstance = $uibModal.open({
            animation: true,
            templateUrl: 'am_modal.html',
            controller: 'amModalCtrl',
            size: "modal-lg",
            resolve: {
                data: function () {
                    return data;
                },
                form: function () {
                    return clone($scope.formData);
                }
            }
        });

        modalInstance.result.then(function () {
            $scope.query();
        }, function () {
            $log.info('Modal dismissed at: ' + new Date());
        });
    };

    $scope.store = function () {
        if (localStorage)
            localStorage.setItem(AM_FORM_DATA, JSON.stringify($scope.formData));
    };

    // list order by and pagination =============================
    $scope.predicate = '';
    $scope.reverse = true;
    $scope.order = function (predicate, reQuery) {
        console.log("order: " + predicate);
        $scope.reverse = ($scope.predicate === predicate) ? !$scope.reverse : false;
        $scope.predicate = predicate;
//        $scope.formData.param.orderby = predicate + ($scope.reverse) ? " desc" : "";
        if (reQuery) {
            var form = clone($scope.formData);
            form.param.orderby = predicate;
            if ($scope.reverse)
                form.param.orderby = form.param.orderby + " desc";
            $scope.query(form);
        }
    }
    ;

    $scope.jump = function (i) {
        var pos = i;
        $scope.currentPage = pos;
    };

    $scope.showValue = function (value) {
        if (value instanceof Object)
            return value[Object.keys(value)[0]];
        else
            return value;
    };

    $scope.metadata = function (schema, link, callback) {
        var form = clone($scope.formData);
        var metadata = "";
        if (schema == 'all') {
            metadata = "metadata/tables";
        } else {
            metadata = "metadata/schema/" + schema;

            // click query table from tree
            if (!link && !callback) {
                $scope.formData['ref-link'] = "db/" + schema;
//                $scope.tableName = schema;
                $scope.formData.param.fields = [];
                $scope.fields = [];
                $scope.query();
            }

        }
        form['metadata'] = metadata;
        $http.post('/am/metadata', form).success(function (data) {

            if (callback instanceof Function) {
                callback(data);
            } else {
                if (data.Tables) {
                    $scope.metadata["tables"] = [];
                    for (var t in data.Tables.Table) {
                        $scope.metadata["tables"].push(data.Tables.Table[t]["$"]);
                    }

//                console.log("meta data: " + JSON.stringify($scope.metadata["tables"]));
                } else if (data.table) {
//                console.log("meta data table: " + JSON.stringify(data));
//                console.log("parent: " + JSON.stringify(parent));

                    if (link) {
                        link["table"] = data.table;

                        if (link["parent"])
                            link["table"].parent = link["parent"] + "." + link["$"]["sqlname"];
                        else
                            link["table"].parent = link["$"]["sqlname"];
//                    console.log("parent's reverse: " + parent["table"].parent);
                    }
                    else
                        $scope.metadata["table"] = data.table;
                }
            }

        });
    };

    $scope.foldChild = function (link) {
//        console.log("child: " + JSON.stringify(child));
        delete link["table"];
    };

    $scope.addBreadcrumb = function (refLink) {
        var bread = {
            label: refLink.split("/")[1] + "[" + refLink.split("/")[2] + "]",
            link: refLink
        };

        if ($scope.breadcrumb.map(function (e) {
            return e.label;
        }).indexOf(bread.label) < 0)
            $scope.breadcrumb.push(bread);
    };

    $scope.removeBreadcrumb = function (refLink) {
//        console.log("refLink: " + refLink);
        if (!refLink)
            $scope.breadcrumb = [];
        else {
            var bread = {
                label: refLink.split("/")[1] + "[" + refLink.split("/")[2] + "]",
                link: refLink
            };
            var pos = $scope.breadcrumb.map(function (e) {
                return e.label;
            }).indexOf(bread.label);

            $scope.breadcrumb.splice(pos, 1);
        }
    };

    $scope.useBreadcrumb = function (bread) {
        var form = clone($scope.formData);
        form["ref-link"] = bread.link;
        $scope.query(form);
        $scope.hiddenRelations();
    };

    $scope.showRelations = function (record, parent) {
        if (!parent) {
            $scope.relations = [];
            $scope.relations.push({
                table: record["ref-link"].split("/")[1],
                active: true,
                records: [record],
                child: null
            });
        } else {
            parent.child = [];
            parent.child.push({
                table: parent.table,
                active: true,
                records: [record],
                child: null
            });
        }

        $scope.addBreadcrumb(record["ref-link"]);

        $scope.metadata(record["ref-link"].split("/")[1], null, function (data) {
            var links = data.table.link;
            for (var i in links) {
                var form = clone($scope.formData);
                var sqlname = links[i]['$']['sqlname'];

                // check 1v1
                if (links[i]['$']['card11']) {
                    form["ref-link"] = "db/" + links[i]['$']['desttable'];
                    form.param.filter = links[i]['$']['reverse'] + ".PK=" + record["ref-link"].split('/')[2];
                } else {
                    form["ref-link"] = record["ref-link"];
                    form["collection"] = "/" + sqlname;
                }

                form.method = "get";
                if (!parent) {
                    $scope.relations.push({
                        table: sqlname,
                        records: [],
                        form: form
                    });
                } else {
                    parent.child.push({
                        table: sqlname,
                        records: [],
                        form: form
                    });
                }

            }
        });

    };

    $scope.getRecords = function (record) {
        if (record.form) {
            $http.post('/am/rest', record.form).success(function (data) {
                record.records = data.entities;
            });
        }
    };

    $scope.showRecord = function (record) {
        record.active = (record.active) ? !record.active : true;
    };

    $scope.hiddenRelations = function (record) {
//        console.log("record: " + JSON.stringify(record));
        if (!record) {
            delete $scope.relations;
            $scope.removeBreadcrumb();
        } else {
            $scope.removeBreadcrumb(record.form["ref-link"]);
            delete record.child;
        }
    };

    $scope.removeOneTable = function () {
        delete $scope.metadata["table"];
        $scope.formData.param.fields = [];
        $scope.fields = [];
        $scope.breadcrumb = [];
        $scope.hiddenRelations();
        delete $scope.tableData;
        delete $scope.tableName;
    };

    $scope.getMeta = function (ref) {
        var words = ref.split('/');
        for (var i in words) {
            if (words[i].indexOf("am") == 0)
                return 'metadata/schema/' + words[i];
        }
        return "metadata/tables";
    };

    $scope.clearMsg = function () {
        delete $scope.message;
    };

    $scope.toggleNavbar = function () {
        if ($scope.navbar) {
            $scope.navbar = !$scope.navbar;
        } else
            $scope.navbar = true;
    };

    $scope.toggleServer = function () {
        if ($scope.serverbar) {
            $scope.serverbar = !$scope.serverbar;
        } else
            $scope.serverbar = true;
    };

    $scope.metadata("all");
});

am.filter('startFrom', function () {
    return function (input, start) {
        if (input) {
            start = +start; //parse to int
            return input.slice(start);
        }
    }
});

am.filter('range', function () {
    return function (input, total) {
        total = Math.ceil(total);
        for (var i = 0; i < total; i++) {
            input.push(i);
        }
        return input;
    };
});

function clone(obj) {
    var o;
    if (typeof obj == "object") {
        if (obj === null) {
            o = null;
        } else {
            if (obj instanceof Array) {
                o = [];
                for (var i = 0, len = obj.length; i < len; i++) {
                    o.push(clone(obj[i]));
                }
            } else {
                o = {};
                for (var j in obj) {
                    o[j] = clone(obj[j]);
                }
            }
        }
    } else {
        o = obj;
    }
    return o;
}<|endoftext|>


<commit_before>// @flow
/**
 * Part of GDL gdl-frontend.
 * Copyright (C) 2018 GDL
 *
 * See LICENSE
 */

import * as React from 'react';
import { Trans } from '@lingui/react';
import styled from 'react-emotion';

import ReadingLevelTrans from '../../components/ReadingLevelTrans';
import type {
  Book,
  Language,
  FeaturedContent,
  ReadingLevel,
  Category
} from '../../types';
import Layout, { Main } from '../../components/Layout';
import Card from '../../components/Card';
import { Container, Text, View } from '../../elements';
import {
  NavContextBar,
  CategoryNavigation
} from '../../components/NavContextBar';
import Head from '../../components/Head';
import { SelectLanguage } from '../../components/LanguageMenu';
import BookList from '../../components/BookList';
import Button from '../../components/Button';
import { colors, fonts, spacing } from '../../style/theme';
import media from '../../style/media';
import { flexCenter } from '../../style/flex';

type Props = {|
  featuredContent: Array<FeaturedContent>,
  newArrivals: { results: Array<Book>, language: Language },
  levels: Array<ReadingLevel>,
  booksByLevel: Array<{ results: Array<Book> }>,
  categories: Array<Category>,
  category: Category
|};

const Banner = styled('div')`
  background-image: ${p => (p.src ? `url(${p.src})` : 'none')};
  background-size: cover;
  position: relative;
  display: flex;
  padding: 15px;
  justify-content: center;
  ${media.mobile`
    height: 210px;
  `} ${media.tablet`
    min-height: 390px;
    padding: 20px;
    justify-content: flex-end;
  `};
`;

const HeroCovertitle = styled('div')`
  position: absolute;
  top: 0;
  left: 0;
  background: rgba(0, 0, 0, 0.5);
  margin: 0;
  padding: 3px 12px;
`;

const HeroCardMobile = styled(Card)`
  ${flexCenter};
  padding: ${spacing.small};
  margin-top: -50px;
  margin-left: 15px;
  margin-right: 15px;
  ${media.tablet`
    display: none;
  `};
`;

const HeroCardTablet = styled(Card)`
  ${flexCenter};
  padding: ${spacing.medium};
  max-width: 375px;
  ${media.mobile`
    display: none;
  `};
`;

export default class HomePage extends React.Component<
  Props,
  { showLanguageMenu: boolean }
> {
  state = {
    showLanguageMenu: false
  };

  render() {
    const {
      category,
      featuredContent,
      levels,
      booksByLevel,
      newArrivals,
      categories
    } = this.props;

    const featured = featuredContent[0];

    const cardContent = (
      <View alignItems="center">
        <Text
          accessibilityRole="heading"
          aria-level="2"
          lang={featured.language.code}
          fontSize={['1.7rem', '2.1rem']}
          fontWeight={fonts.weight.medium}
          textAlign="center"
        >
          {featured.title}
        </Text>
        <View my={spacing.small}>
          <Text lang={featured.language.code} textAlign="center">
            {featured.description}
          </Text>
        </View>
        <Button href={featured.link}>
          <Trans>More</Trans>
        </Button>
      </View>
    );

    const languageCode = newArrivals.language.code;

    return (
      <Layout category={category} wrapWithMain={false}>
        <Head image={featured.imageUrl} />
        <NavContextBar>
          <CategoryNavigation
            category={category}
            categories={categories}
            languageCode={languageCode}
          />
          <SelectLanguage
            language={newArrivals.language}
            linkProps={language => ({
              route: 'books',
              params: { lang: language.code }
            })}
          />
        </NavContextBar>
        <Main>
          <Banner src={featured.imageUrl}>
            <HeroCovertitle>
              <Text
                accessibilityRole="heading"
                color={colors.base.white}
                fontSize="1.1rem"
                fontWeight={fonts.weight.medium}
              >
                <Trans>Featured</Trans>
              </Text>
            </HeroCovertitle>
            <HeroCardTablet>{cardContent}</HeroCardTablet>
          </Banner>
          <HeroCardMobile>{cardContent}</HeroCardMobile>

          {levels.map((level, index) => (
            <View {...bookListViewStyle} key={level}>
              <Container width="100%">
                <BookList
                  heading={<ReadingLevelTrans readingLevel={level} />}
                  browseLinkProps={{
                    lang: languageCode,
                    readingLevel: level,
                    category: category
                  }}
                  books={booksByLevel[index].results}
                />
              </Container>
            </View>
          ))}

          <View {...bookListViewStyle}>
            <Container width="100%">
              <BookList
                heading={<Trans>New arrivals</Trans>}
                browseLinkProps={{
                  lang: languageCode,
                  sort: '-arrivalDate',
                  category: category
                }}
                books={newArrivals.results}
              />
            </Container>
          </View>
        </Main>
      </Layout>
    );
  }
}

const bookListViewStyle = {
  py: spacing.medium,
  borderBottom: `solid 1px ${colors.base.grayLight}`
};
<commit_msg>#335: Filter featured content on chosen category (#145)<commit_after>// @flow
/**
 * Part of GDL gdl-frontend.
 * Copyright (C) 2018 GDL
 *
 * See LICENSE
 */

import * as React from 'react';
import { Trans } from '@lingui/react';
import styled from 'react-emotion';

import ReadingLevelTrans from '../../components/ReadingLevelTrans';
import type {
  Book,
  Language,
  FeaturedContent,
  ReadingLevel,
  Category
} from '../../types';
import Layout, { Main } from '../../components/Layout';
import Card from '../../components/Card';
import { Container, Text, View } from '../../elements';
import {
  NavContextBar,
  CategoryNavigation
} from '../../components/NavContextBar';
import Head from '../../components/Head';
import { SelectLanguage } from '../../components/LanguageMenu';
import BookList from '../../components/BookList';
import Button from '../../components/Button';
import { colors, fonts, spacing } from '../../style/theme';
import media from '../../style/media';
import { flexCenter } from '../../style/flex';

type Props = {|
  featuredContent: Array<FeaturedContent>,
  newArrivals: { results: Array<Book>, language: Language },
  levels: Array<ReadingLevel>,
  booksByLevel: Array<{ results: Array<Book> }>,
  categories: Array<Category>,
  category: Category
|};

const Banner = styled('div')`
  background-image: ${p => (p.src ? `url(${p.src})` : 'none')};
  background-size: cover;
  position: relative;
  display: flex;
  padding: 15px;
  justify-content: center;
  ${media.mobile`
    height: 210px;
  `} ${media.tablet`
    min-height: 390px;
    padding: 20px;
    justify-content: flex-end;
  `};
`;

const HeroCovertitle = styled('div')`
  position: absolute;
  top: 0;
  left: 0;
  background: rgba(0, 0, 0, 0.5);
  margin: 0;
  padding: 3px 12px;
`;

const HeroCardMobile = styled(Card)`
  ${flexCenter};
  padding: ${spacing.small};
  margin-top: -50px;
  margin-left: 15px;
  margin-right: 15px;
  ${media.tablet`
    display: none;
  `};
`;

const HeroCardTablet = styled(Card)`
  ${flexCenter};
  padding: ${spacing.medium};
  max-width: 375px;
  ${media.mobile`
    display: none;
  `};
`;

export default class HomePage extends React.Component<
  Props,
  { showLanguageMenu: boolean }
> {
  state = {
    showLanguageMenu: false
  };

  render() {
    const {
      category,
      featuredContent,
      levels,
      booksByLevel,
      newArrivals,
      categories
    } = this.props;

    const featuredForChosenCategory = featuredContent.filter(
      f => f.category && f.category.name === category
    );
    const featured =
      featuredForChosenCategory.length > 0
        ? featuredForChosenCategory[0]
        : featuredContent[0];

    const cardContent = (
      <View alignItems="center">
        <Text
          accessibilityRole="heading"
          aria-level="2"
          lang={featured.language.code}
          fontSize={['1.7rem', '2.1rem']}
          fontWeight={fonts.weight.medium}
          textAlign="center"
        >
          {featured.title}
        </Text>
        <View my={spacing.small}>
          <Text lang={featured.language.code} textAlign="center">
            {featured.description}
          </Text>
        </View>
        <Button href={featured.link}>
          <Trans>More</Trans>
        </Button>
      </View>
    );

    const languageCode = newArrivals.language.code;

    return (
      <Layout category={category} wrapWithMain={false}>
        <Head image={featured.imageUrl} />
        <NavContextBar>
          <CategoryNavigation
            category={category}
            categories={categories}
            languageCode={languageCode}
          />
          <SelectLanguage
            language={newArrivals.language}
            linkProps={language => ({
              route: 'books',
              params: { lang: language.code }
            })}
          />
        </NavContextBar>
        <Main>
          <Banner src={featured.imageUrl}>
            <HeroCovertitle>
              <Text
                accessibilityRole="heading"
                color={colors.base.white}
                fontSize="1.1rem"
                fontWeight={fonts.weight.medium}
              >
                <Trans>Featured</Trans>
              </Text>
            </HeroCovertitle>
            <HeroCardTablet>{cardContent}</HeroCardTablet>
          </Banner>
          <HeroCardMobile>{cardContent}</HeroCardMobile>

          {levels.map((level, index) => (
            <View {...bookListViewStyle} key={level}>
              <Container width="100%">
                <BookList
                  heading={<ReadingLevelTrans readingLevel={level} />}
                  browseLinkProps={{
                    lang: languageCode,
                    readingLevel: level,
                    category: category
                  }}
                  books={booksByLevel[index].results}
                />
              </Container>
            </View>
          ))}

          <View {...bookListViewStyle}>
            <Container width="100%">
              <BookList
                heading={<Trans>New arrivals</Trans>}
                browseLinkProps={{
                  lang: languageCode,
                  sort: '-arrivalDate',
                  category: category
                }}
                books={newArrivals.results}
              />
            </Container>
          </View>
        </Main>
      </Layout>
    );
  }
}

const bookListViewStyle = {
  py: spacing.medium,
  borderBottom: `solid 1px ${colors.base.grayLight}`
};
<|endoftext|>


<commit_before>var constants = require('../../lib/pixi/src/core/const');

/**
 * String of the current PQ version
 *
 * @static
 * @constant
 * @property {string} VERSION
 */
constants.VERSION = require('../../package.json').version;

/**
 * String of the current PIXI version
 * @constant
 * @property {string} PIXI_VERSION
 */
constants.PIXI_VERSION = require('../../lib/pixi/package.json').version;

/**
 * Constant to identify the Audio Type.
 *
 * @static
 * @constant
 * @property {object} AUDIO_TYPE
 * @property {number} AUDIO_TYPE.UNKNOWN
 * @property {number} AUDIO_TYPE.WEBAUDIO
 * @property {number} AUDIO_TYPE.HTMLAUDIO
 */
constants.AUDIO_TYPE = {
    UNKNOWN : 0,
    WEBAUDIO : 1,
    HTMLAUDIO : 2
};

/**
 * Constant to identify the game scale types
 *
 * @static
 * @constant
 * @property {object} GAME_SCALE_TYPE
 * @property {number} GAME_SCALE_TYPE.NONE
 * @property {number} GAME_SCALE_TYPE.FILL
 * @property {number} GAME_SCALE_TYPE.ASPECT_FIT
 * @property {number} GAME_SCALE_TYPE.ASPECT_FILL
 */
constants.GAME_SCALE_TYPE = {
    NONE: 0,
    FILL: 1,
    ASPECT_FIT: 2,
    ASPECT_FILL: 3
};

/**
 * The default game options if none are supplied to {@link PQ.Game}
 *
 * @static
 * @constant
 * @property {object} DEFAULT_GAME_OPTIONS
 * //TODO: document
 */
constants.DEFAULT_GAME_OPTIONS = {
    id: 'pq.defaultbundle.id',
    version: "0.0.0",
    debug: false,
    sayHello: true,

    useDeltaAnimation: true,
    useSortChildrenByDepth: false,

    game : {
        width: 800,
        height: 600,
        resolution: 1,
        canvas: null,
        backgroundColor: 0x000000,
        transparentBackground: false,
        useAntialias: false,
        useWebGL: true,
        preserveDrawingBuffer: false,
        usePersitantData: false,
        scaleType: constants.GAME_SCALE_TYPE.NONE,
        minFrameLimit: 30,
        stopAtVisibiltyChange: true
        //TODO: fustrum culling?
    },

    audio : {
        useWebAudio: true,
        allowedExtensions: ["mp3", "ogg", "wav"]
    },

    input : {
        enableMouse: true,
        enableKeyboard: false,
        enableGamepad: false,
        enableAccelerometer: false,

        mouseCheckFrecuency: 30,
        disableContextMenu: true,
        preventDefault: true
    }

};

/**
 * The default name for all the constructors when we use the custom class system
 *
 * @static
 * @constant
 * @property {string} DEFAULT_CONSTRUCTOR_NAME
 */
constants.DEFAULT_CONSTRUCTOR_NAME = "_init";

module.exports = constants;
<commit_msg>minor update<commit_after>var constants = require('../../lib/pixi/src/core/const');

/**
 * String of the current PQ version
 *
 * @static
 * @constant
 * @property {string} VERSION
 */
constants.VERSION = require('../../package.json').version;

/**
 * String of the current PIXI version
 * @constant
 * @property {string} PIXI_VERSION
 */
constants.PIXI_VERSION = require('../../lib/pixi/package.json').version;

/**
 * Constant to identify the Audio Type.
 *
 * @static
 * @constant
 * @property {object} AUDIO_TYPE
 * @property {number} AUDIO_TYPE.UNKNOWN
 * @property {number} AUDIO_TYPE.WEBAUDIO
 * @property {number} AUDIO_TYPE.HTMLAUDIO
 */
constants.AUDIO_TYPE = {
    UNKNOWN : 0,
    WEBAUDIO : 1,
    HTMLAUDIO : 2
};

/**
 * Constant to identify the game scale types
 *
 * @static
 * @constant
 * @property {object} GAME_SCALE_TYPE
 * @property {number} GAME_SCALE_TYPE.NONE
 * @property {number} GAME_SCALE_TYPE.FILL
 * @property {number} GAME_SCALE_TYPE.ASPECT_FIT
 * @property {number} GAME_SCALE_TYPE.ASPECT_FILL
 */
constants.GAME_SCALE_TYPE = {
    NONE: 0,
    FILL: 1,
    ASPECT_FIT: 2,
    ASPECT_FILL: 3
};

/**
 * The default game options if none are supplied to {@link PQ.Game}
 *
 * @static
 * @constant
 * @property {object} DEFAULT_GAME_OPTIONS
 * //TODO: document
 */
constants.DEFAULT_GAME_OPTIONS = {
    id: 'pq.defaultbundle.id',
    version: "0.0.0",
    debug: false,
    sayHello: true,

    useDeltaAnimation: true,
    useSortChildrenByDepth: false,

    game : {
        width: 800,
        height: 600,
        resolution: 1,
        canvas: null,
        backgroundColor: 0x000000,
        transparentBackground: false,
        useAntialias: false,
        useWebGL: true,
        preserveDrawingBuffer: false,
        usePersitantData: false,
        scaleType: constants.GAME_SCALE_TYPE.NONE,
        minFrameLimit: 30,
        stopAtVisibiltyChange: true,
        clearBeforeRender: true
        //TODO: fustrum culling?
    },

    audio : {
        useWebAudio: true,
        allowedExtensions: ["mp3", "ogg", "wav"]
    },

    input : {
        enableMouse: true,
        enableKeyboard: false,
        enableGamepad: false,
        enableAccelerometer: false,

        mouseCheckFrecuency: 30,
        disableContextMenu: true,
        preventDefault: true
    }

};

/**
 * The default name for all the constructors when we use the custom class system
 *
 * @static
 * @constant
 * @property {string} DEFAULT_CONSTRUCTOR_NAME
 */
constants.DEFAULT_CONSTRUCTOR_NAME = "_init";

module.exports = constants;
<|endoftext|>


<commit_before>'use strict';

var test = require('./test').module('jats/title');

test(' ', function(t) {
  t.fail('Not implemented yet.');
  t.end();
});
<commit_msg>Add title test.<commit_after>'use strict';

var test = require('./test').module('jats/title');
var testTextNode = require('./testTextNode');

var withAttributes =
  '<title id="title1" content-type="x" xml:base="y" specific-use="z">Introduction</title>';

test.attributesConversion(withAttributes, 'title');

testTextNode(test, 'title', 'title');
<|endoftext|>