<commit_before>define([
    'jquery',
    'underscore',
    'knockout',
    'knockout-mapping',
    'models/card',
    'models/card-widget',
    'arches',
    'require',
    'uuid',
    'utils/dispose',
    'viewmodels/tile'
], function($, _, ko, koMapping, CardModel, CardWidgetModel, arches, require, uuid, dispose) {
    /**
    * A viewmodel used for generic cards
    *
    * @constructor
    * @name CardViewModel
    *
    * @param  {string} params - a configuration object
    */
    var isChildSelected = function(parent) {
        var childSelected = false;
        var children = [];
        if ('tileid' in parent) {
            children = ko.unwrap(parent.cards);
        } else if ('model' in parent) {
            children = ko.unwrap(parent.cards).concat(
                ko.unwrap(parent.tiles),
                ko.unwrap(parent.widgets)
            );
        }
        children.forEach(function(child) {
            if (child.selected && child.selected() || isChildSelected(child)) {
                childSelected = true;
            }
        });
        return childSelected;
    };

    var doesChildHaveProvisionalEdits = function(parent) {
        var hasEdits = false;
        var childrenKey = 'tileid' in parent ? 'cards': 'tiles';
        ko.unwrap(parent[childrenKey]).forEach(function(child) {
            if (child.hasprovisionaledits && child.hasprovisionaledits() || doesChildHaveProvisionalEdits(child)) {
                hasEdits = true;
            }
        });
        return hasEdits;
    };

    var updateDisplayName = function(resourceId, displayname) {
        $.get(
            arches.urls.resource_descriptors + resourceId(),
            function(descriptors) {
                displayname(descriptors.displayname);
            }
        );
    };

    var CardViewModel = function(params) {
        var TileViewModel = require('viewmodels/tile');
        var self = this;
        var hover = params.hover || ko.observable();
        var scrollTo = params.scrollTo || ko.observable();
        var filter = params.filter || ko.observable();
        var loading = params.loading || ko.observable();
        var perms = ko.observableArray();
        var permsLiteral = ko.observableArray();
        var allowProvisionalEditRerender = ko.observable(true);
        var nodegroups = params.graphModel.get('nodegroups');
        var multiselect = params.multiselect || false;
        var isWritable = params.card.is_writable || false;
        var selection;
        var emptyConstraint = [{
            uniquetoallinstances: false,
            nodes:[],
            cardid: self.cardid,
            constraintid:  uuid.generate()
        }];

        var appliedFunctions = params.appliedFunctions;

        if (params.multiselect) {
            selection = params.selection || ko.observableArray([]);
        } else {
            selection = params.selection || ko.observable();
        }
        var nodegroup = _.find(ko.unwrap(nodegroups), function(group) {
            return ko.unwrap(group.nodegroupid) === ko.unwrap(params.card.nodegroup_id);
        });
        var cardModel = new CardModel({
            data: _.extend(params.card, {
                widgets: params.cardwidgets,
                nodes: params.graphModel.get('nodes'),
                nodegroup: nodegroup,
                constraints: params.constraints
            }),
            datatypelookup: params.graphModel.get('datatypelookup'),
        });

        var applySelectedComputed = function(widgets){
            widgets.forEach(function(widget){
                widget.parent = self;
                widget.selected = ko.pureComputed({
                    read: function() {
                        return selection() === this;
                    },
                    write: function(value) {
                        if (value) {
                            selection(this);
                        }
                    },
                    owner: widget
                });
                widget.hovered = ko.pureComputed({
                    read: function() {
                        return hover() === this;
                    },
                    write: function(value) {
                        if (value === true) {
                            hover(this);
                        }
                        if (value === null) {
                            hover(null);
                        }
                    },
                    owner: widget
                });
            });
        };

        applySelectedComputed(cardModel.widgets());

        var widgetsSubscription = cardModel.widgets.subscribe(function(widgets){
            applySelectedComputed(widgets);
        });

        _.extend(this, nodegroup, {
            isWritable: isWritable,
            model: cardModel,
            appliedFunctions: appliedFunctions,
            allowProvisionalEditRerender: allowProvisionalEditRerender,
            multiselect: params.multiselect,
            widgets: cardModel.widgets,
            parent: params.tile,
            parentCard: params.parentCard,
            expanded: ko.observable(false),
            topCards: params.topCards,
            perms: perms,
            constraints: params.constraints || emptyConstraint,
            permsLiteral: permsLiteral,
            scrollTo: ko.pureComputed(function() {
                return scrollTo() === this;
            }, this),
            fullyProvisional: ko.pureComputed(function(){
                var res;
                var provisionalindex;
                var summary = _.map(this.tiles(), function(tile){
                    var dataEmpty = _.keys(koMapping.toJS(tile.data)).length === 0;
                    if (ko.unwrap(tile.provisionaledits) !== null && dataEmpty) {
                        return 2;
                    } else if (ko.unwrap(tile.provisionaledits) !== null && !dataEmpty) {
                        return 1;
                    } else {
                        return 0;
                    }
                });
                provisionalindex = _.reduce(summary, function(a, b){return a + b;});
                if (provisionalindex > 0) {
                    if (_.every(summary, function(val){return val === 2;})) {
                        res = 'fullyprovisional';
                    }
                    else {
                        res = 'provisional';
                    }
                }
                return res;
            }, this),
            highlight: ko.computed(function() {
                var filterText = filter();
                if (!filterText) {
                    return false;
                }
                filterText = filterText.toLowerCase();
                if (params.card.name.toLowerCase().indexOf(filterText) > -1) {
                    return true;
                }
            }, this),
            tiles: ko.observableArray(
                _.filter(params.tiles, function(tile) {
                    return (
                        params.tile ? (tile.parenttile_id === params.tile.tileid) : true
                    ) && ko.unwrap(tile.nodegroup_id) === ko.unwrap(params.card.nodegroup_id);
                }).map(function(tile) {
                    return new TileViewModel({
                        tile: tile,
                        card: self,
                        graphModel: params.graphModel,
                        resourceId: params.resourceId,
                        displayname: params.displayname,
                        handlers: params.handlers,
                        userisreviewer: params.userisreviewer,
                        cards: params.cards,
                        tiles: params.tiles,
                        provisionalTileViewModel: params.provisionalTileViewModel,
                        selection: selection,
                        scrollTo: scrollTo,
                        loading: loading,
                        filter: filter,
                        cardwidgets: params.cardwidgets,
                    });
                })
            ),
            cards: ko.observableArray(_.filter(params.cards, function(card) {
                var nodegroup = _.find(ko.unwrap(nodegroups), function(group) {
                    return ko.unwrap(group.nodegroupid) === ko.unwrap(card.nodegroup_id);
                });
                return ko.unwrap(nodegroup.parentnodegroup_id) === ko.unwrap(params.card.nodegroup_id);
            }).map(function(card) {
                return new CardViewModel({
                    card: _.clone(card),
                    graphModel: params.graphModel,
                    tile: null,
                    resourceId: params.resourceId,
                    displayname: params.displayname,
                    handlers: params.handlers,
                    cards: params.cards,
                    tiles: params.tiles,
                    selection: selection,
                    multiselect: multiselect,
                    scrollTo: scrollTo,
                    loading: loading,
                    filter: filter,
                    provisionalTileViewModel: params.provisionalTileViewModel,
                    cardwidgets: params.cardwidgets,
                    perms: perms,
                    permsLiteral: permsLiteral,
                    parentCard: self,
                    topCards: params.topCards
                });
            })),
            hasprovisionaledits: ko.computed(function() {
                return _.filter(params.tiles, function(tile) {
                    return (params.tile ? (tile.parenttile_id === params.tile.tileid) : true)
                        && ko.unwrap(tile.nodegroup_id) === ko.unwrap(params.card.nodegroup_id)
                        && ko.unwrap(tile.provisionaledits)
                        && _.keys(ko.unwrap(tile.provisionaledits)).length !== 0;
                }).length;
            }),
            selected: ko.pureComputed({
                read: function() {
                    if (self.multiselect) {
                        return _.contains(selection(), this);
                    } else {
                        return selection() === this;
                    }
                },
                write: function(value) {
                    if (self.multiselect){
                        if (value === true){
                            if (_.contains(selection(), this) === false){
                                selection.push(this);
                            }
                        } else if (value === false) {
                            if (_.contains(selection(), this) === true) {
                                selection.remove(this);
                            }
                        }
                    } else if (value) {
                        selection(this);
                    }
                },
                owner: this
            }).extend({ throttle: 1 }),
            showForm: ko.observable(false),
            showSummary: ko.pureComputed(function(){
                return self.canAdd() && self.showForm() === false && self.selected();
            }),
            canAdd: ko.pureComputed({
                read: function() {
                    return this.cardinality === 'n' || this.tiles().length === 0;
                },
                owner: this
            }),
            reorderTiles: function() {
                loading(true);
                var tiles = _.map(self.tiles(), function(tile) {
                    return tile.getAttributes();
                });
                $.ajax({
                    type: 'POST',
                    data: JSON.stringify({
                        tiles: tiles
                    }),
                    url: arches.urls.reorder_tiles,
                    complete: function() {
                        loading(false);
                        updateDisplayName(params.resourceId, params.displayname);
                    }
                });
            },
            reorderCards: function() {
                loading(true);
                var cards = _.map(self.cards(), function(card, i) {
                    card.model.get('sortorder')(i);
                    return {
                        id: card.model.id,
                        name: card.model.get('name')(),
                        sortorder: i
                    };
                });
                $.ajax({
                    type: 'POST',
                    data: JSON.stringify({
                        cards: cards
                    }),
                    url: arches.urls.reorder_cards,
                    complete: function() {
                        loading(false);
                    }
                });
            },

            // used to generate parent tile for nexted data
            saveParentTile: async (optionalParentTile) => {
                return new Promise((resolve, reject) => {
                    if(optionalParentTile && !optionalParentTile.tileid) {
                        optionalParentTile.save((err) => {
                            reject(err);
                        }, () => {
                            resolve(true);
                        });
                    } else if (optionalParentTile && optionalParentTile.tileid) {
                        // parent tile already exists
                        resolve(false);
                        return;
                    } else {
                        tile = self.getNewTile();
                        tile.save((err) => {
                            reject(err);
                        }, () => {
                            resolve(true);
                        })
                    }
                });
            },

            getNewTile: function(forceNewTile) {
                if (!this.newTile || forceNewTile) this.newTile = new TileViewModel({
                    tile: {
                        tileid: '',
                        resourceinstance_id: ko.unwrap(params.resourceId),
                        nodegroup_id: ko.unwrap(self.model.nodegroup_id),
                        parenttile_id: self.parent ? self.parent.tileid : null,
                        data: _.reduce(self.widgets(), function(data, widget) {
                            data[widget.node_id()] = null;
                            return data;
                        }, {})
                    },
                    card: self,
                    graphModel: params.graphModel,
                    resourceId: params.resourceId,
                    displayname: params.displayname,
                    handlers: params.handlers,
                    userisreviewer: params.userisreviewer,
                    cards: params.cards,
                    tiles: params.tiles,
                    selection: selection,
                    scrollTo: scrollTo,
                    filter: filter,
                    provisionalTileViewModel: params.provisionalTileViewModel,
                    loading: loading,
                    cardwidgets: params.cardwidgets,
                });
                return this.newTile;
            },
            isFuncNode: function() {
                var appFuncDesc = false, appFuncName = false, nodegroupId = null;
                if(params.appliedFunctions && params.card) {
                    for(var i=0; i < self.appliedFunctions.length; i++) {
                        if(self.appliedFunctions[i]['function_id'] == "60000000-0000-0000-0000-000000000001") {
                            if(self.appliedFunctions[i]['config']['description']['nodegroup_id']) {
                                appFuncDesc = self.appliedFunctions[i]['config']['description']['nodegroup_id'];
                            }
                            if(self.appliedFunctions[i]['config']['name']['nodegroup_id']) {
                                appFuncName = self.appliedFunctions[i]['config']['name']['nodegroup_id'];
                            }
                            nodegroupId = params.card.nodegroup_id;
                            if(nodegroupId === appFuncDesc) {
                                return arches.translations.cardFunctionNodeDesc;
                            } else if(nodegroupId === appFuncName) {
                                return arches.translations.cardFunctionNodeName;
                            }
                        }
                    }
                }
                return false;
            }
        });

        this.selected.subscribe(function(selected) {
            if (selected) this.expanded(true);
        }, this);
        this.expanded.subscribe(function(expanded) {
            if (expanded && this.parent) this.parent.expanded(true);
        }, this);

        this.isChildSelected = ko.computed(function() {
            return isChildSelected(this);
        }, this);
        this.doesChildHaveProvisionalEdits = ko.computed(function() {
            return doesChildHaveProvisionalEdits(this);
        }, this);

        var expandParents = function(item) {
            if (item.parent) {
                item.parent.expanded(true);
                expandParents(item.parent);
            }
        };

        this.isDirty = function(){
            // Returns true if a tile is dirty and dirty state is not triggered by default values.
            var tile = self.newTile;

            if(tile) {
                if(tile.dirty()) {
                    var res = {};
                    self.widgets().forEach(function(w){
                        res[w.node.nodeid] = ko.unwrap(w.config.defaultValue);
                    });
                    for (var k in tile.data) {
                        if (Object.keys(res).indexOf(k) > -1) {
                            if ((res[k]||null) == (tile.data[k]()||null) !== true) {
                                return true;
                            } else {
                                return false;
                            }
                        }
                    }
                }
            }

            return false;
        };

        this.selectChildCards = function(value) {
            if (value !== undefined){
                this.selected(value);
            } else {
                if (this.selected() === false) {
                    value = true;
                    this.selected(true);
                } else {
                    value = false;
                    this.selected(false);
                }
            }
            if (this.cards().length > 0) {
                this.expanded(true);

                this.cards().forEach(function(childCard){
                    childCard.selectChildCards(value);
                }, this);
            }
        };

        var higlightSubscription = this.highlight.subscribe(function(highlight) {
            if (highlight) {
                this.expanded(true);
                expandParents(this);
            }
        }, this);

        this.copyTile = function(tile) {
            var newTile = self.getNewTile();
            newTile.noDefaults = true;
            self.showForm(true);
            ko.mapping.fromJS(
                ko.mapping.toJS(tile.data),
                newTile.data
            );
        };

        this.disposables = [];
        this.disposables.push(higlightSubscription);
        this.disposables.push(widgetsSubscription);
        this.disposables.push(this.scrollTo);
        this.disposables.push(this.highlight);
        this.disposables.push(this.hasprovisionaledits);
        this.disposables.push(this.selected);
        this.disposables.push(this.canAdd);
        this.disposables.push(this.isChildSelected);
        this.disposables.push(this.doesChildHaveProvisionalEdits);
        this.disposables.push(
            this.cards.subscribe(function() {
                this.model.parseNodes.call(this.model);
            }, this)
        );
        this.disposables.push(this.model);

        this.dispose = function() {
            dispose(self);
        };

    };
    return CardViewModel;
});
<commit_msg>stickler fixes<commit_after>define([
    'jquery',
    'underscore',
    'knockout',
    'knockout-mapping',
    'models/card',
    'models/card-widget',
    'arches',
    'require',
    'uuid',
    'utils/dispose',
    'viewmodels/tile'
], function($, _, ko, koMapping, CardModel, CardWidgetModel, arches, require, uuid, dispose) {
    /**
    * A viewmodel used for generic cards
    *
    * @constructor
    * @name CardViewModel
    *
    * @param  {string} params - a configuration object
    */
    var isChildSelected = function(parent) {
        var childSelected = false;
        var children = [];
        if ('tileid' in parent) {
            children = ko.unwrap(parent.cards);
        } else if ('model' in parent) {
            children = ko.unwrap(parent.cards).concat(
                ko.unwrap(parent.tiles),
                ko.unwrap(parent.widgets)
            );
        }
        children.forEach(function(child) {
            if (child.selected && child.selected() || isChildSelected(child)) {
                childSelected = true;
            }
        });
        return childSelected;
    };

    var doesChildHaveProvisionalEdits = function(parent) {
        var hasEdits = false;
        var childrenKey = 'tileid' in parent ? 'cards': 'tiles';
        ko.unwrap(parent[childrenKey]).forEach(function(child) {
            if (child.hasprovisionaledits && child.hasprovisionaledits() || doesChildHaveProvisionalEdits(child)) {
                hasEdits = true;
            }
        });
        return hasEdits;
    };

    var updateDisplayName = function(resourceId, displayname) {
        $.get(
            arches.urls.resource_descriptors + resourceId(),
            function(descriptors) {
                displayname(descriptors.displayname);
            }
        );
    };

    var CardViewModel = function(params) {
        var TileViewModel = require('viewmodels/tile');
        var self = this;
        var hover = params.hover || ko.observable();
        var scrollTo = params.scrollTo || ko.observable();
        var filter = params.filter || ko.observable();
        var loading = params.loading || ko.observable();
        var perms = ko.observableArray();
        var permsLiteral = ko.observableArray();
        var allowProvisionalEditRerender = ko.observable(true);
        var nodegroups = params.graphModel.get('nodegroups');
        var multiselect = params.multiselect || false;
        var isWritable = params.card.is_writable || false;
        var selection;
        var emptyConstraint = [{
            uniquetoallinstances: false,
            nodes:[],
            cardid: self.cardid,
            constraintid:  uuid.generate()
        }];

        var appliedFunctions = params.appliedFunctions;

        if (params.multiselect) {
            selection = params.selection || ko.observableArray([]);
        } else {
            selection = params.selection || ko.observable();
        }
        var nodegroup = _.find(ko.unwrap(nodegroups), function(group) {
            return ko.unwrap(group.nodegroupid) === ko.unwrap(params.card.nodegroup_id);
        });
        var cardModel = new CardModel({
            data: _.extend(params.card, {
                widgets: params.cardwidgets,
                nodes: params.graphModel.get('nodes'),
                nodegroup: nodegroup,
                constraints: params.constraints
            }),
            datatypelookup: params.graphModel.get('datatypelookup'),
        });

        var applySelectedComputed = function(widgets){
            widgets.forEach(function(widget){
                widget.parent = self;
                widget.selected = ko.pureComputed({
                    read: function() {
                        return selection() === this;
                    },
                    write: function(value) {
                        if (value) {
                            selection(this);
                        }
                    },
                    owner: widget
                });
                widget.hovered = ko.pureComputed({
                    read: function() {
                        return hover() === this;
                    },
                    write: function(value) {
                        if (value === true) {
                            hover(this);
                        }
                        if (value === null) {
                            hover(null);
                        }
                    },
                    owner: widget
                });
            });
        };

        applySelectedComputed(cardModel.widgets());

        var widgetsSubscription = cardModel.widgets.subscribe(function(widgets){
            applySelectedComputed(widgets);
        });

        _.extend(this, nodegroup, {
            isWritable: isWritable,
            model: cardModel,
            appliedFunctions: appliedFunctions,
            allowProvisionalEditRerender: allowProvisionalEditRerender,
            multiselect: params.multiselect,
            widgets: cardModel.widgets,
            parent: params.tile,
            parentCard: params.parentCard,
            expanded: ko.observable(false),
            topCards: params.topCards,
            perms: perms,
            constraints: params.constraints || emptyConstraint,
            permsLiteral: permsLiteral,
            scrollTo: ko.pureComputed(function() {
                return scrollTo() === this;
            }, this),
            fullyProvisional: ko.pureComputed(function(){
                var res;
                var provisionalindex;
                var summary = _.map(this.tiles(), function(tile){
                    var dataEmpty = _.keys(koMapping.toJS(tile.data)).length === 0;
                    if (ko.unwrap(tile.provisionaledits) !== null && dataEmpty) {
                        return 2;
                    } else if (ko.unwrap(tile.provisionaledits) !== null && !dataEmpty) {
                        return 1;
                    } else {
                        return 0;
                    }
                });
                provisionalindex = _.reduce(summary, function(a, b){return a + b;});
                if (provisionalindex > 0) {
                    if (_.every(summary, function(val){return val === 2;})) {
                        res = 'fullyprovisional';
                    }
                    else {
                        res = 'provisional';
                    }
                }
                return res;
            }, this),
            highlight: ko.computed(function() {
                var filterText = filter();
                if (!filterText) {
                    return false;
                }
                filterText = filterText.toLowerCase();
                if (params.card.name.toLowerCase().indexOf(filterText) > -1) {
                    return true;
                }
            }, this),
            tiles: ko.observableArray(
                _.filter(params.tiles, function(tile) {
                    return (
                        params.tile ? (tile.parenttile_id === params.tile.tileid) : true
                    ) && ko.unwrap(tile.nodegroup_id) === ko.unwrap(params.card.nodegroup_id);
                }).map(function(tile) {
                    return new TileViewModel({
                        tile: tile,
                        card: self,
                        graphModel: params.graphModel,
                        resourceId: params.resourceId,
                        displayname: params.displayname,
                        handlers: params.handlers,
                        userisreviewer: params.userisreviewer,
                        cards: params.cards,
                        tiles: params.tiles,
                        provisionalTileViewModel: params.provisionalTileViewModel,
                        selection: selection,
                        scrollTo: scrollTo,
                        loading: loading,
                        filter: filter,
                        cardwidgets: params.cardwidgets,
                    });
                })
            ),
            cards: ko.observableArray(_.filter(params.cards, function(card) {
                var nodegroup = _.find(ko.unwrap(nodegroups), function(group) {
                    return ko.unwrap(group.nodegroupid) === ko.unwrap(card.nodegroup_id);
                });
                return ko.unwrap(nodegroup.parentnodegroup_id) === ko.unwrap(params.card.nodegroup_id);
            }).map(function(card) {
                return new CardViewModel({
                    card: _.clone(card),
                    graphModel: params.graphModel,
                    tile: null,
                    resourceId: params.resourceId,
                    displayname: params.displayname,
                    handlers: params.handlers,
                    cards: params.cards,
                    tiles: params.tiles,
                    selection: selection,
                    multiselect: multiselect,
                    scrollTo: scrollTo,
                    loading: loading,
                    filter: filter,
                    provisionalTileViewModel: params.provisionalTileViewModel,
                    cardwidgets: params.cardwidgets,
                    perms: perms,
                    permsLiteral: permsLiteral,
                    parentCard: self,
                    topCards: params.topCards
                });
            })),
            hasprovisionaledits: ko.computed(function() {
                return _.filter(params.tiles, function(tile) {
                    return (params.tile ? (tile.parenttile_id === params.tile.tileid) : true)
                        && ko.unwrap(tile.nodegroup_id) === ko.unwrap(params.card.nodegroup_id)
                        && ko.unwrap(tile.provisionaledits)
                        && _.keys(ko.unwrap(tile.provisionaledits)).length !== 0;
                }).length;
            }),
            selected: ko.pureComputed({
                read: function() {
                    if (self.multiselect) {
                        return _.contains(selection(), this);
                    } else {
                        return selection() === this;
                    }
                },
                write: function(value) {
                    if (self.multiselect){
                        if (value === true){
                            if (_.contains(selection(), this) === false){
                                selection.push(this);
                            }
                        } else if (value === false) {
                            if (_.contains(selection(), this) === true) {
                                selection.remove(this);
                            }
                        }
                    } else if (value) {
                        selection(this);
                    }
                },
                owner: this
            }).extend({ throttle: 1 }),
            showForm: ko.observable(false),
            showSummary: ko.pureComputed(function(){
                return self.canAdd() && self.showForm() === false && self.selected();
            }),
            canAdd: ko.pureComputed({
                read: function() {
                    return this.cardinality === 'n' || this.tiles().length === 0;
                },
                owner: this
            }),
            reorderTiles: function() {
                loading(true);
                var tiles = _.map(self.tiles(), function(tile) {
                    return tile.getAttributes();
                });
                $.ajax({
                    type: 'POST',
                    data: JSON.stringify({
                        tiles: tiles
                    }),
                    url: arches.urls.reorder_tiles,
                    complete: function() {
                        loading(false);
                        updateDisplayName(params.resourceId, params.displayname);
                    }
                });
            },
            reorderCards: function() {
                loading(true);
                var cards = _.map(self.cards(), function(card, i) {
                    card.model.get('sortorder')(i);
                    return {
                        id: card.model.id,
                        name: card.model.get('name')(),
                        sortorder: i
                    };
                });
                $.ajax({
                    type: 'POST',
                    data: JSON.stringify({
                        cards: cards
                    }),
                    url: arches.urls.reorder_cards,
                    complete: function() {
                        loading(false);
                    }
                });
            },

            // used to generate parent tile for nexted data
            saveParentTile: async(optionalParentTile) => {
                return new Promise((resolve, reject) => {
                    if(optionalParentTile && !optionalParentTile.tileid) {
                        optionalParentTile.save((err) => {
                            reject(err);
                        }, () => {
                            resolve(true);
                        });
                    } else if (optionalParentTile && optionalParentTile.tileid) {
                        // parent tile already exists
                        resolve(false);
                        return;
                    } else {
                        const tile = self.getNewTile();
                        tile.save((err) => {
                            reject(err);
                        }, () => {
                            resolve(true);
                        });
                    }
                });
            },

            getNewTile: function(forceNewTile) {
                if (!this.newTile || forceNewTile) this.newTile = new TileViewModel({
                    tile: {
                        tileid: '',
                        resourceinstance_id: ko.unwrap(params.resourceId),
                        nodegroup_id: ko.unwrap(self.model.nodegroup_id),
                        parenttile_id: self.parent ? self.parent.tileid : null,
                        data: _.reduce(self.widgets(), function(data, widget) {
                            data[widget.node_id()] = null;
                            return data;
                        }, {})
                    },
                    card: self,
                    graphModel: params.graphModel,
                    resourceId: params.resourceId,
                    displayname: params.displayname,
                    handlers: params.handlers,
                    userisreviewer: params.userisreviewer,
                    cards: params.cards,
                    tiles: params.tiles,
                    selection: selection,
                    scrollTo: scrollTo,
                    filter: filter,
                    provisionalTileViewModel: params.provisionalTileViewModel,
                    loading: loading,
                    cardwidgets: params.cardwidgets,
                });
                return this.newTile;
            },
            isFuncNode: function() {
                var appFuncDesc = false, appFuncName = false, nodegroupId = null;
                if(params.appliedFunctions && params.card) {
                    for(var i=0; i < self.appliedFunctions.length; i++) {
                        if(self.appliedFunctions[i]['function_id'] == "60000000-0000-0000-0000-000000000001") {
                            if(self.appliedFunctions[i]['config']['description']['nodegroup_id']) {
                                appFuncDesc = self.appliedFunctions[i]['config']['description']['nodegroup_id'];
                            }
                            if(self.appliedFunctions[i]['config']['name']['nodegroup_id']) {
                                appFuncName = self.appliedFunctions[i]['config']['name']['nodegroup_id'];
                            }
                            nodegroupId = params.card.nodegroup_id;
                            if(nodegroupId === appFuncDesc) {
                                return arches.translations.cardFunctionNodeDesc;
                            } else if(nodegroupId === appFuncName) {
                                return arches.translations.cardFunctionNodeName;
                            }
                        }
                    }
                }
                return false;
            }
        });

        this.selected.subscribe(function(selected) {
            if (selected) this.expanded(true);
        }, this);
        this.expanded.subscribe(function(expanded) {
            if (expanded && this.parent) this.parent.expanded(true);
        }, this);

        this.isChildSelected = ko.computed(function() {
            return isChildSelected(this);
        }, this);
        this.doesChildHaveProvisionalEdits = ko.computed(function() {
            return doesChildHaveProvisionalEdits(this);
        }, this);

        var expandParents = function(item) {
            if (item.parent) {
                item.parent.expanded(true);
                expandParents(item.parent);
            }
        };

        this.isDirty = function(){
            // Returns true if a tile is dirty and dirty state is not triggered by default values.
            var tile = self.newTile;

            if(tile) {
                if(tile.dirty()) {
                    var res = {};
                    self.widgets().forEach(function(w){
                        res[w.node.nodeid] = ko.unwrap(w.config.defaultValue);
                    });
                    for (var k in tile.data) {
                        if (Object.keys(res).indexOf(k) > -1) {
                            if ((res[k]||null) == (tile.data[k]()||null) !== true) {
                                return true;
                            } else {
                                return false;
                            }
                        }
                    }
                }
            }

            return false;
        };

        this.selectChildCards = function(value) {
            if (value !== undefined){
                this.selected(value);
            } else {
                if (this.selected() === false) {
                    value = true;
                    this.selected(true);
                } else {
                    value = false;
                    this.selected(false);
                }
            }
            if (this.cards().length > 0) {
                this.expanded(true);

                this.cards().forEach(function(childCard){
                    childCard.selectChildCards(value);
                }, this);
            }
        };

        var higlightSubscription = this.highlight.subscribe(function(highlight) {
            if (highlight) {
                this.expanded(true);
                expandParents(this);
            }
        }, this);

        this.copyTile = function(tile) {
            var newTile = self.getNewTile();
            newTile.noDefaults = true;
            self.showForm(true);
            ko.mapping.fromJS(
                ko.mapping.toJS(tile.data),
                newTile.data
            );
        };

        this.disposables = [];
        this.disposables.push(higlightSubscription);
        this.disposables.push(widgetsSubscription);
        this.disposables.push(this.scrollTo);
        this.disposables.push(this.highlight);
        this.disposables.push(this.hasprovisionaledits);
        this.disposables.push(this.selected);
        this.disposables.push(this.canAdd);
        this.disposables.push(this.isChildSelected);
        this.disposables.push(this.doesChildHaveProvisionalEdits);
        this.disposables.push(
            this.cards.subscribe(function() {
                this.model.parseNodes.call(this.model);
            }, this)
        );
        this.disposables.push(this.model);

        this.dispose = function() {
            dispose(self);
        };

    };
    return CardViewModel;
});
<|endoftext|>


