Skip to content

Fixing the Nextcloud menu to show more than eight application icons

Internet

I have been late to adopt an on-premise cloud solution as the security of Owncloud a few years ago wasn't so stellar (cf. my comment from 2013 in Encryption files ... for synchronization across the Internet). But the follow-up product Nextcloud has matured quite nicely and we use it for collaboration both in the company and in FLOSS related work at multiple nonprofit organizations.

There is a very annoying "feature" in Nextcloud though that the designers think menu items for apps at the top need to be limited to eight or less to prevent information overload in the header. The whole item discussion is worth reading as it it an archetypical example of design prevalence vs. user choice.

And of course designers think they are right. That's a feature of the trade.
And because they know better there is no user configurable option to extend that 8 items to may be 12 or so which would prevent the annoying overflow menu we are seeing with 10 applications in use:

Screenshot of stock Nextcloud menu

Luckily code can be changed and there are many comments floating around the Internet to change const minAppsDesktop = 8. In this case it is slightly complicated by the fact that the javascript code is distributed in compressed form (aka "minified") as core/js/dist/main.js and you probably don't want to build the whole beast locally to change one constant.

Basically

const breakpoint_mobile_width = 1024;

const resizeMenu = () => {
    const appList = $('#appmenu li')
    const rightHeaderWidth = $('.header-right').outerWidth()
    const headerWidth = $('header').outerWidth()
    const usePercentualAppMenuLimit = 0.33
    const minAppsDesktop = 8
    let availableWidth = headerWidth - $('#nextcloud').outerWidth() - (rightHeaderWidth > 210 ? rightHeaderWidth : 210)
    const isMobile = $(window).width() < breakpoint_mobile_width
    if (!isMobile) {
        availableWidth = availableWidth * usePercentualAppMenuLimit
    }
    let appCount = Math.floor((availableWidth / $(appList).width()))
    if (isMobile && appCount > minAppsDesktop) {
        appCount = minAppsDesktop
    }
    if (!isMobile && appCount < minAppsDesktop) {
        appCount = minAppsDesktop
    }

    // show at least 2 apps in the popover
    if (appList.length - 1 - appCount >= 1) {
        appCount--
    }

    $('#more-apps a').removeClass('active')
    let lastShownApp
    for (let k = 0; k < appList.length - 1; k++) {
        const name = $(appList[k]).data('id')
        if (k < appCount) {
            $(appList[k]).removeClass('hidden')
            $('#apps li[data-id=' + name + ']').addClass('in-header')
            lastShownApp = appList[k]
        } else {
            $(appList[k]).addClass('hidden')
            $('#apps li[data-id=' + name + ']').removeClass('in-header')
            // move active app to last position if it is active
            if (appCount > 0 && $(appList[k]).children('a').hasClass('active')) {
                $(lastShownApp).addClass('hidden')
                $('#apps li[data-id=' + $(lastShownApp).data('id') + ']').removeClass('in-header')
                $(appList[k]).removeClass('hidden')
                $('#apps li[data-id=' + name + ']').addClass('in-header')
            }
        }
    }

    // show/hide more apps icon
    if ($('#apps li:not(.in-header)').length === 0) {
        $('#more-apps').hide()
        $('#navigation').hide()
    } else {
        $('#more-apps').show()
    }
}

gets compressed during build time to become part of one 15,000+ character line. The relevant portion reads:

var f=function(){var e=s()("#appmenu li"),t=s()(".header-right").outerWidth(),n=s()("header").outerWidth()-s()("#nextcloud").outerWidth()-(t>210?t:210),i=s()(window).width()<1024;i||(n*=.33);var r,o=Math.floor(n/s()(e).width());i&&o>8&&(o=8),!i&&o<8&&(o=8),e.length-1-o>=1&&o--,s()("#more-apps a").removeClass("active");for(var a=0;a<e.length-1;a++){var l=s()(e[a]).data("id");a<o?(s()(e[a]).removeClass("hidden"),s()("#apps li[data-id="+l+"]").addClass("in-header"),r=e[a]):(s()(e[a]).addClass("hidden"),s()("#apps li[data-id="+l+"]").removeClass("in-header"),o>0&&s()(e[a]).children("a").hasClass("active")&&(s()(r).addClass("hidden"),s()("#apps li[data-id="+s()(r).data("id")+"]").removeClass("in-header"),s()(e[a]).removeClass("hidden"),s()("#apps li[data-id="+l+"]").addClass("in-header")))}0===s()("#apps li:not(.in-header)").length?(s()("#more-apps").hide(),s()("#navigation").hide()):s()("#more-apps").show()}

Well, we can still patch that, can we?

In our case we have ten application menu items but the following code sets the desktop version to allow twelve before it creates the overflow (three dots) menu:

#!/bin/bash

TARGETFILE=/var/www/nextcloud/core/js/dist/main.js

