<commit_before>/*! Kernel CI Dashboard | Licensed under the GNU GPL v3 (or later) */
define([
    'jquery',
    'sprintf',
    'datatables',
    'datatables.bootstrap'
], function($) {
    'use strict';
    var gMenuFmt,
        gSearchLanguage,
        gTable,
        gTableDom;

    gTableDom = '<"row"<"col-xs-12 col-sm-12 col-md-4 col-lg-4"' +
            '<"length-menu"l>>' +
            '<"col-xs-12 col-sm-12 col-md-4 col-lg-4"<"table-process">>' +
            '<"col-xs-12 col-sm-12 col-md-4 col-lg-4"f>r' +
            '<"col-xs-12 col-sm-12 col-md-12 col-lg-12"t>>' +
            '<"row paging"<"col-xs-12 col-sm-12 col-md-6 col-lg-6"i>' +
            '<"col-xs-12 col-sm-12 col-md-6 col-lg-6"p>>';

    gSearchLanguage = '
' +
        '' +
        '_INPUT_
';

    gMenuFmt = '_MENU_ %s';

    /**
     * Remove the focus from the element that triggers the event.
     * Apply a blur() to the triggering element.
     *
     * @private
     * @param {Event} event: The event triggering this function.
    **/
    function removeFocus(event) {
        var target;

        target = event.target || event.srcElement;
        if (event.keyCode === 27) {
            target.blur();
        }
    }

    var kciTable = {
        tableId: null,
        tableLoadingDivId: null,
        tableDivId: null,
        disableSearch: false,
        tableNode: null,
        tableLoadingNode: null,
        tableDivNode: null,
        table: null,
        oldSearch: null,
        inputNode: null,
        selectNode: null,
        _noIdURL: false,
        _rowURL: '/build/%(job)s/kernel/%(kernel)s/',
        _rowURLElements: ['job', 'kernel'],
        _clickFunction: null,
        _drawFunction: null,
        _lengthMenu: [25, 50, 75, 100],
        _order: [1, 'desc'],
        _dom: gTableDom,
        _data: null,
        _columns: null,
        _paging: true,
        _info: true,
        _languageLengthMenu:
            '_MENU_ reports per page',
        _languageZeroRecords: 'No data found.'
    };

    kciTable.settings = function() {
        return {
            language: {
                lengthMenu: this._languageLengthMenu,
                zeroRecords: this._languageZeroRecords,
                search: gSearchLanguage,
                searchPlaceholder: 'Filter the results'
            },
            search: {
                regex: true,
                smart: true
            },
            data: this._data,
            columns: this._columns,
            paging: this._paging,
            info: this._info,
            order: this._order,
            dom: this._dom,
            lengthMenu: this._lengthMenu,
            deferRender: true,
            ordering: true,
            stateSave: true,
            stateDuration: -1,
            processing: false
        };
    };

    kciTable.paging = function(value) {
        if (value !== undefined) {
            this._paging = Boolean(value);
            return this;
        }
        return this._paging;
    };

    kciTable.info = function(value) {
        if (value !== undefined) {
            this._info = Boolean(value);
            return this;
        }
        return this._info;
    };

    kciTable.setup = function() {
        if (this.tableId) {
            this.tableNode = document.getElementById(this.tableId);

            if (!this.tableNode) {
                throw 'No table ID DOM element found';
            }
        } else {
            throw 'No table ID defined';
        }

        if (this.tableLoadingDivId) {
            this.tableLoadingNode =
                document.getElementById(this.tableLoadingDivId);
        }

        if (this.tableDivId) {
            this.tableDivNode = document.getElementById(this.tableDivId);

            if (this.tableDivNode) {
                this.inputNode =
                    this.tableDivNode.querySelector('input.input-sm');
                this.selectNode =
                    this.tableDivNode.querySelector('select.input-sm');
            }
        }

        return this;
    };

    kciTable.languageZeroRecords = function(value) {
        if (value !== null && value !== undefined) {
            this._languageZeroRecords = value;
            return this;
        }
        return this._languageZeroRecords;
    };

    kciTable.languageLengthMenu = function(value) {
        if (value !== null && value !== undefined) {
            this._languageLengthMenu = sprintf(gMenuFmt, value);
            return this;
        }
        return this._languageLengthMenu;
    };

    kciTable.rowURL = function(value) {
        if (value !== null && value !== undefined) {
            this._rowURL = value;
            return this;
        }
        return this._rowURL;
    };

    kciTable.rowURLElements = function(value) {
        if (value !== null && value !== undefined) {
            this._rowURLElements = value;
            return this;
        }
        return this._rowURLElements;
    };

    kciTable.order = function(value) {
        if (value !== null && value !== undefined) {
            this._order = value;
            return this;
        }
        return this._order;
    };

    kciTable.data = function(value) {
        if (value !== null && value !== undefined) {
            this._data = value;
            return this;
        }
        return this._data;
    };

    kciTable.columns = function(value) {
        if (value !== null && value !== undefined) {
            this._columns = value;
            return this;
        }
        return this._columns;
    };

    /**
     * Control whetever the link for the row click event should have an
     * '_id=' value appended.
     *
     * @param {Boolean} value: If the row link should have an ID or not.
    **/
    kciTable.noIdURL = function(value) {
        if (value !== undefined) {
            this._noIdURL = Boolean(value);
            return this;
        }
        return this._noIdURL;
    };

    kciTable.dom = function(value) {
        if (value !== null && value !== undefined) {
            this._dom = value;
            return this;
        }
        return this._dom;
    };

    /**
     * Get or set the length menu.
     * This is the menu with how many rows to show in the table.
     *
     * @param {Array} value: The array with the number of rows to show.
    **/
    kciTable.lengthMenu = function(value) {
        if (value !== null && value !== undefined) {
            this._lengthMenu = value;
            return this;
        }
        return this._lengthMenu;
    };

    /**
     * Get or set the row click function.
     * This is the function to be called when a row gets clicked.
     *
     * @param {Function} value: The function to associate with the click event.
    **/
    kciTable.clickFunction = function(value) {
        if (value !== null && value !== undefined) {
            this._clickFunction = value;
            return this;
        }
        return this._clickFunction;
    };

    /**
     * Perform a search on the table.
     * A draw() event will be fired.
     *
     * @param {String} value: The search string to perform.
    **/
    kciTable.search = function(value) {
        if (this.inputNode && this.inputNode.disabled) {
            this.inputNode.removeAttribute('disabled');
        }

        if (value && value.length > 0) {
            this.oldSearch = null;
            this.table.search(value, true, true).draw();
        }

        return this;
    };

    /**
     * Change the length of the tables, showing or hiding rows.
     * A draw() event will be fired.
     *
     * @param {String} value: The number of rows to show.
    **/
    kciTable.pageLen = function(value) {
        var len;

        if (value && value.length > 0) {
            len = Number(value);
            if (isNaN(len)) {
                len = this._lengthMenu[0];
            }
            this.table.page.len(len).draw();
        }

        return this;
    };

    /**
     * Associate a new function on the 'draw' event.
     * This is fired whenevr the table draw() event has been completed.
     *
     * @param {Function} func: The function to associate.
    **/
    kciTable.addDrawEvent = function(func) {
        if (func) {
            this.table.on('draw.dt', func);
        }
        return this;
    };

    /**
     * Add new rows to the table, updating and refreshing it.
     *
     * @param {object} value: The data that the rows will be built on.
     * It must be of the same type and with the same structure of the initial
     * data with which the table was populated.
    **/
    kciTable.addRows = function(value) {
        this.table.rows.add(value).draw();
    };

    /**
     * Draw the actual table and bind functions/events.
    **/
    kciTable.draw = function() {
        var settings,
            that;

        that = this;
        settings = that.settings();

        function _observeMutations() {
            var observer,
                observerConfig,
                target;

            observer = new MutationObserver(function(events) {
                events.forEach(function(event) {
                    target = event.target || event.srcElement;
                    if (target === that.inputNode &&
                            event.attributeName === 'disabled') {

                        if (that.oldSearch) {
                            that.table
                                .search(that.oldSearch, true, true)
                                .draw();
                            that.oldSearch = null;
                        }
                    }
                });
                observer.disconnect();
            });
            observerConfig = {
                attributes: true,
                attributeOldValue: true
            };
            observer.observe(that.inputNode, observerConfig);
        }

        settings.initComplete = function() {
            if (that.tableLoadingNode) {
                $(that.tableLoadingNode).remove();
            }
            if (that.tableDivNode) {
                $(that.tableDivNode).fadeIn('slow', 'linear');
            }
        };

        settings.stateLoadParams = function(s, d) {
            if (that.disableSearch && d.search.search.length > 0) {
                that.oldSearch = d.search.search;
                d.search.search = '';
            }
        };

        that.table = $(that.tableNode).DataTable(settings);

        if (that._drawFunction) {
            that.table.on('draw.dt', that._drawFunction);
        }

        if (that.inputNode) {
            // Remove focus from input when Esc is pressed.
            that.inputNode.addEventListener('keyup', removeFocus);

            // Disable search box.
            if (that.disableSearch) {
                that.inputNode.setAttribute('disabled', true);
            }
        }

        if (that.selectNode) {
            // Remove focus from the table length selection on Esc.
            that.selectNode.addEventListener('keyup', removeFocus);
        }

        if (that._clickFunction) {
            that.table.on('click', 'tbody tr', that._clickFunction);
        } else {
            that.table.on('click', 'tbody tr', function() {
                var location,
                    rowData,
                    substitutions;

                rowData = that.table.row(this).data();
                location = '#';
                substitutions = {};

                if (rowData) {
                    that._rowURLElements.forEach(function(value) {
                        substitutions[value] = rowData[value] || null;
                    });

                    location = sprintf(that._rowURL, substitutions);
                    if (!that._noIdURL && rowData._id !== null) {
                        location += '?_id=' + rowData._id.$oid;
                    }
                }
                window.location = location;
            });
        }

        that.table.on('search.dt', function() {
            if (that.disableSearch && that.oldSearch !== null) {
                if (window.MutationObserver) {
                    if (that.inputNode.getAttribute('disabled')) {
                        _observeMutations();
                    }
                } else {
                    // No support for MutationObserver.
                    that.table.search(that.oldSearch, true, true).draw();
                }
            }
        });

        return this;
    };

    /**
     * Factory to create new table elements.
     *
     * @param {object} settings: The settings associated with the table.
    **/
    gTable = function(settings) {
        var key,
            newTable;

        newTable = Object.create(kciTable);

        if (settings) {
            for (key in settings) {
                if (settings.hasOwnProperty(key)) {
                    newTable[key] = settings[key];
                }
            }
        } else {
            throw 'No settings found';
        }

        return newTable.setup();
    };

    return gTable;
});
<commit_msg>Fix id in URL logic<commit_after>/*! Kernel CI Dashboard | Licensed under the GNU GPL v3 (or later) */
define([
    'jquery',
    'sprintf',
    'datatables',
    'datatables.bootstrap'
], function($) {
    'use strict';
    var gMenuFmt,
        gSearchLanguage,
        gTable,
        gTableDom;

    gTableDom = '<"row"<"col-xs-12 col-sm-12 col-md-4 col-lg-4"' +
            '<"length-menu"l>>' +
            '<"col-xs-12 col-sm-12 col-md-4 col-lg-4"<"table-process">>' +
            '<"col-xs-12 col-sm-12 col-md-4 col-lg-4"f>r' +
            '<"col-xs-12 col-sm-12 col-md-12 col-lg-12"t>>' +
            '<"row paging"<"col-xs-12 col-sm-12 col-md-6 col-lg-6"i>' +
            '<"col-xs-12 col-sm-12 col-md-6 col-lg-6"p>>';

    gSearchLanguage = '
' +
        '' +
        '_INPUT_
';

    gMenuFmt = '_MENU_ %s';

    /**
     * Remove the focus from the element that triggers the event.
     * Apply a blur() to the triggering element.
     *
     * @private
     * @param {Event} event: The event triggering this function.
    **/
    function removeFocus(event) {
        var target;

        target = event.target || event.srcElement;
        if (event.keyCode === 27) {
            target.blur();
        }
    }

    var kciTable = {
        tableId: null,
        tableLoadingDivId: null,
        tableDivId: null,
        disableSearch: false,
        tableNode: null,
        tableLoadingNode: null,
        tableDivNode: null,
        table: null,
        oldSearch: null,
        inputNode: null,
        selectNode: null,
        _noIdURL: false,
        _rowURL: '/build/%(job)s/kernel/%(kernel)s/',
        _rowURLElements: ['job', 'kernel'],
        _clickFunction: null,
        _drawFunction: null,
        _lengthMenu: [25, 50, 75, 100],
        _order: [1, 'desc'],
        _dom: gTableDom,
        _data: null,
        _columns: null,
        _paging: true,
        _info: true,
        _languageLengthMenu:
            '_MENU_ reports per page',
        _languageZeroRecords: 'No data found.'
    };

    kciTable.settings = function() {
        return {
            language: {
                lengthMenu: this._languageLengthMenu,
                zeroRecords: this._languageZeroRecords,
                search: gSearchLanguage,
                searchPlaceholder: 'Filter the results'
            },
            search: {
                regex: true,
                smart: true
            },
            data: this._data,
            columns: this._columns,
            paging: this._paging,
            info: this._info,
            order: this._order,
            dom: this._dom,
            lengthMenu: this._lengthMenu,
            deferRender: true,
            ordering: true,
            stateSave: true,
            stateDuration: -1,
            processing: false
        };
    };

    kciTable.paging = function(value) {
        if (value !== undefined) {
            this._paging = Boolean(value);
            return this;
        }
        return this._paging;
    };

    kciTable.info = function(value) {
        if (value !== undefined) {
            this._info = Boolean(value);
            return this;
        }
        return this._info;
    };

    kciTable.setup = function() {
        if (this.tableId) {
            this.tableNode = document.getElementById(this.tableId);

            if (!this.tableNode) {
                throw 'No table ID DOM element found';
            }
        } else {
            throw 'No table ID defined';
        }

        if (this.tableLoadingDivId) {
            this.tableLoadingNode =
                document.getElementById(this.tableLoadingDivId);
        }

        if (this.tableDivId) {
            this.tableDivNode = document.getElementById(this.tableDivId);

            if (this.tableDivNode) {
                this.inputNode =
                    this.tableDivNode.querySelector('input.input-sm');
                this.selectNode =
                    this.tableDivNode.querySelector('select.input-sm');
            }
        }

        return this;
    };

    kciTable.languageZeroRecords = function(value) {
        if (value !== null && value !== undefined) {
            this._languageZeroRecords = value;
            return this;
        }
        return this._languageZeroRecords;
    };

    kciTable.languageLengthMenu = function(value) {
        if (value !== null && value !== undefined) {
            this._languageLengthMenu = sprintf(gMenuFmt, value);
            return this;
        }
        return this._languageLengthMenu;
    };

    kciTable.rowURL = function(value) {
        if (value !== null && value !== undefined) {
            this._rowURL = value;
            return this;
        }
        return this._rowURL;
    };

    kciTable.rowURLElements = function(value) {
        if (value !== null && value !== undefined) {
            this._rowURLElements = value;
            return this;
        }
        return this._rowURLElements;
    };

    kciTable.order = function(value) {
        if (value !== null && value !== undefined) {
            this._order = value;
            return this;
        }
        return this._order;
    };

    kciTable.data = function(value) {
        if (value !== null && value !== undefined) {
            this._data = value;
            return this;
        }
        return this._data;
    };

    kciTable.columns = function(value) {
        if (value !== null && value !== undefined) {
            this._columns = value;
            return this;
        }
        return this._columns;
    };

    /**
     * Control whetever the link for the row click event should have an
     * '_id=' value appended.
     *
     * @param {Boolean} value: If the row link should have an ID or not.
    **/
    kciTable.noIdURL = function(value) {
        if (value !== undefined) {
            this._noIdURL = Boolean(value);
            return this;
        }
        return this._noIdURL;
    };

    kciTable.dom = function(value) {
        if (value !== null && value !== undefined) {
            this._dom = value;
            return this;
        }
        return this._dom;
    };

    /**
     * Get or set the length menu.
     * This is the menu with how many rows to show in the table.
     *
     * @param {Array} value: The array with the number of rows to show.
    **/
    kciTable.lengthMenu = function(value) {
        if (value !== null && value !== undefined) {
            this._lengthMenu = value;
            return this;
        }
        return this._lengthMenu;
    };

    /**
     * Get or set the row click function.
     * This is the function to be called when a row gets clicked.
     *
     * @param {Function} value: The function to associate with the click event.
    **/
    kciTable.clickFunction = function(value) {
        if (value !== null && value !== undefined) {
            this._clickFunction = value;
            return this;
        }
        return this._clickFunction;
    };

    /**
     * Perform a search on the table.
     * A draw() event will be fired.
     *
     * @param {String} value: The search string to perform.
    **/
    kciTable.search = function(value) {
        if (this.inputNode && this.inputNode.disabled) {
            this.inputNode.removeAttribute('disabled');
        }

        if (value && value.length > 0) {
            this.oldSearch = null;
            this.table.search(value, true, true).draw();
        }

        return this;
    };

    /**
     * Change the length of the tables, showing or hiding rows.
     * A draw() event will be fired.
     *
     * @param {String} value: The number of rows to show.
    **/
    kciTable.pageLen = function(value) {
        var len;

        if (value && value.length > 0) {
            len = Number(value);
            if (isNaN(len)) {
                len = this._lengthMenu[0];
            }
            this.table.page.len(len).draw();
        }

        return this;
    };

    /**
     * Associate a new function on the 'draw' event.
     * This is fired whenevr the table draw() event has been completed.
     *
     * @param {Function} func: The function to associate.
    **/
    kciTable.addDrawEvent = function(func) {
        if (func) {
            this.table.on('draw.dt', func);
        }
        return this;
    };

    /**
     * Add new rows to the table, updating and refreshing it.
     *
     * @param {object} value: The data that the rows will be built on.
     * It must be of the same type and with the same structure of the initial
     * data with which the table was populated.
    **/
    kciTable.addRows = function(value) {
        this.table.rows.add(value).draw();
    };

    /**
     * Draw the actual table and bind functions/events.
    **/
    kciTable.draw = function() {
        var settings,
            that;

        that = this;
        settings = that.settings();

        function _observeMutations() {
            var observer,
                observerConfig,
                target;

            observer = new MutationObserver(function(events) {
                events.forEach(function(event) {
                    target = event.target || event.srcElement;
                    if (target === that.inputNode &&
                            event.attributeName === 'disabled') {

                        if (that.oldSearch) {
                            that.table
                                .search(that.oldSearch, true, true)
                                .draw();
                            that.oldSearch = null;
                        }
                    }
                });
                observer.disconnect();
            });
            observerConfig = {
                attributes: true,
                attributeOldValue: true
            };
            observer.observe(that.inputNode, observerConfig);
        }

        settings.initComplete = function() {
            if (that.tableLoadingNode) {
                $(that.tableLoadingNode).remove();
            }
            if (that.tableDivNode) {
                $(that.tableDivNode).fadeIn('slow', 'linear');
            }
        };

        settings.stateLoadParams = function(s, d) {
            if (that.disableSearch && d.search.search.length > 0) {
                that.oldSearch = d.search.search;
                d.search.search = '';
            }
        };

        that.table = $(that.tableNode).DataTable(settings);

        if (that._drawFunction) {
            that.table.on('draw.dt', that._drawFunction);
        }

        if (that.inputNode) {
            // Remove focus from input when Esc is pressed.
            that.inputNode.addEventListener('keyup', removeFocus);

            // Disable search box.
            if (that.disableSearch) {
                that.inputNode.setAttribute('disabled', true);
            }
        }

        if (that.selectNode) {
            // Remove focus from the table length selection on Esc.
            that.selectNode.addEventListener('keyup', removeFocus);
        }

        if (that._clickFunction) {
            that.table.on('click', 'tbody tr', that._clickFunction);
        } else {
            that.table.on('click', 'tbody tr', function() {
                var location,
                    rowData,
                    substitutions;

                rowData = that.table.row(this).data();
                location = '#';
                substitutions = {};

                if (rowData) {
                    that._rowURLElements.forEach(function(value) {
                        substitutions[value] = rowData[value] || null;
                    });

                    location = sprintf(that._rowURL, substitutions);
                    if (!that._noIdURL && rowData._id) {
                        location += '?_id=' + rowData._id.$oid;
                    }
                }
                window.location = location;
            });
        }

        that.table.on('search.dt', function() {
            if (that.disableSearch && that.oldSearch !== null) {
                if (window.MutationObserver) {
                    if (that.inputNode.getAttribute('disabled')) {
                        _observeMutations();
                    }
                } else {
                    // No support for MutationObserver.
                    that.table.search(that.oldSearch, true, true).draw();
                }
            }
        });

        return this;
    };

    /**
     * Factory to create new table elements.
     *
     * @param {object} settings: The settings associated with the table.
    **/
    gTable = function(settings) {
        var key,
            newTable;

        newTable = Object.create(kciTable);

        if (settings) {
            for (key in settings) {
                if (settings.hasOwnProperty(key)) {
                    newTable[key] = settings[key];
                }
            }
        } else {
            throw 'No settings found';
        }

        return newTable.setup();
    };

    return gTable;
});
<|endoftext|>