<commit_before>
angular.module('AjaxService', []).factory('AjaxService', function($http) {
    return {
        startedAjax: 0,
        finishedAjax: 0,

        get: function (url) {
            var promise = $http.get(url);
            var self = this;

            ++self.startedAjax;

            // todo: wrap $http instead of blocking .then()
            promise.then(function () {
                ++self.finishedAjax;
            });

            return promise;
        }
    };
});<commit_msg>rebuild AjaxService with Ajax object in order to succeed behat tests<commit_after>// $http wrapper for behat testing purposes

var Ajax = function ($http) {
    return {
        startedAjax: 0,
        finishedAjax: 0,

        get: function (url) {
            var promise = $http.get(url);
            var self = this;

            ++self.startedAjax;

            // todo: wrap $http instead of blocking .then()
            promise.then(function () {
                ++self.finishedAjax;
            });

            return promise;
        }
    };
};

angular.module('AjaxService', []).factory('AjaxService', function($http) {
    return Ajax($http);
});<|endoftext|>


<commit_before>/*
 * Copyright (c) 2014, Sgt. Kabukiman, https://bitbucket.org/sgt-kabukiman/
 *
 * This file is released under the terms of the MIT license. You can find the
 * complete text in the attached LICENSE file or online at:
 *
 * http://www.opensource.org/licenses/mit-license.php
 */

