/* global Card, uuid */ (function(exports) { 'use strict'; var Folder = function Folder(options) { this._cardsInFolder = options._cardsInFolder || []; this.name = options.name; // folderId is used in cardStore as key this.folderId = options.folderId || uuid.v4(); this._state = options.state || Folder.STATES.NORMAL; Card.prototype.constructor.call(this); }; Folder.STATES = Object.freeze({ // when folder is in DESERIALIZING state, it means we are still in the // process of loading its content from datastore 'DESERIALIZING': 'DESERIALIZING', 'NORMAL': 'NORMAL', // DIRTY state means the folder is out of sync with data store 'DIRTY': 'DIRTY', // DETACHED state means the folder itself is not saved (detached) // in card list 'DETACHED': 'DETACHED' }); Folder.deserialize = function folder_deserialize(cardEntry) { var cardInstance; if (cardEntry && cardEntry.type === 'Folder') { cardInstance = new Folder({ name: cardEntry.name, folderId: cardEntry.folderId, // The content of folder is saved in datastore under key of folderId // thus we are not complete deserialize it yet, mark its state // as 'DESERIALIZING'. Caller needs to put content of the folder // back to its structure. Please refer to CardManager#_reloadCardList(). state: Folder.STATES.DESERIALIZING }); } return cardInstance; }; Folder.prototype = Object.create(Card.prototype); Folder.prototype.constructor = Folder; Object.defineProperty(Folder.prototype, 'state', { get: function() { return this._state; }, set: function(state) { this._state = state; this.fire('state-changed', this._state); } }); Folder.prototype.getCardList = function folder_getCardList() { return this._cardsInFolder; }; Folder.prototype.getFilteredCardList = function folder_getFilteredCardList(filterName) { if (typeof this._cardsInFolder !== 'object') { return; } if (filterName === 'all') { return this._cardsInFolder; } else if (filterName) { return this._cardsInFolder.filter( elem => (elem.group === filterName)); } }; Folder.prototype.isEmpty = function folder_isEmpty() { return this._cardsInFolder.length === 0; }; Folder.prototype.isNotEmpty = function folder_isNotEmpty() { return this._cardsInFolder.length > 0; }; Folder.prototype.isDeserializing = function folder_isDeserializing() { return this._state === Folder.STATES.DESERIALIZING; }; Folder.prototype.isDetached = function folder_isDetached() { return this._state === Folder.STATES.DETACHED; }; Folder.prototype.loadCardsInFolder = function folder_loadCardsInFolder(opts) { var that = this; if (opts.from === 'config') { // load content of folder from config file (e.g. in 'cardEntry' form) this._cardsInFolder = opts.cardEntry._cardsInFolder.map(opts.deserializer); this.state = Folder.STATES.NORMAL; } else if (opts.from === 'datastore') { // load content of folder from data store opts.datastore.getData(this.folderId).then(function(innerCardList) { innerCardList.forEach(function(innerCardEntry) { that._cardsInFolder.push(opts.deserializer(innerCardEntry)); }); that.state = Folder.STATES.NORMAL; }); } }; // get index of card in folder Folder.prototype._indexOfCard = function folder_indexOfCard(query) { return this._cardsInFolder.indexOf(this.findCard(query)); }; // XXX: this method shares almost the same logic as // findCardFromCardList in CardManager. We should consolidate them. See // http://bugzil.la/1156726 // There are three types of query: // 1. query by cardId // 2. query by manifestURL and optionally launchURL // 3. query by bookmark url // 4. query by cardEntry (i.e. serialized card) Folder.prototype.findCard = function folder_findCard(query) { var found; this._cardsInFolder.some(function(card, index) { if (card.cardId === query.cardId) { found = card; return true; } else if (query.manifestURL && card.nativeApp && card.nativeApp.manifestURL === query.manifestURL) { // if we specify launchURL in query, then we must compare // launchURL first if (query.launchURL) { if (card.launchURL === query.launchURL) { found = card; return true; } } else { found = card; return true; } } else if (query.url) { if (query.url === card.url) { found = card; } return true; } else if (query.cardEntry) { // XXX: this could be bad at performance because we serialize card // in every loop. We might need improvement on this query. if (JSON.stringify(card.serialize()) === JSON.stringify(query.cardEntry)) { found = card; return true; } } }); return found; }; Folder.prototype._isInFolder = function folder_isInFolder(card) { if (card) { return this._indexOfCard(card) > -1; } else { return false; } }; Folder.prototype._setDirty = function folder_setDirty() { if (this._state !== Folder.STATES.DETACHED) { this._state = Folder.STATES.DIRTY; this.fire('state-changed', this._state); } this.fire('folder-changed', this); }; Folder.prototype.addCard = function folder_addCard(card, index) { // We don't support folder in folder if (!this._isInFolder(card) && !(card instanceof Folder)) { if (typeof index !== 'number') { index = this._cardsInFolder.length; } this._cardsInFolder.splice(index, 0, card); this._setDirty(); this.fire('card-inserted', card, index); } }; Folder.prototype.removeCard = function folder_removeCard(card) { var index = this._indexOfCard(card); if (index > -1) { this._cardsInFolder.splice(index, 1); this._setDirty(); this.fire('card-removed', [index]); } }; Folder.prototype.updateCard = function folder_updateCard(card, index) { // The card instance is directly reference to card in '_cardsInFolder' // So don't bother to update it again. But we DID need 'folder-changed' // event to notify CardManager to write changes to data store. this._setDirty(); this.fire('card-updated', card, index); }; Folder.prototype.swapCard = function folder_swapCard(card1, card2) { var index1 = (typeof card1 === 'number') ? index1 = card1 : this._indexOfCard(card1); var index2 = (typeof card2 === 'number') ? index2 = card2 : this._indexOfCard(card2); var temp = this._cardsInFolder[index1]; this._cardsInFolder[index1] = this._cardsInFolder[index2]; this._cardsInFolder[index2] = temp; this._setDirty(); this.fire('card-swapped', this._cardsInFolder[index1], this._cardsInFolder[index2], index1, index2); }; Folder.prototype.launch = function folder_launch() { // fire launch event to inform exterior module this.fire('launch', this); }; Folder.prototype.serialize = function folder_serialize() { return { name: this.name, folderId: this.folderId, type: 'Folder' }; }; exports.Folder = Folder; }(window));