diff --git a/src/extensibility/ExtensionManagerDialog.js b/src/extensibility/ExtensionManagerDialog.js index 5c2700f7351..3efaed056ed 100644 --- a/src/extensibility/ExtensionManagerDialog.js +++ b/src/extensibility/ExtensionManagerDialog.js @@ -321,6 +321,7 @@ define(function (require, exports, module) { } models.push(new ExtensionManagerViewModel.InstalledViewModel()); + models.push(new ExtensionManagerViewModel.InstalledDefaultViewModel()); function updateSearchDisabled() { var model = models[_activeTabIndex], diff --git a/src/extensibility/ExtensionManagerView.js b/src/extensibility/ExtensionManagerView.js index 91b470f72fb..92843e34818 100644 --- a/src/extensibility/ExtensionManagerView.js +++ b/src/extensibility/ExtensionManagerView.js @@ -326,9 +326,9 @@ define(function (require, exports, module) { context.removalAllowed = this.model.source === "installed" && !context.failedToStart && !hasPendingAction; - context.disablingAllowed = this.model.source === "installed" && + context.disablingAllowed = (this.model.source === "installed" || this.model.source === "installed-default") && !context.disabled && !hasPendingAction; - context.enablingAllowed = this.model.source === "installed" && + context.enablingAllowed = (this.model.source === "installed" || this.model.source === "installed-default") && context.disabled && !hasPendingAction; // Copy over helper functions that we share with the registry app. diff --git a/src/extensibility/ExtensionManagerViewModel.js b/src/extensibility/ExtensionManagerViewModel.js index d683a5c381a..ccbe685e1c6 100644 --- a/src/extensibility/ExtensionManagerViewModel.js +++ b/src/extensibility/ExtensionManagerViewModel.js @@ -81,6 +81,12 @@ define(function (require, exports, module) { */ ExtensionManagerViewModel.prototype.SOURCE_INSTALLED = "installed"; + /** + * @type {string} + * Constant indicating that this model/view should initialize from the list of default installed extensions. + */ + ExtensionManagerViewModel.prototype.SOURCE_INSTALLED_DEFAULT = "installed-default"; + /** * @type {Object} * The current set of extensions managed by this model. Same as ExtensionManager.extensions. @@ -362,6 +368,63 @@ define(function (require, exports, module) { return entry; }; + + function AbstractInstalledViewModel() { + ExtensionManagerViewModel.call(this); + + // when registry is downloaded, sort extensions again - those with updates will be before others + var self = this; + ExtensionManager.on("registryDownload." + this.source, function () { + self._sortFullSet(); + self._setInitialFilter(); + }); + } + + AbstractInstalledViewModel.prototype = Object.create(ExtensionManagerViewModel.prototype); + AbstractInstalledViewModel.prototype.constructor = AbstractInstalledViewModel; + + /** + * @protected + * Re-sorts the current full set + */ + AbstractInstalledViewModel.prototype._sortFullSet = function () { + var self = this; + + this.sortedFullSet = this.sortedFullSet.sort(function (key1, key2) { + // before sorting by name, put first extensions that have updates + var ua1 = self.extensions[key1].installInfo.updateAvailable, + ua2 = self.extensions[key2].installInfo.updateAvailable; + + if (ua1 && !ua2) { + return -1; + } else if (!ua1 && ua2) { + return 1; + } + + var metadata1 = self.extensions[key1].installInfo.metadata, + metadata2 = self.extensions[key2].installInfo.metadata, + id1 = (metadata1.title || metadata1.name).toLocaleLowerCase(), + id2 = (metadata2.title || metadata2.name).toLocaleLowerCase(); + + return id1.localeCompare(id2); + }); + }; + + /** + * @protected + * Finds the extension metadata by id. If there is no extension matching the given id, + * this returns `null`. + * @param {string} id of the extension + * @return {Object?} extension metadata or null if there's no matching extension + */ + AbstractInstalledViewModel.prototype._getEntry = function (id) { + var entry = this.extensions[id]; + if (entry) { + return entry.installInfo; + } + return entry; + }; + /** * The model for the ExtensionManagerView that is responsible for handling previously-installed extensions. * This extends ExtensionManagerViewModel. @@ -374,17 +437,10 @@ define(function (require, exports, module) { * @constructor */ function InstalledViewModel() { - ExtensionManagerViewModel.call(this); - - // when registry is downloaded, sort extensions again - those with updates will be before others - var self = this; - ExtensionManager.on("registryDownload." + this.source, function () { - self._sortFullSet(); - self._setInitialFilter(); - }); + AbstractInstalledViewModel.call(this); } - InstalledViewModel.prototype = Object.create(ExtensionManagerViewModel.prototype); + InstalledViewModel.prototype = Object.create(AbstractInstalledViewModel.prototype); InstalledViewModel.prototype.constructor = InstalledViewModel; /** @@ -413,33 +469,6 @@ define(function (require, exports, module) { return new $.Deferred().resolve().promise(); }; - /** - * @private - * Re-sorts the current full set - */ - InstalledViewModel.prototype._sortFullSet = function () { - var self = this; - - this.sortedFullSet = this.sortedFullSet.sort(function (key1, key2) { - // before sorting by name, put first extensions that have updates - var ua1 = self.extensions[key1].installInfo.updateAvailable, - ua2 = self.extensions[key2].installInfo.updateAvailable; - - if (ua1 && !ua2) { - return -1; - } else if (!ua1 && ua2) { - return 1; - } - - var metadata1 = self.extensions[key1].installInfo.metadata, - metadata2 = self.extensions[key2].installInfo.metadata, - id1 = (metadata1.title || metadata1.name).toLocaleLowerCase(), - id2 = (metadata2.title || metadata2.name).toLocaleLowerCase(); - - return id1.localeCompare(id2); - }); - }; - /** * @private * Updates notifyCount based on number of extensions with an update available @@ -488,18 +517,47 @@ define(function (require, exports, module) { }; /** - * @private - * Finds the extension metadata by id. If there is no extension matching the given id, - * this returns `null`. - * @param {string} id of the extension - * @return {Object?} extension metadata or null if there's no matching extension + * The model for the ExtensionManagerView that is responsible for handling default installed extensions. + * This extends InstalledViewModel. + * Must be disposed with dispose() when done. + * + * Events: + * - change - triggered when the data for a given extension changes. Second parameter is the extension id. + * - filter - triggered whenever the filtered set changes (including on initialize). + * + * @constructor */ - InstalledViewModel.prototype._getEntry = function (id) { - var entry = this.extensions[id]; - if (entry) { - return entry.installInfo; - } - return entry; + function InstalledDefaultViewModel() { + AbstractInstalledViewModel.call(this); + } + + InstalledDefaultViewModel.prototype = Object.create(AbstractInstalledViewModel.prototype); + InstalledDefaultViewModel.prototype.constructor = InstalledDefaultViewModel; + + /** + * @type {string} + * InstalledDefaultViewModel always have a source of SOURCE_INSTALLED_DEFAULT. + */ + InstalledDefaultViewModel.prototype.source = ExtensionManagerViewModel.prototype.SOURCE_INSTALLED_DEFAULT; + + /** + * Initializes the model from the set of default installed extensions, sorted + * alphabetically by id (or name of the extension folder for legacy extensions). + * @return {$.Promise} a promise that's resolved when we're done initializing. + */ + InstalledDefaultViewModel.prototype._initializeFromSource = function () { + var self = this; + this.extensions = ExtensionManager.extensions; + this.sortedFullSet = Object.keys(this.extensions) + .filter(function (key) { + return self.extensions[key].installInfo && + self.extensions[key].installInfo.locationType === ExtensionManager.LOCATION_DEFAULT && + _.isEmpty(self.extensions[key].installInfo.metadata.theme); + }); + this._sortFullSet(); + this._setInitialFilter(); + + return new $.Deferred().resolve().promise(); }; /** @@ -572,4 +630,5 @@ define(function (require, exports, module) { exports.RegistryViewModel = RegistryViewModel; exports.ThemesViewModel = ThemesViewModel; exports.InstalledViewModel = InstalledViewModel; + exports.InstalledDefaultViewModel = InstalledDefaultViewModel; }); diff --git a/src/htmlContent/extension-manager-dialog.html b/src/htmlContent/extension-manager-dialog.html index ebc3162bb60..dcbf7349804 100644 --- a/src/htmlContent/extension-manager-dialog.html +++ b/src/htmlContent/extension-manager-dialog.html @@ -7,6 +7,7 @@ {{/showRegistry}}

  • {{Strings.EXTENSIONS_INSTALLED_TITLE}}
  • +

  • {{Strings.EXTENSIONS_INSTALLED_DEFAULT_TITLE}}