function PluginManager(database, log) {
	this.db         = database;
	this.log        = log;
	this.plugins    = [];
	this.map        = {};
	this.loaded     = {};
	this.dispatcher = null;
}

PluginManager.prototype = {
	add: function(plugin) {
		this.plugins.push(plugin);

		if (plugin.getKey() !== null) {
			this.map[plugin.getKey()] = plugin;
		}
	},

	getPluginByKey: function(key) {
		return (key in this.map) ? this.map[key] : null;
	},

	getPlugins: function() {
		return this.plugins;
	},

	getPluginMap: function() {
		return this.map;
	},

	getPluginKeys: function() {
		var keys = [], key;

		for (key in this.map) {
			keys.push(key);
		}

		return keys;
	},

	getLoadedPlugins: function(channel) {
		var result = {}, chan = channel.getName(), pluginKey, idx;

		for (idx in this.loaded[chan]) {
			pluginKey         = this.loaded[chan][idx];
			result[pluginKey] = this.map[pluginKey];
		}

		return result;
	},

	setup: function(kabukibot, eventDispatcher) {
		for (var i in this.plugins) {
			this.plugins[i].setup(kabukibot, eventDispatcher);
		}

		this.kabukibot  = kabukibot;
		this.dispatcher = eventDispatcher;
	},

	setupChannel: function(channel) {
		var self = this, name = channel.getName();

		if (name in this.loaded) {
			this.log.warning('Channel #' + name + ' has already been setup!');
			return false;
		}

		this.loaded[name] = [];

		this.db.select('plugin', '*', { channel: name }, function(err, rows) {
			var i, len;

			if (err) {
				self.log.error('Could not query plugin data: ' + err.message);
				return;
			}

			self.log.debug('Loaded ' + rows.length + ' plugins #' + name + '.');

			for (i = 0, len = rows.length; i < len; ++i) {
				self.loadPlugin(channel, rows[i].plugin);
			}

			self.log.debug('Plugins for #' + name + ' have been setup.');
		});
	},

	teardownChannel: function(channel) {
		var chan = channel.getName(), idx;

		if (!(chan in this.loaded)) {
			this.log.warning('Attempted to unload channel #' + chan + ', which has not been loaded!');
			return false;
		}

		for (idx in this.loaded[chan]) {
			this.unloadPlugin(channel, this.loaded[chan][idx]);
		}

		delete this.plugins[chan];

		this.log.debug('Plugins for #' + chan + ' have been torn down.');
	},

	loadPlugin: function(channel, pluginKey) {
		var chan = channel.getName();

		if (!(pluginKey in this.map)) {
			this.log.warning('Attempted to load unknown plugin "' + pluginKey + '"!');
			return false;
		}

		if (this.isLoaded(channel, pluginKey)) {
			this.log.debug('Plugin "' + pluginKey + '" has already been loaded on #' + chan + '!');
			return false;
		}

		this.getPluginByKey(pluginKey).load(channel, this.kabukibot, this.dispatcher);
		this.loaded[chan].push(pluginKey);
		this.log.debug('Loaded plugin "' + pluginKey + '" on #' + chan + '.');
	},

	unloadPlugin: function(channel, pluginKey) {
		var chan = channel.getName();

		if (!(pluginKey in this.map)) {
			this.log.warning('Attempted to un-load unknown plugin "' + pluginKey + '"!');
			return false;
		}

		if (!this.isLoaded(channel, pluginKey)) {
			this.log.debug('Plugin "' + pluginKey + '" is not loaded on #' + chan + '!');
			return false;
		}

		this.getPluginByKey(pluginKey).unload(channel, this.kabukibot, this.dispatcher);
		this.loaded[chan].splice(this.loaded[chan].indexOf(pluginKey), 1);
		this.log.debug('Un-loaded plugin "' + pluginKey + '" on #' + chan + '.');
	},

	isLoaded: function(channel, pluginKey) {
		var chan = channel.getName();

		return (chan in this.loaded) && (this.loaded[chan].indexOf(pluginKey) >= 0);
	},

	addPluginToChannel: function(channel, pluginKey) {
		var chan = channel.getName();

		if (!(pluginKey in this.map)) {
			this.log.warning('Attempted to add unknown plugin "' + pluginKey + '"!');
			return false;
		}

		if (!(chan in this.loaded)) {
			this.loaded[chan] = {};
		}

		if (this.isLoaded(channel, pluginKey)) {
			return false;
		}

		this.db.insert('plugin', { channel: chan, plugin: pluginKey });
		this.loadPlugin(channel, pluginKey);
		this.log.debug('Added ' + pluginKey + ' plugin to #' + chan + '.');

		return true;
	},

	removePluginFromChannel: function(channel, pluginKey) {
		var chan = channel.getName();

		if (!(pluginKey in this.map)) {
			this.log.warning('Attempted to remove unknown plugin "' + pluginKey + '"!');
			return false;
		}

		if (!this.isLoaded(channel, pluginKey)) {
			return false;
		}

		this.db.del('plugin', { channel: chan, plugin: pluginKey });
		this.unloadPlugin(channel, pluginKey);
		this.log.debug('Revmoed ' + pluginKey + ' plugin from #' + chan + '.');

		return true;
	}
};

