/*
* Copyright 2025 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.
*/
/**
* This class provides a TreePanel for projects resources, it contains a combobox for selecting a project
* and the store load the selected project's resources
*/
Ext.define('Ametys.plugins.workspaces.project.resources.ProjectResourcesTree', {
extend: 'Ametys.explorer.tree.ExplorerTree',
cls: 'project-resource-tree',
animate: true,
scrollable: true,
/**
* Initialize the tree
* @param {Function} [initCb] function to call after initialization process
* @param {String} projectName name of project related to current selected resource, can be null if no resource is selected
*/
initialize: function (initCb, projectName)
{
this._projectName = null;
// Load available projects
this._projectsCombo.getStore().load({callback: Ext.bind(this._loadProjectsCb, this, [initCb, projectName], true)});
},
/**
* @private
* Function called after loading of project store
* @param {Function} [initCb] function to call after initialization process
* @param {String} projectName name of project related to current selected resource, can be null if no resource is selected
*/
_loadProjectsCb: function (records, operation, success, initCb, projectName)
{
if (records.length == 0 || projectName && this._projectsCombo.getStore().find("name", projectName) == -1)
{
this._showNoAccessPanel(projectName ? "{{i18n PLUGINS_WORKSPACES_PROJECT_RESOURCE_NO_ACCESS}}" : "{{i18n PLUGINS_WORKSPACES_PROJECT_RESOURCE_NO_PROJECT}}");
if (typeof initCb == 'function')
{
initCb(null);
}
return;
}
this._hideNoAccessPanel();
if (projectName)
{
this._projectsCombo.setValue(projectName);
this._projectName = projectName;
}
else
{
var selectByDefault = this._projectsCombo.getStore().getData().items[0];
if (selectByDefault)
{
this._projectsCombo.setValue(selectByDefault);
this._projectName = selectByDefault.getData().name
}
}
this._getRootNodeConfiguration(initCb, this._projectName);
},
/**
* @private
* Get the root node configuration
* @param {Function} [initCb] function to call after initialization process
* @param {String} projectName name of project related to current selected resource, can be null if no resource is selected
*/
_getRootNodeConfiguration: function (initCb, projectName)
{
Ametys.data.ServerComm.callMethod({
role: "org.ametys.plugins.workspaces.documents.WorkspaceExplorerResourceDAO",
methodName: "getRootNodeInfo",
parameters: [projectName],
callback: {
scope: this,
handler: this._getRootNodeConfigurationCb,
ignoreOnError: false,
arguments: {
callback: initCb
}
},
errorMessage: {
msg: "{{i18n PLUGINS_WORKSPACES_UITOOL_PROJECT_RESSOURCES_ERROR}}",
category: this.self.getName()
}
});
},
/**
* @private
* Callback function invoked after retrieving the content's attachment root node
* @param {Object} response The root properties
* @param {Object[]} args the callback arguments
* @param {Function} args.callback the callback function to call after initialization process
*/
_getRootNodeConfigurationCb: function (response, args)
{
this.setRootNodes([Ext.apply(response, {
expanded: true,
allowDrag: false,
allowDrop: false,
editable : false,
isModifiable: false,
canCreateChild: false,
iconCls: 'a-tree-glyph ametysicon-folder249',
path: '/dummy/documents',
type: 'collection',
text: Ext.String.format("{{i18n PLUGINS_WORKSPACES_PROJECT_RESOURCE_ROOT_NODE}}", response.title),
})], args.callback);
},
/**
* Get the dock items
* @param {Object} config The initial tree panel configuration
* @return {Object[]} The dock items configuration
*/
getDockItems: function (config)
{
var dockItems = this.callParent(arguments);
// Panel when no access
dockItems.push({
dock: 'top',
hidden: false,
itemId: 'noaccess',
ui: 'tool-hintmessage',
text: "{{i18n PLUGINS_WORKSPACES_PROJECT_RESOURCE_NO_PROJECT}}",
xtype: 'button'
});
// Add project combobox
this._projectsCombo = Ext.create('Ext.form.field.ComboBox', Ext.apply(this._getProjectsComboConfig(), {
flex: .6,
style: { marginRight: '6px' }
}));
this._projectsCombo.on('select', Ext.bind(this._onSelectProject, this));
var projectToolbar = Ext.create('Ext.toolbar.Toolbar', {
dock: config.dock || 'top',
xtype: 'toolbar',
layout: {
type: 'hbox',
align: 'stretch'
},
border: false,
defaults : {
cls: 'ametys',
labelWidth: 55,
labelSeparator: ''
},
items: [this._projectsCombo]
});
// Insert projects combox at first position
dockItems = Ext.Array.insert(dockItems, 0, [projectToolbar]);
return dockItems;
},
/**
* Hide the panel showing there is no result.
* @private
*/
_hideNoAccessPanel: function ()
{
var noAccessPanel = this.getDockedItems('#noaccess')[0];
if (noAccessPanel)
{
noAccessPanel.hide();
this._projectsCombo.setDisabled(false);
// Enable all inputs and buttons of toolbar
this.getDockedItems('container[dock="top"]')[1]
.items
.filterBy((item, key) => item.xtype == 'button' || item.xtype == 'textfield')
.each(item => item.setDisabled(false));
}
},
/**
* Set the tree in no access mode
* @private
*/
_showNoAccessPanel: function (msg)
{
var noAccessPanel = this.getDockedItems('#noaccess')[0];
if (noAccessPanel)
{
noAccessPanel.setText(msg);
noAccessPanel.show();
this._projectsCombo.setDisabled(true);
// Disable all inputs and buttons of toolbar
this.getDockedItems('container[dock="top"]')[1]
.items
.filterBy((item, key) => item.xtype == 'button' || item.xtype == 'textfield')
.each(item => item.setDisabled(true));
}
},
/**
* @private
* Function called when a project is selected
* refresh the filter and resources
*/
_onSelectProject: function (combo, record)
{
var value = combo.getValue();
if (this._projectName == value)
{
// No change
return;
}
this._projectName = value;
this._clearSearchFilter();
// Reload tree with current project
this._getRootNodeConfiguration(null, this._projectName);
},
_clearSearchFilter: function(btn)
{
this.clearFilter();
//Container has changed place after project combobox insertion
this.getDockedItems('container[dock="top"]')[1].down('#search-filter-input').reset();
this._hideNoResultPanel();
},
/**
* @private
* Create the project combobox
*/
_getProjectsComboConfig: function ()
{
var store = Ext.create('Ext.data.Store', {
model: 'Ametys.plugins.workspaces.project.model.Project',
proxy: {
type: 'ametys',
role: 'org.ametys.plugins.workspaces.documents.WorkspaceExplorerResourceDAO',
methodName: 'getUserProjects2JSON',
methodArguments: [],
reader: {
type: 'json',
rootProperty: 'projects'
}
},
sorters: [{property: 'title', direction: 'ASC' }]
});
return {
xtype: 'combobox',
itemId: 'project-combo',
forceSelection: true,
triggerAction: 'all',
queryMode: 'local',
editable: true,
name: 'projects',
fieldLabel: "{{i18n PLUGINS_WORKSPACES_LINKS_PROJECTS_DIALOG_LABEL}}",
labelWidth: 40,
store: store,
valueField: 'name',
displayField: 'title',
iconClsField: 'type',
tpl: Ext.create('Ext.XTemplate',
'<ul class="x-list-plain">',
'<tpl for=".">',
'<li role="option" class="x-boundlist-item">{title} <em>({name})</em></li>',
'</tpl>',
'</ul>')
}
},
/**
* Create the tree store
* @param {Object} config The tree panel configuration
* @return {Ext.data.TreeStore} The created tree store
*/
createTreeStore: function (config)
{
return Ext.create('Ext.data.TreeStore', Ext.apply({
model: 'Ametys.explorer.tree.ExplorerTree.NodeEntry',
proxy: {
type: 'ametys',
plugin: 'workspaces',
url: 'child-nodes',
reader: {
type: 'xml',
rootProperty: 'Nodes',
record: '> Node'
}
},
autoLoad : false,
listeners: {
'beforeload': {fn: this._handleBeforeLoad, scope: this}
}
}, this._getStoreSortInfo()));
},
/**
* Get the resources the name matches the given value
* @param {String} value The value to match
* @param {Ext.data.Model[]} nodes The nodes where starting search
* @param {Boolean} [childNodesOnly] set to 'true' to filter the child nodes only.
* @param {Ext.data.TreeModel} [rootNode] The node to start filtering
* @private
*/
_getFilteredResources: function (value, nodes, childNodesOnly, rootNode)
{
if (!this._filterRunning)
{
this._filterRunning = true;
this._filterCounter = nodes.length;
this._hasFilterResult = false;
this._filteredPaths = {};
for (var i=0; i < nodes.length; i++)
{
var node = nodes[i];
Ametys.data.ServerComm.callMethod({
role: "org.ametys.plugins.workspaces.documents.WorkspaceExplorerResourceDAO",
methodName: 'filterResourcesByRegExp',
parameters: [node.getId(), value, this._allowedExtensions],
errorMessage: "{{i18n plugin.explorer:PLUGINS_EXPLORER_UITOOL_EXPLORER_TREE_SEARCH_ERROR}}",
callback: {
handler: this._getFilteredResourcesCb,
scope: this,
arguments: {
node: node,
childNodesOnly: childNodesOnly,
rootNode: rootNode || node
}
}
});
}
}
else
{
Ext.defer(this._getFilteredResources, 100, this, [value, nodes, childNodesOnly, rootNode]);
}
}
});