diff --git a/css/backoffice/layout/blocks-integrations/_panel-with-tab-container.scss b/css/backoffice/layout/blocks-integrations/_panel-with-tab-container.scss index 1c0934465..c6970cb86 100644 --- a/css/backoffice/layout/blocks-integrations/_panel-with-tab-container.scss +++ b/css/backoffice/layout/blocks-integrations/_panel-with-tab-container.scss @@ -12,11 +12,16 @@ $ibo-panel-with-tab-container--tab-container--margin-x: -1 * $ibo-panel--body--p > .ibo-panel--body { > .ibo-tab-container { margin-top: $ibo-panel-with-tab-container--body--padding-top; + height: 80vh; > .ibo-tab-container--tabs-list{ margin-left: $ibo-panel-with-tab-container--tabs-list--margin-x; margin-right: $ibo-panel-with-tab-container--tabs-list--margin-x; } + > .ibo-tab-container--tab-container-list{ + height: 100%; + overflow-y: auto; + } > .ibo-tab-container--tab-container{ margin-left: $ibo-panel-with-tab-container--tab-container--margin-x; margin-right: $ibo-panel-with-tab-container--tab-container--margin-x; diff --git a/dictionaries/ui/pages/en.dictionary.itop.preferences.php b/dictionaries/ui/pages/en.dictionary.itop.preferences.php index 9f3928703..836079dc6 100644 --- a/dictionaries/ui/pages/en.dictionary.itop.preferences.php +++ b/dictionaries/ui/pages/en.dictionary.itop.preferences.php @@ -26,4 +26,8 @@ Dict::Add('EN US', 'English', 'English', array( 'UI:RichText:ToolbarState' => 'Toolbar default state', 'UI:RichText:ToolbarState:Expanded' => 'Expanded', 'UI:RichText:ToolbarState:Collapsed' => 'Collapsed', -)); \ No newline at end of file + 'UI:Tabs:Preferences' => 'Tabs', + 'UI:Tabs:Scrollable:Label' => 'Tabs navigation', + 'UI:Tabs:Scrollable:Classic' => 'Classic', + 'UI:Tabs:Scrollable:Scrollable' => 'Scrollable', +)); diff --git a/js/ScrollMagic.min.js b/js/ScrollMagic.min.js new file mode 100644 index 000000000..003f0f920 --- /dev/null +++ b/js/ScrollMagic.min.js @@ -0,0 +1,2 @@ +/*! ScrollMagic v2.0.8 | (c) 2020 Jan Paepke (@janpaepke) | license & info: http://scrollmagic.io */ +!function(e,t){"function"==typeof define&&define.amd?define(t):"object"==typeof exports?module.exports=t():e.ScrollMagic=t()}(this,function(){"use strict";function _(){}_.version="2.0.8","undefined"!=typeof window&&window.addEventListener("mousewheel",void 0);var P="data-scrollmagic-pin-spacer";_.Controller=function(e){function t(){var e,t,n;v&&u&&(e=R.type.Array(u)?u:f.slice(0),u=!1,t=d,0!=(n=(d=l.scrollPos())-t)&&(h=0t.scrollOffset()?1:-1}),t}return this.addScene=function(e){if(R.type.Array(e))e.forEach(function(e,t){l.addScene(e)});else if(e instanceof _.Scene)if(e.controller()!==l)e.addTo(l);else if(!~f.indexOf(e))for(var t in f.push(e),f=x(f),e.on("shift.controller_sort",function(){f=x(f)}),c.globalSceneOptions)e[t]&&e[t].call(e,c.globalSceneOptions[t]);return l},this.removeScene=function(e){var t;return R.type.Array(e)?e.forEach(function(e,t){l.removeScene(e)}):-1<(t=f.indexOf(e))&&(e.off("shift.controller_sort"),f.splice(t,1),e.remove()),l},this.updateScene=function(e,n){return R.type.Array(e)?e.forEach(function(e,t){l.updateScene(e,n)}):n?e.update(!0):!0!==u&&e instanceof _.Scene&&(~(u=u||[]).indexOf(e)||u.push(e),u=x(u),r()),l},this.update=function(e){return b({type:"resize"}),e&&t(),l},this.scrollTo=function(e,t){if(R.type.Number(e))S.call(c.container,e,t);else if(e instanceof _.Scene)e.controller()===l&&l.scrollTo(e.scrollOffset(),t);else if(R.type.Function(e))S=e;else{var n=R.get.elements(e)[0];if(n){for(;n.parentNode.hasAttribute(P);)n=n.parentNode;var r=c.vertical?"top":"left",o=R.get.offset(c.container),i=R.get.offset(n);p||(o[r]-=l.scrollPos()),l.scrollTo(i[r]-o[r],t)}}return l},this.scrollPos=function(e){return arguments.length?(R.type.Function(e)&&(w=e),l):w.call(l)},this.info=function(e){var t={size:g,vertical:c.vertical,scrollPos:d,scrollDirection:h,container:c.container,isDocument:p};return arguments.length?void 0!==t[e]?t[e]:void 0:t},this.loglevel=function(e){return l},this.enabled=function(e){return arguments.length?(v!=e&&(v=!!e,l.updateScene(f,!0)),l):v},this.destroy=function(e){window.clearTimeout(o);for(var t=f.length;t--;)f[t].destroy(e);return c.container.removeEventListener("resize",b),c.container.removeEventListener("scroll",b),R.cAF(n),null},function(){for(var e in c)a.hasOwnProperty(e)||delete c[e];if(c.container=R.get.elements(c.container)[0],!c.container)throw"ScrollMagic.Controller init failed.";(p=c.container===window||c.container===document.body||!document.body.contains(c.container))&&(c.container=window),g=y(),c.container.addEventListener("resize",b),c.container.addEventListener("scroll",b);var t=parseInt(c.refreshInterval,10);c.refreshInterval=R.type.Number(t)?t:a.refreshInterval,m()}(),l};var z={defaults:{container:window,vertical:!0,globalSceneOptions:{},loglevel:2,refreshInterval:100}};_.Controller.addOption=function(e,t){z.defaults[e]=t},_.Controller.extend=function(e){var t=this;_.Controller=function(){return t.apply(this,arguments),this.$super=R.extend({},this),e.apply(this,arguments)||this},R.extend(_.Controller,t),_.Controller.prototype=t.prototype,_.Controller.prototype.constructor=_.Controller},_.Scene=function(e){var n,l,c="BEFORE",f="DURING",u="AFTER",r=D.defaults,d=this,h=R.extend({},r,e),p=c,g=0,a={start:0,end:0},v=0,o=!0,s={};this.on=function(e,o){return R.type.Function(o)&&(e=e.trim().split(" ")).forEach(function(e){var t=e.split("."),n=t[0],r=t[1];"*"!=n&&(s[n]||(s[n]=[]),s[n].push({namespace:r||"",callback:o}))}),d},this.off=function(e,i){return e&&(e=e.trim().split(" ")).forEach(function(e,t){var n=e.split("."),r=n[0],o=n[1]||"";("*"===r?Object.keys(s):[r]).forEach(function(e){for(var t=s[e]||[],n=t.length;n--;){var r=t[n];!r||o!==r.namespace&&"*"!==o||i&&i!=r.callback||t.splice(n,1)}t.length||delete s[e]})}),d},this.trigger=function(e,n){var t,r,o,i;return e&&(t=e.trim().split("."),r=t[0],o=t[1],(i=s[r])&&i.forEach(function(e,t){o&&o!==e.namespace||e.callback.call(d,new _.Event(r,e.namespace,d,n))})),d},d.on("change.internal",function(e){"loglevel"!==e.what&&"tweenChanges"!==e.what&&("triggerElement"===e.what?y():"reverse"===e.what&&d.update())}).on("shift.internal",function(e){t(),d.update()}),this.addTo=function(e){return e instanceof _.Controller&&l!=e&&(l&&l.removeScene(d),l=e,E(),i(!0),y(!0),t(),l.info("container").addEventListener("resize",S),e.addScene(d),d.trigger("add",{controller:l}),d.update()),d},this.enabled=function(e){return arguments.length?(o!=e&&(o=!!e,d.update(!0)),d):o},this.remove=function(){var e;return l&&(l.info("container").removeEventListener("resize",S),e=l,l=void 0,e.removeScene(d),d.trigger("remove")),d},this.destroy=function(e){return d.trigger("destroy",{reset:e}),d.remove(),d.off("*.*"),null},this.update=function(e){var t,n;return l&&(e?l.enabled()&&o?(t=l.info("scrollPos"),n=0=a.start?1:0,d.trigger("update",{startPos:a.start,endPos:a.end,scrollPos:t}),d.progress(n)):m&&p===f&&T(!0):l.updateScene(d,!1)),d},this.refresh=function(){return i(),y(),d},this.progress=function(e){if(arguments.length){var t,n,r,o=!1,i=p,s=l?l.info("scrollDirection"):"PAUSED",a=h.reverse||g<=e;return 0===h.duration?(o=g!=e,p=0===(g=e<1&&a?0:1)?c:f):e<0&&p!==c&&a?(p=c,o=!(g=0)):0<=e&&e<1&&a?(g=e,p=f,o=!0):1<=e&&p!==u?(g=1,p=u,o=!0):p!==f||a||T(),o&&(t={progress:g,state:p,scrollDirection:s},r=function(e){d.trigger(e,t)},(n=p!=i)&&i!==f&&(r("enter"),r(i===c?"start":"end")),r("progress"),n&&p!==f&&(r(p===c?"start":"end"),r("leave"))),d}return g};var m,w,t=function(){a={start:v+h.offset},l&&h.triggerElement&&(a.start-=l.info("size")*h.triggerHook),a.end=a.start+h.duration},i=function(e){var t;!n||x(t="duration",n.call(d))&&!e&&(d.trigger("change",{what:t,newval:h[t]}),d.trigger("shift",{reason:t}))},y=function(e){var t=0,n=h.triggerElement;if(l&&(n||0AddSubBlock($oRichTextForm); $oContentLayout->AddMainBlock($oRichTextBlock); + ////////////////////////////////////////////////////////////////////////// + // + // Tabs preferences + // + ////////////////////////////////////////////////////////////////////////// + + $oTabsBlock = new Panel(Dict::S('UI:Tabs:Preferences'), array(), 'grey', 'ibo-tabs'); + + $oTabsForm = new Form(); + $oTabsForm->AddSubBlock(InputFactory::MakeForHidden('operation', 'apply_tab_config')); + + $sTabsScrollableValue = appUserPreferences::GetPref('tab_scrollable', false);; + $oTabsScrollable = InputFactory::MakeForSelectWithLabel('tab_scrollable', Dict::S('UI:Tabs:Scrollable:Label')); + $oTabsScrollable->GetInput()->AddOption(InputFactory::MakeForSelectOption('true', Dict::S('UI:Tabs:Scrollable:Scrollable'), true === $sTabsScrollableValue)); + $oTabsScrollable->GetInput()->AddOption(InputFactory::MakeForSelectOption('false', Dict::S('UI:Tabs:Scrollable:Classic'), false === $sTabsScrollableValue)); + $oTabsForm->AddSubBlock($oTabsScrollable); + + // - Cancel button + $oTabsCancelButton = ButtonFactory::MakeForSecondaryAction(Dict::S('UI:Button:Cancel')); + $oTabsCancelButton->SetOnClickJsCode("window.location.href = '$sURL'"); + $oTabsForm->AddSubBlock($oTabsCancelButton); + // - Submit button + $oTabsSubmitButton = ButtonFactory::MakeForPrimaryAction(Dict::S('UI:Button:Apply'), null, null, true); + $oTabsForm->AddSubBlock($oTabsSubmitButton); + + $oTabsBlock->AddSubBlock($oTabsForm); + $oContentLayout->AddMainBlock($oTabsBlock); + ////////////////////////////////////////////////////////////////////////// // // User picture placeholder @@ -472,6 +500,10 @@ try { appUserPreferences::SetPref('richtext_config', json_encode($aRichTextConfig)); DisplayPreferences($oPage); break; + case 'apply_tab_config': + $bScrollable= utils::ReadParam('tab_scrollable', 'false') === 'true'; + appUserPreferences::SetPref('tab_scrollable', $bScrollable); + break; case 'apply_language': $sLangCode = utils::ReadParam('language', 'EN US'); $oUser = UserRights::GetUserObject(); diff --git a/sources/application/UI/Base/Layout/TabContainer/TabContainer.php b/sources/application/UI/Base/Layout/TabContainer/TabContainer.php index 9a04f68cd..eae540483 100644 --- a/sources/application/UI/Base/Layout/TabContainer/TabContainer.php +++ b/sources/application/UI/Base/Layout/TabContainer/TabContainer.php @@ -41,7 +41,8 @@ class TabContainer extends UIContentBlock public const DEFAULT_HTML_TEMPLATE_REL_PATH = 'base/layouts/tab-container/layout'; public const DEFAULT_JS_TEMPLATE_REL_PATH = 'base/layouts/tab-container/layout'; public const DEFAULT_JS_FILES_REL_PATH = [ - 'js/layouts/tab-container.js' + 'js/layouts/tab-container/tab-container.js', + 'js/layouts/tab-container/scrollable-tabs.js' ]; // Specific constants @@ -51,6 +52,9 @@ class TabContainer extends UIContentBlock public const ENUM_LAYOUT_VERTICAL = 'vertical'; /** @var string */ public const DEFAULT_LAYOUT = self::ENUM_LAYOUT_HORIZONTAL; + /** @var bool */ + public const DEFAULT_SCROLLABLE = false; + /** @var string $sName */ private $sName; @@ -58,6 +62,8 @@ class TabContainer extends UIContentBlock private $sPrefix; /** @var string $sLayout Layout of the tabs (horizontal, vertical, ...), see static::ENUM_LAYOUT_XXX */ private $sLayout; + /** @var bool $bIsScrollable Define if we can scroll through tabs */ + private $bIsScrollable; /** * TabContainer constructor. @@ -80,6 +86,8 @@ class TabContainer extends UIContentBlock $this->sName = $sName; $this->sPrefix = $sPrefix; $this->sLayout = appUserPreferences::GetPref('tab_layout', static::DEFAULT_LAYOUT); + $this->bIsScrollable = appUserPreferences::GetPref('tab_scrollable', static::DEFAULT_SCROLLABLE); + } /** @@ -183,4 +191,21 @@ class TabContainer extends UIContentBlock public function GetLayout(): string { return $this->sLayout; } + + /** + * @param bool $bIsScrollable + * @return $this + */ + public function SetIsScrollable($bIsScrollable) { + $this->bIsScrollable = $bIsScrollable; + + return $this; + } + + /** + * @return bool + */ + public function GetIsScrollable(): bool { + return $this->bIsScrollable; + } } diff --git a/sources/application/WebPage/NiceWebPage.php b/sources/application/WebPage/NiceWebPage.php index 119c781b9..6c5868732 100644 --- a/sources/application/WebPage/NiceWebPage.php +++ b/sources/application/WebPage/NiceWebPage.php @@ -140,6 +140,7 @@ EOF $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_date_time.js'); $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/clipboard.min.js'); $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/clipboardwidget.js'); + $this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/ScrollMagic.min.js'); } /** diff --git a/templates/base/layouts/tab-container/layout.html.twig b/templates/base/layouts/tab-container/layout.html.twig index cc81a4f65..4a634e7e5 100644 --- a/templates/base/layouts/tab-container/layout.html.twig +++ b/templates/base/layouts/tab-container/layout.html.twig @@ -1,6 +1,7 @@ {# @copyright Copyright (C) 2010-2020 Combodo SARL #} {# @license http://opensource.org/licenses/AGPL-3.0 #} -
+
{% block iboTabContainer %}
    {% if not aPage.isPrintable %} @@ -42,13 +43,15 @@ {% block iboTabContainerTabsContainers %} {% if not aPage.isPrintable %} - {% for oTab in oUIBlock.GetSubBlocks() %} - {% if oTab.GetType() == constant('TabManager::ENUM_TAB_TYPE_HTML') %} -
    - {{ render_block(oTab, {aPage: aPage}) }} -
    - {% endif %} - {% endfor %} +
    + {% for oTab in oUIBlock.GetSubBlocks() %} + {% if oTab.GetType() == constant('TabManager::ENUM_TAB_TYPE_HTML') %} +
    + {{ render_block(oTab, {aPage: aPage}) }} +
    + {% endif %} + {% endfor %} +
    {% else %} {% for oTab in oUIBlock.GetSubBlocks() %}