Browse Source

Studio dock: a disabled app is dimmed, and its context menu works on every page

The dock (top menu) items never reflected the app state: a disabled app
looked like any other while its launcher tile was dimmed, and toggling
Active from the dock left the icon as it was. The dock now marks a
disabled app on render (bx-menu-item-disabled, the same 40% the tile
uses), and the activate action returns the acted-on app and its new
state so the JS toggles the dock item wherever the action came from.

The menu also only worked on the launcher: its Active and Uninstall
items call oBxDolStudioModule, which other pages never created, so a
click did nothing. The top menu now ships module.js and defines the
object when the page has not. activate() acts on the requested page
(it read the module name as the page array) and only redraws the page
content when the action came from the app's own page, so the dock cannot
swap the dashboard for a module page.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Andrey Yasko 1 month ago
parent
commit
2cd2f0f30f

+ 23 - 0
studio/classes/BxDolStudioMenuTop.php

@@ -81,6 +81,7 @@ class BxDolStudioMenuTop extends BxDol implements iBxDolSingleton
         };
 
         $aFeatured = $oWidgetsDb->getWidgets(array('type' => 'all_featured', 'featured' => 1));
+        $aFeatured = array_column($aFeatured, null, 'page_name');
         foreach($aFeatured as $aItem)
             if(empty($aItem['type']) || $oRolesUtils->isActionAllowed('use ' . $aItem['type']))
                 $aMenuItems[$aItem['page_name']] = array(
@@ -98,6 +99,7 @@ class BxDolStudioMenuTop extends BxDol implements iBxDolSingleton
         foreach($aBookmarks as $aBookmark) {
             if(array_key_exists($aBookmark['page_name'], $aMenuItems))
                 continue;
+            $aFeatured[$aBookmark['page_name']] = $aBookmark; // the module lookup below reads it like a featured row
             
             if(!empty($aBookmark['type']) && !$oRolesUtils->isActionAllowed('use ' . $aBookmark['type']))
                 continue;
@@ -137,6 +139,27 @@ class BxDolStudioMenuTop extends BxDol implements iBxDolSingleton
                 $bHistory = true;
             }
 
+        // A disabled app is dimmed in the dock as it is in the launcher (the widget rows carry the module; history rows are looked up by page).
+        $oModuleQuery = BxDolModuleQuery::getInstance();
+        $oPageQuery = BxDolStudioPageQuery::getInstance();
+        foreach($aMenuItems as $sPageName => &$aMenuItem) {
+            if(in_array($sPageName, ['divider', 'launcher']))
+                continue;
+
+            $sModule = '';
+            if(isset($aFeatured[$sPageName]['module']))
+                $sModule = $aFeatured[$sPageName]['module'];
+            else if(($aPage = $oPageQuery->getPages(['type' => 'by_page_name_full', 'value' => $sPageName])) && !empty($aPage['wid_module']))
+                $sModule = $aPage['wid_module'];
+            if(!$sModule)
+                continue;
+
+            $aModule = $oModuleQuery->getModuleByName($sModule);
+            if(!empty($aModule) && is_array($aModule) && (int)$aModule['enabled'] == 0)
+                $aMenuItem['class_add'] = 'bx-menu-item-disabled';
+        }
+        unset($aMenuItem);
+
         // The divider only makes sense between two groups: drop it while either side is empty (e.g. right after install).
         if(!$bStatic || !$bHistory)
             unset($aMenuItems['divider']);

+ 7 - 4
studio/classes/BxDolStudioModule.php

@@ -212,8 +212,8 @@ class BxDolStudioModule extends BxTemplStudioWidget
 
     public function activate($sPage, $iWidgetId = 0)
     {
-        $aPage = $this->aPage['wid_module'];
-        if(empty($aPage) || !is_array($aPage))           
+        $aPage = $this->aPage;
+        if(empty($aPage) || !is_array($aPage) || $aPage['name'] != $sPage)
             $aPage = $this->oDb->getPages(array('type' => 'by_page_name_full', 'value' => $sPage));
 
         $aModule = BxDolModuleQuery::getInstance()->getModuleByName($aPage['wid_module']);
@@ -226,10 +226,13 @@ class BxDolStudioModule extends BxTemplStudioWidget
 
         $this->aModule = BxDolModuleQuery::getInstance()->getModuleByName($this->sModule);
 
-        $aResult = array('code' => 0, 'message' => _t('_adm_scs_operation_done'));
+        // the acted-on app (not necessarily this page's): its new state, for the dock item and the launcher tile
+        $aModule = BxDolModuleQuery::getInstance()->getModuleByName($aPage['wid_module']);
+        $aResult = array('code' => 0, 'message' => _t('_adm_scs_operation_done'), 'page' => $aPage['name'], 'enabled' => (int)$aModule['enabled']);
         if($iWidgetId == 0) {
+            // the app's own page redraws itself; from the dock on another page there is nothing to redraw
             $aResult['content'] = '';
-            if(!$this->_bShowHeaderBreadcrumb || (int)$aModule['enabled'] == 0)
+            if($this->sModule == $aPage['wid_module'] && (!$this->_bShowHeaderBreadcrumb || (int)$aModule['enabled'] == 0))
                 $aResult['content'] = BxDolStudioTemplate::getInstance()->parseHtmlByName('page_content_2_col.html', [
                     'page_menu_code' => $this->getPageMenu(),
                     'page_main_code' => $this->getPageCode()

+ 4 - 0
studio/js/module.js

@@ -50,6 +50,10 @@ BxDolStudioModule.prototype.activate = function(oCheckbox, sName, iWidgetId) {
                 return;
             }
 
+            // the app's dock item dims or brightens with it (the launcher tile is replaced below)
+            if(oData.page)
+                $('#bx-menu-item-' + oData.page).toggleClass('bx-menu-item-disabled', !parseInt(oData.enabled));
+
             if(iWidgetId != 0 && oData.widget.length > 0) {
                 $('#bx-std-widget-' + iWidgetId).replaceWith(oData.widget);
                 if(oBxDolStudioLauncher.bJitterMode)

+ 3 - 0
studio/template/css/menu_top.css

@@ -261,6 +261,9 @@ div.bx-std-menu-top .bx-menu-breadcrumb li.bx-menu-bc-actions svg {
     height: 1.0rem;
 }
 
+div.bx-std-menu-top .bx-menu-top-center li.bx-menu-item-disabled .bx-mi-icon {
+    opacity: 0.4; /* a disabled app, dimmed as its launcher tile is (.bx-std-widget-icon-disabled) */
+}
 div.bx-std-menu-top .bx-menu-top-center li.bx-menu-item-divider {
     width: 2px; /* the breadcrumb's separator bar: 2 x 16px, rounded, no room of its own (-1px margins) */
     height: 1rem;

+ 4 - 1
studio/template/menu_top.html

@@ -3,10 +3,13 @@
 var oBxDolStudioMenuTop = new BxDolStudioMenuTop({
     sActionUrl: '<bx_url_studio />ajax.php'
 });
+if(typeof oBxDolStudioModule == 'undefined') {
+    __js_code_module__
+}
 -->
 </script>
 <div class="bx-std-menu-top-wrp">
-    <div class="bx-std-menu-top-gap flex justify-between items-center flex-1 bg-white/60 dark:bg-white/[0.06] ring-1 ring-inset ring-white/50 dark:ring-white/[0.025] smooth-shadow-sm">
+    <div class="bx-std-menu-top-gap flex justify-between items-center flex-1 bg-white/60 dark:bg-white/10 ring-1 ring-inset ring-white/50 dark:ring-white/[0.025] smooth-shadow-sm">
         <bx_repeat:menus>
             <div id="bx-std-menu-top-__name__" class="bx-std-menu-top bx-std-menu-top-__name__ bx-def-box-sizing">__content__</div>
         </bx_repeat:menus>

+ 7 - 2
studio/template/scripts/BxBaseStudioMenuTop.php

@@ -22,7 +22,9 @@ class BxBaseStudioMenuTop extends BxDolStudioMenuTop
 
     function getJs()
     {
-        return array('menu_top.js', 'context_menu.js'); // the dock's app items are context menu triggers, on every Studio page
+        // the dock's app items are context menu triggers on every Studio page, and the menu's actions (activate, uninstall)
+        // run through the module JS object, so its script comes with the menu wherever the page itself does not load it
+        return array_merge(BxTemplStudioModules::getInstance()->getJs(), array('menu_top.js', 'context_menu.js'));
     }
 
     function getJsObject()
@@ -77,7 +79,10 @@ class BxBaseStudioMenuTop extends BxDolStudioMenuTop
 
         $oTemplate->addJs($this->getJs());
         $oTemplate->addCss($this->getCss());
-        return $oTemplate->parseHtmlByName('menu_top.html', array('bx_repeat:menus' => $aTmplVars));
+        return $oTemplate->parseHtmlByName('menu_top.html', array(
+            'bx_repeat:menus' => $aTmplVars,
+            'js_code_module' => BxTemplStudioModules::getInstance()->getJsCode(array(), false) // pages with their own instance define it again below
+        ));
     }
 }