/*
* Copyright 2024 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 tool displays the properties of the current selected targets.
* It concatenates several properties tool.
* @private
*/
Ext.define('Ametys.plugins.cms.properties.PropertiesTool', {
extend: "Ametys.tool.Tool",
/**
* @property {String[]} _hiddenSections The hidden sections, by default the technical section is hidden
* @private
*/
_hiddenSections: [ 'technical-section' ],
/**
* @property {Ametys.ui.tool.ToolPanel} _emptyTab The tab with empty text to display if no other tabs have something to display.
* @private
*/
_emptyTab: null,
/**
* @property {Object} _tabs The properties tab into the properties tool by id
* @property {String} _tabs.key The "tool" id of the tab
* @property {Ametys.tool.Tool} _tabs.value A reference to the tab, in most cases, it is a {@link Ametys.plugins.cms.properties.PropertiesTab}
* @private
*/
_tabs: {},
/**
* @property {Object} _tabSelectionWeight The tab weight when selected
* @property {String} _tabSelectionWeight.key The "tool" id of the tab
* @property {Number} _tabSelectionWeight.value The weight of the tab, more the number is high, more the tab is prior to be selected by default
* @private
*/
_tabSelectionWeight: {},
statics: {
/**
* Copy value in clipboard
* @param {Object} button The copy button.
* @param {String} value The value to copy.
*/
copyToClipboard: function (button, value)
{
let onclickEvent = button.onclick;
if (onclickEvent != null)
{
// Remove the onclick to avoid multiple clicks on the button
// Class of the button can be saved safely
// Re-add the onclick at the end of the process
button.onclick = null;
$(document.body).append('<input type="text" id="input-text-clipboard" style="opacity: 0; position: absolute; top: -1000px;"/>');
let clipboardInput = document.getElementById("input-text-clipboard");
clipboardInput.value = value;
clipboardInput.select();
let spanIcon = Ext.get(button).child("span");
let saveCls = Object.keys(spanIcon.getClassMap());
spanIcon.removeCls(saveCls);
// Use the deprecated document.execCommand because navigator.clipboard is only compatible with HTTPS
if (document.execCommand("copy"))
{
spanIcon.addCls("ametysicon-sign-raw-check");
}
else
{
spanIcon.addCls("ametysicon-sign-raw-cross");
}
try
{
clipboardInput.remove();
}
finally
{
setTimeout(function() {
spanIcon.removeCls(["ametysicon-sign-raw-check", "ametysicon-sign-raw-cross"]);
spanIcon.addCls(saveCls);
button.onclick = onclickEvent;
}, 1000);
}
}
}
},
constructor: function(/*config*/)
{
this.callParent(arguments);
Ametys.message.MessageBus.on(Ametys.message.Message.SELECTION_CHANGING, this._onSelectionChanging, this);
},
createPanel: function ()
{
return Ext.create('Ext.tab.Panel', {
tabPosition: 'top',
tabRotation: 0,
tabBar: {flex: 1000000}, // HACK : with such weigh, the tab will be wide and the space before the tool (flex:1) will be narrow
tabBarHeaderPosition: 0,
stateful: true,
stateId: "AmetysPropertiesTool",
getState: Ext.bind(this._getState, this),
applyState: Ext.bind(this._applyState, this),
tools: [
// Show/hide technical data section
this._createShowHideSectionButton(
'technical-section',
"{{i18n UITOOL_DETAILS_HIDE_TECHNICAL_DATA}}",
"{{i18n UITOOL_DETAILS_SHOW_TECHNICAL_DATA}}"
)
],
style: {
margin: '5px'
},
header: {
style: {
paddingLeft: '0px',
paddingRight: '0px'
}
},
items: [],
listeners: {
// After render because we need the header and tabs to be accessible
"afterrender": {
fn: this._lazyItemsLoad,
options: { single: true },
scope: this
}
}
});
},
_createShowHideSectionButton: function(sectionName, enabledText, disabledText)
{
return {
xtype: 'button',
scope: this,
height: 22,
width: 22,
style: {
marginLeft: '3px'
},
enableToggle: true,
sectionName: sectionName,
enabledText: enabledText,
disabledText: disabledText,
iconCls: 'ametysicon-three115',
toggleHandler: this._updateHideStatus,
listeners: {
"afterrender": {
fn: function(btn)
{
let showStatus = !Ext.Array.contains(this._hiddenSections, sectionName);
btn.toggle(showStatus);
// Force the toggle handler because it is called only if the value is modified.
// We always want to call it on first load to update the section visibility
// and the tooltip
this._updateHideStatus(btn, showStatus);
},
options: { single: true },
scope: this
}
}
};
},
/**
* @private
* Load and add items to the content panel.
* This method is called when the panel is added to its parent.
* We do that because of conflics on listeners.
*/
_lazyItemsLoad: function()
{
// Add the empty tab
this._emptyTab = Ext.create(
'Ametys.ui.tool.ToolPanel',
{
items: [
{
xtype: 'component',
cls: 'a-panel-text-empty',
border: false,
html: "{{i18n UITOOL_DETAILS_NO_MATCHED_SELECTION}}"
}
]
}
);
this.getContentPanel().add(this._emptyTab);
this._emptyTab.tab.hide();
this.getContentPanel().getHeader().hide();
// Create tab factory, needed for events
let factory = Ext.create("Ametys.plugins.cms.properties.PropertiesTool.TabFactory", {});
// Load add tabs into the properties tool
// Add it to an array to be able to iterate on it
// Add the wrapper of the tab as items of the main panel
this._tabs = {};
for (let propertiesTabConf of this.initialConfig.propertiesTab)
{
// Create the tab and its wrapper
let tab = factory.openTool(propertiesTabConf);
tab.setParentTool(this.getId());
let wrapper = tab.createWrapper();
// Adjust the configuration of the tab wrapper
wrapper.setClosable(false);
// Add tab wrapper to content panel items
this.getContentPanel().add(wrapper);
// Initialize and refresh tool
tab.setParams({});
// Add tab to registered tabs
this._tabs[tab.id] = tab;
}
},
getMBSelectionInteraction: function()
{
return Ametys.tool.Tool.MB_TYPE_LISTENING;
},
refresh: function(manual)
{
// Update the tabs into the current tool and not the tool itself
if (this.isVisible())
{
for (let tab of Object.values(this._tabs))
{
if (
manual ||
tab.isOutOfDate() && !tab.isRefreshing()
)
{
tab.refresh();
}
}
}
},
isOutOfDate: function()
{
// Check the maximum status of out dating in children
let isOutOfDate = Ametys.tool.Tool.OOD_UPTODATE;
for (let tab of Object.values(this._tabs))
{
if (tab.isOutOfDate() && tab.isOutOfDate() > isOutOfDate)
{
isOutOfDate = tab.isOutOfDate();
}
}
return isOutOfDate;
},
onFocus: function()
{
// Call event method on tabs into the current tool
for (let tab of Object.values(this._tabs))
{
tab.onFocus();
}
this.callParent(arguments);
},
onBlur: function()
{
// Call event method on tabs into the current tool
for (let tab of Object.values(this._tabs))
{
tab.onBlur();
}
this.callParent(arguments);
},
onActivate: function()
{
// Call event method on tabs into the current tool
for (let tab of Object.values(this._tabs))
{
tab.onActivate();
}
this.callParent(arguments);
},
onDeactivate: function()
{
// Call event method on tabs into the current tool
for (let tab of Object.values(this._tabs))
{
tab.onDeactivate();
}
this.callParent(arguments);
},
onOpen: function()
{
// Call event method on tabs into the current tool
for (let tab of Object.values(this._tabs))
{
tab.onOpen();
}
this.callParent(arguments);
},
onClose: function(hadFocus)
{
// Call event method on tabs into the current tool
for (let tab of Object.values(this._tabs))
{
tab.onClose(hadFocus);
}
this._tabs = null;
this._emptyTab = null;
this._hiddenSections = [];
this._tabSelectionWeight = {};
this.callParent(arguments);
},
onShow: function()
{
// Call event method on tabs into the current tool
for (let tab of Object.values(this._tabs))
{
tab.onShow();
}
this.callParent(arguments);
},
_onBeforeManualClose: function(panel)
{
// Call event method on tabs into the current tool
for (let tab of Object.values(this._tabs))
{
tab._onBeforeManualClose(panel);
}
this.callParent(arguments);
},
/**
* Listener when the selection is going to changed. Registered only if #cfg-selection-target-id is specified.
* Will save the selected tab.
*/
_onSelectionChanging: function()
{
let contentPanel = this.getContentPanel();
let activeTab = contentPanel.getActiveTab();
// Nothing to do if the current activeTab is the empty tab
if (activeTab.uiTool != undefined)
{
// By default the weight of the selected tab is set to the max value between the one is saved and the number of displayed items
let activeTabWeight = 0;
let maxVisibleItemsWeight = 0;
contentPanel.items.each(
function(panelItem)
{
// Ignore the empty tab and tab with no data (not displayed)
if (panelItem.uiTool != undefined && !this._tabs[panelItem.uiTool].isEmpty())
{
activeTabWeight++;
let tabWeight = this._tabSelectionWeight[panelItem.uiTool] || 0;
if (panelItem != activeTab && tabWeight > maxVisibleItemsWeight)
{
maxVisibleItemsWeight = tabWeight;
}
}
},
this
);
// Save the weight for opened tab if superior to saved weight
let currentWeight = this._tabSelectionWeight[activeTab.uiTool] || 0;
// Add one to the max visible items weight to be superior to the max item (not including the current selection)
let maxWeight = Math.max(maxVisibleItemsWeight + 1, activeTabWeight);
// If max weight is superior to current weight, update it
if (currentWeight < maxWeight)
{
this._tabSelectionWeight[activeTab.uiTool] = maxWeight;
// Save the state
this.getContentPanel().saveState();
}
}
},
/**
* Show the item in the tab and update the active tab
* @param {Object} item The item
*/
showTab: function(item)
{
let contentPanel = this.getContentPanel();
// Show the tab when the item is showed
item.tab.show();
// Remove empty tab
contentPanel.getHeader().show();
let activeTab = contentPanel.getActiveTab();
// Select the item only if it is not currently selected and the active tab has no more data or is the empty tab (no uiTool)
// If it is the tab to show has a bigger weight than the currently selected tab, force the selection
if (activeTab == undefined || activeTab.uiTool == undefined || this._tabSelectionWeight[item.uiTool] > this._tabSelectionWeight[activeTab.uiTool] || (activeTab != item && this._tabs[activeTab.uiTool].isEmpty()))
{
contentPanel.setActiveTab(item);
}
},
/**
* Hide the item in the tab and update the active tab
* @param {Object} item The item
*/
hideTab: function(item)
{
let contentPanel = this.getContentPanel();
// Among active tabs, select the one with the biggest weight
let newActiveTab = null;
contentPanel.items.each(
function(panelItem)
{
// Empty panel is ignored, it has no uiTool defined
if (panelItem.uiTool != undefined)
{
if (panelItem != item && (newActiveTab == null || this._tabSelectionWeight[panelItem.uiTool] > this._tabSelectionWeight[newActiveTab.uiTool]) && !this._tabs[panelItem.uiTool].isEmpty())
{
newActiveTab = panelItem;
}
}
},
this
);
if (newActiveTab != null)
{
contentPanel.setActiveTab(newActiveTab);
}
// No more active tab
else
{
contentPanel.setActiveTab(this._emptyTab);
contentPanel.getHeader().hide();
}
// Hide the tab when the item is hidden
item.tab.hide();
},
/**
* @private
* Load the hide status of the section.
* @param {String} sectionName The name to identify the section
*/
_loadSectionHideStatus: function(sectionName)
{
this.getContentPanel().toggleCls(`hide-${sectionName}`, Ext.Array.contains(this._hiddenSections, sectionName));
},
/**
* @private
* Load the hide status of the section.
* @param {Ext.Button} btn The name to identify the section
* @param {Boolea} showStatus The toggled status of the button
*/
_updateHideStatus: function(btn, showStatus)
{
if (showStatus)
{
Ext.Array.remove(this._hiddenSections, btn.sectionName);
}
else
{
Ext.Array.include(this._hiddenSections, btn.sectionName);
}
// Hide or display sections
this._loadSectionHideStatus(btn.sectionName);
// Change the button tooltip
btn.setTooltip(showStatus ? btn.enabledText : btn.disabledText);
// Save the state
this.getContentPanel().saveState();
},
/**
* @private
* Apply state for stateful content panel.
*/
_applyState: function (state)
{
this._hiddenSections = state.hiddenSections || [];
this._tabSelectionWeight = state.tabSelectionWeight || {};
},
/**
* @private
* Get state of the content panel buttons.
*/
_getState: function ()
{
return {
hiddenSections: this._hiddenSections,
tabSelectionWeight: this._tabSelectionWeight
};
}
});