<commit_before>/*
    Framework for building object relational database apps
    Copyright (C) 2019  John Rogelstad

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
/*jslint browser*/
/**
    @module Core
*/
import catalog from "./catalog.js";
import createModel from "./model.js";
import list from "./list.js";

const f = window.f;

function feather(data, spec) {
    spec = spec || catalog.getFeather("Feather");
    if (data === undefined) {
        data = {
            authorizations: [{
                role: "everyone",
                canCreate: true,
                canRead: true,
                canUpdate: true,
                canDelete: true
            }]
        };
    }
    let model;
    let inheritedProperties = f.prop([]);
    let re = new RegExp(" ", "g");

    inheritedProperties().canAdd = f.prop(false);

    model = createModel(data, spec);

    function featherList() {
        let feathers = catalog.store().feathers();
        let keys = Object.keys(feathers);

        keys = keys.filter(function (key) {
            return !feathers[key].isSystem;
        }).sort();

        return keys.map(function (key) {
            return {
                value: key,
                label: key
            };
        });
    }

    function handleReadOnly() {
        model.data.name.isReadOnly(true);
        model.data.plural.isReadOnly(true);
        model.data.inherits.isReadOnly(true);
    }

    function handleReadOnlyProps() {
        model.data.properties().forEach((prop) => prop.handleReadOnly());
    }

    function calculateInherited() {
        let parent = model.data.inherits();
        let featherProperty;
        let props = inheritedProperties();

        if (parent) {
            parent = catalog.getFeather(parent);
            featherProperty = catalog.store().models().featherProperty;
            props.length = 0;

            Object.keys(parent.properties).forEach(function (key) {
                let prop = parent.properties[key];
                let instance;

                if (prop.default === undefined) {
                    prop.default = "";
                }
                prop.name = key;
                instance = featherProperty(prop);
                instance.state().goto("/Ready/Fetched/ReadOnly");
                instance.parent(model);
                props.push(instance);
            });
        }
        handleReadOnlyProps();
    }

    function sanitize(prop) {
        let value = prop.newValue();

        value = value.replace(re, "");
        value = value.slice(0, 1).toUpperCase() + value.slice(1);
        prop.newValue(value);
    }

    /**
        Feathers datalist.

        __Type:__ `Array`

        __Is Calculated__

        __Read Only__

        @property data.feathers
        @for Models.Feather
        @type Property
    */
    model.addCalculated({
        name: "feathers",
        type: "array",
        function: featherList
    });

    /**
        Inherited properties child records.

        __Type:__ `Array`

        __Is Calculated__

        __Read Only__

        @property data.inheritedProperties
        @for Models.Feather
        @type Property
    */
    model.addCalculated({
        name: "inheritedProperties",
        type: {
            relation: "FeatherProperty",
            parentOf: "inheritedProperties"
        },
        isReadOnly: true,
        function: inheritedProperties
    });

    /**
        Modules datalist.

        __Type:__ `Array`

        __Is Calculated__

        __Read Only__

        @property data.modules
        @for Models.Feather
        @type Property
    */
    model.addCalculated({
        name: "modules",
        type: "array",
        function: catalog.store().data().modules
    });

    model.onChange("name", sanitize);
    model.onChanged("name", function (prop) {
        if (prop() && !model.data.plural()) {
            model.data.plural(prop() + "s");
        }
    });
    model.onChange("plural", sanitize);

    model.onChanged("properties", handleReadOnlyProps);
    model.onChanged("properties.isNaturalKey", handleReadOnlyProps);
    model.onChanged("properties.isLabelKey", handleReadOnlyProps);
    model.onChanged("inherits", calculateInherited);
    model.onLoad(calculateInherited);
    model.onLoad(handleReadOnly);

    model.onValidate(function () {
        let authRoles = [];

        model.data.authorizations().forEach(function (auth) {
            let role = auth.data.role();

            if (authRoles.indexOf(role) !== -1) {
                throw new Error(
                    "Role '" + role +
                    "' must only be in one authorization per feather."
                );
            }
            authRoles.push(role);
        });
    });

    return model;
}