grep -F -q '!i&&o<8&&(o=8)' $TARGETFILE && \
 sed -i.backup_$(date --iso-8601=seconds) 's/!i&&o<8&&(o=8)/!i\&\&o<12\&\&(o=12)/' $TARGETFILE
exit 0
 

Download patch_nextcloud_menu (1kB).

Adjust the path to your installation, change the 12 (twice) if you need more App icons and drop your patch_nextcloud_menu into /etc/cron.hourly/ and make it executable. That way your Nextcloud instance fixes itself within an hour of an upgrade. Or make that run more often via a crontab if your users would complain faster :-). If the compressed variable name ("o") changes in future versions of Nextcloud, the script can be updated to use a bit more sed magic (see the Update below when updating to Nextcloud 19 where this becomes necessary). That would not contribute to readability through and hence I left it as is for now.

This is what it looks like after patching and clearing the browser cache:

Screenshot of patched Nextcloud menu

Done. Saving one annoying click many times a day. Nice.

Update

18.09.20 So with Nextcloud 19 the regex above doesn't match anymore as the variable name changed from "o" to "s".

So, let's replace the "o" with a match any (".") and insert that again as a back reference. We need to fix the grep to see if our file is still patchable, too:

#!/bin/bash

TARGETFILE=/var/www/nextcloud/core/js/dist/main.js

grep -q '!i&&.<8&&(.=8)' $TARGETFILE && \
 sed -i.backup_$(date --iso-8601=seconds) 's/!i&&\(.\)<8&&(.=8)/!i\&\&\1<12\&\&(\1=12)/' $TARGETFILE
exit 0
 

Download as patch_nextcloud_menu_v2 (1kB).

The good thing is, the default has been changed to 12 icons now and the associated issue at Nextcloud has been closed. So the next time the script above will not find something to patch, we should have a more resonable default. And if you need 13 icons, you'll probably have an idea what you could do :-).

Trackbacks

No Trackbacks

Comments

Display comments as Linear | Threaded

Joe on :

You are my hero :-) Thanks!

Brendon Higgins on :

That github issue thread you linked is rather infuriating, isn't it? This jancborchardt fellow clearly understands less than he thinks he does. The "7" rule that he keeps defending as justification for their interface limitation is not even relevant, as described quite aptly in the nngroup article jancborchardt himself links to: "[...] confused designers will sometimes misuse this finding to justify unnecessary design limitations. [...] users don’t need to keep all of the menu items in their short-term memory, because all the available options are continuously displayed on the screen."

It's not even debatable. The designers are misinterpreting research that applies to recall, not recognition, applying unjustified limitations with misplaced confidence, and arrogantly refusing to reconsider their position when users complain. (Aside from whether they might deign to allow anyone else to patch the code.)

Jan C. Borchardt on :

Hi there :-) Unfortunately that issue thread is very heated and has lots of replies, so the solution that was agreed upon probably got lost to many: https://github.com/nextcloud/server/issues/13079#issuecomment-485825038

To quote:

The design proposal we will go with is as mentioned above by @skjnldsv and @juliushaertl is to do it like Chrome, with the "More apps" icon being the draggable element.

This makes everyone happy: Doesn’t fill up the top bar by default, but if you do want to see more apps you can simply drag the element.

Since you know the relevant code already, it would be very cool if you join in and contribute that to the project, so it’s fixed for everyone. What do you think?

Cheers and thanks

Daniel Lange on :

Hi Jan,

I think this proposal is overly complicated as:

  1. No other Nextcloud menus or major UI elements are drag-and-drop sortable
  2. This needs javascript frontend + php backend support and a per-user config storing the resulting top bar layout. Which then again needs an admin option to reset that (think new app made available).
  3. This need not be user-configurable but sysadmin-configurable (otherwise support issues ... "why do I not see the Notes app but at my colleague's PC...")

The better idea is to just make a config.php option where the sysadmin can set the # of items before the rest is put into the overflow (three dots) menu.

I'm much for following the KISS principle here. Simple problem, simple solution.

Have a good weekend!
Daniel

Daniel Lange on :

Lorenz Schori (znerol) proposed a Pull Request that would have made the "three dots" somewhat configurable via CSS variables:

https://github.com/nextcloud/server/pull/20250

It was denied by Jan Borchardt: "This solution is only accessible to expert users". He still tries to promote his "dragable three dots icon" proposal.

So the script published in the article above will probably be good for another few releases :-).

Add Comment

Markdown format allowed
Standard emoticons like :-) and ;-) are converted to images.
E-Mail addresses will not be displayed and will only be used for E-Mail notifications.

To prevent automated Bots from commentspamming, please enter the string you see in the image below in the appropriate input box. Your comment will only be submitted if the strings match. Please ensure that your browser supports and accepts cookies, or your comment cannot be verified correctly.
CAPTCHA

Form options

Submitted comments will be subject to moderation before being displayed.