module.exports = PluginManager;
<commit_msg>fixed tearing down a channel's plugin (i.e. leaving it) and then setting the channel up again (i.e. join again)<commit_after>/*
 * Copyright (c) 2014, Sgt. Kabukiman, https://bitbucket.org/sgt-kabukiman/
 *
 * This file is released under the terms of the MIT license. You can find the
 * complete text in the attached LICENSE file or online at:
 *
 * http://www.opensource.org/licenses/mit-license.php
 */

function PluginManager(database, log) {
	this.db         = database;
	this.log        = log;
	this.plugins    = [];
	this.map        = {};
	this.loaded     = {};
	this.dispatcher = null;
}

PluginManager.prototype = {
	add: function(plugin) {
		this.plugins.push(plugin);

		if (plugin.getKey() !== null) {
			this.map[plugin.getKey()] = plugin;
		}
	},

	getPluginByKey: function(key) {
		return (key in this.map) ? this.map[key] : null;
	},

	getPlugins: function() {
		return this.plugins;
	},

	getPluginMap: function() {
		return this.map;
	},

	getPluginKeys: function() {
		var keys = [], key;

		for (key in this.map) {
			keys.push(key);
		}

		return keys;
	},

	getLoadedPlugins: function(channel) {
		var result = {}, chan = channel.getName(), pluginKey, idx;

		for (idx in this.loaded[chan]) {
			pluginKey         = this.loaded[chan][idx];
			result[pluginKey] = this.map[pluginKey];
		}

		return result;
	},

	setup: function(kabukibot, eventDispatcher) {
		for (var i in this.plugins) {
			this.plugins[i].setup(kabukibot, eventDispatcher);
		}

		this.kabukibot  = kabukibot;
		this.dispatcher = eventDispatcher;
	},

	setupChannel: function(channel) {
		var self = this, name = channel.getName();

		if (name in this.loaded) {
			this.log.warning('Channel #' + name + ' has already been setup!');
			return false;
		}

		this.loaded[name] = [];

		this.db.select('plugin', '*', { channel: name }, function(err, rows) {
			var i, len;

			if (err) {
				self.log.error('Could not query plugin data: ' + err.message);
				return;
			}

			self.log.debug('Loaded ' + rows.length + ' plugins #' + name + '.');

			for (i = 0, len = rows.length; i < len; ++i) {
				self.loadPlugin(channel, rows[i].plugin);
			}

			self.log.debug('Plugins for #' + name + ' have been setup.');
		});
	},

	teardownChannel: function(channel) {
		var chan = channel.getName(), idx;

		if (!(chan in this.loaded)) {
			this.log.warning('Attempted to unload channel #' + chan + ', which has not been loaded!');
			return false;
		}

		for (idx in this.loaded[chan]) {
			this.unloadPlugin(channel, this.loaded[chan][idx]);
		}

		delete this.loaded[chan];

		this.log.debug('Plugins for #' + chan + ' have been torn down.');
	},

	loadPlugin: function(channel, pluginKey) {
		var chan = channel.getName();

		if (!(pluginKey in this.map)) {
			this.log.warning('Attempted to load unknown plugin "' + pluginKey + '"!');
			return false;
		}

		if (this.isLoaded(channel, pluginKey)) {
			this.log.debug('Plugin "' + pluginKey + '" has already been loaded on #' + chan + '!');
			return false;
		}

		this.getPluginByKey(pluginKey).load(channel, this.kabukibot, this.dispatcher);
		this.loaded[chan].push(pluginKey);
		this.log.debug('Loaded plugin "' + pluginKey + '" on #' + chan + '.');
	},

	unloadPlugin: function(channel, pluginKey) {
		var chan = channel.getName();

		if (!(pluginKey in this.map)) {
			this.log.warning('Attempted to un-load unknown plugin "' + pluginKey + '"!');
			return false;
		}

		if (!this.isLoaded(channel, pluginKey)) {
			this.log.debug('Plugin "' + pluginKey + '" is not loaded on #' + chan + '!');
			return false;
		}

		this.getPluginByKey(pluginKey).unload(channel, this.kabukibot, this.dispatcher);
		this.loaded[chan].splice(this.loaded[chan].indexOf(pluginKey), 1);
		this.log.debug('Un-loaded plugin "' + pluginKey + '" on #' + chan + '.');
	},

	isLoaded: function(channel, pluginKey) {
		var chan = channel.getName();

		return (chan in this.loaded) && (this.loaded[chan].indexOf(pluginKey) >= 0);
	},

	addPluginToChannel: function(channel, pluginKey) {
		var chan = channel.getName();

		if (!(pluginKey in this.map)) {
			this.log.warning('Attempted to add unknown plugin "' + pluginKey + '"!');
			return false;
		}

		if (!(chan in this.loaded)) {
			this.loaded[chan] = {};
		}

		if (this.isLoaded(channel, pluginKey)) {
			return false;
		}

		this.db.insert('plugin', { channel: chan, plugin: pluginKey });
		this.loadPlugin(channel, pluginKey);
		this.log.debug('Added ' + pluginKey + ' plugin to #' + chan + '.');

		return true;
	},

	removePluginFromChannel: function(channel, pluginKey) {
		var chan = channel.getName();

		if (!(pluginKey in this.map)) {
			this.log.warning('Attempted to remove unknown plugin "' + pluginKey + '"!');
			return false;
		}

		if (!this.isLoaded(channel, pluginKey)) {
			return false;
		}

		this.db.del('plugin', { channel: chan, plugin: pluginKey });
		this.unloadPlugin(channel, pluginKey);
		this.log.debug('Revmoed ' + pluginKey + ' plugin from #' + chan + '.');

		return true;
	}
};