feather.list = list("Feather");
feather.static = f.prop({});
feather.calculated = f.prop({
    inheritedProperties: {
        description: "Properties inherited from parent feather",
        type: {
            relation: "FeatherProperty",
            parentOf: "inheritedProperties"
        },
        isReadOnly: true
    }
});

catalog.register("models", "feather", feather);

function featherOverload(data, spec) {
    spec = spec || catalog.getFeather("FeatherOverload");
    spec.properties.type.default = "";
    spec.properties.default.default = "";

    let model;
    let d;

    function propertyNames() {
        let parent = model.parent();
        let names = parent.data.properties().map((prop) => prop.data.name());
        let inherits = parent.data.inherits();
        let inheritFeather;

        if (inherits) {
            inheritFeather = catalog.getFeather(inherits);
            names = names.concat(Object.keys(inheritFeather.properties));
        }

        names.sort();
        names = names.map(function (name) {
            return {
                value: name,
                label: name
            };
        });
        names.unshift({
            value: "",
            label: ""
        });

        return names;
    }

    function handleReadOnly() {
        d.description.isReadOnly(!d.overloadDescription());
        d.alias.isReadOnly(!d.overloadAlias());
        d.type.isReadOnly(!d.overloadType());
        d.default.isReadOnly(!d.overloadDefault());
        d.dataList.isReadOnly(!d.overloadDataList());
    }

    model = f.createModel(data, spec);
    d = model.data;

    model.onChanged("overloadDescription", handleReadOnly);
    model.onChanged("overloadDescription", function () {
        if (!d.overloadDescription()) {
            d.description("");
        }
    });
    model.onChanged("overloadAlias", handleReadOnly);
    model.onChanged("overloadAlias", function () {
        if (!d.overloadAlias()) {
            d.alias("");
        }
    });
    model.onChanged("overloadType", handleReadOnly);
    model.onChanged("overloadType", function () {
        if (!d.overloadType()) {
            d.type("");
        } else {
            d.type({
                type: "relation"
            });
        }
    });
    model.onChanged("overloadDefault", handleReadOnly);
    model.onChanged("overloadDefault", function () {
        if (!d.overloadDefault()) {
            d.default("");
        }
    });
    model.onChanged("overloadDataList", handleReadOnly);
    model.onChanged("overloadDataList", function () {
        if (!d.overloadDataList()) {
            d.dataList("");
        }
    });

    model.onLoad(handleReadOnly);

    model.onValidate(function () {
        let names = propertyNames().map((item) => item.value);

        if (names.indexOf(d.name()) === -1) {
            throw new Error(
                "Property \"" + d.name() +
                "\" referenced in overload does not exist on this feather."
            );
        }

        if (d.overloadType()) {
            if (!d.type().relation) {
                throw new Error(
                    "Feather name required on relation type \"" +
                    d.name() + "\""
                );
            }
        }

        if (d.overloadDescription() && !d.description()) {
            throw new Error("Overload description is empty");
        }

        if (d.overloadAlias() && !d.alias()) {
            throw new Error("Overload alias is empty");
        }
    });

    // ..........................................................
    // PUBLIC
    //

    /**
        Property names datalist.

        __Type:__ `Array`

        __Is Calculated__

        __Read Only__

        @property data.propertyNames
        @for Models.FeatherOverload
        @type Property
    */
    model.addCalculated({
        name: "propertyNames",
        type: "array",
        function: propertyNames
    });

    return model;
}

