|  | // Copyright (c) 2018, Compiler Explorer Authors | 
|  | // All rights reserved. | 
|  | // | 
|  | // Redistribution and use in source and binary forms, with or without | 
|  | // modification, are permitted provided that the following conditions are met: | 
|  | // | 
|  | //     * Redistributions of source code must retain the above copyright notice, | 
|  | //       this list of conditions and the following disclaimer. | 
|  | //     * Redistributions in binary form must reproduce the above copyright | 
|  | //       notice, this list of conditions and the following disclaimer in the | 
|  | //       documentation and/or other materials provided with the distribution. | 
|  | // | 
|  | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | 
|  | // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | 
|  | // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | 
|  | // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE | 
|  | // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR | 
|  | // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | 
|  | // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | 
|  | // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | 
|  | // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | 
|  | // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE | 
|  | // POSSIBILITY OF SUCH DAMAGE. | 
|  |  | 
|  | 'use strict'; | 
|  |  | 
|  | var options = require('options'), | 
|  | _ = require('underscore'), | 
|  | $ = require('jquery'); | 
|  |  | 
|  | function LibsWidget(langId, compiler, dropdownButton, state, onChangeCallback) { | 
|  | this.dropdownButton = dropdownButton; | 
|  | var possibleLibs = false; | 
|  | if (compiler) { | 
|  | this.currentCompilerId = compiler.id; | 
|  | possibleLibs = compiler.libs; | 
|  | } else { | 
|  | this.currentCompilerId = '_default_'; | 
|  | } | 
|  | this.currentLangId = langId; | 
|  | this.initButtons(); | 
|  | this.domRoot = null; | 
|  | this.onChangeCallback = onChangeCallback; | 
|  | this.availableLibs = {}; | 
|  | this.updateAvailableLibs(possibleLibs); | 
|  | _.each(state.libs, _.bind(function (lib) { | 
|  | this.markLibrary(lib.name, lib.ver, true); | 
|  | }, this)); | 
|  | } | 
|  |  | 
|  | LibsWidget.prototype.initButtons = function () { | 
|  | this.noLibsPanel = $('#libs-dropdown .no-libs'); | 
|  | this.libsEntry = $('#libs-entry .input-group'); | 
|  | }; | 
|  |  | 
|  | LibsWidget.prototype.initLangDefaultLibs = function () { | 
|  | var defaultLibs = options.defaultLibs[this.currentLangId]; | 
|  | if (!defaultLibs) return; | 
|  | _.each(defaultLibs.split(':'), _.bind(function (libPair) { | 
|  | var pairSplits = libPair.split('.'); | 
|  | if (pairSplits.length === 2) { | 
|  | var lib = pairSplits[0]; | 
|  | var ver = pairSplits[1]; | 
|  | this.markLibrary(lib, ver, true); | 
|  | } | 
|  | }, this)); | 
|  | }; | 
|  |  | 
|  | LibsWidget.prototype.updateAvailableLibs = function (possibleLibs) { | 
|  | if (!this.availableLibs[this.currentLangId]) { | 
|  | this.availableLibs[this.currentLangId] = {}; | 
|  | } | 
|  |  | 
|  | if (!this.availableLibs[this.currentLangId][this.currentCompilerId]) { | 
|  | if (this.currentCompilerId === '_default_') { | 
|  | this.availableLibs[this.currentLangId][this.currentCompilerId] = | 
|  | $.extend(true, {}, options.libs[this.currentLangId]); | 
|  | } else { | 
|  | this.availableLibs[this.currentLangId][this.currentCompilerId] = | 
|  | $.extend(true, {}, possibleLibs); | 
|  | } | 
|  | } | 
|  |  | 
|  | this.initLangDefaultLibs(); | 
|  | this.updateLibsDropdown(); | 
|  | }; | 
|  |  | 
|  | LibsWidget.prototype.setNewLangId = function (langId, compilerId, possibleLibs) { | 
|  | var libsInUse = this.listUsedLibs(); | 
|  |  | 
|  | this.currentLangId = langId; | 
|  |  | 
|  | if (compilerId) { | 
|  | this.currentCompilerId = compilerId; | 
|  | } else { | 
|  | this.currentCompilerId = '_default_'; | 
|  | } | 
|  |  | 
|  | // Clear the dom Root so it gets rebuilt with the new language libraries | 
|  | this.domRoot = null; | 
|  | this.updateAvailableLibs(possibleLibs); | 
|  |  | 
|  | _.forEach(libsInUse, _.bind(function (version, lib) { | 
|  | this.markLibrary(lib, version, true); | 
|  | }, this)); | 
|  | }; | 
|  |  | 
|  | LibsWidget.prototype.lazyDropdownLoad = function () { | 
|  | var libsCount = _.keys(this.availableLibs[this.currentLangId][this.currentCompilerId]).length; | 
|  | if (libsCount === 0) { | 
|  | return this.noLibsPanel; | 
|  | } | 
|  | if (this.domRoot === null) { | 
|  | var MAX_COLUMNS = 3; | 
|  | var currentColumn = null; | 
|  | var currentColumnItemCount = 0; | 
|  | var libsKeys = _.keys(this.availableLibs[this.currentLangId][this.currentCompilerId]).sort(); | 
|  | var itemsPerColumn = Math.ceil(libsKeys.length / MAX_COLUMNS); | 
|  | this.domRoot = $('<div></div>'); | 
|  | var libsPanel = $('<div></div>') | 
|  | .addClass('card-columns'); | 
|  | var getOrCreateNextColumn = function () { | 
|  | if (currentColumn === null || currentColumnItemCount >= itemsPerColumn) { | 
|  | currentColumn = $('<div></div>').addClass('card'); | 
|  | libsPanel.append(currentColumn); | 
|  | currentColumnItemCount = 0; | 
|  | } | 
|  | return currentColumn; | 
|  | }; | 
|  | var addLibCardToColumn = function (libCard) { | 
|  | var column = getOrCreateNextColumn(); | 
|  | column.append(libCard); | 
|  | currentColumnItemCount++; | 
|  | }; | 
|  | var libsInUse = this.listUsedLibs(); | 
|  |  | 
|  | _.each(libsKeys, _.bind(function (id) { | 
|  | var libEntry = this.availableLibs[this.currentLangId][this.currentCompilerId][id]; | 
|  | if (libEntry.versions.autodetect) return; | 
|  |  | 
|  | var newLibCard = this.libsEntry.clone(); | 
|  | var label = newLibCard.find('.input-group-prepend label') | 
|  | .text(libEntry.name) | 
|  | .prop('title', libEntry.description || '') | 
|  | .prop('for', id); | 
|  | var select = newLibCard.find('select') | 
|  | .prop('id', id) | 
|  | .append($('<option>', { | 
|  | value: '-', | 
|  | text: '-' | 
|  | })); | 
|  | _.each(libEntry.versions, _.bind(function (version, versionId) { | 
|  | select.append($('<option>', { | 
|  | value: versionId, | 
|  | text: version.version, | 
|  | selected: libsInUse[id] && libsInUse[id] === versionId | 
|  | })); | 
|  | }, this)); | 
|  | label.toggleClass('bg-success text-white', select.val() !== '-'); | 
|  | select.on('change', _.bind(function () { | 
|  | var newVal = select.val(); | 
|  | label.toggleClass('bg-success text-white', newVal !== '-'); | 
|  | // Disable every version for this lib | 
|  | _.each(libEntry.versions, _.bind(function (version, verId) { | 
|  | this.markLibrary(id, verId, false); | 
|  | }, this)); | 
|  | if (newVal !== '-') { | 
|  | this.markLibrary(id, newVal, true); | 
|  | } | 
|  | this.onChangeCallback(); | 
|  | }, this)); | 
|  | addLibCardToColumn(newLibCard); | 
|  | }, this)); | 
|  | this.domRoot.append(libsPanel); | 
|  | return this.domRoot; | 
|  | } | 
|  | return this.domRoot; | 
|  | }; | 
|  |  | 
|  | LibsWidget.prototype.updateLibsDropdown = function () { | 
|  | this.dropdownButton.popover('dispose'); | 
|  | this.dropdownButton.popover({ | 
|  | container: 'body', | 
|  | content: _.bind(this.lazyDropdownLoad, this), | 
|  | html: true, | 
|  | placement: 'bottom', | 
|  | trigger: 'click', | 
|  | template: '<div class="popover libs-popover" role="tooltip"><div class="arrow"></div>' + | 
|  | '<h3 class="popover-header"></h3>' + | 
|  | '<div class="popover-body"></div></div>' | 
|  | }); | 
|  | }; | 
|  |  | 
|  | LibsWidget.prototype.getVersionOrAlias = function (name, version) { | 
|  | if (this.availableLibs[this.currentLangId] && | 
|  | this.availableLibs[this.currentLangId][this.currentCompilerId] && | 
|  | this.availableLibs[this.currentLangId][this.currentCompilerId][name]) { | 
|  | if (this.availableLibs[this.currentLangId][this.currentCompilerId][name].versions[version]) { | 
|  | return version; | 
|  | } else { | 
|  | var foundAlias = _.findKey( | 
|  | this.availableLibs[this.currentLangId][this.currentCompilerId][name].versions, | 
|  | function (ver) { | 
|  | return ver.alias && ver.alias.includes(version); | 
|  | }); | 
|  |  | 
|  | return foundAlias; | 
|  | } | 
|  | } | 
|  | }; | 
|  |  | 
|  | LibsWidget.prototype.markLibrary = function (name, version, used) { | 
|  | var actualVersion = this.getVersionOrAlias(name, version); | 
|  |  | 
|  | if (this.availableLibs[this.currentLangId] && | 
|  | this.availableLibs[this.currentLangId][this.currentCompilerId] && | 
|  | this.availableLibs[this.currentLangId][this.currentCompilerId][name] && | 
|  | this.availableLibs[this.currentLangId][this.currentCompilerId][name].versions[actualVersion]) { | 
|  | this.availableLibs[this.currentLangId][this.currentCompilerId][name].versions[actualVersion].used = used; | 
|  | } | 
|  | }; | 
|  |  | 
|  | LibsWidget.prototype.get = function () { | 
|  | return _.map(this.listUsedLibs(), function (item, libId) { | 
|  | return {name: libId, ver: item}; | 
|  | }); | 
|  | }; | 
|  |  | 
|  | LibsWidget.prototype.listUsedLibs = function () { | 
|  | var libs = {}; | 
|  | _.each(this.availableLibs[this.currentLangId][this.currentCompilerId], function (library, libId) { | 
|  | _.each(library.versions, function (version, ver) { | 
|  | if (library.versions[ver].used) { | 
|  | // We trust the invariant of only 1 used version at any given time per lib | 
|  | libs[libId] = ver; | 
|  | } | 
|  | }); | 
|  | }); | 
|  | return libs; | 
|  | }; | 
|  |  | 
|  | LibsWidget.prototype.getLibsInUse = function () { | 
|  | var libs = []; | 
|  | _.each(this.availableLibs[this.currentLangId][this.currentCompilerId], function (library, libId) { | 
|  | _.each(library.versions, function (version, ver) { | 
|  | if (library.versions[ver].used) { | 
|  | var libVer = Object.assign({libId: libId, versionId: ver}, library.versions[ver]); | 
|  | libs.push(libVer); | 
|  | } | 
|  | }); | 
|  | }); | 
|  | return libs; | 
|  | }; | 
|  |  | 
|  | module.exports = { | 
|  | Widget: LibsWidget | 
|  | }; |