<commit_before>var CardView = function(cardModel, issueMap, checkBoxes, isParentDescriptionEnabled, isComponentEnabled, isTagEnabled, isColorEnabled, isQRCodeEnabled, isBusinessValueEnabled, isEpicEnabled) {
	this.cardModel = cardModel;
	this.issueMap = issueMap;
	this.checkBoxes = checkBoxes;
	this.isParentDescriptionEnabled = isParentDescriptionEnabled;
	this.isComponentEnabled = isComponentEnabled;
	this.isTagEnabled = isTagEnabled;
	this.isColorEnabled = isColorEnabled;
	this.isQRCodeEnabled = isQRCodeEnabled;
	this.isBusinessValueEnabled = isBusinessValueEnabled;
    this.isEpicEnabled = isEpicEnabled;
	this.element = null;
};

CardView.prototype.getElement = function () {
	if (this.element == null) {
		this.element = document.createElement('div');
		this.element.className = "ticket "+(this.isColorEnabled ? "color" : "mono")+" "+this.cardModel.issueType.replace(" ", "_");

		this.addTitle(this.cardModel.issueId, this.cardModel.estimate, this.cardModel.parentIssueId);
		this.addSideBar(this.isQRCodeEnabled, this.cardModel.issueUrl);
		this.addSummary(this.cardModel.summary, this.getCardParentSummary(), this.cardModel.component, this.cardModel.tag, this.cardModel.businessValue, this.cardModel.epic);
	}
	return this.element;
};

CardView.prototype.getCardParentSummary = function() {
	var parentIssue = this.issueMap[this.cardModel.parentIssueId];
	return parentIssue != null ? parentIssue.summary : null;
}

CardView.prototype.addTitle = function (issueId, estimate, parent) {
	var titleElement = document.createElement("div");
	titleElement.className = "titleRow";

	var issueIdElement = this.createSummaryElement(issueId || "Issue Id");
	issueIdElement.className += " key";

	var estimateElement = this.createTitleElement(estimate || "Estimate");
	estimateElement.className += " estimate";

	var actualElement = this.createTitleElement("Actual");
	actualElement.className += " actual";

	var ownerElement = this.createTitleElement("Owner");
	ownerElement.className += " owner";

	titleElement.appendChild(issueIdElement);
	titleElement.appendChild(estimateElement);
	titleElement.appendChild(actualElement);
	titleElement.appendChild(ownerElement);

	this.element.appendChild(titleElement);
};

CardView.prototype.addSummary = function (summary, parentSummary, component, tag, businessvalue, epic) {
	var sideElement = document.createElement("div");
	sideElement.className = "summaryElement";

    if (this.isComponentEnabled && component != null) {
        sideElement.innerHTML += "" + component + "";
    }

    if (this.isEpicEnabled && epic != null) {
        var epicData = epics.filter(function(_) {
            return _.key === epic
        })[0];
        sideElement.innerHTML += "" + epicData.epicLabel + "";
    }

	if (this.isParentDescriptionEnabled && parentSummary != null) {
		sideElement.innerHTML += "" + parentSummary + ""
	}

	sideElement.innerHTML += "" + summary + "";

	if (this.isTagEnabled) {
		var tagElement = document.createElement("div");
		tagElement.className = "tag";
		tagElement.innerHTML = tag;
		this.element.appendChild(tagElement);
	}

	if (this.isBusinessValueEnabled) {
		var tagElement = document.createElement("div");
		tagElement.className = "businessvalue cat" + businessvalue;
		tagElement.innerHTML = "";
		this.element.appendChild(tagElement);
	}

	this.element.appendChild(sideElement);
};

CardView.prototype.addSideBar = function (bAddQRCode, url) {
	var sideElement = document.createElement("div");
	sideElement.className = "sidebar";

	for (var i = 0; i < this.checkBoxes.length; ++i) {
		var checkBoxName = this.checkBoxes[i];
		var element = this.createTitleElement(checkBoxName);
		element.className += " "+checkBoxName.replace(" ", "_");
		sideElement.appendChild(element);
	}

	if (bAddQRCode) {
		var qrcodeElement = this.createTitleElement("QRCode");
		qrcodeElement.className += " qrcode";
		qrcodeElement.innerHTML = '';
		sideElement.appendChild(qrcodeElement);
	}

	this.element.appendChild(sideElement);
};

CardView.prototype.createTitleElement = function (text) {
	var titleElement = document.createElement("span");
	titleElement.className = "titleElement";

	if (typeof text != 'number') {
		var textArray = text.split("\n");
		for (var i = 0; i < textArray.length; i++) {
			titleElement.appendChild(document.createTextNode(textArray[i]));
		}
	} else {
		titleElement.appendChild(document.createTextNode(text));
	}

	return titleElement;
};

CardView.prototype.createSummaryElement = function (text) {
	var multiline;
	var titleElement = document.createElement("span");
	titleElement.className = "titleElement";

	var textArray = text.split("\n");
	for (var i = 0; i < 1; i++) {
		var project = textArray[i].split("-")[0];
		var number = textArray[i].split("-")[1];
		var projectDiv = document.createElement("div");
		projectDiv.className = "project";
		projectDiv.innerHTML = project;
		var numberDiv = document.createElement("div");
		numberDiv.className = "number";
		numberDiv.innerHTML = number;
		titleElement.appendChild(projectDiv);
		titleElement.appendChild(numberDiv);
		titleElement.appendChild(document.createElement("br"));
	}
	multiline = textArray.length > 1;

	if (multiline) {
		titleElement.className += " multiline";
	}

	return titleElement;
};

<commit_msg>Bug: Card printer works when not using XBoard<commit_after>var CardView = function(cardModel, issueMap, checkBoxes, isParentDescriptionEnabled, isComponentEnabled, isTagEnabled, isColorEnabled, isQRCodeEnabled, isBusinessValueEnabled, isEpicEnabled) {
	this.cardModel = cardModel;
	this.issueMap = issueMap;
	this.checkBoxes = checkBoxes;
	this.isParentDescriptionEnabled = isParentDescriptionEnabled;
	this.isComponentEnabled = isComponentEnabled;
	this.isTagEnabled = isTagEnabled;
	this.isColorEnabled = isColorEnabled;
	this.isQRCodeEnabled = isQRCodeEnabled;
	this.isBusinessValueEnabled = isBusinessValueEnabled;
    this.isEpicEnabled = isEpicEnabled;
	this.element = null;
};

CardView.prototype.getElement = function () {
	if (this.element == null) {
		this.element = document.createElement('div');
		this.element.className = "ticket "+(this.isColorEnabled ? "color" : "mono")+" "+this.cardModel.issueType.replace(" ", "_");

		this.addTitle(this.cardModel.issueId, this.cardModel.estimate, this.cardModel.parentIssueId);
		this.addSideBar(this.isQRCodeEnabled, this.cardModel.issueUrl);
		this.addSummary(this.cardModel.summary, this.getCardParentSummary(), this.cardModel.component, this.cardModel.tag, this.cardModel.businessValue, this.cardModel.epic);
	}
	return this.element;
};

CardView.prototype.getCardParentSummary = function() {
	var parentIssue = this.issueMap[this.cardModel.parentIssueId];
	return parentIssue != null ? parentIssue.summary : null;
}

CardView.prototype.addTitle = function (issueId, estimate, parent) {
	var titleElement = document.createElement("div");
	titleElement.className = "titleRow";

	var issueIdElement = this.createSummaryElement(issueId || "Issue Id");
	issueIdElement.className += " key";

	var estimateElement = this.createTitleElement(estimate || "Estimate");
	estimateElement.className += " estimate";

	var actualElement = this.createTitleElement("Actual");
	actualElement.className += " actual";

	var ownerElement = this.createTitleElement("Owner");
	ownerElement.className += " owner";

	titleElement.appendChild(issueIdElement);
	titleElement.appendChild(estimateElement);
	titleElement.appendChild(actualElement);
	titleElement.appendChild(ownerElement);

	this.element.appendChild(titleElement);
};

CardView.prototype.addSummary = function (summary, parentSummary, component, tag, businessvalue, epic) {
	var sideElement = document.createElement("div");
	sideElement.className = "summaryElement";

    if (this.isComponentEnabled && component != null) {
        sideElement.innerHTML += "" + component + "";
    }

    if (this.isEpicEnabled && epic != null && typeof epics !== "undefined") {
        var epicData = epics.filter(function(_) {
            return _.key === epic
        })[0];
        sideElement.innerHTML += "" + epicData.epicLabel + "";
    }

	if (this.isParentDescriptionEnabled && parentSummary != null) {
		sideElement.innerHTML += "" + parentSummary + ""
	}

	sideElement.innerHTML += "" + summary + "";

	if (this.isTagEnabled) {
		var tagElement = document.createElement("div");
		tagElement.className = "tag";
		tagElement.innerHTML = tag;
		this.element.appendChild(tagElement);
	}

	if (this.isBusinessValueEnabled) {
		var tagElement = document.createElement("div");
		tagElement.className = "businessvalue cat" + businessvalue;
		tagElement.innerHTML = "";
		this.element.appendChild(tagElement);
	}

	this.element.appendChild(sideElement);
};

CardView.prototype.addSideBar = function (bAddQRCode, url) {
	var sideElement = document.createElement("div");
	sideElement.className = "sidebar";

	for (var i = 0; i < this.checkBoxes.length; ++i) {
		var checkBoxName = this.checkBoxes[i];
		var element = this.createTitleElement(checkBoxName);
		element.className += " "+checkBoxName.replace(" ", "_");
		sideElement.appendChild(element);
	}

	if (bAddQRCode) {
		var qrcodeElement = this.createTitleElement("QRCode");
		qrcodeElement.className += " qrcode";
		qrcodeElement.innerHTML = '';
		sideElement.appendChild(qrcodeElement);
	}

	this.element.appendChild(sideElement);
};

CardView.prototype.createTitleElement = function (text) {
	var titleElement = document.createElement("span");
	titleElement.className = "titleElement";

	if (typeof text != 'number') {
		var textArray = text.split("\n");
		for (var i = 0; i < textArray.length; i++) {
			titleElement.appendChild(document.createTextNode(textArray[i]));
		}
	} else {
		titleElement.appendChild(document.createTextNode(text));
	}

	return titleElement;
};