catalog.registerModel("FeatherOverload", featherOverload);

function featherProperty(data, spec) {
    spec = spec || catalog.getFeather("FeatherProperty");
    spec.properties.type.default = "string";
    spec.properties.default.default = "";

    let model;
    let d;
    let types = [
        "array",
        "boolean",
        "integer",
        "number",
        "string",
        "object"
    ];

    // ..........................................................
    // PUBLIC
    //

    model = f.createModel(data, spec);
    d = model.data;

    function formats() {
        let type = model.data.type();
        let ret = [];
        let fmts = f.formats();

        if (types.indexOf(type)) {
            ret = Object.keys(fmts).filter(
                (key) => fmts[key].type === type
            );
            ret = ret.map(function (key) {
                return {
                    value: key,
                    label: key
                };
            });
        }

        return ret;
    }

    function handleReadOnly() {
        let isNotNumber = d.type() !== "number";
        let pd = model.parent().data;
        let parentHasNaturalKey = (
            pd.properties().some(
                (prop) => prop !== model && prop.data.isNaturalKey()
            ) ||
            pd.inheritedProperties().some((prop) => prop.data.isNaturalKey())
        );
        let parentHasLabelKey = (
            pd.properties().some(
                (prop) => prop !== model && prop.data.isLabelKey()
            ) ||
            pd.inheritedProperties().some((prop) => prop.data.isLabelKey())
        );
        let type = d.type();

        d.scale.isReadOnly(isNotNumber);
        d.precision.isReadOnly(isNotNumber);
        d.min.isReadOnly(isNotNumber);
        d.max.isReadOnly(isNotNumber);
        d.isNaturalKey.isReadOnly(d.isLabelKey() || parentHasNaturalKey);
        d.isIndexed.isReadOnly(d.isNaturalKey());
        d.isLabelKey.isReadOnly(d.isNaturalKey() || parentHasLabelKey);
        d.autonumber.isReadOnly(!d.isNaturalKey());

        if (
            type === "array" ||
            type === "object" ||
            typeof type === "object"
        ) {
            d.default.isReadOnly(true);
        } else {
            d.default.isReadOnly(false);
        }
    }

    /**
        Formats datalist.

        __Type:__ `Array`

        __Is Calculated__

        __Read Only__

        @property data.formats
        @for Models.FeatherProperty
        @type Property
    */
    model.addCalculated({
        name: "formats",
        type: "array",
        function: formats
    });

    model.onChange("name", function (prop) {
        let re = new RegExp(" ", "g");
        let value = prop.newValue().toCamelCase().replace(re, "");

        prop.newValue(value);
    });
    model.onChanged("type", handleReadOnly);
    model.onChanged("type", function () {
        let type = d.type();

        if (type === "number") {
            d.scale(f.SCALE_DEFAULT);
            d.precision(f.PRECISION_DEFAULT);
        } else {
            d.scale(-1);
            d.precision(-1);
            d.min(0);
            d.max(0);
        }

        if (
            type === "array" ||
            type === "object" ||
            typeof type === "object"
        ) {
            d.default("");
        }

        d.format.isReadOnly(formats().length === 0);
        d.format("");
    });
    model.onChanged("isNaturalKey", handleReadOnly);
    model.onChanged("isNaturalKey", function () {
        if (d.isNaturalKey()) {
            d.isIndexed(false);
        }
    });
    model.onChanged("isLabelKey", handleReadOnly);

    model.onLoad(handleReadOnly);

    model.onValidate(function () {
        let type = model.data.type();
        let defaultValue = model.data.default();
        let name = model.data.name();

        if (typeof type !== "string") {
            if (!type.relation) {
                throw new Error(
                    "Feather name required on relation type \"" +
                    name + "\""
                );
            }

            if (
                !type.childOf &&
                !type.parentOf && (
                    !type.properties || !type.properties.length
                )
            ) {
                throw new Error(
                    "One or more properties required on relation type \"" +
                    name + "\""
                );
            }
        }

        switch (type) {
        case "integer":
        case "number":
            if (Number.isNaN(Number(defaultValue))) {
                throw new Error(
                    "Default for \"" + name +
                    "\" must be a number or blank"
                );
            }
            break;
        case "boolean":
            if (
                defaultValue !== null &&
                defaultValue !== "" &&
                defaultValue !== true &&
                defaultValue !== false &&
                defaultValue.toLowerCase() !== "true" &&
                defaultValue.toLowerCase() !== "false"
            ) {
                throw new Error(
                    "Default for \"" + name +
                    "\" must be  \"true\", \"false\" or blank"
                );
            }
            break;
        }
    });

    model.handleReadOnly = handleReadOnly;

    model.data.default.toJSON = function () {
        let type = model.data.type();
        let defaultValue = model.data.default();

        switch (type) {
        case "integer":
        case "number":
            return Number(defaultValue);
        case "boolean":
            if (defaultValue === null || defaultValue === "") {
                return null;
            }

            if (defaultValue === true || defaultValue === false) {
                return defaultValue;
            }

            if (defaultValue.toLowerCase() === "true") {
                return true;
            }

            return false;
        default:
            return defaultValue;
        }
    };

    return model;
}