module.exports = PluginManager;
<|endoftext|>


<commit_before>'use strict';

const
	$C = require('collection.js'),
	Sugar = require('sugar');

const
	fs = require('fs-extra-promise'),
	path = require('upath');

const
	glob = require('glob-promise'),
	vinyl = require('vinyl-fs'),
	isPathInside = require('is-path-inside');

const
	objectHash = require('node-object-hash'),
	hashFiles = require('hash-files');

const
	resolve = require('./resolve'),
	Declaration = require('./declaration');

const
	{blockTypeList} = /** @type {{blockTypeList: Array<string>}} */ require('./validators'),
	{createSerializableMap, createSerializableSet, jsonReviver} = require('./helpers');

const
	filesCache = Object.create(null),
	blockCache = Object.create(null);

/**
 * Validates a file by the specified path and return meta information
 *
 * @param {string} path
 * @param {!Object} cache - cache object
 * @returns {{mtime: !Object, fromCache: boolean}}
 */
function validateCache(path, cache) {
	const
		{mtime} = fs.statSync(path);

	return {
		mtime,
		fromCache: Boolean(cache[path] && Sugar.Date.is(mtime, cache[path].mtime))
	};
}

/**
 * Returns a file content by the specified path
 *
 * @param {string} path
 * @returns {string}
 */
function getFile(path) {
	const
		cache = validateCache(path, filesCache);

	if (!cache.fromCache) {
		filesCache[path] = {
			mtime: cache.mtime,
			content: fs.readFileSync(path, 'utf-8')
		};
	}

	return filesCache[path].content;
}

/**
 * Throws a new blockNotFound error by the specified name
 * @param {string} name
 */
function blockNotFound(name) {
	throw new Error(`Block "${name}" is not defined`);
}

class Block {
	/**
	 * Object that affects the project hash
	 *
	 * @protected
	 * @type {Object}
	 */
	static objToHash = null;

	/**
	 * Sets an object that affects the project hash
	 * @param obj
	 */
	static setObjToHash(obj) {
		this.objToHash = obj;
	}

	/**
	 * Returns a block manifest file by the specified parameter
	 *
	 * @param {string} folder - block folder name
	 * @param {string} file - manifest file
	 * @returns {!Promise<Block>}
	 */
	static async get(folder, file = 'index.js') {
		const
			blockPath = await resolve.block(path.join(folder, file)),
			cache = validateCache(blockPath, blockCache);

		if (cache.fromCache) {
			return new this(blockPath, blockCache[blockPath].decl);
		}

		async function getDecl(url) {
			return new Declaration(getFile(path.join(await url, file)));
		}

		const
			decl = await getDecl(resolve.block(folder));

		if (decl.mixin) {
			let
				skip = 1,
				parent;

			while ((parent = await resolve.block(decl.name, skip))) {
				const
					parentDecl = await getDecl(parent.path);

				if (!decl.parent && parentDecl.parent) {
					decl.parent = parentDecl.parent;
				}

				decl.dependencies = [...new Set(decl.dependencies.concat(parentDecl.dependencies))];
				decl.libs = [...new Set(decl.libs.concat(parentDecl.libs))];

				if (parentDecl.mixin) {
					skip = parent.from;

				} else {
					break;
				}
			}
		}

		blockCache[blockPath] = {
			mtime: cache.mtime,
			decl
		};

		return new this(blockPath, decl);
	}