CardView.prototype.createSummaryElement = function (text) {
	var multiline;
	var titleElement = document.createElement("span");
	titleElement.className = "titleElement";

	var textArray = text.split("\n");
	for (var i = 0; i < 1; i++) {
		var project = textArray[i].split("-")[0];
		var number = textArray[i].split("-")[1];
		var projectDiv = document.createElement("div");
		projectDiv.className = "project";
		projectDiv.innerHTML = project;
		var numberDiv = document.createElement("div");
		numberDiv.className = "number";
		numberDiv.innerHTML = number;
		titleElement.appendChild(projectDiv);
		titleElement.appendChild(numberDiv);
		titleElement.appendChild(document.createElement("br"));
	}
	multiline = textArray.length > 1;

	if (multiline) {
		titleElement.className += " multiline";
	}

	return titleElement;
};

<|endoftext|>


<commit_before>import analyticsApp from 'app/js/reducers/reducers.js'
import * as actions from 'app/js/actions/actions.js'

describe('reducers', () => {
  it('returns the initial state', () => {
    expect(analyticsApp(undefined, {})).toEqual({ leftHandNavVisible: true })
  })

  it('returns the current state if action type is not defined', () => {
    expect(
      analyticsApp(
        {leftHandNavVisible: true},
        {type: { actions: undefined }}
      )å
    ).toEqual(
      {leftHandNavVisible: true}
    )
  })

  describe('when the left hand nav is open', () => {
    it('closes the left hand nav', () => {
      expect(
        analyticsApp(
          {leftHandNavVisible: true},
          {type: actions.TOGGLE_LEFT_HAND_NAV}
        )
      ).toEqual(
        {leftHandNavVisible: false}
      )
    })
  })

  describe('when the left hand nav is closed', () => {
    it('opens the left hand nav', () => {
      expect(
        analyticsApp(
          {leftHandNavVisible: false},
          {type: actions.TOGGLE_LEFT_HAND_NAV}
        )
      ).toEqual(
        {leftHandNavVisible: true}
      )
    })
  })
})
<commit_msg>[+ES] Er, whoops....<commit_after>import analyticsApp from 'app/js/reducers/reducers.js'
import * as actions from 'app/js/actions/actions.js'

describe('reducers', () => {
  it('returns the initial state', () => {
    expect(analyticsApp(undefined, {})).toEqual({ leftHandNavVisible: true })
  })

  it('returns the current state if action type is not defined', () => {
    expect(
      analyticsApp(
        {leftHandNavVisible: true},
        {type: { actions: undefined }}
      )
    ).toEqual(
      {leftHandNavVisible: true}
    )
  })

  describe('when the left hand nav is open', () => {
    it('closes the left hand nav', () => {
      expect(
        analyticsApp(
          {leftHandNavVisible: true},
          {type: actions.TOGGLE_LEFT_HAND_NAV}
        )
      ).toEqual(
        {leftHandNavVisible: false}
      )
    })
  })

  describe('when the left hand nav is closed', () => {
    it('opens the left hand nav', () => {
      expect(
        analyticsApp(
          {leftHandNavVisible: false},
          {type: actions.TOGGLE_LEFT_HAND_NAV}
        )
      ).toEqual(
        {leftHandNavVisible: true}
      )
    })
  })
})
<|endoftext|>


<commit_before>import Tile from './tile';
import TilePyramid from './tile_pyramid';
import log from './utils/log';
import WorkerBroker from './utils/worker_broker';

export default class TileManager {

    constructor({ scene, view }) {
        this.scene = scene;
        this.view = view;
        this.tiles = {};
        this.pyramid = new TilePyramid();
        this.visible_coords = {};
        this.queued_coords = [];
        this.building_tiles = null;
        this.renderable_tiles = [];
        this.active_styles = [];

        // Provide a hook for this object to be called from worker threads
        this.main_thread_target = ['TileManager', this.scene.id].join('_');
        WorkerBroker.addTarget(this.main_thread_target, this);
    }

    destroy() {
        this.forEachTile(tile => tile.destroy());
        this.tiles = {};
        this.pyramid = null;
        this.visible_coords = {};
        this.queued_coords = [];
        this.scene = null;
        this.view = null;
    }

    keepTile(tile) {
        this.tiles[tile.key] = tile;
        this.pyramid.addTile(tile);
    }

    hasTile(key) {
        return this.tiles[key] !== undefined;
    }

    forgetTile(key) {
        if (this.hasTile(key)) {
            let tile = this.tiles[key];
            this.pyramid.removeTile(tile);
        }

        delete this.tiles[key];
        this.tileBuildStop(key);
    }

    // Remove a single tile
    removeTile(key) {
        log('trace', `tile unload for ${key}`);

        var tile = this.tiles[key];

        if (tile != null) {
            tile.destroy();
        }

        this.forgetTile(tile.key);
        this.scene.requestRedraw();
    }

    // Run a function on each tile
    forEachTile(func) {
        for (let t in this.tiles) {
            func(this.tiles[t]);
        }
    }

    // Remove tiles that pass a filter condition
    removeTiles(filter) {
        let remove_tiles = [];
        for (let t in this.tiles) {
            let tile = this.tiles[t];
            if (filter(tile)) {
                remove_tiles.push(t);
            }
        }
        for (let r=0; r < remove_tiles.length; r++) {
            let key = remove_tiles[r];
            this.removeTile(key);
        }
    }

    updateTilesForView() {
        // Find visible tiles and load new ones
        let prev_coords = Object.keys(this.visible_coords);
        this.visible_coords = {};
        let tile_coords = this.view.findVisibleTileCoordinates();
        for (let c=0; c < tile_coords.length; c++) {
            const coords = tile_coords[c];
            this.queueCoordinate(coords);
            this.visible_coords[coords.key] = coords;
        }

        // Check if visible coords changed
        // TODO: move to a new view manager object
        let new_coords = Object.keys(this.visible_coords);
        let coords_changed = false;
        if (prev_coords.length !== new_coords.length) {
            coords_changed = true;
        }
        else {
            prev_coords.sort();
            new_coords.sort();
            if (!prev_coords.every((c, i) => new_coords[i] === c)) {
                coords_changed = true;
            }
        }

        this.updateTileStates();
    }

    updateTileStates () {
        this.forEachTile(tile => {
            this.updateVisibility(tile);
            tile.update();
        });

        this.loadQueuedCoordinates();
        this.updateProxyTiles();
        this.view.pruneTilesForView();
        this.updateRenderableTiles();
        this.updateActiveStyles();
    }

    updateProxyTiles () {
        if (this.view.zoom_direction === 0) {
            return;
        }

        // Clear previous proxies
        this.forEachTile(tile => tile.setProxyFor(null));

        let proxy = false;
        this.forEachTile(tile => {
            if (this.view.zoom_direction === 1) {
                if (tile.visible && !tile.built && tile.coords.z > 0) {
                    const parent = this.pyramid.getAncestor(tile);
                    if (parent) {
                        parent.setProxyFor(tile);
                        proxy = true;
                    }
                }
            }
            else if (this.view.zoom_direction === -1) {
                if (tile.visible && !tile.built) {
                    const descendants = this.pyramid.getDescendants(tile);
                    for (let i=0; i < descendants.length; i++) {
                        descendants[i].setProxyFor(tile);
                        proxy = true;
                    }
                }
            }
        });

        if (!proxy) {
            this.view.zoom_direction = 0;
        }
    }

    updateVisibility(tile) {
        tile.visible = false;
        if (tile.style_zoom === this.view.tile_zoom) {
            if (this.visible_coords[tile.coords.key]) {
                tile.visible = true;
            }
            else {
                // brute force
                for (let key in this.visible_coords) {
                    if (Tile.isDescendant(tile.coords, this.visible_coords[key])) {
                        tile.visible = true;
                        break;
                    }
                }
            }
        }
    }

    // Remove tiles that aren't visible, and flag remaining visible ones to be updated (for loading, proxy, etc.)
    pruneToVisibleTiles () {
        this.removeTiles(tile => !tile.visible);
    }

    getRenderableTiles () {
        return this.renderable_tiles;
    }

    updateRenderableTiles() {
        this.renderable_tiles = [];
        for (let t in this.tiles) {
            let tile = this.tiles[t];
            if (tile.visible && tile.loaded) {
                this.renderable_tiles.push(tile);
            }
        }
        return this.renderable_tiles;
    }

    getActiveStyles () {
        return this.active_styles;
    }

    updateActiveStyles () {
        let tiles = this.renderable_tiles;
        let active = {};
        for (let t=0; t < tiles.length; t++) {
            let tile = tiles[t];
            Object.keys(tile.meshes).forEach(s => active[s] = true);
        }
        this.active_styles = Object.keys(active);
        return this.active_styles;
    }

    isLoadingVisibleTiles() {
        return Object.keys(this.tiles).some(k => this.tiles[k].visible && !this.tiles[k].built);
    }

    // Queue a tile for load
    queueCoordinate(coords) {
        this.queued_coords[this.queued_coords.length] = coords;
    }

    // Load all queued tiles
    loadQueuedCoordinates() {
        if (this.queued_coords.length === 0) {
            return;
        }

        // Sort queued tiles from center tile
        this.queued_coords.sort((a, b) => {
            let ad = Math.abs(this.view.center.tile.x - a.x) + Math.abs(this.view.center.tile.y - a.y);
            let bd = Math.abs(this.view.center.tile.x - b.x) + Math.abs(this.view.center.tile.y - b.y);
            return (bd > ad ? -1 : (bd === ad ? 0 : 1));
        });
        this.queued_coords.forEach(coords => this.loadCoordinate(coords));
        this.queued_coords = [];
    }

    // Load all tiles to cover a given logical tile coordinate
    loadCoordinate(coords) {
        // Skip if not at current scene zoom
        if (coords.z !== this.view.center.tile.z) {
            return;
        }

        // Determine necessary tiles for each source
        for (let s in this.scene.sources) {
            let source = this.scene.sources[s];
            // Check if data source should build this tile
            if (!source.builds_geometry_tiles || !source.includesTile(coords, this.view.tile_zoom)) {
                continue;
            }

            let key = Tile.key(coords, source, this.view.tile_zoom);
            if (key && !this.hasTile(key)) {
                let tile = Tile.create({
                    source,
                    coords,
                    worker: this.scene.getWorkerForDataSource(source),
                    style_zoom: this.view.styleZoom(coords.z),
                    view: this.view
                });

                this.keepTile(tile);
                this.buildTile(tile);
            }
        }
    }

    // Sort and build a list of tiles
    buildTiles(tiles) {
        Tile.sort(tiles).forEach(tile => this.buildTile(tile));
        this.checkBuildQueue();
    }

    buildTile(tile, options) {
        this.tileBuildStart(tile.key);
        this.updateVisibility(tile);
        tile.update();
        tile.build(this.scene.generation, options);
    }