catalog.registerModel("FeatherProperty", featherProperty);
<commit_msg>Fix inherited from if parent<commit_after>/*
    Framework for building object relational database apps
    Copyright (C) 2019  John Rogelstad

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Affero General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Affero General Public License for more details.

    You should have received a copy of the GNU Affero General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
/*jslint browser*/
/**
    @module Core
*/
import catalog from "./catalog.js";
import createModel from "./model.js";
import list from "./list.js";

const f = window.f;

function feather(data, spec) {
    spec = spec || catalog.getFeather("Feather");
    if (data === undefined) {
        data = {
            authorizations: [{
                role: "everyone",
                canCreate: true,
                canRead: true,
                canUpdate: true,
                canDelete: true
            }]
        };
    }
    let model;
    let inheritedProperties = f.prop([]);
    let re = new RegExp(" ", "g");

    inheritedProperties().canAdd = f.prop(false);

    model = createModel(data, spec);

    function featherList() {
        let feathers = catalog.store().feathers();
        let keys = Object.keys(feathers);

        keys = keys.filter(function (key) {
            return !feathers[key].isSystem;
        }).sort();

        return keys.map(function (key) {
            return {
                value: key,
                label: key
            };
        });
    }

    function handleReadOnly() {
        model.data.name.isReadOnly(true);
        model.data.plural.isReadOnly(true);
        model.data.inherits.isReadOnly(true);
    }

    function handleReadOnlyProps() {
        model.data.properties().forEach((prop) => prop.handleReadOnly());
    }

    function calculateInherited() {
        let parent = model.data.inherits();
        let featherProperty;
        let props = inheritedProperties();

        if (parent) {
            parent = catalog.getFeather(parent);
            featherProperty = catalog.store().models().featherProperty;
            props.length = 0;

            Object.keys(parent.properties).forEach(function (key) {
                let prop = f.copy(parent.properties[key]);
                let instance;

                if (prop.default === undefined) {
                    prop.default = "";
                }
                if (!prop.inheritedFrom) {
                    prop.inheritedFrom = parent;
                }
                prop.name = key;
                instance = featherProperty(prop);
                instance.state().goto("/Ready/Fetched/ReadOnly");
                instance.parent(model);
                props.push(instance);
            });
        }
        handleReadOnlyProps();
    }

    function sanitize(prop) {
        let value = prop.newValue();

        value = value.replace(re, "");
        value = value.slice(0, 1).toUpperCase() + value.slice(1);
        prop.newValue(value);
    }

    /**
        Feathers datalist.

        __Type:__ `Array`

        __Is Calculated__

        __Read Only__

        @property data.feathers
        @for Models.Feather
        @type Property
    */
    model.addCalculated({
        name: "feathers",
        type: "array",
        function: featherList
    });

    /**
        Inherited properties child records.

        __Type:__ `Array`

        __Is Calculated__

        __Read Only__

        @property data.inheritedProperties
        @for Models.Feather
        @type Property
    */
    model.addCalculated({
        name: "inheritedProperties",
        type: {
            relation: "FeatherProperty",
            parentOf: "inheritedProperties"
        },
        isReadOnly: true,
        function: inheritedProperties
    });

    /**
        Modules datalist.

        __Type:__ `Array`

        __Is Calculated__

        __Read Only__

        @property data.modules
        @for Models.Feather
        @type Property
    */
    model.addCalculated({
        name: "modules",
        type: "array",
        function: catalog.store().data().modules
    });

    model.onChange("name", sanitize);
    model.onChanged("name", function (prop) {
        if (prop() && !model.data.plural()) {
            model.data.plural(prop() + "s");
        }
    });
    model.onChange("plural", sanitize);

    model.onChanged("properties", handleReadOnlyProps);
    model.onChanged("properties.isNaturalKey", handleReadOnlyProps);
    model.onChanged("properties.isLabelKey", handleReadOnlyProps);
    model.onChanged("inherits", calculateInherited);
    model.onLoad(calculateInherited);
    model.onLoad(handleReadOnly);

    model.onValidate(function () {
        let authRoles = [];

        model.data.authorizations().forEach(function (auth) {
            let role = auth.data.role();

            if (authRoles.indexOf(role) !== -1) {
                throw new Error(
                    "Role '" + role +
                    "' must only be in one authorization per feather."
                );
            }
            authRoles.push(role);
        });
    });

    return model;
}

feather.list = list("Feather");
feather.static = f.prop({});
feather.calculated = f.prop({
    inheritedProperties: {
        description: "Properties inherited from parent feather",
        type: {
            relation: "FeatherProperty",
            parentOf: "inheritedProperties"
        },
        isReadOnly: true
    }
});

catalog.register("models", "feather", feather);

