/*
* Copyright 2015 Anyware Services
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Resource Collection tool.
* Display the resources of a resource collection.
* @private
*/
Ext.define('Ametys.plugins.explorer.applications.resources.ResourceCollectionTool', {
extend: 'Ametys.tool.Tool',
inheritableStatics: {
/**
* @property {String} INTERNAL_PREFIX an internal prefix
* @private
* @readonly
*/
INTERNAL_PREFIX: 'resources-app-',
},
/**
* @property {Ext.data.Store} _store The file store
* @private
*/
/**
* @property {Ametys.plugins.explorer.view.ImageThumbnailViewer} _thumbnailView The thumbnail view
* @private
*/
/**
* @property {Ametys.plugins.explorer.view.DetailsViewer} _detailsView The details view
* @private
*/
/**
* @property {Ext.panel.Panel} _mainPanel The application panel.
* @private
*/
/**
* @property {String[]} _currentFilesId The currently selected file id's.
*/
constructor: function(config)
{
this.callParent(arguments);
// Listening to some bus messages.
Ametys.message.MessageBus.on(Ametys.message.Message.CREATED, this._onResourceCreated, this);
Ametys.message.MessageBus.on(Ametys.message.Message.DELETED, this._onResourceDeleted, this);
Ametys.message.MessageBus.on(Ametys.message.Message.MOVED, this._onResourceMoved, this);
Ametys.message.MessageBus.on(Ametys.message.Message.MODIFIED, this._onResourceModified, this);
},
getMBSelectionInteraction: function()
{
return Ametys.tool.Tool.MB_TYPE_ACTIVE;
},
createPanel: function()
{
this._store = this._createStore();
this._thumbnailView = this._createThumbnailView();
this._detailsView = this._createDetailsView();
this.cardView = Ext.create ('Ext.Panel', {
layout: 'card',
activeItem : 0,
border: false,
scrollable: true,
items: [this._detailsView, this._thumbnailView]
});
return this.cardView;
},
/**
* Get the resources store
* @return {Ext.data.Store} The store
*/
getStore: function ()
{
return this._store;
},
sendCurrentSelection: function()
{
var targets, subtargets;
// Sending explorer_collection or resource message depending on the selection.
var selection = this._getSelection();
if (selection.length > 0)
{
ids = Ext.Array.map(selection, function(resource) {
return resource.getId();
});
subtargets = {
id: Ametys.message.MessageTarget.RESOURCE,
parameters: { ids: ids }
};
}
targets = {
id: Ametys.message.MessageTarget.EXPLORER_COLLECTION,
parameters: { ids: [this._parentId] },
subtargets: subtargets
};
Ext.create("Ametys.message.Message", {
type: Ametys.message.Message.SELECTION_CHANGED,
targets: targets
});
},
/**
* Get the active selection model
* @return {Ext.selection.Model} the selection model
* @private
*/
_getSelectionModel: function()
{
var activeItem = this.cardView.getLayout().getActiveItem();
return Ext.isFunction(activeItem.getSelectionModel) ? activeItem.getSelectionModel() : null;
},
/**
* Get the selection
* @return {Ext.data.Model[]} The selection
* @private
*/
_getSelection: function()
{
var sm = this._getSelectionModel();
return sm != null ? sm.getSelection() : [];
},
/**
* Creates and returns the details view for resources
* @return {Ametys.plugins.explorer.view.DetailsViewer} The view
* @private
*/
_createDetailsView: function ()
{
return Ext.create('Ametys.plugins.explorer.view.DetailsViewer', {
itemId : this.self.INTERNAL_PREFIX + 'resources-details-view',
stateful: true,
stateId: this.getId() + "$grid",
store : this.getStore(),
border: false,
columns: this._getColumns(),
listeners: {
activate: {fn: this._onViewActivate, scope: this},
selectionchange: {fn: this._onSelectFiles, scope: this},
itemdblclick: {fn: this._onDblClick, scope: this},
}
});
},
/**
* Creates and returns the thumbnail view for resources
* @return {Ametys.plugins.explorer.view.ImageThumbnailViewer} The view
* @private
*/
_createThumbnailView: function ()
{
return Ext.create('Ametys.plugins.explorer.view.ImageThumbnailViewer', {
itemId: this.self.INTERNAL_PREFIX + 'resources-icons-view',
store : this.getStore(),
listeners: {
activate: {fn: this._onViewActivate, scope: this},
selectionchange: {fn: this._onSelectFiles, scope: this},
itemdblclick: {fn: this._onDblClick, scope: this},
}
});
},
/**
* @protected
* Create the file store.
* @return {Ext.data.Store} the configured store
*/
_createStore: function()
{
return Ext.create('Ext.data.Store', {
autoDestroy: true,
model: 'Ametys.plugins.explorer.applications.resources.ResourcesApplication.ResourceEntry',
proxy: {
type: 'ametys',
plugin: this.getPluginName(),
url: 'files',
reader: {
type: 'xml',
record: '> Node'
}
},
sorters: [{property: 'name'}]
});
},
/**
* @protected
* Create the columns configuration array.
* @return {Object[]} An array of column definition object. See {@link Ext.grid.column.Column}
*/
_getColumns: function()
{
return [
{text: "{{i18n UITOOL_EXPLORER_APP_RESOURCES_COL_NAME}}", stateId: 'name', width: 250, dataIndex: 'name', hideable: false, renderer: this._renderFilename, scope: this, editor: {
xtype: 'textfield',
allowBlank: false,
selectOnFocus: true
}
},
{text: "{{i18n UITOOL_EXPLORER_APP_RESOURCES_COL_KEYWORDS}}", stateId: 'keywords', width: 100, dataIndex: 'keywords', hidden: true},
{text: "{{i18n UITOOL_EXPLORER_APP_RESOURCES_COL_LOCATION}}", stateId: 'path', width: 150, dataIndex: 'path', renderer: this._renderFilePath, scope: this, hidden: true},
{text: "{{i18n UITOOL_EXPLORER_APP_RESOURCES_COL_DATE}}", stateId: 'lastModified', width: 130, dataIndex: 'lastModified', renderer: Ext.util.Format.dateRenderer('d/m/Y H:i')},
{text: "{{i18n UITOOL_EXPLORER_APP_RESOURCES_COL_AUTHOR}}", stateId: 'author', width: 200, dataIndex: 'author'},
{text: "{{i18n UITOOL_EXPLORER_APP_RESOURCES_COL_SIZE}}", stateId: 'size', width: 90, dataIndex: 'size', renderer: Ext.util.Format.fileSize, align: 'right'}
];
},
/**
* Function to render filename in result grid
* @param {Object} value The data value
* @param {Object} metaData A collection of metadata about the current cell
* @param {Ext.data.Model} record The record
* @private
*/
_renderFilename: function (value, metaData, record)
{
var icon = this.getIcon (value);
return '<img class="icon" src="' + icon + '"/>' + value;
},
/**
* Function to render path of a file in result grid
* @param {Object} value The data value
* @param {Object} metadata A collection of metadata about the current cell
* @param {Ext.data.Model} record The record
* @private
*/
_renderFilePath: function(value, metadata, record)
{
var index = value.lastIndexOf('/');
var path = '/' + value.substring(0, index);
return path;
// return '<a class="location" href="#" onClick="Ametys.plugins.explorer.applications.resources.ResourcesApplication.selectNodeByPath(\'' + this.getExplorer().getTree().getId() + '\', \'' + path + '\'); return(false);">' + path + '</a>'
},
/**
* Get the icon full path of a resource by its name
* @param {String} filename The resource name
* @return {String} The path to the icon
*/
getIcon: function (filename)
{
var extension = "unknown";
var index = filename.lastIndexOf('.');
if (index > 0)
{
extension = filename.substring(index + 1).toLowerCase();
}
return Ametys.getPluginDirectPrefix('explorer') + '/icon/' + extension + '.png';
},
setParams: function (params)
{
params.view = params.view || 'details';
this.callParent(arguments);
var itemIdx = 'images' == this.getParams().view ? 1 : 0;
this.cardView.getLayout().setActiveItem(itemIdx);
if (!this._parentId || this._parentId != this.getParams().id)
{
this._parentId = this.getParams().id;
this._applicationId = this.getParams().applicationId;
this._detailsView.setApplicationId(this._applicationId);
this._thumbnailView.setApplicationId(this._applicationId);
this._detailsView.setParentId(this._parentId);
this._updateInfos();
this._initLoad();
}
},
/**
* The first auto load
*/
_initLoad: function()
{
this._load();
},
/**
* Change the tool view.
* @param {String} [view=details] the name of the view : details or images
*/
changeView: function(view)
{
var params = Ext.apply(this.getParams(), {
view: view || 'details'
});
this.setParams(params);
},
/**
* Update tool information
* @private
*/
_updateInfos: function()
{
var me = this;
Ametys.explorer.ExplorerNodeDAO.getExplorerNode(this._parentId, function(explorerNode) {
var text = explorerNode.getText();
me.setTitle(text);
});
},
/**
* Load and displays the file in the grid
* @param {Object} [loadParams] the load params
* @private
*/
_load: function(loadParams)
{
var loadParams = Ext.apply(loadParams || {}, {id: this._parentId});
this.getStore().load(loadParams);
},
refresh: function(manual)
{
this._load({
callback: this.showUpToDate,
scope: this
});
},
/**
* Deletes file entries
* @param {String} ids The ids of the entries to delete
* @private
*/
_deleteEntries: function(ids)
{
var store = this.getStore(),
l = ids.length,
records = [],
record;
for (var i = 0; i < l; i++)
{
record = store.getById(ids[i]);
if (record != null)
{
records.push(record);
}
}
if (records.length > 0)
{
var sm = this._getSelectionModel();
if (sm != null)
{
sm.deselect(records);
}
this.getStore().remove(records);
}
},
/**
* Listener on {@link Ametys.message.Message#CREATED} message.
* If the tool is concerned by the created object, the view will be refreshed
* @param {Ametys.message.Message} message The created message.
* @private
*/
_onResourceCreated: function (message)
{
var resourceTarget = message.getTarget(Ametys.message.MessageTarget.RESOURCE);
if (resourceTarget)
{
// Ensure this tool is concerned by the message.
if (resourceTarget.getParameters().parentId != this._parentId)
{
return;
}
if (!this.hasFocus())
{
this.showOutOfDate();
}
else
{
// Reload
this._load();
}
}
},
/**
* Listener on {@link Ametys.message.Message#DELETED} message.
* If the tool is concerned by the deleted object, the corresponding row will be removed
* @param {Ametys.message.Message} message The deleted message.
* @private
*/
_onResourceDeleted: function(message)
{
var collectionTarget = message.getTarget(Ametys.message.MessageTarget.EXPLORER_COLLECTION);
if (collectionTarget)
{
if (collectionTarget.getParameters().id != this._parentId)
{
return;
}
// Async tool removal
var me = this;
setTimeout(function() {
Ametys.tool.ToolsManager.removeTool(me);
}, 0);
// Remove file from navigation history
Ametys.navhistory.HistoryDAO.removeEntry (this.getId());
}
var resourceTargets = message.getTargets(Ametys.message.MessageTarget.RESOURCE);
if (resourceTargets.length > 0)
{
// Ensure this tool is concerned by the message.
if (resourceTargets[0].getParameters().parentId != this._parentId)
{
return;
}
var ids = [];
for (var i=0; i < resourceTargets.length; i++)
{
ids.push(resourceTargets[i].getParameters().id);
}
this._deleteEntries(ids);
}
},
/**
* Listener on {@link Ametys.message.Message#MOVED} message.
* If the tool is concerned by the moved object, the new parent node will be refreshed.
* @param {Ametys.message.Message} message The moved message.
* @private
*/
_onResourceMoved: function(message)
{
var targets = message.getTargets(Ametys.message.MessageTarget.RESOURCE);
if (targets.length > 0)
{
var targetFolderId = targets[0].getParameters().parentId;
var movedRecords = [], movedRecord;
for (var i = 0; i < targets.length; i++)
{
movedRecord = this.getStore().getById(targets[i].getParameters().id);
if (movedRecord != null)
{
movedRecords.push(movedRecord);
}
}
if (movedRecords.length == 0 && targetFolderId != this._parentId)
{
// Tool is not concerned by this message.
return;
}
if (targetFolderId != this._parentId)
{
// Remove moved nodes.
var sm = this._getSelectionModel();
if (sm != null)
{
sm.deselect(movedRecords);
}
this.getStore().remove(movedRecords);
}
else
{
// Moved nodes are still in the same folder, but some other nodes might have been added.
if (targets.length != movedRecords.length)
{
this.showOutOfDate();
}
}
}
},
/**
* Listener on {@link Ametys.message.Message#MODIFIED} message.
* If the tool is concerned by the modified object, the node of modified object will be updated.
* @param {Ametys.message.Message} message The modified message.
* @private
*/
_onResourceModified: function(message)
{
var collectionTarget = message.getTarget(Ametys.message.MessageTarget.EXPLORER_COLLECTION);
if (collectionTarget)
{
if (collectionTarget.getParameters().id != this._parentId)
{
return;
}
this._updateInfos();
}
var resourceTargets = message.getTargets(Ametys.message.MessageTarget.RESOURCE);
if (resourceTargets.length > 0)
{
// Ensure this tool is concerned by the message.
if (resourceTargets[0].getParameters().parentId != this._parentId)
{
return;
}
if (message.getParameters().major)
{
this.showOutOfDate();
}
else
{
for (var i = 0; i < resourceTargets.length; i++)
{
this._updateFile(resourceTargets[i].getParameters());
}
}
}
},
/**
* Update a file entry.
* Currently only update its name
* @param {Object} resourceParams The parameters of a Resource.
*/
_updateFile: function(resourceParams)
{
var record = this.getStore().getById(resourceParams.id);
record.beginEdit();
record.set('name', resourceParams.name);
record.endEdit();
record.commit();
},
/**
* @private
* Listen to the *activate* event of the #_detailsView and #_thumbnailView.
* Keep the selection model up to date between both panels.
* @param {Ext.Component} activeItem The active component.
* @param {Object} eOpts The event option object
*/
_onViewActivate: function(activeItem, eOpts)
{
function updateSelectionModel(oldModel, newModel)
{
var selected = oldModel.getSelection();
oldModel.deselectAll(true);
selectedIds = Ext.Array.map(selected, function(record) {
return record.getId();
});
var toSelect = [];
var store = newModel.getStore(),
record;
for (var i = 0; i < selectedIds.length; i++)
{
record = store.getById(selectedIds[i]);
if (record != null)
{
toSelect.push(record);
}
}
newModel.select(toSelect);
}
var id = activeItem.getId();
// Update selection model of the active item.
if (id == this._detailsView.getId())
{
updateSelectionModel(this._thumbnailView.getSelectionModel(), this._detailsView.getSelectionModel());
}
else if (id == this._thumbnailView.getId())
{
updateSelectionModel(this._detailsView.getSelectionModel(), this._thumbnailView.getSelectionModel())
}
},
/**
* @private
* Listen to the 'selectionchange' event of the #_detailsView and #_thumbnailView.
* Handle a part of the internal click logic for this component.
* See {@link Ext.selection.Model#event-selectionchange}
*/
_onSelectFiles: function (sm, selected, eOpts)
{
var ids = Ext.Array.map(selected, function(record) { return record.getId(); });
if (!this._areTheSame(this._currentFilesId, ids))
{
this._currentFilesId = ids;
this.sendCurrentSelection();
}
},
/**
* @private
* Tells whether files1 and files2 are the same.
* @param {String[]} files1 an array of String.
* @param {String[]} files2 another array of String.
* @return {Boolean} true if files1 and files2 are the same.
*/
_areTheSame: function(files1, files2)
{
if (files1 == files2)
{
return true;
}
if (files1 == null || files2 == null || files1.length != files2.length)
{
return false;
}
for (var i=0; i < files1.length; i++)
{
if (files2.indexOf(files1[i]) == -1)
{
return false;
}
}
return true;
},
/**
* @private
* This listener is called when a item of the details view or thumbnail view is 'double-clicked'
* Download the file.
* @param {Ext.view.View} view The view
* @param {Ext.data.Model} record The record that belongs to the item
*/
_onDblClick: function(view, record)
{
if (record != null)
{
Ametys.explorer.resources.actions.File.download(record.getId());
}
}
});