    // Called on main thread when a web worker completes processing for a single tile (initial load, or rebuild)
    buildTileStylesCompleted({ tile, progress }) {
        // Removed this tile during load?
        if (this.tiles[tile.key] == null) {
            log('trace', `discarded tile ${tile.key} in TileManager.buildTileStylesCompleted because previously removed`);
            Tile.abortBuild(tile);
            this.updateTileStates();
        }
        // Built with an outdated scene configuration?
        else if (tile.generation !== this.scene.generation) {
            log('debug', `discarded tile ${tile.key} in TileManager.buildTileStylesCompleted because built with ` +
                `scene config gen ${tile.generation}, current ${this.scene.generation}`);
            Tile.abortBuild(tile);
            this.updateTileStates();
        }
        else {
            // Update tile with properties from worker
            if (this.tiles[tile.key]) {
                tile = this.tiles[tile.key].merge(tile);
            }

            if (progress.done) {
                tile.built = true;
            }

            tile.buildMeshes(this.scene.styles, progress);
            this.updateTileStates();
            this.scene.requestRedraw();
        }

        if (progress.done) {
            this.tileBuildStop(tile.key);
        }
    }

    // Called on main thread when web worker encounters an error building a tile
    buildTileError(tile) {
        log('error', `Error building tile ${tile.key}:`, tile.error);
        this.forgetTile(tile.key);
        Tile.abortBuild(tile);
    }

    // Track tile build state
    tileBuildStart(key) {
        this.building_tiles = this.building_tiles || {};
        this.building_tiles[key] = true;
        log('trace', `tileBuildStart for ${key}: ${Object.keys(this.building_tiles).length}`);
    }

    tileBuildStop(key) {
        // Done building?
        if (this.building_tiles) {
            log('trace', `tileBuildStop for ${key}: ${Object.keys(this.building_tiles).length}`);
            delete this.building_tiles[key];
            this.checkBuildQueue();
        }
    }

    // Check status of tile building queue and notify scene when we're done
    checkBuildQueue() {
        if (!this.building_tiles || Object.keys(this.building_tiles).length === 0) {
            this.building_tiles = null;
            this.scene.tileManagerBuildDone();
        }
    }

    // Sum of a debug property across tiles
    getDebugSum(prop, filter) {
        var sum = 0;
        for (var t in this.tiles) {
            if (this.tiles[t].debug[prop] != null && (typeof filter !== 'function' || filter(this.tiles[t]) === true)) {
                sum += this.tiles[t].debug[prop];
            }
        }
        return sum;
    }

    // Average of a debug property across tiles
    getDebugAverage(prop, filter) {
        return this.getDebugSum(prop, filter) / Object.keys(this.tiles).length;
    }

}
<commit_msg>remove unused code<commit_after>import Tile from './tile';
import TilePyramid from './tile_pyramid';
import log from './utils/log';
import WorkerBroker from './utils/worker_broker';

export default class TileManager {

    constructor({ scene, view }) {
        this.scene = scene;
        this.view = view;
        this.tiles = {};
        this.pyramid = new TilePyramid();
        this.visible_coords = {};
        this.queued_coords = [];
        this.building_tiles = null;
        this.renderable_tiles = [];
        this.active_styles = [];

        // Provide a hook for this object to be called from worker threads
        this.main_thread_target = ['TileManager', this.scene.id].join('_');
        WorkerBroker.addTarget(this.main_thread_target, this);
    }

    destroy() {
        this.forEachTile(tile => tile.destroy());
        this.tiles = {};
        this.pyramid = null;
        this.visible_coords = {};
        this.queued_coords = [];
        this.scene = null;
        this.view = null;
    }

    keepTile(tile) {
        this.tiles[tile.key] = tile;
        this.pyramid.addTile(tile);
    }

    hasTile(key) {
        return this.tiles[key] !== undefined;
    }

    forgetTile(key) {
        if (this.hasTile(key)) {
            let tile = this.tiles[key];
            this.pyramid.removeTile(tile);
        }

        delete this.tiles[key];
        this.tileBuildStop(key);
    }

    // Remove a single tile
    removeTile(key) {
        log('trace', `tile unload for ${key}`);

        var tile = this.tiles[key];

        if (tile != null) {
            tile.destroy();
        }

        this.forgetTile(tile.key);
        this.scene.requestRedraw();
    }

    // Run a function on each tile
    forEachTile(func) {
        for (let t in this.tiles) {
            func(this.tiles[t]);
        }
    }

    // Remove tiles that pass a filter condition
    removeTiles(filter) {
        let remove_tiles = [];
        for (let t in this.tiles) {
            let tile = this.tiles[t];
            if (filter(tile)) {
                remove_tiles.push(t);
            }
        }
        for (let r=0; r < remove_tiles.length; r++) {
            let key = remove_tiles[r];
            this.removeTile(key);
        }
    }

    updateTilesForView() {
        // Find visible tiles and load new ones
        this.visible_coords = {};
        let tile_coords = this.view.findVisibleTileCoordinates();
        for (let c=0; c < tile_coords.length; c++) {
            const coords = tile_coords[c];
            this.queueCoordinate(coords);
            this.visible_coords[coords.key] = coords;
        }

        this.updateTileStates();
    }

    updateTileStates () {
        this.forEachTile(tile => {
            this.updateVisibility(tile);
            tile.update();
        });

        this.loadQueuedCoordinates();
        this.updateProxyTiles();
        this.view.pruneTilesForView();
        this.updateRenderableTiles();
        this.updateActiveStyles();
    }

    updateProxyTiles () {
        if (this.view.zoom_direction === 0) {
            return;
        }

        // Clear previous proxies
        this.forEachTile(tile => tile.setProxyFor(null));

        let proxy = false;
        this.forEachTile(tile => {
            if (this.view.zoom_direction === 1) {
                if (tile.visible && !tile.built && tile.coords.z > 0) {
                    const parent = this.pyramid.getAncestor(tile);
                    if (parent) {
                        parent.setProxyFor(tile);
                        proxy = true;
                    }
                }
            }
            else if (this.view.zoom_direction === -1) {
                if (tile.visible && !tile.built) {
                    const descendants = this.pyramid.getDescendants(tile);
                    for (let i=0; i < descendants.length; i++) {
                        descendants[i].setProxyFor(tile);
                        proxy = true;
                    }
                }
            }
        });

        if (!proxy) {
            this.view.zoom_direction = 0;
        }
    }

    updateVisibility(tile) {
        tile.visible = false;
        if (tile.style_zoom === this.view.tile_zoom) {
            if (this.visible_coords[tile.coords.key]) {
                tile.visible = true;
            }
            else {
                // brute force
                for (let key in this.visible_coords) {
                    if (Tile.isDescendant(tile.coords, this.visible_coords[key])) {
                        tile.visible = true;
                        break;
                    }
                }
            }
        }
    }

    // Remove tiles that aren't visible, and flag remaining visible ones to be updated (for loading, proxy, etc.)
    pruneToVisibleTiles () {
        this.removeTiles(tile => !tile.visible);
    }

    getRenderableTiles () {
        return this.renderable_tiles;
    }

    updateRenderableTiles() {
        this.renderable_tiles = [];
        for (let t in this.tiles) {
            let tile = this.tiles[t];
            if (tile.visible && tile.loaded) {
                this.renderable_tiles.push(tile);
            }
        }
        return this.renderable_tiles;
    }

    getActiveStyles () {
        return this.active_styles;
    }

    updateActiveStyles () {
        let tiles = this.renderable_tiles;
        let active = {};
        for (let t=0; t < tiles.length; t++) {
            let tile = tiles[t];
            Object.keys(tile.meshes).forEach(s => active[s] = true);
        }
        this.active_styles = Object.keys(active);
        return this.active_styles;
    }

    isLoadingVisibleTiles() {
        return Object.keys(this.tiles).some(k => this.tiles[k].visible && !this.tiles[k].built);
    }

    // Queue a tile for load
    queueCoordinate(coords) {
        this.queued_coords[this.queued_coords.length] = coords;
    }

    // Load all queued tiles
    loadQueuedCoordinates() {
        if (this.queued_coords.length === 0) {
            return;
        }

        // Sort queued tiles from center tile
        this.queued_coords.sort((a, b) => {
            let ad = Math.abs(this.view.center.tile.x - a.x) + Math.abs(this.view.center.tile.y - a.y);
            let bd = Math.abs(this.view.center.tile.x - b.x) + Math.abs(this.view.center.tile.y - b.y);
            return (bd > ad ? -1 : (bd === ad ? 0 : 1));
        });
        this.queued_coords.forEach(coords => this.loadCoordinate(coords));
        this.queued_coords = [];
    }

    // Load all tiles to cover a given logical tile coordinate
    loadCoordinate(coords) {
        // Skip if not at current scene zoom
        if (coords.z !== this.view.center.tile.z) {
            return;
        }

        // Determine necessary tiles for each source
        for (let s in this.scene.sources) {
            let source = this.scene.sources[s];
            // Check if data source should build this tile
            if (!source.builds_geometry_tiles || !source.includesTile(coords, this.view.tile_zoom)) {
                continue;
            }

            let key = Tile.key(coords, source, this.view.tile_zoom);
            if (key && !this.hasTile(key)) {
                let tile = Tile.create({
                    source,
                    coords,
                    worker: this.scene.getWorkerForDataSource(source),
                    style_zoom: this.view.styleZoom(coords.z),
                    view: this.view
                });

                this.keepTile(tile);
                this.buildTile(tile);
            }
        }
    }

    // Sort and build a list of tiles
    buildTiles(tiles) {
        Tile.sort(tiles).forEach(tile => this.buildTile(tile));
        this.checkBuildQueue();
    }

    buildTile(tile, options) {
        this.tileBuildStart(tile.key);
        this.updateVisibility(tile);
        tile.update();
        tile.build(this.scene.generation, options);
    }

    // Called on main thread when a web worker completes processing for a single tile (initial load, or rebuild)
    buildTileStylesCompleted({ tile, progress }) {
        // Removed this tile during load?
        if (this.tiles[tile.key] == null) {
            log('trace', `discarded tile ${tile.key} in TileManager.buildTileStylesCompleted because previously removed`);
            Tile.abortBuild(tile);
            this.updateTileStates();
        }
        // Built with an outdated scene configuration?
        else if (tile.generation !== this.scene.generation) {
            log('debug', `discarded tile ${tile.key} in TileManager.buildTileStylesCompleted because built with ` +
                `scene config gen ${tile.generation}, current ${this.scene.generation}`);
            Tile.abortBuild(tile);
            this.updateTileStates();
        }
        else {
            // Update tile with properties from worker
            if (this.tiles[tile.key]) {
                tile = this.tiles[tile.key].merge(tile);
            }

            if (progress.done) {
                tile.built = true;
            }

            tile.buildMeshes(this.scene.styles, progress);
            this.updateTileStates();
            this.scene.requestRedraw();
        }

        if (progress.done) {
            this.tileBuildStop(tile.key);
        }
    }