function featherOverload(data, spec) {
    spec = spec || catalog.getFeather("FeatherOverload");
    spec.properties.type.default = "";
    spec.properties.default.default = "";

    let model;
    let d;

    function propertyNames() {
        let parent = model.parent();
        let names = parent.data.properties().map((prop) => prop.data.name());
        let inherits = parent.data.inherits();
        let inheritFeather;

        if (inherits) {
            inheritFeather = catalog.getFeather(inherits);
            names = names.concat(Object.keys(inheritFeather.properties));
        }

        names.sort();
        names = names.map(function (name) {
            return {
                value: name,
                label: name
            };
        });
        names.unshift({
            value: "",
            label: ""
        });

        return names;
    }

    function handleReadOnly() {
        d.description.isReadOnly(!d.overloadDescription());
        d.alias.isReadOnly(!d.overloadAlias());
        d.type.isReadOnly(!d.overloadType());
        d.default.isReadOnly(!d.overloadDefault());
        d.dataList.isReadOnly(!d.overloadDataList());
    }

    model = f.createModel(data, spec);
    d = model.data;

    model.onChanged("overloadDescription", handleReadOnly);
    model.onChanged("overloadDescription", function () {
        if (!d.overloadDescription()) {
            d.description("");
        }
    });
    model.onChanged("overloadAlias", handleReadOnly);
    model.onChanged("overloadAlias", function () {
        if (!d.overloadAlias()) {
            d.alias("");
        }
    });
    model.onChanged("overloadType", handleReadOnly);
    model.onChanged("overloadType", function () {
        if (!d.overloadType()) {
            d.type("");
        } else {
            d.type({
                type: "relation"
            });
        }
    });
    model.onChanged("overloadDefault", handleReadOnly);
    model.onChanged("overloadDefault", function () {
        if (!d.overloadDefault()) {
            d.default("");
        }
    });
    model.onChanged("overloadDataList", handleReadOnly);
    model.onChanged("overloadDataList", function () {
        if (!d.overloadDataList()) {
            d.dataList("");
        }
    });

    model.onLoad(handleReadOnly);

    model.onValidate(function () {
        let names = propertyNames().map((item) => item.value);

        if (names.indexOf(d.name()) === -1) {
            throw new Error(
                "Property \"" + d.name() +
                "\" referenced in overload does not exist on this feather."
            );
        }

        if (d.overloadType()) {
            if (!d.type().relation) {
                throw new Error(
                    "Feather name required on relation type \"" +
                    d.name() + "\""
                );
            }
        }

        if (d.overloadDescription() && !d.description()) {
            throw new Error("Overload description is empty");
        }

        if (d.overloadAlias() && !d.alias()) {
            throw new Error("Overload alias is empty");
        }
    });

    // ..........................................................
    // PUBLIC
    //

    /**
        Property names datalist.

        __Type:__ `Array`

        __Is Calculated__

        __Read Only__

        @property data.propertyNames
        @for Models.FeatherOverload
        @type Property
    */
    model.addCalculated({
        name: "propertyNames",
        type: "array",
        function: propertyNames
    });

    return model;
}

catalog.registerModel("FeatherOverload", featherOverload);

function featherProperty(data, spec) {
    spec = spec || catalog.getFeather("FeatherProperty");
    spec.properties.type.default = "string";
    spec.properties.default.default = "";

    let model;
    let d;
    let types = [
        "array",
        "boolean",
        "integer",
        "number",
        "string",
        "object"
    ];

    // ..........................................................
    // PUBLIC
    //

    model = f.createModel(data, spec);
    d = model.data;

    function formats() {
        let type = model.data.type();
        let ret = [];
        let fmts = f.formats();

        if (types.indexOf(type)) {
            ret = Object.keys(fmts).filter(
                (key) => fmts[key].type === type
            );
            ret = ret.map(function (key) {
                return {
                    value: key,
                    label: key
                };
            });
        }

        return ret;
    }

    function handleReadOnly() {
        let isNotNumber = d.type() !== "number";
        let pd = model.parent().data;
        let parentHasNaturalKey = (
            pd.properties().some(
                (prop) => prop !== model && prop.data.isNaturalKey()
            ) ||
            pd.inheritedProperties().some((prop) => prop.data.isNaturalKey())
        );
        let parentHasLabelKey = (
            pd.properties().some(
                (prop) => prop !== model && prop.data.isLabelKey()
            ) ||
            pd.inheritedProperties().some((prop) => prop.data.isLabelKey())
        );
        let type = d.type();

        d.scale.isReadOnly(isNotNumber);
        d.precision.isReadOnly(isNotNumber);
        d.min.isReadOnly(isNotNumber);
        d.max.isReadOnly(isNotNumber);
        d.isNaturalKey.isReadOnly(d.isLabelKey() || parentHasNaturalKey);
        d.isIndexed.isReadOnly(d.isNaturalKey());
        d.isLabelKey.isReadOnly(d.isNaturalKey() || parentHasLabelKey);
        d.autonumber.isReadOnly(!d.isNaturalKey());

        if (
            type === "array" ||
            type === "object" ||
            typeof type === "object"
        ) {
            d.default.isReadOnly(true);
        } else {
            d.default.isReadOnly(false);
        }
    }

    /**
        Formats datalist.

        __Type:__ `Array`

        __Is Calculated__

        __Read Only__

        @property data.formats
        @for Models.FeatherProperty
        @type Property
    */
    model.addCalculated({
        name: "formats",
        type: "array",
        function: formats
    });

    model.onChange("name", function (prop) {
        let re = new RegExp(" ", "g");
        let value = prop.newValue().toCamelCase().replace(re, "");

        prop.newValue(value);
    });
    model.onChanged("type", handleReadOnly);
    model.onChanged("type", function () {
        let type = d.type();

        if (type === "number") {
            d.scale(f.SCALE_DEFAULT);
            d.precision(f.PRECISION_DEFAULT);
        } else {
            d.scale(-1);
            d.precision(-1);
            d.min(0);
            d.max(0);
        }

        if (
            type === "array" ||
            type === "object" ||
            typeof type === "object"
        ) {
            d.default("");
        }

        d.format.isReadOnly(formats().length === 0);
        d.format("");
    });
    model.onChanged("isNaturalKey", handleReadOnly);
    model.onChanged("isNaturalKey", function () {
        if (d.isNaturalKey()) {
            d.isIndexed(false);
        }
    });
    model.onChanged("isLabelKey", handleReadOnly);

    model.onLoad(handleReadOnly);

    model.onValidate(function () {
        let type = model.data.type();
        let defaultValue = model.data.default();
        let name = model.data.name();

        if (typeof type !== "string") {
            if (!type.relation) {
                throw new Error(
                    "Feather name required on relation type \"" +
                    name + "\""
                );
            }

            if (
                !type.childOf &&
                !type.parentOf && (
                    !type.properties || !type.properties.length
                )
            ) {
                throw new Error(
                    "One or more properties required on relation type \"" +
                    name + "\""
                );
            }
        }

        switch (type) {
        case "integer":
        case "number":
            if (Number.isNaN(Number(defaultValue))) {
                throw new Error(
                    "Default for \"" + name +
                    "\" must be a number or blank"
                );
            }
            break;
        case "boolean":
            if (
                defaultValue !== null &&
                defaultValue !== "" &&
                defaultValue !== true &&
                defaultValue !== false &&
                defaultValue.toLowerCase() !== "true" &&
                defaultValue.toLowerCase() !== "false"
            ) {
                throw new Error(
                    "Default for \"" + name +
                    "\" must be  \"true\", \"false\" or blank"
                );
            }
            break;
        }
    });

    model.handleReadOnly = handleReadOnly;

    model.data.default.toJSON = function () {
        let type = model.data.type();
        let defaultValue = model.data.default();

        switch (type) {
        case "integer":
        case "number":
            return Number(defaultValue);
        case "boolean":
            if (defaultValue === null || defaultValue === "") {
                return null;
            }

            if (defaultValue === true || defaultValue === false) {
                return defaultValue;
            }

            if (defaultValue.toLowerCase() === "true") {
                return true;
            }

            return false;
        default:
            return defaultValue;
        }
    };

    return model;
}

catalog.registerModel("FeatherProperty", featherProperty);
<|endoftext|>


<commit_before>'use strict';

angular.module('arachne.directives')

    .directive('arEntitySections', function() {
        return {
            isArray: function(value){
                if (angular.isArray(value)) {
                    if (value.length == 1) return false;
                    return true;
                }
                return false;
            },
            scope: { entity: '=' },
            templateUrl: 'app/entity/ar-entity-sections.html'
        }
    });<commit_msg>fixed broken list view in sections<commit_after>'use strict';

angular.module('arachne.directives')

    .directive('arEntitySections', function() {
        return {
            link: function(scope) {
                scope.isArray = function isArray(value){
                if (angular.isArray(value)) {
                    if (value.length === 1) {
                        return false;
                    }
                    return true;
                }
                return false;
            }
            },
            scope: {
                entity: '='
            },
            templateUrl: 'app/entity/ar-entity-sections.html'
        }
    });<|endoftext|>


<commit_before>
// Note: All CmdXXX classes are used from Brain.Player. 'this' is bound to the
// Brain.Player object by using execute.call(this)

const RG = require('./rg');
const Path = require('./path');

const Cmd = {};

