/*
* 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.
*/
/**
* The search menu for the ribbon panel.
* @inheritdoc
* @private
*/
Ext.define(
"Ametys.ui.fluent.ribbon.Ribbon.SearchMenu",
{
extend: "Ext.form.field.Text",
alias: 'widget.ametys.ribbon-searchmenu',
/**
* @cfg {String} [emptyText="Tell me what you want to do..."] @inheritdoc.
*/
/**
* @cfg {Object/Object[]} items The menu items to search into
* @cfg {String/String[]} items.ELEMENT.keywords Terms that will be part of the search (additionnaly to 'text' configuration)
*/
/**
* @cfg {Boolean} allowSearch When true, a menu item is added at the end to be able to search in the HelpTool.
*/
/**
* @property {Boolean} allowSearch True if search is allowed. See #cfg-allowSearch
* @private
*/
/**
* @private
* @cfg {String} ui Not overidable
*/
ui: 'ribbon-header-text',
/**
* @property {String} searchMenuCls The CSS classname to set on the search menu field
* @readonly
* @private
*/
searchMenuCls: 'a-fluent-searchmenu',
/**
* @property {String} searchMenuDefaultText If Ametys.ui.fluent.ribbon.Ribbon#cfg-searchMenu is not a string, this text will be used as default value)
* @readonly
* @private
*/
searchMenuDefaultText: "{{i18n plugin.core-ui:PLUGINS_CORE_UI_WORKSPACE_AMETYS_RIBBON_SEARCHMENU_PLACEHOLDER}}",
/**
* @property {String} searchMenuHelpItem A template string for the final item menu to open help with that search. An object with a string property "text" is provided to the template.
* @readonly
* @private
*/
searchMenuHelpItem: "{{i18n plugin.core-ui:PLUGINS_CORE_UI_WORKSPACE_AMETYS_RIBBON_SEARCHMENU_SEARCHITEM_LABEL}}",
/**
* @readonly
* @private
* @property {Number} searchMenuMaxResults The max number of results displayed
*/
searchMenuMaxResults: 15,
/**
* @readonly
* @private
* @property {Number} searchMenuMaxDisabledResults The max number of results which are disabled controllers displayed
*/
searchMenuMaxDisabledResults: 5,
/**
* @private
* @property {Ext.menu.Menu} _menu The menu instance for search results.
*/
/**
* @readonly
* @private
* @property {Number} searchAfterTime Time in millisecond to wait after last change to launch a search
*/
searchAfterTime: 500,
/**
* @property {Number} codeStep The current step for "the code"
*/
codeStep: 0,
/**
* @private
* @property {Array} _lastResults The results of the last search by lunr.
*/
constructor: function(config)
{
var me = this;
config = config || {};
config.cls = Ext.Array.from(config.cls);
config.cls.push(this.searchMenuCls);
config.emptyText = config.emptyText || this.searchMenuDefaultText;
config.items = Ext.Array.from(config.items);
config.items.push({
id: "enabledControllersGroup",
text: "<b>{{i18n PLUGINS_CORE_UI_WORKSPACE_AMETYS_RIBBON_SEARCHMENU_ITEMS_AVAILABLE}}</b>",
hideOnClick: false,
focusable: false
}, {
xtype: 'menuseparator',
id: 'groupsSeparator'
}, {
id: "disabledControllersGroup",
text: "<b>{{i18n PLUGINS_CORE_UI_WORKSPACE_AMETYS_RIBBON_SEARCHMENU_ITEMS_UNAVAILABLE}}</b>",
hideOnClick: false,
focusable: false
});
this.searchMenuHelpItem = Ext.create("Ext.Template", this.searchMenuHelpItem);
if (config.allowSearch)
{
this.allowSearch = true;
config.items.push("-");
config.items.push({
id: 'openHelpButton',
handler: this._openHelp,
scope: this
});
delete config.allowSearch;
}
config.width = config.width | 200;
this._menu = Ext.create("Ext.menu.Menu", {
defaultAlign: "tr-br",
ui: 'ribbon-menu',
listeners: {
'close': this._onMenuClose,
scope: this
},
items: config.items
})
delete config.items;
this.callParent([config]);
this.on('click', this._reopenLastSearchIfAvailable, this, { element: 'inputEl' });
this.on('change', Ext.Function.createBuffered(this._searchNow, this.searchAfterTime, this));
this.on('specialkey', this._onSpecialKey);
this.on('render', this._setMinWidthMenu, this);
this.on('blur', this._onBlur, this);
},
/**
* @private
* Set the minimum width of the menu to the value of the search menu width
*/
_setMinWidthMenu: function()
{
this._menu.setMinWidth(this.getWidth());
},
/**
* @private
* On blur we should remove value if menu is closed
*/
_onBlur: function()
{
if (!this._menu.isVisible())
{
this.reset();
}
},
/**
* @private
* When the menu is closed
*/
_onMenuClose: function()
{
if (!this.hasFocus)
{
this.reset();
}
},
/**
* @private
* Open the help
*/
_openHelp: function()
{
Ametys.tool.ToolsManager.openTool("uitool-help", {searchQuery: this.getValue()});
},
/**
* @private
* Event when a special key is pressed
* @param {Ext.form.field.Text} input The search input
* @param {Ext.event.Event} event The key event
*/
_onSpecialKey: function(input, event)
{
if (event.getKey() == event.UP)
{
if (this.codeStep == 0 || this.codeStep == 1)
{
this.codeStep++;
}
else
{
this.codeStep = 0;
}
}
else if (event.getKey() == event.LEFT)
{
if (this.codeStep == 4 || this.codeStep == 6)
{
this.codeStep++;
}
else
{
this.codeStep = 0;
}
}
else if (event.getKey() == event.RIGHT)
{
if (this.codeStep == 5 || this.codeStep == 7)
{
this.codeStep++;
}
else
{
this.codeStep = 0;
}
}
else if (event.getKey() == event.DOWN)
{
if (this.codeStep == 2 || this.codeStep == 3)
{
this.codeStep++;
}
else
{
this.codeStep = 0;
this._reopenLastSearchIfAvailable();
this._menu.focus();
}
}
else
{
this.codeStep = 0;
}
},
/**
* @private
* Launch a search now
*/
_searchNow: function()
{
var me = this;
if (!this.getValue())
{
this._menu.hide();
return;
}
if (this.getValue().toLowerCase() == 'ba' && this.codeStep == 8)
{
this.codeStep = 0;
this._loadCompanions();
this.reset();
return;
}
if (this.allowSearch)
{
// Update the search menu items
var value = this.getValue();
if (value.length > 22)
{
value = value.substring(0, 20) + "…";
}
this._menu.items.getByKey("openHelpButton").setText(this.searchMenuHelpItem.apply({ text: value }));
}
// Search with lunr.js the relevant controllers ids
var foundControllers = lunr.controllersIndex.search(this.getValue());
this._lastResults = foundControllers;
this._displayResults(foundControllers);
},
/**
* @private
* Display the results of the query
* @param {Array} controllers The controllers found by lunr
*/
_displayResults: function(controllers)
{
var me = this;
// Browse all items for hiding them
this._menu.items.each(function(item) {
if (item.getId() != "openHelpButton" && Ext.ClassManager.getName(item) != "Ext.menu.Separator")
{
item.hide();
}
});
// Retrieve the controllers and put the top 'searchMenumaxResults' results in arrays
var enableItems = [],
disableItems = [];
Ext.each(controllers, function(controller, index) {
if (enableItems.length + disableItems.length >= this.searchMenuMaxResults) {return false;}
var menuItem = this._menu.items.getByKey( controller.ref );
if (!menuItem.isDisabled())
{
enableItems.push(menuItem);
}
else if (menuItem.isDisabled() && disableItems.length < this.searchMenuMaxDisabledResults)
{
disableItems.push(menuItem);
}
}, this);
// Finally reverse the lists, put the items at the top and show them
Ext.Array.forEach(disableItems.reverse(), function (item) {
me._menu.insert(0, item);
item.show();
});
var disableGroup = me._menu.items.getByKey('disabledControllersGroup');
me._menu.insert(0, disableGroup);
disableGroup.setVisible(disableItems.length > 0);
var separator = me._menu.items.getByKey('groupsSeparator');
me._menu.insert(0, separator);
separator.setVisible(disableItems.length > 0 && enableItems.length > 0);
Ext.Array.forEach(enableItems.reverse(), function (item) {
me._menu.insert(0, item);
item.show();
});
var enableGroup = me._menu.items.getByKey('enabledControllersGroup');
me._menu.insert(0, enableGroup);
enableGroup.setVisible(enableItems.length > 0);
// Show the open help button separator (the item just before the last one)
if (this.allowSearch)
{
this._menu.items.get(this._menu.items.getCount() - 2).setVisible(controllers.length > 0);
}
this._menu.showBy(this);
},
/**
* @private
* Load companions
*/
_loadCompanions: function(msg)
{
var me = this;
if (!me._agents)
{
me._agents = {};
me._currentAgent = null;
me._companions = ['Bonzi', 'Clippy', 'F1', 'Genie', 'Genius', 'Links', 'Merlin', 'Peedy', 'Rocky', 'Rover'];
Ext.override(Ametys.ui.fluent.ribbon.Ribbon.Notificator, {
notify: function(config)
{
this.callParent(arguments);
if (me._currentAgent)
{
var text = config.isModel ?
(config.get('title')) :
(config.title);
me._currentAgent.speak(text);
}
}
});
Ext.override(Ametys.ui.fluent.ribbon.Ribbon.Notificator.Toast, {
show: function()
{
if (!me._currentAgent)
{
this.callParent(arguments);
}
else
{
Ext.destroy(this);
}
}
});
}
function loadAgentRnd()
{
if (me._currentAgent != null)
{
me._currentAgent.hide();
me._currentAgent = null;
return;
}
var index = Math.floor(Math.random() * me._companions.length);
if (me._agents[me._companions[index]])
{
me._agents[me._companions[index]].show();
me._currentAgent = me._agents[me._companions[index]];
}
else
{
clippy.load(me._companions[index], function (agent) {
me._agents[me._companions[index]] = agent;
agent.show()
me._currentAgent = agent;
});
}
}
if (!this._companionsInitialization)
{
/**
* @private
* @property {Boolean} _backLoadInitialized Was the back load already initialize
*/
this._companionsInitialization = 'loading';
Ametys.loadStyle("//clippy.js.s3.amazonaws.com/clippy.css");
if (!window.jQuery)
{
Ametys.loadScript("//code.jquery.com/jquery-1.11.3.min.js");
}
Ametys.loadScript("//clippy.js.s3.amazonaws.com/clippy.js", function() {
me._companionsInitialization = 'loaded';
clippy.BASE_PATH = '//clippy.js.s3.amazonaws.com/Agents/';
loadAgentRnd();
});
}
else if (this._companionsInitialization == 'loaded')
{
loadAgentRnd();
}
},
/**
* @private
* Reshow last menu
*/
_reopenLastSearchIfAvailable: function()
{
if (this.getValue() && this._lastResults)
{
this._displayResults(this._lastResults);
}
}
}
);