    // Called on main thread when web worker encounters an error building a tile
    buildTileError(tile) {
        log('error', `Error building tile ${tile.key}:`, tile.error);
        this.forgetTile(tile.key);
        Tile.abortBuild(tile);
    }

    // Track tile build state
    tileBuildStart(key) {
        this.building_tiles = this.building_tiles || {};
        this.building_tiles[key] = true;
        log('trace', `tileBuildStart for ${key}: ${Object.keys(this.building_tiles).length}`);
    }

    tileBuildStop(key) {
        // Done building?
        if (this.building_tiles) {
            log('trace', `tileBuildStop for ${key}: ${Object.keys(this.building_tiles).length}`);
            delete this.building_tiles[key];
            this.checkBuildQueue();
        }
    }

    // Check status of tile building queue and notify scene when we're done
    checkBuildQueue() {
        if (!this.building_tiles || Object.keys(this.building_tiles).length === 0) {
            this.building_tiles = null;
            this.scene.tileManagerBuildDone();
        }
    }

    // Sum of a debug property across tiles
    getDebugSum(prop, filter) {
        var sum = 0;
        for (var t in this.tiles) {
            if (this.tiles[t].debug[prop] != null && (typeof filter !== 'function' || filter(this.tiles[t]) === true)) {
                sum += this.tiles[t].debug[prop];
            }
        }
        return sum;
    }

    // Average of a debug property across tiles
    getDebugAverage(prop, filter) {
        return this.getDebugSum(prop, filter) / Object.keys(this.tiles).length;
    }

}
<|endoftext|>


<commit_before>/**
 * @fileoverview Pops jobs from the to process queues,
 *               essentialy implementing an atomic shift.
 */

var util = require('util');
var when = require('when');
var log = require('logg').getLogger('kickq.model.PopModel');

var Model = require('./model');
var kconfig = require('../utility/config');
var kError = require('../utility/kerror');
var JobModel = require('./job.model');
var states = require('./states');
var kRedis = require('../utility/kredis');

/**
 * Fetching jobs from the db.
 *
 * @constructor
 * @extends {Kickq.Model}
 */
var PopModel = module.exports = function() {
  log.finer('Ctor() :: Init');
  Model.call(this);

  // fetch is blocking the connection, request a brand new one
  this.clientBlocking = kRedis.client(true);

  /** @type {boolean} if instance has been disposed */
  this._disposed = false;

};
util.inherits(PopModel, Model);

/**
 * Perform fetch operation.
 *
 * @param {Array.<string>} jobNames Array of job names.
 * @return {when.Promise} a promise.
 */
PopModel.prototype.fetch = function(jobNames) {
  log.finest('fetch() :: Init. jobNames: ' + util.inspect(jobNames));

  var def = when.defer();

  if (!Array.isArray(jobNames)) {
    throw new TypeError('argument not an Array');
  }

  var blpopArgs = [];
  jobNames.forEach(function(jobName){
    blpopArgs.push(this.NS + ':queue:' + jobName);
  }.bind(this));

  // set the timeout
  blpopArgs.push(kconfig.get('fetchTimeout'));

  // set the callback
  blpopArgs.push(this._onResponse.bind(this, def.resolver));
  // go
  this.clientBlocking.blpop.apply(this.clientBlocking, blpopArgs);

  return def.promise;
};

/**
 * blpop response.
 *
 * @param {when.Resolver} resolver the promise resolver.
 * @param {?string} err Error message.
 * @param {?Array} response response from db.
 * @private
 */
PopModel.prototype._onResponse = function(resolver, err, response) {
  log.finer('_onResponse() :: Init. err: ', err);
  if (this._disposed) {
    return;
  }
  if (err) {
    log.db('_onResponse() :: "blpop" Failed! err: ', err);
    resolver.reject(new kError.Database(err));
    return;
  }

  if (!Array.isArray(response)) {
    resolver.reject(new kError.Timeout('fetch timeout exceeded'));
    return;
  }

  var jobId = response[1];
  var jobModel = new JobModel(jobId);

  jobModel.fetch()
    .then(this._onJobItemFetch.bind(this, resolver), resolver.reject)
    .then(jobModel.dispose.bind(jobModel), resolver.reject);
};

/**
 * When jobItem has been fetched, update state and resolve.
 *
 * @param {when.Resolver} resolver the promise resolver.
 * @param {Kickq.JobItem} jobItem  the job item
 * @private
 */
PopModel.prototype._onJobItemFetch = function(resolver, jobItem) {
  log.fine('_onJobItemFetch() :: Init. jobId, state, Queue: ', jobItem.id,
    jobItem.state, jobItem.name);

  jobItem.setState(states.Job.PROCESSING).then(function(){
    resolver.resolve(jobItem);
  }, resolver.reject);
};

/**
 * Dispose current instance, references, timeouts, everything.
 *
 * Instance becomes unusable after this method is invoked.
 */
PopModel.prototype.dispose = function() {
  // cut the oxygen
  this.fetch = function(){};

  this.clientBlocking.end();
  this.clientBlocking = null;
  this._disposed = true;
};
<commit_msg>update popjob model<commit_after>/**
 * @fileoverview Pops jobs from the to process queues,
 *               essentialy implementing an atomic shift.
 */
var util = require('util');

var Promise = require('method');
var log = require('logg').getLogger('kickq.model.PopModel');

var Model = require('./model');
var kconfig = require('../utility/config');
var kError = require('../utility/kerror');
var JobModel = require('./job.model');
var states = require('./states');
var kRedis = require('../utility/kredis');

/**
 * Fetching jobs from the db.
 *
 * @constructor
 * @extends {Kickq.Model}
 */
var PopModel = module.exports = Model.extend(function() {
  log.finer('Ctor() :: Init');

  // fetch is blocking the connection, request a brand new one
  this.clientBlocking = kRedis.client(true);

  /** @type {boolean} if instance has been disposed */
  this._disposed = false;
});

/**
 * Perform fetch operation.
 *
 * @param {Array.<string>} jobNames Array of job names.
 * @return {Promise} a promise.
 */
PopModel.prototype.fetch = Promise.method(function(jobNames) {
  log.finest('fetch() :: Init. jobNames: ' + util.inspect(jobNames));

  if (!Array.isArray(jobNames)) {
    throw new TypeError('argument not an Array');
  }

  var blpopArgs = [];
  jobNames.forEach(function(jobName){
    blpopArgs.push(this.NS + ':queue:' + jobName);
  }, this);

  // set the timeout
  blpopArgs.push(kconfig.get('fetchTimeout'));

  var self = this;
  return new Promise(function(resolve, reject) {
    // set the callback
    blpopArgs.push(self._onResponse.bind(self, resolve, reject));

    // go
    self.clientBlocking.blpop.apply(self.clientBlocking, blpopArgs);
  });

});

/**
 * blpop response.
 *
 * @param {Function} resolve the promise resolve.
 * @param {Function} reject the promise reject.
 * @param {?string} err Error message.
 * @param {?Array} response response from db.
 * @private
 */
PopModel.prototype._onResponse = function(resolve, reject, err, response) {
  log.finer('_onResponse() :: Init. err: ', err);
  if (this._disposed) {
    return;
  }
  if (err) {
    log.db('_onResponse() :: "blpop" Failed! err: ', err);
    reject(new kError.Database(err));
    return;
  }

  if (!Array.isArray(response)) {
    reject(new kError.Timeout('fetch timeout exceeded'));
    return;
  }

  var jobId = response[1];
  var jobModel = new JobModel(jobId);

  jobModel.fetch()
    .then(this._onJobItemFetch.bind(this, resolve, reject))
    .then(jobModel.dispose.bind(jobModel))
    .catch(reject);
};

/**
 * When jobItem has been fetched, update state and resolve.
 *
 * @param {Function} resolve the promise resolve.
 * @param {Function} reject the promise reject.
 * @param {Kickq.JobItem} jobItem  the job item
 * @private
 */
PopModel.prototype._onJobItemFetch = function(resolve, reject, jobItem) {
  log.fine('_onJobItemFetch() :: Init. jobId, state, Queue: ', jobItem.id,
    jobItem.state, jobItem.name);

  jobItem.setState(states.Job.PROCESSING)
    .return(jobItem)
    .then(resolve)
    .catch(reject);
};

/**
 * Dispose current instance, references, timeouts, everything.
 *
 * Instance becomes unusable after this method is invoked.
 */
PopModel.prototype.dispose = function() {
  // cut the oxygen
  this.fetch = function(){};

  this.clientBlocking.end();
  this.clientBlocking = null;
  this._disposed = true;
};
<|endoftext|>


<commit_before>/*!
 * ${copyright}
 */