	/**
	 * Returns block manifests file by the specified names
	 *
	 * @param {Array<string>=} [names]
	 * @returns {!Promise<!Map<string, !Block>>}
	 */
	static async getAll(names) {
		const
			projectCWD = await resolve.cwd,
			componentsLockFile = path.join(projectCWD, 'components-lock.json');

		const projectHash = objectHash().hash({
			srcHash: hashFiles.sync({files: [path.join(resolve.sourceDir, '/**/index.js')], algorithm: 'md5'}),
			objToHash: this.objToHash
		}, {alg: 'md5'});

		try {
			if (fs.existsSync(componentsLockFile)) {
				const normalizedJSONReviver = (key, val) => {
					val = jsonReviver(key, val);

					if (Sugar.Object.isString(val) && path.extname(val) !== '') {
						return path.resolve(path.dirname(componentsLockFile), val);
					}

					if (Sugar.Object.isObject(val) && val.declaration != null) {
						Object.setPrototypeOf(val, Block.prototype);
					}

					return val;
				};

				const
					fromCache = fs.readJSONSync(componentsLockFile, {reviver: normalizedJSONReviver});

				if (fromCache.hash === projectHash) {
					return fromCache.data;
				}
			}

		} catch {}

		if (!names) {
			const
				b = `@(${blockTypeList.join('|')})-*`,
				components = `/**/${b}/index.js`,
				virtualComponents = `/**/${b}.index.js`;

			names = await $C([await resolve.block(), ...resolve.dependencies])
				.async
				.to(new Map())
				.reduce(async (map, root) => {
					const src = [
						path.join(root, components),
						path.join(root, virtualComponents)
					];

					if (!map.has(src)) {
						map.set(
							src,

							await $C(vinyl.src(src, {read: false}))
								.async
								.to([])
								.map((file) => {
									file.base = root;
									return [path.dirname(file.relative), file.basename];
								})
						);
					}

					return map;
				});
		}

		const
			set = Object.create(null);

		const blocks = await $C(names)
			.parallel()
			.to([])
			.reduce(async (res, el) => {
				const
					i = res.push([]) - 1;

				await $C([].concat(el || [])).async.forEach(async ([folder, name], j) => {
					if (set[folder] && set[folder] === name) {
						return;
					}

					set[folder] = name;

					const
						block = await this.get(folder, name);

					if (block) {
						res[i][j] = block;
					}
				});

				return res;
			});

		const blockMap = await $C(Sugar.Array.flatten(blocks)).to(new Map()).reduce((map, decl) => {
			if (!map.has(decl.name)) {
				return map.set(decl.name, decl);
			}

			return map;
		});

		const normalizedBlockMap = await $C(blockMap).async.to(createSerializableMap()).reduce(async (map, decl, key) => {
			map.set(key, {
				...decl,

				name: decl.name,
				parent: decl.parent,
				dependencies: decl.dependencies,

				libs: decl.libs,
				resolvedLibs: await decl.getLibs({cache: blockMap}),
				resolvedOwnLibs: await decl.getLibs({onlyOwn: true, cache: blockMap}),

				type: decl.type,
				mixin: decl.mixin,

				index: await decl.index,
				logic: await decl.logic,
				styles: await decl.styles,
				tpl: await decl.tpl,
				etpl: await decl.etpl,

				__proto__: Block.prototype
			});

			return map;
		});

		const cacheData = {
			hash: projectHash,
			data: normalizedBlockMap
		};

		const jsonReplacer = (key, val) => {
			if (Sugar.Object.isString(val) && path.extname(val) !== '' && path.isAbsolute(val)) {
				return path.relative(projectCWD, val);
			}

			if (Sugar.Object.isObject(val) && val.declaration != null) {
				return normalizedBlockMap.get(key);
			}

			return val;
		};

		fs.writeFileSync(componentsLockFile, JSON.stringify(cacheData, jsonReplacer, 2));

		return normalizedBlockMap;
	}

	/**
	 * Returns a path to the logic file
	 * @returns {!Promise<?string>}
	 */
	get logic() {
		return resolve.block(`${this.name}/${this.name}.logic`);
	}

	/**
	 * Returns a path to the template file
	 * @returns {!Promise<?string>}
	 */
	get tpl() {
		return resolve.block(`${this.name}/${this.name}.ss`);
	}

	/**
	 * Returns a path to the ess template file
	 * @returns {!Promise<?string>}
	 */
	get etpl() {
		return resolve.block(`${this.name}/${this.name}.ess`);
	}

	/**
	 * Returns paths to the style files
	 * @returns {!Promise<!Array<string>>}
	 */
	get styles() {
		return (async () => {
			const
				root = await resolve.block(),
				style = await resolve.block(`${this.name}/${this.name}.styl`),
				styles = [].concat(style || []);

			if (this.mixin && !isPathInside(style, root)) {
				styles.push(...await glob(path.join(root, `/**/${this.name}/${this.name}_*.styl`)));
			}

			return styles;
		})();
	}

	/**
	 * Block name
	 * @returns {string}
	 */
	get name() {
		return this.declaration.name;
	}

	/**
	 * Block type
	 * @returns {string}
	 */
	get type() {
		return this.declaration.type;
	}

	/**
	 * Block parent
	 * @returns {?string}
	 */
	get parent() {
		return this.declaration.parent;
	}

	/**
	 * Block mixin status
	 * @returns {boolean}
	 */
	get mixin() {
		return this.declaration.mixin;
	}

	/**
	 * Block dependencies
	 * @returns {!Array<string>}
	 */
	get dependencies() {
		return this.declaration.dependencies;
	}

	/**
	 * Block libraries
	 * @returns {!Array<string>}
	 */
	get libs() {
		return this.declaration.libs;
	}

	/**
	 * @param index - block index src
	 * @param declaration - block declaration
	 */
	constructor(index, declaration) {
		this.index = index;
		this.declaration = declaration;
	}

	/**
	 * Returns the block parent manifest
	 *
	 * @param {Map<string, !Block>=} [cache] - optional cache object with predefined blocks
	 * @returns {!Promise<Block>}
	 */
	async getParent({cache} = {}) {
		if (!this.parent) {
			return null;
		}

		const
			block = cache ? cache.get(this.parent) : await this.constructor.get(this.parent);

		if (!block) {
			blockNotFound(this.parent);
		}

		return block;
	}

	/**
	 * Returns manifests of all block dependencies
	 *
	 * @param {boolean=} [onlyOwn] - if true parent dependencies also included
	 * @param {Map<string, !Block>=} [cache] - optional cache object with predefined blocks
	 * @returns {!Promise<!Map<string, !Block>>}
	 */
	async getDependencies({onlyOwn, cache} = {}) {
		let
			names = this.dependencies.slice();

		if (!onlyOwn) {
			let
				parent = await this.getParent({cache});

			while (parent) {
				names = [...parent.dependencies, ...names];
				parent = await parent.getParent({cache});
			}
		}

		if (cache) {
			return $C(names).to(createSerializableMap()).reduce((map, name) => {
				const
					block = cache.get(name);

				if (!block) {
					blockNotFound(name);
				}

				return map.set(name, block);
			});
		}

		return this.constructor.getAll([...new Set(names)]);
	}

	/**
	 * Returns manifests of all block libraries
	 *
	 * @param {boolean=} [onlyOwn] - if true parent libraries also included
	 * @param {Map<string, !Block>=} [cache] - optional cache object with predefined blocks
	 * @returns {!Promise<!Set<string>>}
	 */
	async getLibs({onlyOwn, cache} = {}) {
		if (onlyOwn) {
			if ('resolvedOwnLibs' in this) {
				return this.resolvedOwnLibs;
			}

		} else if ('resolvedLibs' in this) {
			return this.resolvedLibs;
		}

		let
			libs = this.libs.slice();

		if (!onlyOwn) {
			let
				parent = await this.getParent({cache});

			while (parent) {
				libs = [...parent.libs, ...libs];
				parent = await parent.getParent({cache});
			}
		}

		return createSerializableSet(libs);
	}

	/**
	 * Returns an object with block runtime dependencies
	 *
	 * @param {Map<string, !Block>=} [cache] - optional cache object with predefined blocks
	 * @returns {!Promise<{runtime: !Map<string, !Block>, parents: !Map<string, !Block>, libs: !Set<string>}>}
	 */
	async getRuntimeDependencies({cache} = {}) {
		let
			runtime = createSerializableMap(),
			parents = createSerializableMap();

		const get = async (name, isParent) => {
			const
				block = name === this.name ? this : cache ? cache.get(name) : await this.constructor.get(name);

			if (!block) {
				blockNotFound(name);
			}

			runtime = createSerializableMap([[name, block], ...runtime.entries()]);
			if (!isParent && parents.has(name)) {
				parents.delete(name);
			}

			await $C(await block.getDependencies({onlyOwn: true, cache})).async.forEach(async (block) => {
				runtime = createSerializableMap([[block.name, block], ...runtime.entries()]);
				await get(block.name, false, runtime, parents);
			});

			const
				parentName = block.parent;

			if (parentName) {
				const
					parent = await block.getParent({cache});

				if (!runtime.has(parentName)) {
					parents = createSerializableMap([[parentName, parent], ...parents.entries()]);
					runtime = createSerializableMap([[parentName, parent], ...runtime.entries()]);
				}

				await get(parentName, true, runtime, parents);
			}
		};

		const [libs] = await Promise.all([
			this.getLibs({cache}),
			get(this.name)
		]);

		return {runtime, parents, libs};
	}
}

module.exports = Block;
<commit_msg>Added project files to hash<commit_after>'use strict';

const
	$C = require('collection.js'),
	Sugar = require('sugar');

const
	fs = require('fs-extra-promise'),
	path = require('upath');

const
	glob = require('glob-promise'),
	vinyl = require('vinyl-fs'),
	isPathInside = require('is-path-inside');

const
	objectHash = require('node-object-hash'),
	hashFiles = require('hash-files');

const
	resolve = require('./resolve'),
	Declaration = require('./declaration');

const
	{blockTypeList} = /** @type {{blockTypeList: Array<string>}} */ require('./validators'),
	{createSerializableMap, createSerializableSet, jsonReviver} = require('./helpers');

const
	filesCache = Object.create(null),
	blockCache = Object.create(null);

/**
 * Validates a file by the specified path and return meta information
 *
 * @param {string} path
 * @param {!Object} cache - cache object
 * @returns {{mtime: !Object, fromCache: boolean}}
 */
function validateCache(path, cache) {
	const
		{mtime} = fs.statSync(path);

	return {
		mtime,
		fromCache: Boolean(cache[path] && Sugar.Date.is(mtime, cache[path].mtime))
	};
}

/**
 * Returns a file content by the specified path
 *
 * @param {string} path
 * @returns {string}
 */
