N°3649 - Activity panel: Continue improvement following the alpha feedbacks

- Add possibility to choose which caselogs are displayed in any tab
- Change case log entry form submit button label from "Add entry" to "Save"
This commit is contained in:
Molkobain
2021-01-27 21:58:21 +01:00
parent 222fab27d1
commit 977d1d0246
8 changed files with 238 additions and 30 deletions

View File

@@ -60,9 +60,20 @@ $ibo-activity-panel--tab-toolbar-right-actions--elements-spacing: 16px !default;
$ibo-activity-panel--tab-toolbar-action--elements-separator-content: "-" !default;
$ibo-activity-panel--tab-toolbar-action--elements-separator-margin-x: 8px !default;
$ibo-activity-panel--tab-toolbar-info-icon--margin-left: 8px !default;
$ibo-activity-panel--tab-toolbar-filter--sibling-spacing: 18px !default;
$ibo-activity-panel--tab-toolbar-filter--checkbox-margin-right: 8px !default;
$ibo-activity-panel--tab-toolbar-info-icon--margin-left: 8px !default;
$ibo-activity-panel--filter-options-toggler--margin-left: 0.5rem !default;
$ibo-activity-panel--filter-options--padding-x: 12px !default;
$ibo-activity-panel--filter-options--padding-y: 8px !default;
$ibo-activity-panel--filter-options--top: 24px !default;
$ibo-activity-panel--filter-options--left: -1 * $ibo-activity-panel--filter-options--padding-x !default;
$ibo-activity-panel--filter-options--max-width: 200px !default;
$ibo-activity-panel--filter-options--background-color: $ibo-activity-panel--tab-toolbar--background-color !default;
$ibo-activity-panel--filter-options--border-radius: $ibo-border-radius-300 !default;
$ibo-activity-panel--filter-option--sibling-spacing: 8px !default;
$ibo-activity-panel--filter-option-input--margin-right: 0.5rem !default;
/* - Body */
$ibo-activity-panel--body--padding-y: $ibo-activity-panel--padding-x !default;
@@ -233,8 +244,49 @@ $ibo-activity-panel--add-caselog-entry-button--icon--line-height: 33px !default;
}
}
.ibo-activity-panel--tab-toolbar-action{
position: relative;
@extend %ibo-fully-centered-content;
}
.ibo-activity-panel--filter{
cursor: pointer;
}
.ibo-activity-panel--filter-options-toggler{
margin-left: $ibo-activity-panel--filter-options-toggler--margin-left;
&.ibo-is-closed{
transform: rotateX(180deg);
+ .ibo-activity-panel--filter-options{
display: none;
}
}
}
.ibo-activity-panel--filter-options{
position: absolute;
z-index: 1; /* To be over the activity body */
display: flex;
flex-direction: column;
top: $ibo-activity-panel--filter-options--top;
left: $ibo-activity-panel--filter-options--left;
max-width: $ibo-activity-panel--filter-options--max-width;
padding: $ibo-activity-panel--filter-options--padding-y $ibo-activity-panel--filter-options--padding-x;
background-color: $ibo-activity-panel--filter-options--background-color;
border-radius: $ibo-activity-panel--filter-options--border-radius;
@extend %ibo-elevation-300;
}
.ibo-activity-panel--filter-option{
cursor: pointer;
@extend %ibo-vertically-centered-content;
@extend %ibo-text-truncated-with-ellipsis;
&:not(:first-child){
margin-top: $ibo-activity-panel--filter-option--sibling-spacing;
}
}
.ibo-activity-panel--filter-option-input{
margin-right: $ibo-activity-panel--filter-option-input--margin-right;
}
/* Body */
.ibo-activity-panel--body{

View File

@@ -403,8 +403,6 @@ Dict::Add('EN US', 'English', 'English', array(
'UI:Button:Close' => 'Close',
'UI:Button:Apply' => 'Apply',
'UI:Button:Send' => 'Send',
'UI:Button:AddEntryAndWithChoice' => 'Add entry and...',
'UI:Button:AddEntryToWithChoice' => 'Add entry to...',
'UI:Button:Back' => ' << Back ',
'UI:Button:Restart' => ' |<< Restart ',
'UI:Button:Next' => ' Next >> ',

View File

@@ -42,9 +42,13 @@ $(function()
tab_toggler: '[data-role="ibo-activity-panel--tab-toggler"]',
tab_title: '[data-role="ibo-activity-panel--tab-title"]',
tab_toolbar: '[data-role="ibo-activity-panel--tab-toolbar"]',
tab_toolbar_action: '[data-role="ibo-activity-panel--tab-toolbar-action"]',
caselog_tab_open_all: '[data-role="ibo-activity-panel--caselog-open-all"]',
caselog_tab_close_all: '[data-role="ibo-activity-panel--caselog-close-all"]',
activity_filter: '[data-role="ibo-activity-panel--activity-filter"]',
activity_filter: '[data-role="ibo-activity-panel--filter"]',
activity_filter_options: '[data-role="ibo-activity-panel--filter-options"]',
activity_filter_options_toggler: '[data-role="ibo-activity-panel--filter-options-toggler"]',
activity_filter_option_input: '[data-role="ibo-activity-panel--filter-option-input"]',
authors_count: '[data-role="ibo-activity-panel--tab-toolbar-info-authors-count"]',
messages_count: '[data-role="ibo-activity-panel--tab-toolbar-info-messages-count"]',
compose_button: '[data-role="ibo-activity-panel--add-caselog-entry-button"]',
@@ -102,9 +106,17 @@ $(function()
});
// Tabs toolbar
// - Change on an activity filter
// - Change on a filter
this.element.find(this.js_selectors.activity_filter).on('change', function(){
me._onActivityFilterChange($(this));
me._onFilterChange($(this));
});
// - Click on a filter options toggler
this.element.find(this.js_selectors.activity_filter_options_toggler).on('click', function(oEvent){
me._onFilterOptionsTogglerClick(oEvent, $(this));
})
// - Change on a filter option
this.element.find(this.js_selectors.activity_filter_option_input).on('change', function(){
me._onFilterOptionChange($(this));
});
// - Click on open all case log messages
this.element.find(this.js_selectors.caselog_tab_open_all).on('click', function(){
@@ -182,8 +194,48 @@ $(function()
this._ShowActivityTab();
}
},
_onActivityFilterChange: function(oInputElem)
/**
* @param oInputElem {Object} jQuery object representing the filter's input
* @private
*/
_onFilterChange: function(oInputElem)
{
// Propagate on filter options
if ('caselogs' === oInputElem.attr('name')) {
oInputElem.closest(this.js_selectors.tab_toolbar_action).find(this.js_selectors.activity_filter_option_input).prop('checked', oInputElem.prop('checked'));
}
this._ApplyEntriesFilters();
},
/**
* @param oEvent {Object} jQuery event
* @param oElem {Object} jQuery object representing the filter's options toggler
* @private
*/
_onFilterOptionsTogglerClick: function(oEvent, oElem)
{
oEvent.preventDefault();
this._ToggleFilterOptions(oElem.closest(this.js_selectors.tab_toolbar_action).find(this.js_selectors.activity_filter));
},
/**
* @param oInputElem {Object} jQuery object representing the filter option's input
* @private
*/
_onFilterOptionChange: function(oInputElem)
{
const oFilterOptionsElem = oInputElem.closest(this.js_selectors.activity_filter_options);
const oFilterInputElem = oInputElem.closest(this.js_selectors.tab_toolbar_action).find(this.js_selectors.activity_filter);
// If all options checked, checked the parent
if (oFilterOptionsElem.find(this.js_selectors.activity_filter_option_input + ':not(:checked)').length === 0) {
oFilterInputElem.prop('checked', true);
}
// Else, uncheck the parent
else {
oFilterInputElem.prop('checked', false);
}
this._ApplyEntriesFilters();
},
_onCaseLogOpenAllClick: function(oIconElem)
@@ -235,13 +287,33 @@ $(function()
oEntryElem.toggleClass(this.css_classes.is_opened);
},
/**
* Callback for mouse clicks that should interact with the activity panel (eg. Clic outside a dropdown should close it, ...)
*
* @param oEvent {Object} The jQuery event
* @private
*/
_onBodyClick: function(oEvent)
{
// Hide all filters' options only if click wasn't on one of them
if(($(oEvent.target).closest(this.js_selectors.activity_filter_options_toggler).length === 0)
&& $(oEvent.target).closest(this.js_selectors.activity_filter_options).length === 0) {
this._HideAllFiltersOptions();
}
},
/**
* Callback for key hits that should interact with the activity panel (eg. "Esc" to close all dropdowns, ...)
*
* @param oEvent {Object} The jQuery event
* @private
*/
_onBodyKeyUp: function(oEvent)
{
// On "Esc" key
if(oEvent.key === 'Escape') {
// Hide all filters's options
this._HideAllFiltersOptions();
}
},
// Methods
@@ -377,6 +449,48 @@ $(function()
}
return iIdx;
},
/**
* Show the oFilterElem's options
*
* @param oFilterElem {Object}
* @private
*/
_ShowFilterOptions: function(oFilterElem)
{
oFilterElem.parent().find(this.js_selectors.activity_filter_options_toggler).removeClass(this.css_classes.is_closed);
},
/**
* Hide the oFilterElem's options
*
* @param oFilterElem {Object}
* @private
*/
_HideFilterOptions: function(oFilterElem)
{
oFilterElem.parent().find(this.js_selectors.activity_filter_options_toggler).addClass(this.css_classes.is_closed);
},
/**
* Toggle the visibility of the oFilterElem's options
*
* @param oFilterElem {Object}
* @private
*/
_ToggleFilterOptions: function(oFilterElem)
{
oFilterElem.parent().find(this.js_selectors.activity_filter_options_toggler).toggleClass(this.css_classes.is_closed);
},
/**
* Hide all the filters' options from all toolbars
*
* @private
*/
_HideAllFiltersOptions: function()
{
const me = this;
this.element.find(this.js_selectors.activity_filter_options_toggler).each(function(){
me._HideFilterOptions($(this));
});
},
// - Helpers on messages
_OpenMessage: function(oEntryElem)
@@ -443,13 +557,18 @@ $(function()
const aTargetEntryTypes = $(this).attr('data-target-entry-types').split(' ');
const sCallbackMethod = ($(this).prop('checked')) ? '_ShowEntries' : '_HideEntries';
for(let iIdx in aTargetEntryTypes)
let aFilterOptions = [];
$(this).closest(me.js_selectors.tab_toolbar_action).find(me.js_selectors.activity_filter_option_input + ':checked').each(function(){
aFilterOptions.push($(this).val());
});
for(let sTargetEntryType of aTargetEntryTypes)
{
me[sCallbackMethod](aTargetEntryTypes[iIdx]);
me[sCallbackMethod](sTargetEntryType, aFilterOptions);
}
});
// Show only the last visible entry's medallion of a group (can be done through CSS yet 😕)
// Show only the last visible entry's medallion of a group (cannot be done through CSS yet 😕)
this.element.find(this.js_selectors.entry_group).each(function(){
$(this).find(me.js_selectors.entry_medallion).removeClass(me.css_classes.is_visible);
$(this).find(me.js_selectors.entry + ':visible:last').find(me.js_selectors.entry_medallion).addClass(me.css_classes.is_visible);
@@ -471,23 +590,46 @@ $(function()
/**
* Show entries of type sEntryType but do not hide the others
*
* @param sEntryType string
* @param sEntryType {string}
* @private
*/
_ShowEntries: function(sEntryType)
{
this.element.find(this.js_selectors.entry+'[data-entry-type="'+sEntryType+'"]').removeClass(this.css_classes.is_hidden);
let sEntrySelector = this.js_selectors.entry+'[data-entry-type="'+sEntryType+'"]';
// Note: Unlike, the _HideEntries() method, we don't have a special case for caselogs options. This is because this
// method is called when the main filter is checked, which means that all options are checked as well, so there is no
// need for a special treatment.
this.element.find(sEntrySelector).removeClass(this.css_classes.is_hidden);
this._UpdateEntryGroupsVisibility();
},
/**
* Hide entries of type sEntryType but do not hide the others
*
* @param sEntryType string
* @param sEntryType {string}
* @param aOptions {Array} Options for the sEntryType, used differently depending on the sEntryType
* @private
*/
_HideEntries: function(sEntryType)
_HideEntries: function(sEntryType, aOptions = [])
{
this.element.find(this.js_selectors.entry+'[data-entry-type="'+sEntryType+'"]').addClass(this.css_classes.is_hidden);
let sEntrySelector = this.js_selectors.entry+'[data-entry-type="'+sEntryType+'"]';
// Special case for options
if ((this.enums.entry_types.caselog === sEntryType) && (aOptions.length > 0)) {
// Hide all caselogs...
this._HideEntries(sEntryType);
// ... except the selected
for (let sCaseLogAttCode of aOptions) {
this.element.find(sEntrySelector + '[data-entry-caselog-attribute-code="' + sCaseLogAttCode + '"]').removeClass(this.css_classes.is_hidden);
}
}
// General case
else {
this.element.find(sEntrySelector).addClass(this.css_classes.is_hidden);
}
this._UpdateEntryGroupsVisibility();
},
_UpdateEntryGroupsVisibility: function()

View File

@@ -37,7 +37,7 @@ class CaseLogEntryFormFactory
$oCaseLogEntryForm = new CaseLogEntryForm();
$oCaseLogEntryForm->SetSubmitModeFromHostObjectMode($sObjectMode)
->AddMainActionButtons(static::PrepareCancelButton())
->AddMainActionButtons(static::PrepareSendButton()->SetLabel(Dict::S('UI:Button:AddEntryAndWithChoice')))
->AddMainActionButtons(static::PrepareSaveButton())
->SetSendButtonPopoverMenu(static::PrepareSendActionSelectionPopoverMenu($oObject, $sCaseLogAttCode));
return $oCaseLogEntryForm;
@@ -54,9 +54,9 @@ class CaseLogEntryFormFactory
/**
* @return \Combodo\iTop\Application\UI\Base\Component\Button\Button
*/
protected static function PrepareSendButton(): Button
protected static function PrepareSaveButton(): Button
{
$oButton = ButtonUIBlockFactory::MakeForPrimaryAction(Dict::S('UI:Button:Send'), 'send', 'send');
$oButton = ButtonUIBlockFactory::MakeForPrimaryAction(Dict::S('UI:Button:Save'), 'save', 'save');
$oButton->SetIconClass('fas fa-paper-plane');
return $oButton;

View File

@@ -37,7 +37,7 @@
</div>
<div class="ibo-activity-panel--tabs-toolbars">
{% for sCaseLogAttCode, aCaseLogData in oUIBlock.GetCaseLogTabs() %}
{{ include('base/layouts/activity-panel/tab-toolbar/caselog.html.twig', {oUIBlock: oUIBlock, iRank: loop.index, bIsActive: (loop.index == 1)}) }}
{{ include('base/layouts/activity-panel/tab-toolbar/caselog.html.twig', {oUIBlock: oUIBlock, iRank: loop.index, bIsActive: (loop.index == 1), aFilteredCaseLogsAttCodes: [sCaseLogAttCode]}) }}
{% endfor %}
{{ include('base/layouts/activity-panel/tab-toolbar/activity.html.twig', {oUIBlock: oUIBlock, bIsActive: (oUIBlock.GetCaseLogTabs()|length == 0)}) }}
</div>

View File

@@ -11,7 +11,7 @@
{% for sCaseLogAttCode, aCaseLogData in oUIBlock.GetCaseLogTabs() %}
<label class="ibo-activity-panel--tab-toolbar-action ibo-activity-panel--tab-toggler-for-caselog-{{ loop.index }} "
data-tooltip-content="{{ 'UI:Layout:ActivityPanel:Tab:Activity:Toolbar:CaselogsFilter:Tooltip'|dict_s }}"
data-role="ibo-activity-panel--activity-filter"
data-role="ibo-activity-panel--filter"
data-target-entry-types="caselog"
data-entry-caselog-attribute-code="{{ sCaseLogAttCode }}" >
<span class=".ibo-activity-panel--tab-toggler-for-caselog-{{ loop.index }} ibo-activity-panel--tab-title-decoration"></span>
@@ -22,14 +22,14 @@
{% if oUIBlock.HasStates() %}
<label class="ibo-activity-panel--tab-toolbar-action"
data-tooltip-content="{{ 'UI:Layout:ActivityPanel:Tab:Activity:Toolbar:TransitionsFilter:Tooltip'|dict_s }}"
data-role="ibo-activity-panel--activity-filter"
data-role="ibo-activity-panel--filter"
data-target-entry-types="transition">
{{ 'UI:Layout:ActivityPanel:Tab:Activity:Toolbar:TransitionsFilter:Title'|dict_s }}
</label>
{% endif %}
<label class="ibo-activity-panel--tab-toolbar-action"
data-tooltip-content="{{ 'UI:Layout:ActivityPanel:Tab:Activity:Toolbar:EditsFilter:Tooltip'|dict_s }}"
data-role="ibo-activity-panel--activity-filter"
data-role="ibo-activity-panel--filter"
data-target-entry-types="edits">
{{ 'UI:Layout:ActivityPanel:Tab:Activity:Toolbar:EditsFilter:Title'|dict_s }}
</label>

View File

@@ -1,5 +1,5 @@
// Change on activity filters
$('[data-role="ibo-activity-panel--activity-filter"]').on('click', function(){
$('[data-role="ibo-activity-panel--filter"]').on('click', function(){
const sTabType = $(this).attr('data-target-entry-types');
if(sTabType=="caselog")
{

View File

@@ -2,7 +2,7 @@
data-role="ibo-activity-panel--tab-toolbar"
data-tab-type="{% block bDataTabType %}{% endblock %}"
{% block bExtraDataAttributes %}{% endblock %}>
{% block btabToolbarFirstRow %}
{% block bTabToolbarFirstRow %}
<div class="ibo-activity-panel--tab-toolbar-actions">
{% block bTabToolbarActions %}
<div class="ibo-activity-panel--tab-toolbar-left-actions">
@@ -17,22 +17,38 @@
</div>
<div class="ibo-activity-panel--tab-toolbar-middle-actions">
{% if oUIBlock.HasCaseLogTabs() %}
<label class="ibo-activity-panel--tab-toolbar-action"
<label class="ibo-activity-panel--tab-toolbar-action" data-role="ibo-activity-panel--tab-toolbar-action"
data-tooltip-content="{{ 'UI:Layout:ActivityPanel:Tab:Activity:Toolbar:CaselogsFilter:Tooltip'|dict_s }}">
<input type="checkbox" name="caselogs" data-role="ibo-activity-panel--activity-filter" data-target-entry-types="caselog" checked>
<input type="checkbox" name="caselogs" data-role="ibo-activity-panel--filter" data-target-entry-types="caselog" {% if (aFilteredCaseLogsAttCodes is not defined) or (aFilteredCaseLogsAttCodes is empty) %}checked{% endif %}>
{{ 'UI:Layout:ActivityPanel:Tab:Activity:Toolbar:CaselogsFilter:Title'|dict_s }}
{% if oUIBlock.GetCaseLogTabs()|length > 0 %}
<a class="ibo-activity-panel--filter-options-toggler ibo-is-closed" data-role="ibo-activity-panel--filter-options-toggler" href="#">
<span class="fas fa-caret-up"></span>
</a>
<div class="ibo-activity-panel--filter-options" data-role="ibo-activity-panel--filter-options">
{% for sCaseLogAttCode, aCaseLogData in oUIBlock.GetCaseLogTabs() %}
<label class="ibo-activity-panel--filter-option" data-role="ibo-activity-panel--filter-option" title="{{ aCaseLogData['title'] }}">
<input type="checkbox" name="caselog" value="{{ sCaseLogAttCode }}"
class="ibo-activity-panel--filter-option-input"
data-role="ibo-activity-panel--filter-option-input"
{% if (aFilteredCaseLogsAttCodes is not defined) or (sCaseLogAttCode in aFilteredCaseLogsAttCodes) %}checked{% endif %}>
{{ aCaseLogData['title'] }}
</label>
{% endfor %}
</div>
{% endif %}
</label>
{% endif %}
{% if oUIBlock.HasStates() %}
<label class="ibo-activity-panel--tab-toolbar-action"
data-tooltip-content="{{ 'UI:Layout:ActivityPanel:Tab:Activity:Toolbar:TransitionsFilter:Tooltip'|dict_s }}">
<input type="checkbox" name="transitions" data-role="ibo-activity-panel--activity-filter" data-target-entry-types="transition" checked>
<input type="checkbox" name="transitions" data-role="ibo-activity-panel--filter" data-target-entry-types="transition" checked>
{{ 'UI:Layout:ActivityPanel:Tab:Activity:Toolbar:TransitionsFilter:Title'|dict_s }}
</label>
{% endif %}
<label class="ibo-activity-panel--tab-toolbar-action"
data-tooltip-content="{{ 'UI:Layout:ActivityPanel:Tab:Activity:Toolbar:EditsFilter:Tooltip'|dict_s }}">
<input type="checkbox" name="edits" data-role="ibo-activity-panel--activity-filter" data-target-entry-types="edits" checked>
<input type="checkbox" name="edits" data-role="ibo-activity-panel--filter" data-target-entry-types="edits" checked>
{{ 'UI:Layout:ActivityPanel:Tab:Activity:Toolbar:EditsFilter:Title'|dict_s }}
</label>
</div>