// Provides control sap.m.Panel.
sap.ui.define(['jquery.sap.global', './library', 'sap/ui/core/Control', 'sap/ui/core/IconPool'],
	function(jQuery, library, Control, IconPool) {
	"use strict";

	/**
	 * Constructor for a new Panel.
	 *
	 * @param {string} [sId] id for the new control, generated automatically if no id is given
	 * @param {object} [mSettings] initial settings for the new control
	 *
	 * @class
	 * The Panel control is a container for controls which has a header and content.
	 * The header is always visible while the content can be collapsed if the Panel is expandable.
	 * @extends sap.ui.core.Control
	 *
	 * @author SAP SE
	 * @version ${version}
	 *
	 * @constructor
	 * @public
	 * @since 1.16
	 * @alias sap.m.Panel
	 * @ui5-metamodel This control/element also will be described in the UI5 (legacy) designtime metamodel
	 */
	var Panel = Control.extend("sap.m.Panel", /** @lends sap.m.Panel.prototype */ { metadata: {
		library: "sap.m",
		properties: {

			/**
			 * This property is used to set the header text of the Panel.
			 * The "headerText" is visible in both expanded and collapsed state.
			 * Note: This property is overwritten by the "headerToolbar" aggregation.
			 */
			headerText: {type: "string", group: "Data", defaultValue: ""},

			/**
			 * Determines the Panel width.
			 */
			width: {type: "sap.ui.core.CSSSize", group: "Appearance", defaultValue: "100%"},

			/**
			 * Determines the Panel height.
			 */
			height: {type: "sap.ui.core.CSSSize", group: "Appearance", defaultValue: "auto"},

			/**
			 * Specifies whether the control is expandable.
			 * This allows for collapsing or expanding the infoToolbar (if available) and content of the Panel.
			 * Note: If expandable is set to false, the Panel will always be rendered expanded.
			 * @since 1.22
			 */
			expandable: {type: "boolean", group: "Appearance", defaultValue: false},

			/**
			 * Indicates whether the Panel is expanded or not.
			 * If expanded is set to true, then both the infoToolbar (if available) and the content are rendered.
			 * If expanded is set to false, then only the headerText or headerToolbar is rendered.
			 * @since 1.22
			 */
			expanded: {type: "boolean", group: "Appearance", defaultValue: false},

			/**
			 * Indicates whether the transition between the expanded and the collapsed state of the control is animated.
			 * By default the animation is enabled.
			 * @since 1.26
			 */
			expandAnimation: {type: "boolean", group: "Behavior", defaultValue: true},

			/**
			 * This property is used to set the background color of the Panel.
			 * Depending on the theme you can change the state of the background from "Solid" over "Translucent" to "Transparent".
			 * @since 1.30
			 */
			backgroundDesign: {type: "sap.m.BackgroundDesign", group: "Appearance", defaultValue: sap.m.BackgroundDesign.Translucent}
		},
		defaultAggregation: "content",
		aggregations: {

			/**
			 * Determines the content of the Panel.
			 * The content will be visible only when the Panel is expanded.
			 */
			content: {type: "sap.ui.core.Control", multiple: true, singularName: "content"},

			/**
			 * This aggregation allows the use of a custom Toolbar as header for the Panel.
			 * The "headerToolbar" is visible in both expanded and collapsed state.
			 * Use it when you want to add extra controls for user interactions in the header.
			 * Note: This aggregation overwrites "headerText" property.
			 * @since 1.16
			 */
			headerToolbar: {type: "sap.m.Toolbar", multiple: false},

			/**
			 * This aggregation allows the use of a custom Toolbar as information bar for the Panel.
			 * The "infoToolbar" is placed below the header and is visible only in expanded state.
			 * Use it when you want to show extra information to the user.
			 * @since 1.16
			 */
			infoToolbar: {type: "sap.m.Toolbar", multiple: false}
		},
		events: {

			/**
			 * Indicates that the panel will expand or collapse
			 * @since 1.22
			 */
			expand: {
				parameters: {

					/**
					 * If the panel will expand, this is true.
					 * If the panel will collapse, this is false.
					 */
					expand: {type : "boolean"}
				}
			}
		}
	}});

	Panel.prototype.init = function () {
		this.data("sap-ui-fastnavgroup", "true", true); // Define group for F6 handling
	};

	/**
	 * Sets the width of the panel.
	 * @param {sap.ui.core.CSSSize} sWidth The width of the Panel as CSS size.
	 * @returns {sap.m.Panel} Pointer to the control instance to allow method chaining.
	 * @public
	 */
	Panel.prototype.setWidth = function (sWidth) {
		this.setProperty("width", sWidth, true);

		var oDomRef = this.getDomRef();
		if (oDomRef) {
			oDomRef.style.width = sWidth;
		}

		return this;
	};

	/**
	 * Sets the height of the panel.
	 * @param {sap.ui.core.CSSSize} sHeight The height of the panel as CSS size.
	 * @returns {sap.m.Panel} Pointer to the control instance to allow method chaining.
	 * @public
	 */
	Panel.prototype.setHeight = function (sHeight) {
		this.setProperty("height", sHeight, true);

		var oDomRef = this.getDomRef();
		if (oDomRef) {
			oDomRef.style.height = sHeight;
			this._setContentHeight();
		}

		return this;
	};

	/**
	 * Sets the expandable property of the control.
	 * @param {boolean} bExpandable Defines whether the control is expandable or not.
	 * @returns {sap.m.Panel} Pointer to the control instance to allow method chaining.
	 * @public
	 */
	Panel.prototype.setExpandable = function (bExpandable) {
		this.setProperty("expandable", bExpandable, false); // rerender since we set certain css classes

		if (bExpandable && !this.oIconCollapsed) {
			this.oIconCollapsed = this._createIcon();
		}

		return this;
	};

	/**
	 * Sets the expanded property of the control.
	 * @param {boolean} bExpanded Defines whether control is expanded or not.
	 * @returns {sap.m.Panel} Pointer to the control instance to allow method chaining.
	 * @public
	 */
	Panel.prototype.setExpanded = function (bExpanded) {
		if (bExpanded === this.getExpanded()) {
			return this;
		}

		this.setProperty("expanded", bExpanded, true);

		if (!this.getExpandable()) {
			return this;
		}

		// ARIA
		this._getIcon().$().attr("aria-expanded", bExpanded.toString());

		this._toggleExpandCollapse();
		this._toggleCssClasses();
		this.fireExpand({ expand : bExpanded });

		return this;
	};

	Panel.prototype.onBeforeRendering = function () {
		this._updateIconAriaLabelledBy();
	};

	Panel.prototype.onAfterRendering = function () {
		var $this = this.$(),
			$icon;

		this._setContentHeight();

		if (this.getExpandable()) {
			$icon = this.oIconCollapsed.$();
			if (this.getExpanded()) {
				//ARIA
				$icon.attr("aria-expanded", "true");
			} else {
				// hide those parts which are collapsible (w/o animation, otherwise initial loading doesn't look good ...)
				$this.children(".sapMPanelExpandablePart").hide();
				//ARIA
				$icon.attr("aria-expanded", "false");
			}
		}
	};

	Panel.prototype.exit = function () {
		if (this.oIconCollapsed) {
			this.oIconCollapsed.destroy();
			this.oIconCollapsed = null;
		}
	};

	Panel.prototype._createIcon = function () {
		var that = this,
			sCollapsedIconURI = IconPool.getIconURI("navigation-right-arrow");

		return IconPool.createControlByURI({
			id: that.getId() + "-CollapsedImg",
			src: sCollapsedIconURI,
			decorative: false,
			useIconTooltip: false,
			press: function () {
				that.setExpanded(!that.getExpanded());
			}
		}).addStyleClass("sapMPanelExpandableIcon");
	};

	Panel.prototype._getIcon = function () {
		return this.oIconCollapsed;
	};

	Panel.prototype._setContentHeight = function () {
		if (this.getHeight() === "auto") {
			return;
		}

		var thisDomRef = this.getDomRef();

		// 'offsetTop' measures the vertical space occupied by siblings before this one
		// Earlier each previous sibling's height was calculated separately and then all height values were summed up
		var iOffsetTop = thisDomRef.querySelector(".sapMPanelContent").offsetTop;
		var iAdjustedContentHeight = thisDomRef.clientHeight - iOffsetTop;

		thisDomRef.querySelector(".sapMPanelContent")
			.style.height = iAdjustedContentHeight + 'px';
	};

	Panel.prototype._toggleExpandCollapse = function () {
		var oOptions = {};
		if (!this.getExpandAnimation()) {
			oOptions.duration = 0;
		}

		this.$().children(".sapMPanelExpandablePart").slideToggle(oOptions);
	};

	Panel.prototype._toggleCssClasses = function () {
		var $this = this.$();

		// for controlling the visibility of the border
		$this.children(".sapMPanelWrappingDiv").toggleClass("sapMPanelWrappingDivExpanded");
		$this.children(".sapMPanelWrappingDivTb").toggleClass("sapMPanelWrappingDivTbExpanded");
		$this.find(".sapMPanelExpandableIcon").first().toggleClass("sapMPanelExpandableIconExpanded");
	};

	Panel.prototype._updateIconAriaLabelledBy = function () {
		var sLabelId, aAriaLabels;

		if (!this.oIconCollapsed) {
			return;
		}

		sLabelId = this._getLabellingElementId();
		aAriaLabels = this.oIconCollapsed.getAriaLabelledBy();

		// If the old label is different we should reinitialize the association, because we can have only one label
		if (aAriaLabels.indexOf(sLabelId) === -1) {
			this.oIconCollapsed.removeAllAssociation("ariaLabelledBy");
			this.oIconCollapsed.addAriaLabelledBy(sLabelId);
		}
	};

	Panel.prototype._getLabellingElementId = function () {
		var headerToolbar = this.getHeaderToolbar(),
			id;

		if (headerToolbar) {
			id = headerToolbar.getTitleId();
		} else {
			id = this.getId() + "-header";
		}

		return id;
	};

	return Panel;

}, /* bExport= */ true);
<commit_msg>[FIX] sap.m.Panel - Fix for height property<commit_after>/*!
 * ${copyright}
 */