function getFile(path) {
	const
		cache = validateCache(path, filesCache);

	if (!cache.fromCache) {
		filesCache[path] = {
			mtime: cache.mtime,
			content: fs.readFileSync(path, 'utf-8')
		};
	}

	return filesCache[path].content;
}

/**
 * Throws a new blockNotFound error by the specified name
 * @param {string} name
 */
function blockNotFound(name) {
	throw new Error(`Block "${name}" is not defined`);
}

class Block {
	/**
	 * Object that affects the project hash
	 *
	 * @protected
	 * @type {Object}
	 */
	static objToHash = null;

	/**
	 * Sets an object that affects the project hash
	 * @param obj
	 */
	static setObjToHash(obj) {
		this.objToHash = obj;
	}

	/**
	 * Returns a block manifest file by the specified parameter
	 *
	 * @param {string} folder - block folder name
	 * @param {string} file - manifest file
	 * @returns {!Promise<Block>}
	 */
	static async get(folder, file = 'index.js') {
		const
			blockPath = await resolve.block(path.join(folder, file)),
			cache = validateCache(blockPath, blockCache);

		if (cache.fromCache) {
			return new this(blockPath, blockCache[blockPath].decl);
		}

		async function getDecl(url) {
			return new Declaration(getFile(path.join(await url, file)));
		}

		const
			decl = await getDecl(resolve.block(folder));

		if (decl.mixin) {
			let
				skip = 1,
				parent;

			while ((parent = await resolve.block(decl.name, skip))) {
				const
					parentDecl = await getDecl(parent.path);

				if (!decl.parent && parentDecl.parent) {
					decl.parent = parentDecl.parent;
				}

				decl.dependencies = [...new Set(decl.dependencies.concat(parentDecl.dependencies))];
				decl.libs = [...new Set(decl.libs.concat(parentDecl.libs))];

				if (parentDecl.mixin) {
					skip = parent.from;

				} else {
					break;
				}
			}
		}

		blockCache[blockPath] = {
			mtime: cache.mtime,
			decl
		};

		return new this(blockPath, decl);
	}

	/**
	 * Returns block manifests file by the specified names
	 *
	 * @param {Array<string>=} [names]
	 * @returns {!Promise<!Map<string, !Block>>}
	 */
	static async getAll(names) {
		const
			projectCWD = await resolve.cwd,
			componentsLockFile = path.join(projectCWD, 'components-lock.json');

		const projectHash = objectHash().hash({
			srcHash: hashFiles.sync({files: [path.join(resolve.sourceDir, '/**/index.js')], algorithm: 'md5'}),
			projectFiles: glob.sync(path.join(resolve.sourceDir, '/**/*')),
			objToHash: this.objToHash
		}, {alg: 'md5'});

		try {
			if (fs.existsSync(componentsLockFile)) {
				const normalizedJSONReviver = (key, val) => {
					val = jsonReviver(key, val);

					if (Sugar.Object.isString(val) && path.extname(val) !== '') {
						return path.resolve(path.dirname(componentsLockFile), val);
					}

					if (Sugar.Object.isObject(val) && val.declaration != null) {
						Object.setPrototypeOf(val, Block.prototype);
					}

					return val;
				};

				const
					fromCache = fs.readJSONSync(componentsLockFile, {reviver: normalizedJSONReviver});

				if (fromCache.hash === projectHash) {
					return fromCache.data;
				}
			}

		} catch {}

		if (!names) {
			const
				b = `@(${blockTypeList.join('|')})-*`,
				components = `/**/${b}/index.js`,
				virtualComponents = `/**/${b}.index.js`;

			names = await $C([await resolve.block(), ...resolve.dependencies])
				.async
				.to(new Map())
				.reduce(async (map, root) => {
					const src = [
						path.join(root, components),
						path.join(root, virtualComponents)
					];

					if (!map.has(src)) {
						map.set(
							src,

							await $C(vinyl.src(src, {read: false}))
								.async
								.to([])
								.map((file) => {
									file.base = root;
									return [path.dirname(file.relative), file.basename];
								})
						);
					}

					return map;
				});
		}

		const
			set = Object.create(null);

		const blocks = await $C(names)
			.parallel()
			.to([])
			.reduce(async (res, el) => {
				const
					i = res.push([]) - 1;

				await $C([].concat(el || [])).async.forEach(async ([folder, name], j) => {
					if (set[folder] && set[folder] === name) {
						return;
					}

					set[folder] = name;

					const
						block = await this.get(folder, name);

					if (block) {
						res[i][j] = block;
					}
				});

				return res;
			});

		const blockMap = await $C(Sugar.Array.flatten(blocks)).to(new Map()).reduce((map, decl) => {
			if (!map.has(decl.name)) {
				return map.set(decl.name, decl);
			}

			return map;
		});

		const normalizedBlockMap = await $C(blockMap).async.to(createSerializableMap()).reduce(async (map, decl, key) => {
			map.set(key, {
				...decl,

				name: decl.name,
				parent: decl.parent,
				dependencies: decl.dependencies,

				libs: decl.libs,
				resolvedLibs: await decl.getLibs({cache: blockMap}),
				resolvedOwnLibs: await decl.getLibs({onlyOwn: true, cache: blockMap}),

				type: decl.type,
				mixin: decl.mixin,

				index: await decl.index,
				logic: await decl.logic,
				styles: await decl.styles,
				tpl: await decl.tpl,
				etpl: await decl.etpl,

				__proto__: Block.prototype
			});

			return map;
		});

		const cacheData = {
			hash: projectHash,
			data: normalizedBlockMap
		};

		const jsonReplacer = (key, val) => {
			if (Sugar.Object.isString(val) && path.extname(val) !== '' && path.isAbsolute(val)) {
				return path.relative(projectCWD, val);
			}

			if (Sugar.Object.isObject(val) && val.declaration != null) {
				return normalizedBlockMap.get(key);
			}

			return val;
		};

		fs.writeFileSync(componentsLockFile, JSON.stringify(cacheData, jsonReplacer, 2));

		return normalizedBlockMap;
	}

	/**
	 * Returns a path to the logic file
	 * @returns {!Promise<?string>}
	 */
	get logic() {
		return resolve.block(`${this.name}/${this.name}.logic`);
	}

	/**
	 * Returns a path to the template file
	 * @returns {!Promise<?string>}
	 */
	get tpl() {
		return resolve.block(`${this.name}/${this.name}.ss`);
	}

	/**
	 * Returns a path to the ess template file
	 * @returns {!Promise<?string>}
	 */
	get etpl() {
		return resolve.block(`${this.name}/${this.name}.ess`);
	}

	/**
	 * Returns paths to the style files
	 * @returns {!Promise<!Array<string>>}
	 */
	get styles() {
		return (async () => {
			const
				root = await resolve.block(),
				style = await resolve.block(`${this.name}/${this.name}.styl`),
				styles = [].concat(style || []);

			if (this.mixin && !isPathInside(style, root)) {
				styles.push(...await glob(path.join(root, `/**/${this.name}/${this.name}_*.styl`)));
			}

			return styles;
		})();
	}

	/**
	 * Block name
	 * @returns {string}
	 */
	get name() {
		return this.declaration.name;
	}

	/**
	 * Block type
	 * @returns {string}
	 */
	get type() {
		return this.declaration.type;
	}

	/**
	 * Block parent
	 * @returns {?string}
	 */
	get parent() {
		return this.declaration.parent;
	}

	/**
	 * Block mixin status
	 * @returns {boolean}
	 */
	get mixin() {
		return this.declaration.mixin;
	}

	/**
	 * Block dependencies
	 * @returns {!Array<string>}
	 */
	get dependencies() {
		return this.declaration.dependencies;
	}

	/**
	 * Block libraries
	 * @returns {!Array<string>}
	 */
	get libs() {
		return this.declaration.libs;
	}

	/**
	 * @param index - block index src
	 * @param declaration - block declaration
	 */
	constructor(index, declaration) {
		this.index = index;
		this.declaration = declaration;
	}

	/**
	 * Returns the block parent manifest
	 *
	 * @param {Map<string, !Block>=} [cache] - optional cache object with predefined blocks
	 * @returns {!Promise<Block>}
	 */
	async getParent({cache} = {}) {
		if (!this.parent) {
			return null;
		}

		const
			block = cache ? cache.get(this.parent) : await this.constructor.get(this.parent);

		if (!block) {
			blockNotFound(this.parent);
		}

		return block;
	}

	/**
	 * Returns manifests of all block dependencies
	 *
	 * @param {boolean=} [onlyOwn] - if true parent dependencies also included
	 * @param {Map<string, !Block>=} [cache] - optional cache object with predefined blocks
	 * @returns {!Promise<!Map<string, !Block>>}
	 */
	async getDependencies({onlyOwn, cache} = {}) {
		let
			names = this.dependencies.slice();

		if (!onlyOwn) {
			let
				parent = await this.getParent({cache});

			while (parent) {
				names = [...parent.dependencies, ...names];
				parent = await parent.getParent({cache});
			}
		}

		if (cache) {
			return $C(names).to(createSerializableMap()).reduce((map, name) => {
				const
					block = cache.get(name);

				if (!block) {
					blockNotFound(name);
				}

				return map.set(name, block);
			});
		}

		return this.constructor.getAll([...new Set(names)]);
	}