const ACTION_ALREADY_DONE = () => {};
const ACTION_ZERO_ENERGY = null;
Cmd.ACTION_ALREADY_DONE = ACTION_ALREADY_DONE;
Cmd.ACTION_ZERO_ENERGY = ACTION_ZERO_ENERGY;

/* Executes one attack against target actor/cell. */
class CmdAttack {

    execute(obj) {
      let actor = obj.target;
      if (actor.hasActors) { // We're dealing with cell
        if (obj.target.hasActors()) {
          actor = obj.target.getSentientActors()[0];
        }
      }
      if (actor) {
        const [pX, pY] = this._actor.getXY();
        const [tX, tY] = actor.getXY();
        const dist = Path.shortestDist(pX, pY, tX, tY);
        const attackRange = this._actor.get('Combat').getAttackRange();
        console.log('CmdAttack dist is ' + dist + ' range: ' + attackRange);
        if (dist <= attackRange) {
            const attackComp = new RG.Component.Attack({target: actor});
            this._actor.add('Attack', attackComp);
            return ACTION_ALREADY_DONE;
        }
        else {
          return this.cmdNotPossible('Target not within attack range');
        }
      }
      return this.cmdNotPossible('No valid targets for attack');
    }

}
Cmd.Attack = CmdAttack;

class CmdMissile {

    execute(obj) {
        const invEq = this._actor.getInvEq();
        let fireRate = 1;
        if (this._actor.has('DoubleShot')) {
            fireRate = 2;
        }

        // Fires one missile comp each firerate increment
        for (let i = 0; i < fireRate; i++) {

            // TODO changes to fire more than 1 missile
            const missile = invEq.unequipAndGetItem('missile', 1);
            if (!RG.isNullOrUndef([missile])) {

                // Check for missile weapon for ammunition
                if (missile.has('Ammo')) {
                    const missWeapon = invEq.getEquipment()
                        .getEquipped('missileweapon');
                    if (missWeapon === null) {
                        const msg = 'No missile weapon equipped.';
                        return this.cmdNotPossible(msg);
                    }
                    else { // Check ammo/weapon compatibility
                        const ammoType = missile.getAmmoType();
                        const weaponType = missWeapon.getWeaponType();
                        if (this._actor.has('MixedShot')) {
                            const re = /bow/;
                            if (!re.test(ammoType) || !re.test(weaponType)) {
                                if (ammoType !== weaponType) {
                                    const msg = 'Ammo/weapon not compatible.';
                                    return this.cmdNotPossible(msg);
                                }
                            }
                        }
                        else if (ammoType !== weaponType) {
                            const msg = 'Ammo/weapon not compatible.';
                            return this.cmdNotPossible(msg);
                        }
                    }
                }

                if (!RG.isNullOrUndef([obj.target])) {
                    const x = obj.target.getX();
                    const y = obj.target.getY();
                    const mComp = new RG.Component.Missile(this._actor);
                    mComp.setTargetXY(x, y);
                    mComp.setDamage(RG.getMissileDamage(this._actor, missile));
                    mComp.setAttack(RG.getMissileAttack(this._actor, missile));
                    mComp.setRange(RG.getMissileRange(this._actor, missile));
                    missile.add('Missile', mComp);
                    this.energy = RG.energy.MISSILE;
                }
                else {
                    RG.err('Brain.Player', 'handleCommand',
                        'No x,y given for missile.');
                }
            }
            else {
                return this.cmdNotPossible('No missile equipped.');
            }
        }

        return ACTION_ALREADY_DONE;
    }

}
Cmd.Missile = CmdMissile;

/* Executed when player uses an item. */
class CmdUseItem {

    execute(obj) {
        if (obj.hasOwnProperty('item')) {
            const item = obj.item;
            let result = false;
            let msg = `You failed to use ${item.getName()}.`;
            if (typeof item.useItem === 'function') {
                this.energy = RG.energy.USE;
                item.useItem({target: obj.target});
                result = true;
            }

            if (obj.hasOwnProperty('callback')) {
                if (result) {
                    msg = `You used ${item.getName()}!`;
                }
                obj.callback({msg: msg, result});
            }
            else if (!result) {
                return this.cmdNotPossible('You cannot use that item.');
            }
            else {
              RG.gameMsg(`You used ${item.getName()}!`);
            }
        }
        else {
            RG.err('Brain.Player', 'handleCommand', 'obj has no item');
        }
        return ACTION_ALREADY_DONE;
    }

}
Cmd.UseItem = CmdUseItem;

/* Command for using an element. */
class CmdUseElement {

    execute(obj) {
        const cell = obj.target;
        const elems = cell.getElements();
        const useComp = new RG.Component.UseElement();
        elems.forEach(elem => {
            if (elem.onUse) {
                console.log('Setting use element to ' + JSON.stringify(elem));
                useComp.setElement(elem);
            }
        });
        this._actor.add(useComp);
        return ACTION_ALREADY_DONE;
    }
}
Cmd.UseElement = CmdUseElement;

class CmdDropItem {

  execute(obj) {
      const invEq = this._actor.getInvEq();
      const actorCell = this._actor.getCell();
      let result = false;
      let msg = `Failed to drop ${obj.item.getName()}`;
      const dropCount = obj.count <= obj.item.count ? obj.count
        : obj.item.count;
      if (actorCell.hasShop()) {
          const shopElem = actorCell.getPropType('shop')[0];
          const price = shopElem.getItemPriceForSelling(obj.item);

          this._wantConfirm = true;
          this._confirmCallback = () => {
              // const sellOk = shopElem.sellItem(obj.item, this._actor);
              const trans = new RG.Component.Transaction();
              trans.setArgs({item: obj.item, seller: this._actor,
                  shop: shopElem, callback: obj.callback,
                  buyer: shopElem.getShopkeeper()});
              this._actor.add(trans);
          };

          msg = `Press y to sell item for ${price} gold coins.`;
          if (obj.hasOwnProperty('callback')) {
              obj.callback({msg: msg, result});
          }
      }
      else if (invEq.dropNItems(obj.item, dropCount)) {
          result = true;
          msg = 'Item dropped!';
      }
      if (obj.hasOwnProperty('callback')) {
          obj.callback({msg: msg, result});
      }
      return ACTION_ALREADY_DONE;
  }

}
Cmd.DropItem = CmdDropItem;

class CmdEquipItem {

    execute(obj) {
        const invEq = this._actor.getInvEq();
        const item = obj.item;
        let result = false;
        let msg = `Failed to equip ${item.getName()}`;
        if (item.getType().match(/^(missile|ammo)$/)) {
            if (invEq.equipNItems(item, obj.count)) {
                result = true;
            }
        }
        else if (invEq.equipItem(item)) {
            result = true;
        }
        if (obj.hasOwnProperty('callback')) {
            if (result) {
                msg = `Equipping ${item.getName()} succeeded!`;
            }
            obj.callback({msg: msg, result});
        }
        return ACTION_ALREADY_DONE;
    }

}
Cmd.EquipItem = CmdEquipItem;

/* Executed when an actor unequips an item. */
class CmdUnequipItem {

    execute(obj) {
        const name = obj.slot;
        const slotNumber = obj.slotNumber;
        const invEq = this._actor.getInvEq();
        let result = false;
        let msg = `Failed to remove item from slot ${name}.`;

        if (name === 'missile') {
            const eqItem = invEq.getEquipment().getItem('missile');

            if (eqItem !== null) {
                if (invEq.unequipItem(name, obj.count)) {
                    result = true;
                }
            }
        }
        else if (invEq.unequipItem(name, 1, slotNumber)) {
            result = true;
        }

        if (obj.hasOwnProperty('callback')) {
            if (result) {
                msg = `Unequipping from ${name} succeeded!`;
            }
            obj.callback({msg: msg, result});
        }
        return ACTION_ALREADY_DONE;
    }

}
Cmd.UnequipItem = CmdUnequipItem;

module.exports = Cmd;

<commit_msg>Gets the attack range by calling RG.getMeleeRange() now.<commit_after>
// Note: All CmdXXX classes are used from Brain.Player. 'this' is bound to the
// Brain.Player object by using execute.call(this)