// Provides control sap.m.Panel.
sap.ui.define(['jquery.sap.global', './library', 'sap/ui/core/Control', 'sap/ui/core/IconPool'],
	function(jQuery, library, Control, IconPool) {
	"use strict";

	/**
	 * Constructor for a new Panel.
	 *
	 * @param {string} [sId] id for the new control, generated automatically if no id is given
	 * @param {object} [mSettings] initial settings for the new control
	 *
	 * @class
	 * The Panel control is a container for controls which has a header and content.
	 * The header is always visible while the content can be collapsed if the Panel is expandable.
	 * @extends sap.ui.core.Control
	 *
	 * @author SAP SE
	 * @version ${version}
	 *
	 * @constructor
	 * @public
	 * @since 1.16
	 * @alias sap.m.Panel
	 * @ui5-metamodel This control/element also will be described in the UI5 (legacy) designtime metamodel
	 */
	var Panel = Control.extend("sap.m.Panel", /** @lends sap.m.Panel.prototype */ { metadata: {
		library: "sap.m",
		properties: {

			/**
			 * This property is used to set the header text of the Panel.
			 * The "headerText" is visible in both expanded and collapsed state.
			 * Note: This property is overwritten by the "headerToolbar" aggregation.
			 */
			headerText: {type: "string", group: "Data", defaultValue: ""},

			/**
			 * Determines the Panel width.
			 */
			width: {type: "sap.ui.core.CSSSize", group: "Appearance", defaultValue: "100%"},

			/**
			 * Determines the Panel height.
			 */
			height: {type: "sap.ui.core.CSSSize", group: "Appearance", defaultValue: "auto"},

			/**
			 * Specifies whether the control is expandable.
			 * This allows for collapsing or expanding the infoToolbar (if available) and content of the Panel.
			 * Note: If expandable is set to false, the Panel will always be rendered expanded.
			 * @since 1.22
			 */
			expandable: {type: "boolean", group: "Appearance", defaultValue: false},

			/**
			 * Indicates whether the Panel is expanded or not.
			 * If expanded is set to true, then both the infoToolbar (if available) and the content are rendered.
			 * If expanded is set to false, then only the headerText or headerToolbar is rendered.
			 * @since 1.22
			 */
			expanded: {type: "boolean", group: "Appearance", defaultValue: false},

			/**
			 * Indicates whether the transition between the expanded and the collapsed state of the control is animated.
			 * By default the animation is enabled.
			 * @since 1.26
			 */
			expandAnimation: {type: "boolean", group: "Behavior", defaultValue: true},

			/**
			 * This property is used to set the background color of the Panel.
			 * Depending on the theme you can change the state of the background from "Solid" over "Translucent" to "Transparent".
			 * @since 1.30
			 */
			backgroundDesign: {type: "sap.m.BackgroundDesign", group: "Appearance", defaultValue: sap.m.BackgroundDesign.Translucent}
		},
		defaultAggregation: "content",
		aggregations: {

			/**
			 * Determines the content of the Panel.
			 * The content will be visible only when the Panel is expanded.
			 */
			content: {type: "sap.ui.core.Control", multiple: true, singularName: "content"},

			/**
			 * This aggregation allows the use of a custom Toolbar as header for the Panel.
			 * The "headerToolbar" is visible in both expanded and collapsed state.
			 * Use it when you want to add extra controls for user interactions in the header.
			 * Note: This aggregation overwrites "headerText" property.
			 * @since 1.16
			 */
			headerToolbar: {type: "sap.m.Toolbar", multiple: false},

			/**
			 * This aggregation allows the use of a custom Toolbar as information bar for the Panel.
			 * The "infoToolbar" is placed below the header and is visible only in expanded state.
			 * Use it when you want to show extra information to the user.
			 * @since 1.16
			 */
			infoToolbar: {type: "sap.m.Toolbar", multiple: false}
		},
		events: {

			/**
			 * Indicates that the panel will expand or collapse
			 * @since 1.22
			 */
			expand: {
				parameters: {

					/**
					 * If the panel will expand, this is true.
					 * If the panel will collapse, this is false.
					 */
					expand: {type : "boolean"}
				}
			}
		}
	}});

	Panel.prototype.init = function () {
		this.data("sap-ui-fastnavgroup", "true", true); // Define group for F6 handling
	};

	/**
	 * Sets the width of the panel.
	 * @param {sap.ui.core.CSSSize} sWidth The width of the Panel as CSS size.
	 * @returns {sap.m.Panel} Pointer to the control instance to allow method chaining.
	 * @public
	 */
	Panel.prototype.setWidth = function (sWidth) {
		this.setProperty("width", sWidth, true);

		var oDomRef = this.getDomRef();
		if (oDomRef) {
			oDomRef.style.width = sWidth;
		}

		return this;
	};

	/**
	 * Sets the height of the panel.
	 * @param {sap.ui.core.CSSSize} sHeight The height of the panel as CSS size.
	 * @returns {sap.m.Panel} Pointer to the control instance to allow method chaining.
	 * @public
	 */
	Panel.prototype.setHeight = function (sHeight) {
		this.setProperty("height", sHeight, true);

		var oDomRef = this.getDomRef();
		if (oDomRef) {
			oDomRef.style.height = sHeight;
			oDomRef.querySelector(".sapMPanelContent").style.height = sHeight;
			this._setContentHeight();
		}

		return this;
	};

	/**
	 * Sets the expandable property of the control.
	 * @param {boolean} bExpandable Defines whether the control is expandable or not.
	 * @returns {sap.m.Panel} Pointer to the control instance to allow method chaining.
	 * @public
	 */
	Panel.prototype.setExpandable = function (bExpandable) {
		this.setProperty("expandable", bExpandable, false); // rerender since we set certain css classes

		if (bExpandable && !this.oIconCollapsed) {
			this.oIconCollapsed = this._createIcon();
		}

		return this;
	};

	/**
	 * Sets the expanded property of the control.
	 * @param {boolean} bExpanded Defines whether control is expanded or not.
	 * @returns {sap.m.Panel} Pointer to the control instance to allow method chaining.
	 * @public
	 */
	Panel.prototype.setExpanded = function (bExpanded) {
		if (bExpanded === this.getExpanded()) {
			return this;
		}

		this.setProperty("expanded", bExpanded, true);

		if (!this.getExpandable()) {
			return this;
		}

		// ARIA
		this._getIcon().$().attr("aria-expanded", bExpanded.toString());

		this._toggleExpandCollapse();
		this._toggleCssClasses();
		this.fireExpand({ expand : bExpanded });

		return this;
	};

	Panel.prototype.onBeforeRendering = function () {
		this._updateIconAriaLabelledBy();
	};

	Panel.prototype.onAfterRendering = function () {
		var $this = this.$(),
			$icon;

		this._setContentHeight();

		if (this.getExpandable()) {
			$icon = this.oIconCollapsed.$();
			if (this.getExpanded()) {
				//ARIA
				$icon.attr("aria-expanded", "true");
			} else {
				// hide those parts which are collapsible (w/o animation, otherwise initial loading doesn't look good ...)
				$this.children(".sapMPanelExpandablePart").hide();
				//ARIA
				$icon.attr("aria-expanded", "false");
			}
		}
	};

	Panel.prototype.exit = function () {
		if (this.oIconCollapsed) {
			this.oIconCollapsed.destroy();
			this.oIconCollapsed = null;
		}
	};

	Panel.prototype._createIcon = function () {
		var that = this,
			sCollapsedIconURI = IconPool.getIconURI("navigation-right-arrow");

		return IconPool.createControlByURI({
			id: that.getId() + "-CollapsedImg",
			src: sCollapsedIconURI,
			decorative: false,
			useIconTooltip: false,
			press: function () {
				that.setExpanded(!that.getExpanded());
			}
		}).addStyleClass("sapMPanelExpandableIcon");
	};

	Panel.prototype._getIcon = function () {
		return this.oIconCollapsed;
	};

	Panel.prototype._setContentHeight = function () {
		if (this.getHeight() === "auto") {
			return;
		}

		var thisDomRef = this.getDomRef();

		// 'offsetTop' measures the vertical space occupied by siblings before this one
		// Earlier each previous sibling's height was calculated separately and then all height values were summed up
		var iOffsetTop = thisDomRef.querySelector(".sapMPanelContent").offsetTop;
		var iAdjustedContentHeight = thisDomRef.clientHeight - iOffsetTop;

		thisDomRef.querySelector(".sapMPanelContent")
			.style.height = iAdjustedContentHeight + 'px';
	};

	Panel.prototype._toggleExpandCollapse = function () {
		var oOptions = {};
		if (!this.getExpandAnimation()) {
			oOptions.duration = 0;
		}

		this.$().children(".sapMPanelExpandablePart").slideToggle(oOptions);
	};

	Panel.prototype._toggleCssClasses = function () {
		var $this = this.$();

		// for controlling the visibility of the border
		$this.children(".sapMPanelWrappingDiv").toggleClass("sapMPanelWrappingDivExpanded");
		$this.children(".sapMPanelWrappingDivTb").toggleClass("sapMPanelWrappingDivTbExpanded");
		$this.find(".sapMPanelExpandableIcon").first().toggleClass("sapMPanelExpandableIconExpanded");
	};

	Panel.prototype._updateIconAriaLabelledBy = function () {
		var sLabelId, aAriaLabels;

		if (!this.oIconCollapsed) {
			return;
		}

		sLabelId = this._getLabellingElementId();
		aAriaLabels = this.oIconCollapsed.getAriaLabelledBy();

		// If the old label is different we should reinitialize the association, because we can have only one label
		if (aAriaLabels.indexOf(sLabelId) === -1) {
			this.oIconCollapsed.removeAllAssociation("ariaLabelledBy");
			this.oIconCollapsed.addAriaLabelledBy(sLabelId);
		}
	};

	Panel.prototype._getLabellingElementId = function () {
		var headerToolbar = this.getHeaderToolbar(),
			id;

		if (headerToolbar) {
			id = headerToolbar.getTitleId();
		} else {
			id = this.getId() + "-header";
		}

		return id;
	};

	return Panel;

}, /* bExport= */ true);
<|endoftext|>