	/**
	 * Returns manifests of all block libraries
	 *
	 * @param {boolean=} [onlyOwn] - if true parent libraries also included
	 * @param {Map<string, !Block>=} [cache] - optional cache object with predefined blocks
	 * @returns {!Promise<!Set<string>>}
	 */
	async getLibs({onlyOwn, cache} = {}) {
		if (onlyOwn) {
			if ('resolvedOwnLibs' in this) {
				return this.resolvedOwnLibs;
			}

		} else if ('resolvedLibs' in this) {
			return this.resolvedLibs;
		}

		let
			libs = this.libs.slice();

		if (!onlyOwn) {
			let
				parent = await this.getParent({cache});

			while (parent) {
				libs = [...parent.libs, ...libs];
				parent = await parent.getParent({cache});
			}
		}

		return createSerializableSet(libs);
	}

	/**
	 * Returns an object with block runtime dependencies
	 *
	 * @param {Map<string, !Block>=} [cache] - optional cache object with predefined blocks
	 * @returns {!Promise<{runtime: !Map<string, !Block>, parents: !Map<string, !Block>, libs: !Set<string>}>}
	 */
	async getRuntimeDependencies({cache} = {}) {
		let
			runtime = createSerializableMap(),
			parents = createSerializableMap();

		const get = async (name, isParent) => {
			const
				block = name === this.name ? this : cache ? cache.get(name) : await this.constructor.get(name);

			if (!block) {
				blockNotFound(name);
			}

			runtime = createSerializableMap([[name, block], ...runtime.entries()]);
			if (!isParent && parents.has(name)) {
				parents.delete(name);
			}

			await $C(await block.getDependencies({onlyOwn: true, cache})).async.forEach(async (block) => {
				runtime = createSerializableMap([[block.name, block], ...runtime.entries()]);
				await get(block.name, false, runtime, parents);
			});

			const
				parentName = block.parent;

			if (parentName) {
				const
					parent = await block.getParent({cache});

				if (!runtime.has(parentName)) {
					parents = createSerializableMap([[parentName, parent], ...parents.entries()]);
					runtime = createSerializableMap([[parentName, parent], ...runtime.entries()]);
				}

				await get(parentName, true, runtime, parents);
			}
		};

		const [libs] = await Promise.all([
			this.getLibs({cache}),
			get(this.name)
		]);

		return {runtime, parents, libs};
	}
}

module.exports = Block;
<|endoftext|>


<commit_before>#!/usr/bin / env node
const yargs = require('yargs')
const retyped = require('./retyped_node')

const a = yargs
  .usage('$0 <cmd> [args]')
  .command(
    'generate [files...]',
    'Generate BuckleScript interfaces from a file'
  )
  .help()
  .argv

console.log(a)<commit_msg>Basic CLI<commit_after>#!/usr/bin / env node

const fs = require('fs')
const path = require('path')
const yargs = require('yargs')
const retyped = require('./retyped_node')

yargs
  .usage('retyped')
  .version(require('./package.json').version)
  .command('compile [files...]', 'Generate BuckleScript interfaces from a file', {}, compileFiles)
  .option('flow-typed', {
    describe: 'Generate interfaces from the flow-typed directory'
  })
  .demandCommand(1, '')
  .help()
  .argv

function compileFiles ({ files }) {
  files.forEach(compileFile)
}

function compileFile (filePath) {
  return new Promise((resolve, reject) => {
    fs.readFile(filePath, (err, data) => {
      if (err) return reject(err)

      const fileName = path.basename(filePath)
      const [moduleName, _, bsCode] = retyped.compile(fileName, data.toString())

      const writeDirName = path.dirname(filePath)
      const writeFileName = path.join(writeDirName, moduleName + '.re')

      console.log(`Writing ${filePath} -> ${writeFileName}`)
      fs.writeFile(writeFileName, bsCode, err => {
        if (err) return reject(err)

        resolve()
      })
    })
  })
}
<|endoftext|>


<commit_before>var cmd = require('commander');
var amok = require('./');
var temp = require('temp');

var pkg = require('./package.json');

cmd.usage('[options] <script>');

cmd.option('-h, --host <HOST>', 'specify http host', 'localhost');
cmd.option('-p, --port <PORT>', 'specify http port', 9966);

cmd.option('-H, --debugger-host <HOST>', 'specify debugger host', 'localhost');
cmd.option('-P, --debugger-port <PORT>', 'specify debugger port', 9222);

cmd.option('--no-bundler', 'disable bundling');
cmd.option('--no-browser', 'disable bundling');
cmd.option('--no-debugger', 'disable debugging');

cmd.version(pkg.version);
cmd.parse(process.argv);

cmd.cwd = process.cwd();
if (cmd.browser !== false) {
  cmd.browser = process.env['BROWSER'];
}

if (cmd.bundler !== false) {
  cmd.bundler = process.env['BUNDLER'];
}

cmd.scripts = cmd.args.reduce(function(object, value, key) {
  object[value] = value;
  return object;
}, {});

if (cmd.bundler) {
  cmd.scripts = { 'bundle.js': temp.path({suffix: '.js'}) };

  cmd.args.push('-o');
  cmd.args.push(cmd.scripts['bundle.js']);

  amok.bundle(cmd, function(error, stdout, stderr) {
//    process.stdout.pipe(stdout);
//    process.stderr.pipe(stderr);
  });
}

var watcher = amok.watch(cmd, function() {
  for (var script in cmd.scripts) {
    var filename = cmd.scripts[script];
    watcher.add(filename);
  }
});

setTimeout(function() {
  if (cmd.debugger) {
    var bugger = amok.debug(cmd, function(target) {
    });

    bugger.on('attach', function(target) {
      console.info('debugger attatched to target %s', target.title);
    });

    bugger.on('detatch', function() {
      var id = setInterval(function() {
        client.targets(function(targets) {
          var target = targets.filter(function(target) {
            return target.url.indexOf(cmd.host) > -1;
          })[0];

          client.attach(target);
        });
      }, 500);

      bugger.once('attach', function() {
        clearInterval(id);
      });
    });

    watcher.on('change', function(filename) {
      var script = Object.keys(cmd.scripts).filter(function(key) { return cmd.scripts[key] === filename})[0];
      if (script) {
        bugger.source(script, null, function(error) {
          if (error) {
            return console.error(error);
          }

          console.info('re-compilation succesful');
        });
      }
    });
  }
}, 500);

var server = amok.serve(cmd, function() {
  var address = server.address();
  console.info('http server listening on http://%s:%d', address.address, address.port);

  if (cmd.browser) {
    var browser = amok.browse(cmd, function(error, stdout, stderr) {
//      process.stdout.pipe(stdout);
//      process.stderr.pipe(stderr);
    });
  }
});
<commit_msg>Fix --no-browser option description<commit_after>var cmd = require('commander');
var amok = require('./');
var temp = require('temp');

var pkg = require('./package.json');

cmd.usage('[options] <script>');

cmd.option('-h, --host <HOST>', 'specify http host', 'localhost');
cmd.option('-p, --port <PORT>', 'specify http port', 9966);

cmd.option('-H, --debugger-host <HOST>', 'specify debugger host', 'localhost');
cmd.option('-P, --debugger-port <PORT>', 'specify debugger port', 9222);

cmd.option('--no-bundler', 'disable bundling');
cmd.option('--no-browser', 'disable browsing');
cmd.option('--no-debugger', 'disable debugging');

cmd.version(pkg.version);
cmd.parse(process.argv);

cmd.cwd = process.cwd();
if (cmd.browser !== false) {
  cmd.browser = process.env['BROWSER'];
}

if (cmd.bundler !== false) {
  cmd.bundler = process.env['BUNDLER'];
}

cmd.scripts = cmd.args.reduce(function(object, value, key) {
  object[value] = value;
  return object;
}, {});

if (cmd.bundler) {
  cmd.scripts = { 'bundle.js': temp.path({suffix: '.js'}) };

  cmd.args.push('-o');
  cmd.args.push(cmd.scripts['bundle.js']);

  amok.bundle(cmd, function(error, stdout, stderr) {
//    process.stdout.pipe(stdout);
//    process.stderr.pipe(stderr);
  });
}

var watcher = amok.watch(cmd, function() {
  for (var script in cmd.scripts) {
    var filename = cmd.scripts[script];
    watcher.add(filename);
  }
});

setTimeout(function() {
  if (cmd.debugger) {
    var bugger = amok.debug(cmd, function(target) {
    });

    bugger.on('attach', function(target) {
      console.info('debugger attatched to target %s', target.title);
    });

    bugger.on('detatch', function() {
      var id = setInterval(function() {
        client.targets(function(targets) {
          var target = targets.filter(function(target) {
            return target.url.indexOf(cmd.host) > -1;
          })[0];

          client.attach(target);
        });
      }, 500);

      bugger.once('attach', function() {
        clearInterval(id);
      });
    });

    watcher.on('change', function(filename) {
      var script = Object.keys(cmd.scripts).filter(function(key) { return cmd.scripts[key] === filename})[0];
      if (script) {
        bugger.source(script, null, function(error) {
          if (error) {
            return console.error(error);
          }

          console.info('re-compilation succesful');
        });
      }
    });
  }
}, 500);

var server = amok.serve(cmd, function() {
  var address = server.address();
  console.info('http server listening on http://%s:%d', address.address, address.port);

  if (cmd.browser) {
    var browser = amok.browse(cmd, function(error, stdout, stderr) {
//      process.stdout.pipe(stdout);
//      process.stderr.pipe(stderr);
    });
  }
});
<|endoftext|>