const RG = require('./rg');
const Path = require('./path');

const Cmd = {};

const ACTION_ALREADY_DONE = () => {};
const ACTION_ZERO_ENERGY = null;
Cmd.ACTION_ALREADY_DONE = ACTION_ALREADY_DONE;
Cmd.ACTION_ZERO_ENERGY = ACTION_ZERO_ENERGY;

/* Executes one attack against target actor/cell. */
class CmdAttack {

    execute(obj) {
      let actor = obj.target;
      if (actor.hasActors) { // We're dealing with cell
        if (obj.target.hasActors()) {
          actor = obj.target.getSentientActors()[0];
        }
      }
      if (actor) {
        const [pX, pY] = this._actor.getXY();
        const [tX, tY] = actor.getXY();
        const dist = Path.shortestDist(pX, pY, tX, tY);
        const attackRange = RG.getMeleeAttackRange(this._actor);
        console.log('CmdAttack dist is ' + dist + ' range: ' + attackRange);
        if (dist <= attackRange) {
            const attackComp = new RG.Component.Attack({target: actor});
            this._actor.add('Attack', attackComp);
            return ACTION_ALREADY_DONE;
        }
        else {
          return this.cmdNotPossible('Target not within attack range');
        }
      }
      return this.cmdNotPossible('No valid targets for attack');
    }

}
Cmd.Attack = CmdAttack;

class CmdMissile {

    execute(obj) {
        const invEq = this._actor.getInvEq();
        let fireRate = 1;
        if (this._actor.has('DoubleShot')) {
            fireRate = 2;
        }

        // Fires one missile comp each firerate increment
        for (let i = 0; i < fireRate; i++) {

            // TODO changes to fire more than 1 missile
            const missile = invEq.unequipAndGetItem('missile', 1);
            if (!RG.isNullOrUndef([missile])) {

                // Check for missile weapon for ammunition
                if (missile.has('Ammo')) {
                    const missWeapon = invEq.getEquipment()
                        .getEquipped('missileweapon');
                    if (missWeapon === null) {
                        const msg = 'No missile weapon equipped.';
                        return this.cmdNotPossible(msg);
                    }
                    else { // Check ammo/weapon compatibility
                        const ammoType = missile.getAmmoType();
                        const weaponType = missWeapon.getWeaponType();
                        if (this._actor.has('MixedShot')) {
                            const re = /bow/;
                            if (!re.test(ammoType) || !re.test(weaponType)) {
                                if (ammoType !== weaponType) {
                                    const msg = 'Ammo/weapon not compatible.';
                                    return this.cmdNotPossible(msg);
                                }
                            }
                        }
                        else if (ammoType !== weaponType) {
                            const msg = 'Ammo/weapon not compatible.';
                            return this.cmdNotPossible(msg);
                        }
                    }
                }

                if (!RG.isNullOrUndef([obj.target])) {
                    const x = obj.target.getX();
                    const y = obj.target.getY();
                    const mComp = new RG.Component.Missile(this._actor);
                    mComp.setTargetXY(x, y);
                    mComp.setDamage(RG.getMissileDamage(this._actor, missile));
                    mComp.setAttack(RG.getMissileAttack(this._actor, missile));
                    mComp.setRange(RG.getMissileRange(this._actor, missile));
                    missile.add('Missile', mComp);
                    this.energy = RG.energy.MISSILE;
                }
                else {
                    RG.err('Brain.Player', 'handleCommand',
                        'No x,y given for missile.');
                }
            }
            else {
                return this.cmdNotPossible('No missile equipped.');
            }
        }

        return ACTION_ALREADY_DONE;
    }

}
Cmd.Missile = CmdMissile;

/* Executed when player uses an item. */
class CmdUseItem {

    execute(obj) {
        if (obj.hasOwnProperty('item')) {
            const item = obj.item;
            let result = false;
            let msg = `You failed to use ${item.getName()}.`;
            if (typeof item.useItem === 'function') {
                this.energy = RG.energy.USE;
                item.useItem({target: obj.target});
                result = true;
            }

            if (obj.hasOwnProperty('callback')) {
                if (result) {
                    msg = `You used ${item.getName()}!`;
                }
                obj.callback({msg: msg, result});
            }
            else if (!result) {
                return this.cmdNotPossible('You cannot use that item.');
            }
            else {
              RG.gameMsg(`You used ${item.getName()}!`);
            }
        }
        else {
            RG.err('Brain.Player', 'handleCommand', 'obj has no item');
        }
        return ACTION_ALREADY_DONE;
    }

}
Cmd.UseItem = CmdUseItem;

/* Command for using an element. */
class CmdUseElement {

    execute(obj) {
        const cell = obj.target;
        const elems = cell.getElements();
        const useComp = new RG.Component.UseElement();
        elems.forEach(elem => {
            if (elem.onUse) {
                console.log('Setting use element to ' + JSON.stringify(elem));
                useComp.setElement(elem);
            }
        });
        this._actor.add(useComp);
        return ACTION_ALREADY_DONE;
    }
}
Cmd.UseElement = CmdUseElement;

class CmdDropItem {

  execute(obj) {
      const invEq = this._actor.getInvEq();
      const actorCell = this._actor.getCell();
      let result = false;
      let msg = `Failed to drop ${obj.item.getName()}`;
      const dropCount = obj.count <= obj.item.count ? obj.count
        : obj.item.count;
      if (actorCell.hasShop()) {
          const shopElem = actorCell.getPropType('shop')[0];
          const price = shopElem.getItemPriceForSelling(obj.item);

          this._wantConfirm = true;
          this._confirmCallback = () => {
              // const sellOk = shopElem.sellItem(obj.item, this._actor);
              const trans = new RG.Component.Transaction();
              trans.setArgs({item: obj.item, seller: this._actor,
                  shop: shopElem, callback: obj.callback,
                  buyer: shopElem.getShopkeeper()});
              this._actor.add(trans);
          };

          msg = `Press y to sell item for ${price} gold coins.`;
          if (obj.hasOwnProperty('callback')) {
              obj.callback({msg: msg, result});
          }
      }
      else if (invEq.dropNItems(obj.item, dropCount)) {
          result = true;
          msg = 'Item dropped!';
      }
      if (obj.hasOwnProperty('callback')) {
          obj.callback({msg: msg, result});
      }
      return ACTION_ALREADY_DONE;
  }

}
Cmd.DropItem = CmdDropItem;

class CmdEquipItem {

    execute(obj) {
        const invEq = this._actor.getInvEq();
        const item = obj.item;
        let result = false;
        let msg = `Failed to equip ${item.getName()}`;
        if (item.getType().match(/^(missile|ammo)$/)) {
            if (invEq.equipNItems(item, obj.count)) {
                result = true;
            }
        }
        else if (invEq.equipItem(item)) {
            result = true;
        }
        if (obj.hasOwnProperty('callback')) {
            if (result) {
                msg = `Equipping ${item.getName()} succeeded!`;
            }
            obj.callback({msg: msg, result});
        }
        return ACTION_ALREADY_DONE;
    }

}
Cmd.EquipItem = CmdEquipItem;

/* Executed when an actor unequips an item. */
class CmdUnequipItem {

    execute(obj) {
        const name = obj.slot;
        const slotNumber = obj.slotNumber;
        const invEq = this._actor.getInvEq();
        let result = false;
        let msg = `Failed to remove item from slot ${name}.`;

        if (name === 'missile') {
            const eqItem = invEq.getEquipment().getItem('missile');

            if (eqItem !== null) {
                if (invEq.unequipItem(name, obj.count)) {
                    result = true;
                }
            }
        }
        else if (invEq.unequipItem(name, 1, slotNumber)) {
            result = true;
        }

        if (obj.hasOwnProperty('callback')) {
            if (result) {
                msg = `Unequipping from ${name} succeeded!`;
            }
            obj.callback({msg: msg, result});
        }
        return ACTION_ALREADY_DONE;
    }

}
Cmd.UnequipItem = CmdUnequipItem;

module.exports = Cmd;

<|endoftext|>