<commit_before>pc.extend(pc.scene, function () {

    var identity = pc.math.mat4.create();

    /**
     * @name pc.scene.GraphNode
     * @class A node.
     * @param {String} name Non-unique human readable name.
     */
    var GraphNode = function GraphNode(name) {
        this._name = name || ""; // Non-unique human readable name
        this._ltm = pc.math.mat4.create();
        this._wtm = pc.math.mat4.create();
        this._parent = null;
        this._children = [];
        this._labels = {};
        this._graphId = -1;
    };

    GraphNode.prototype = {

        clone: function () {
            var clone = new pc.scene.GraphNode();

            // GraphNode
            clone.setName(this.getName());
            clone.setLocalTransform(pc.math.mat4.clone(this.getLocalTransform()));
            clone._graphId = this._graphId;

            return clone;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#addGraphId
         * @description The Graph ID is used to patch up nodes while loading graph data from files. A Graph ID is added during the loading process and should
         * be removed again by called removeGraphId() once the loading is complete.
         * @param {String} id The ID from the data file which is added to the node temporarily
         */
        addGraphId: function (id) {
            this._graphId = id;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#removeGraphId
         * @description The Graph ID is used to patch up nodes while loading graph data from files. A Graph ID is added during the loading process by calling addGraphId() and should
         * be removed again by called removeGraphId() once the loading is complete.
         */
        removeGraphId: function () {
            delete this._graphId;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#find
         * @description Search the graph for nodes using a supplied property or method name to get the value to search on.
         * @param {String} attr The attribute name on the node to search for, if this corresponds to a function name then the function return value is used in the comparison
         * @param {String} value The value of the attr to look for
         * @returns {Array} An array of GraphNodes
         * @example
         * var graph = ... // Get a pc.fw.Entity hierarchy from somewhere
         * var results = graph.find("getGuid", "1234");
         */
        find: function (attr, value) {
            var i;
            var children = this.getChildren();
            var length = children.length;
            var results = [];
            var testValue;
            if(this[attr]) {
                if(this[attr] instanceof Function) {
                    testValue = this[attr]();
                } else {
                    testValue = this[attr];
                }
                if(testValue === value) {
                    results.push(this);
                }
            }

            for(i = 0; i < length; ++i) {
                results = results.concat(children[i].find(attr, value));
            }

            return results;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#findOne
         * @description @see pc.scene.GraphNode#find, but this will only return the first GraphNode that it finds
         * @param {String} attr The property or function name to search using
         * @param {String} value The value to search for
         * @returns {pc.scene.GraphNode} A single GraphNode
         */
        findOne: function(attr, value) {
            var i;
            var children = this.getChildren();
            var length = children.length;
            var result = null;
            var testValue;
            if(this[attr]) {
                if(this[attr] instanceof Function) {
                    testValue = this[attr]();
                } else {
                    testValue = this[attr];
                }
                if(testValue === value) {
                    return this;
                }
            }

            for(i = 0; i < length; ++i) {
                 result = children[i].findOne(attr, value);
                 if(result !== null) {
                     return result;
                 }
            }

            return null;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#findByName
         * @description Get the first node found in the graph with the name.
         * @returns {pc.scene.GraphNode}
         * @author Will Eastcott
         */
        findByName: function (name) {
            if (this._name === name) return this;

            for (var i = 0; i < this._children.length; i++) {
                var found = this._children[i].findByName(name);
                if (found !== null) return found;
            }
            return null;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#findByGraphId
         * @description
         * @returns {pc.scene.GraphNode}
         */
        findByGraphId: function (id) {
            if (this._graphId === id) return this;

            for (var i = 0; i < this._children.length; i++) {
                var found = this._children[i].findByGraphId(id);
                if (found !== null) return found;
            }
            return null;

        },

        /**
         * @function
         * @name pc.scene.GraphNode#getRoot
         * @description Get the highest ancestor node from this one.
         * @return {pc.scene.GraphNode} The root node
         */
        getRoot: function () {
            var parent = this.getParent()
            if(!parent) {
                return this;
            }

            while(parent.getParent()) {
                parent = parent.getParent();
            }
            return parent;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#getParent
         * @description Get the parent GraphNode
         * @returns {pc.scene.GraphNode} The parent node
         */
        getParent: function () {
            return this._parent;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#getChildren
         * @description Get the children of this GraphNode
         * @returns {Array}
         * @author Will Eastcott
         */
        getChildren: function () {
            return this._children;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#getLocalPosition
         * @description Get the position in local space for the specified GraphNode.
         * @returns {Float32Array} The local space position of the graph node.
         * @author Will Eastcott
         */
        getLocalPosition: function () {
            return this._ltm.subarray(12, 15);
        },

        /**
         * @function
         * @name pc.scene.GraphNode#getLocalTransform
         * @description Get the local transform matrix for this GraphNode
         * @returns {Array}
         * @author Will Eastcott
         */
        getLocalTransform: function () {
            return this._ltm;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#getName
         * @description Get the human-readable name for this GraphNode. Note the name is not guaranteed to be unique.
         * @returns {String}
         * @author Will Eastcott
         */
        getName: function () {
            return this._name;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#getWorldPosition
         * @description Get the position in world space for the specified GraphNode.
         * @returns {Float32Array} The world space position of the graph node.
         * @author Will Eastcott
         */
        getWorldPosition: function () {
            return this._wtm.subarray(12, 15);
        },

        /**
         * @function
         * @name pc.scene.GraphNode#getWorldTransform
         * @description Get the world transformation matrix for this GraphNode
         * @returns {Array}
         * @author Will Eastcott
         */
        getWorldTransform: function () {
            return this._wtm;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#setParent
         * @description Set the parent node of this node. Note, this does not update the children of the parent
         * @param {pc.scene.GraphNode} node The node to use as the parent
         */
        setParent: function (node) {
            this._parent = node;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#setChildren
         * @description Set the child array of this node. Note, this does not update the parent value of the children.
         * @param {Array} children
         * @author Will Eastcott
         */
        setChildren: function (children) {
            this._children = children;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#setLocalPosition
         * @description Sets the local space position of the specified graph node.
         * @param {pc.math.vec3} pos Position vector of graph node in local-space.
         * @author Will Eastcott
         */
        /**
         * @function
         * @name pc.scene.GraphNode#setLocalPosition
         * @description Sets the local space position of the specified graph node.
         * @param {Number} x x-coordinate of local-space position.
         * @param {Number} y y-coordinate of local-space position.
         * @param {Number} z z-coordinate of local-space position.
         * @author Will Eastcott
         */
        setLocalPosition: function () {
            this._ltm.set((arguments.length === 1) ? arguments[0] : arguments, 12);
        },

        /**
         * @function
         * @name pc.scene.GraphNode#setLocalTransform
         * @description Sets the local space transform (relative to the parent graph node's
         * world transform) of the specified graph node.
         * @param {pc.math.mat4} ltm Local transformation matrix to apply.
         * @author Will Eastcott
         */
        setLocalTransform: function (ltm) {
            this._ltm = ltm;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#setName
         * @description
         * @param {String} name
         * @author Will Eastcott
         */
        setName: function (name) {
            this._name = name;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#addChild
         * @description Add a new child to the child list and update the parent value of the child node
         * @param {pc.scene.GraphNode} node The new child to add
         */
        addChild: function (node) {
            if(node.getParent() != null) {
                throw new Error("GraphNode is already parented")
            }

            this._children.push(node);
            node.setParent(this);
        },

        /**
         * @function
         * @name pc.scene.GraphNode#removeChild
         * @description Remove the node from the child list and update the parent value of the child.
         * @param {pc.scene.GraphNode} node The node to remove
         */
        removeChild: function (child) {
            var i;
            var length = this._children.length;

            // Clear parent
            child.setParent(null);

            // Remove from child list
            for(i = 0; i < length; ++i) {
                if(this._children[i] === child) {
                    this._children.splice(i, 1);
                    return;
                }
            }
        },

        /**
         * @function
         * @name pc.scene.GraphNode#addLabel
         * @description Add a string label to this GraphNode, labels can be used to group and filter nodes. e.g. The 'enemies' label could be applied to a group of NPCs who are enemies
         * @param {String} label The label to apply to this GraphNode
         */
        addLabel: function (label) {
            this._labels[label] = true;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#getLabels
         * @description Get a list of all labels applied to this GraphNode
         * @param {Array} A list of all labels
         */
        getLabels: function () {
            return Object.keys(this._labels);
        },

        /**
         * @function
         * @name pc.scene.GraphNode#hasLabel
         * @description Test if a label has been applied to this GraphNode
         * @param label {String} The label to test for
         * @returns {Boolean} True if the label has been added to this GraphNode
         */
        hasLabel: function (label) {
            return !!this._labels[label];
        },

        /**
         * @function
         * @name pc.scene.GraphNode#removeLabel
         * @description Remove label from this GraphNode
         * @param label {String} The label to remove from this node
         */
        removeLabel: function (label) {
            delete this._labels[label];
        },

        /**
         * @function
         * @name pc.scene.GraphNode#findByLabel
         * @description Find all GraphNodes from the root and all descendants with the label
         * @param label {String} The label to search for
         * @param [results] {Array} An array to store the results in
         * @returns The array passed in or a new array of results
         */
        findByLabel: function (label, results) {
            var i, length = this._children.length;
            results = results || [];

            if(this.hasLabel(label)) {
                results.push(this);
            }

            for(i = 0; i < length; ++i) {
                results = this._children[i].findByLabel(label, results);
            }

            return results;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#syncHierarchy
         * @description Updates the world transformation matrices at this node and all of its descendants.
         * @author Will Eastcott
         */
        syncHierarchy: function () {
            function _syncHierarchy(node, parentTransform) {
                // Now calculate this nodes world space transform
                pc.math.mat4.multiply(parentTransform, node._ltm, node._wtm);

                // Sync subhierarchy
                for (var i = 0, len = node._children.length; i < len; i++) {
                    _syncHierarchy(node._children[i], node._wtm);
                }
            }

            _syncHierarchy(this, this._parent ? this._parent._wtm : identity);
        },

        /**
         * @function
         * @name pc.scene.GraphNode#lookAt
         * @description Reorients the graph node so that the z axis points towards the target.
         * @param {pc.math.vec3} target The world space coordinate to 'look at'.
         * @param {pc.math.vec3} up The up vector for the look at transform. If left unspecified,
         * this is set to the world space y axis.
         * @author Will Eastcott
         */
        lookAt: function (target, up) {
            up = up || pc.math.vec3.create(0, 1, 0);
            pc.math.mat4.makeLookAt(this.getLocalPosition(), target, up, this._ltm);
        },

        /**
         * @function
         * @name pc.scene.GraphNode#translate
         * @description Translates the graph node by the given translation vector.
         * @param {pc.math.vec3} translation The translation vector to apply.
         * @param {pc.scene.Space} space The coordinate system that the translation is relative to.
         * In left unspecified, local space is assumed.
         * @author Will Eastcott
         */
        translate: function () {
            var relativeTo;
            if (arguments.length >= 3) {
                relativeTo = arguments[3];
                if ((relativeTo === undefined) || (relativeTo === pc.scene.Space.LOCAL)) {
                    this._ltm[12] += arguments[0];
                    this._ltm[13] += arguments[1];
                    this._ltm[14] += arguments[2];
                }
            } else {
                relativeTo = arguments[1];
                if ((relativeTo === undefined) || (relativeTo === pc.scene.Space.LOCAL)) {
                    this._ltm[12] += arguments[0][0];
                    this._ltm[13] += arguments[0][1];
                    this._ltm[14] += arguments[0][2];
                }
            }
        }
    };

    return {
        GraphNode: GraphNode
    };
}());<commit_msg>Doc fix.<commit_after>pc.extend(pc.scene, function () {

    var identity = pc.math.mat4.create();

    /**
     * @name pc.scene.GraphNode
     * @class A node.
     * @param {String} name Non-unique human readable name.
     */
    var GraphNode = function GraphNode(name) {
        this._name = name || ""; // Non-unique human readable name
        this._ltm = pc.math.mat4.create();
        this._wtm = pc.math.mat4.create();
        this._parent = null;
        this._children = [];
        this._labels = {};
        this._graphId = -1;
    };

    GraphNode.prototype = {

        clone: function () {
            var clone = new pc.scene.GraphNode();

            // GraphNode
            clone.setName(this.getName());
            clone.setLocalTransform(pc.math.mat4.clone(this.getLocalTransform()));
            clone._graphId = this._graphId;

            return clone;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#addGraphId
         * @description The Graph ID is used to patch up nodes while loading graph data from files. A Graph ID is added during the loading process and should
         * be removed again by called removeGraphId() once the loading is complete.
         * @param {String} id The ID from the data file which is added to the node temporarily
         */
        addGraphId: function (id) {
            this._graphId = id;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#removeGraphId
         * @description The Graph ID is used to patch up nodes while loading graph data from files. A Graph ID is added during the loading process by calling addGraphId() and should
         * be removed again by called removeGraphId() once the loading is complete.
         */
        removeGraphId: function () {
            delete this._graphId;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#find
         * @description Search the graph for nodes using a supplied property or method name to get the value to search on.
         * @param {String} attr The attribute name on the node to search for, if this corresponds to a function name then the function return value is used in the comparison
         * @param {String} value The value of the attr to look for
         * @returns {Array} An array of GraphNodes
         * @example
         * var graph = ... // Get a pc.fw.Entity hierarchy from somewhere
         * var results = graph.find("getGuid", "1234");
         */
        find: function (attr, value) {
            var i;
            var children = this.getChildren();
            var length = children.length;
            var results = [];
            var testValue;
            if(this[attr]) {
                if(this[attr] instanceof Function) {
                    testValue = this[attr]();
                } else {
                    testValue = this[attr];
                }
                if(testValue === value) {
                    results.push(this);
                }
            }

            for(i = 0; i < length; ++i) {
                results = results.concat(children[i].find(attr, value));
            }

            return results;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#findOne
         * @description @see pc.scene.GraphNode#find, but this will only return the first GraphNode that it finds
         * @param {String} attr The property or function name to search using
         * @param {String} value The value to search for
         * @returns {pc.scene.GraphNode} A single GraphNode
         */
        findOne: function(attr, value) {
            var i;
            var children = this.getChildren();
            var length = children.length;
            var result = null;
            var testValue;
            if(this[attr]) {
                if(this[attr] instanceof Function) {
                    testValue = this[attr]();
                } else {
                    testValue = this[attr];
                }
                if(testValue === value) {
                    return this;
                }
            }

            for(i = 0; i < length; ++i) {
                 result = children[i].findOne(attr, value);
                 if(result !== null) {
                     return result;
                 }
            }

            return null;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#findByName
         * @description Get the first node found in the graph with the name.
         * @returns {pc.scene.GraphNode}
         * @author Will Eastcott
         */
        findByName: function (name) {
            if (this._name === name) return this;

            for (var i = 0; i < this._children.length; i++) {
                var found = this._children[i].findByName(name);
                if (found !== null) return found;
            }
            return null;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#findByGraphId
         * @description
         * @returns {pc.scene.GraphNode}
         */
        findByGraphId: function (id) {
            if (this._graphId === id) return this;

            for (var i = 0; i < this._children.length; i++) {
                var found = this._children[i].findByGraphId(id);
                if (found !== null) return found;
            }
            return null;

        },

        /**
         * @function
         * @name pc.scene.GraphNode#getRoot
         * @description Get the highest ancestor node from this one.
         * @return {pc.scene.GraphNode} The root node
         */
        getRoot: function () {
            var parent = this.getParent()
            if(!parent) {
                return this;
            }

            while(parent.getParent()) {
                parent = parent.getParent();
            }
            return parent;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#getParent
         * @description Get the parent GraphNode
         * @returns {pc.scene.GraphNode} The parent node
         */
        getParent: function () {
            return this._parent;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#getChildren
         * @description Get the children of this GraphNode
         * @returns {Array}
         * @author Will Eastcott
         */
        getChildren: function () {
            return this._children;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#getLocalPosition
         * @description Get the position in local space for the specified GraphNode.
         * @returns {Float32Array} The local space position of the graph node.
         * @author Will Eastcott
         */
        getLocalPosition: function () {
            return this._ltm.subarray(12, 15);
        },

        /**
         * @function
         * @name pc.scene.GraphNode#getLocalTransform
         * @description Get the local transform matrix for this GraphNode
         * @returns {Array}
         * @author Will Eastcott
         */
        getLocalTransform: function () {
            return this._ltm;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#getName
         * @description Get the human-readable name for this GraphNode. Note the name is not guaranteed to be unique.
         * @returns {String}
         * @author Will Eastcott
         */
        getName: function () {
            return this._name;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#getWorldPosition
         * @description Get the position in world space for the specified GraphNode.
         * @returns {Float32Array} The world space position of the graph node.
         * @author Will Eastcott
         */
        getWorldPosition: function () {
            return this._wtm.subarray(12, 15);
        },

        /**
         * @function
         * @name pc.scene.GraphNode#getWorldTransform
         * @description Get the world transformation matrix for this GraphNode
         * @returns {Array}
         * @author Will Eastcott
         */
        getWorldTransform: function () {
            return this._wtm;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#setParent
         * @description Set the parent node of this node. Note, this does not update the children of the parent
         * @param {pc.scene.GraphNode} node The node to use as the parent
         */
        setParent: function (node) {
            this._parent = node;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#setChildren
         * @description Set the child array of this node. Note, this does not update the parent value of the children.
         * @param {Array} children
         * @author Will Eastcott
         */
        setChildren: function (children) {
            this._children = children;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#setLocalPosition
         * @description Sets the local space position of the specified graph node.
         * @param {pc.math.vec3} pos Position vector of graph node in local-space.
         * @author Will Eastcott
         */
        /**
         * @function
         * @name pc.scene.GraphNode#setLocalPosition^2
         * @description Sets the local space position of the specified graph node.
         * @param {Number} x x-coordinate of local-space position.
         * @param {Number} y y-coordinate of local-space position.
         * @param {Number} z z-coordinate of local-space position.
         * @author Will Eastcott
         */
        setLocalPosition: function () {
            this._ltm.set((arguments.length === 1) ? arguments[0] : arguments, 12);
        },

        /**
         * @function
         * @name pc.scene.GraphNode#setLocalTransform
         * @description Sets the local space transform (relative to the parent graph node's
         * world transform) of the specified graph node.
         * @param {pc.math.mat4} ltm Local transformation matrix to apply.
         * @author Will Eastcott
         */
        setLocalTransform: function (ltm) {
            this._ltm = ltm;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#setName
         * @description
         * @param {String} name
         * @author Will Eastcott
         */
        setName: function (name) {
            this._name = name;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#addChild
         * @description Add a new child to the child list and update the parent value of the child node
         * @param {pc.scene.GraphNode} node The new child to add
         */
        addChild: function (node) {
            if(node.getParent() != null) {
                throw new Error("GraphNode is already parented")
            }

            this._children.push(node);
            node.setParent(this);
        },

        /**
         * @function
         * @name pc.scene.GraphNode#removeChild
         * @description Remove the node from the child list and update the parent value of the child.
         * @param {pc.scene.GraphNode} node The node to remove
         */
        removeChild: function (child) {
            var i;
            var length = this._children.length;

            // Clear parent
            child.setParent(null);

            // Remove from child list
            for(i = 0; i < length; ++i) {
                if(this._children[i] === child) {
                    this._children.splice(i, 1);
                    return;
                }
            }
        },

        /**
         * @function
         * @name pc.scene.GraphNode#addLabel
         * @description Add a string label to this GraphNode, labels can be used to group and filter nodes. e.g. The 'enemies' label could be applied to a group of NPCs who are enemies
         * @param {String} label The label to apply to this GraphNode
         */
        addLabel: function (label) {
            this._labels[label] = true;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#getLabels
         * @description Get a list of all labels applied to this GraphNode
         * @param {Array} A list of all labels
         */
        getLabels: function () {
            return Object.keys(this._labels);
        },

        /**
         * @function
         * @name pc.scene.GraphNode#hasLabel
         * @description Test if a label has been applied to this GraphNode
         * @param label {String} The label to test for
         * @returns {Boolean} True if the label has been added to this GraphNode
         */
        hasLabel: function (label) {
            return !!this._labels[label];
        },

        /**
         * @function
         * @name pc.scene.GraphNode#removeLabel
         * @description Remove label from this GraphNode
         * @param label {String} The label to remove from this node
         */
        removeLabel: function (label) {
            delete this._labels[label];
        },

        /**
         * @function
         * @name pc.scene.GraphNode#findByLabel
         * @description Find all GraphNodes from the root and all descendants with the label
         * @param label {String} The label to search for
         * @param [results] {Array} An array to store the results in
         * @returns The array passed in or a new array of results
         */
        findByLabel: function (label, results) {
            var i, length = this._children.length;
            results = results || [];

            if(this.hasLabel(label)) {
                results.push(this);
            }

            for(i = 0; i < length; ++i) {
                results = this._children[i].findByLabel(label, results);
            }

            return results;
        },

        /**
         * @function
         * @name pc.scene.GraphNode#syncHierarchy
         * @description Updates the world transformation matrices at this node and all of its descendants.
         * @author Will Eastcott
         */
        syncHierarchy: function () {
            function _syncHierarchy(node, parentTransform) {
                // Now calculate this nodes world space transform
                pc.math.mat4.multiply(parentTransform, node._ltm, node._wtm);

                // Sync subhierarchy
                for (var i = 0, len = node._children.length; i < len; i++) {
                    _syncHierarchy(node._children[i], node._wtm);
                }
            }

            _syncHierarchy(this, this._parent ? this._parent._wtm : identity);
        },

        /**
         * @function
         * @name pc.scene.GraphNode#lookAt
         * @description Reorients the graph node so that the z axis points towards the target.
         * @param {pc.math.vec3} target The world space coordinate to 'look at'.
         * @param {pc.math.vec3} up The up vector for the look at transform. If left unspecified,
         * this is set to the world space y axis.
         * @author Will Eastcott
         */
        lookAt: function (target, up) {
            up = up || pc.math.vec3.create(0, 1, 0);
            pc.math.mat4.makeLookAt(this.getLocalPosition(), target, up, this._ltm);
        },

        /**
         * @function
         * @name pc.scene.GraphNode#translate
         * @description Translates the graph node by the given translation vector.
         * @param {pc.math.vec3} translation The translation vector to apply.
         * @param {pc.scene.Space} space The coordinate system that the translation is relative to.
         * In left unspecified, local space is assumed.
         * @author Will Eastcott
         */
        translate: function () {
            var relativeTo;
            if (arguments.length >= 3) {
                relativeTo = arguments[3];
                if ((relativeTo === undefined) || (relativeTo === pc.scene.Space.LOCAL)) {
                    this._ltm[12] += arguments[0];
                    this._ltm[13] += arguments[1];
                    this._ltm[14] += arguments[2];
                }
            } else {
                relativeTo = arguments[1];
                if ((relativeTo === undefined) || (relativeTo === pc.scene.Space.LOCAL)) {
                    this._ltm[12] += arguments[0][0];
                    this._ltm[13] += arguments[0][1];
                    this._ltm[14] += arguments[0][2];
                }
            }
        }
    };

    return {
        GraphNode: GraphNode
    };
}());<|endoftext|>