Skip to content

Commit

Permalink
Merge pull request #7051 from nextcloud/breadcrumbs-refactor
Browse files Browse the repository at this point in the history
Breadcrumbs refactor
  • Loading branch information
MorrisJobke committed Nov 13, 2017
2 parents 26bcf40 + 8c2dbeb commit ff2d443
Show file tree
Hide file tree
Showing 14 changed files with 435 additions and 327 deletions.
22 changes: 9 additions & 13 deletions apps/files/css/files.scss
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,6 @@
top: 44px;
}

/* make sure there's enough room for the file actions */
#body-user #filestable {
min-width: 688px; /* 768 (mobile break) - 80 (nav width) */
}
#body-user #controls {
min-width: 688px; /* 768 (mobile break) - 80 (nav width) */
}

#filestable tbody tr {
height: 51px;
}
Expand All @@ -74,12 +66,16 @@
background-color: rgb(179, 230, 255)!important;
}

.app-files #app-content.dir-drop, .file-drag #filestable tbody tr, .file-drag #filestable tbody tr:hover{
background-color: rgba(0, 0, 0, 0)!important;
.app-files #app-content.dir-drop {
background-color: $color-main-background !important;
}

.file-drag #filestable tbody tr, .file-drag #filestable tbody tr:hover{
background-color: transparent !important;
}

.app-files #app-content.dir-drop #filestable tbody tr.dropping-to-dir{
background-color: rgb(179, 230, 255)!important;
background-color: rgb(179, 230, 255) !important;
}

/* icons for sidebar */
Expand Down Expand Up @@ -740,9 +736,9 @@ table.dragshadow td.size {
margin-bottom: 2px;
}

.canDrop,
.breadcrumb .canDrop > a,
#filestable tbody tr.canDrop {
background-color: rgba(255, 255, 140, 1);
background-color: rgb(179, 230, 255);
}


Expand Down
223 changes: 142 additions & 81 deletions apps/files/js/breadcrumb.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
*/
var BreadCrumb = function(options){
this.$el = $('<div class="breadcrumb"></div>');
this.$menu = $('<div class="popovermenu menu-center"><ul></ul></div>');

this.crumbSelector = '.crumb:not(.hidden):not(.crumbhome):not(.crumbmenu)';
options = options || {};
if (options.onClick) {
this.onClick = options.onClick;
Expand All @@ -47,6 +50,7 @@
}
this._detailViews = [];
};

/**
* @memberof OCA.Files
*/
Expand Down Expand Up @@ -110,19 +114,32 @@
* Renders the breadcrumb elements
*/
render: function() {
// Menu is destroyed on every change, we need to init it
OC.unregisterMenu($('.crumbmenu'), $('.crumbmenu > .popovermenu'));

var parts = this._makeCrumbs(this.dir || '/');
var $crumb;
var $menuItem;
this.$el.empty();
this.breadcrumbs = [];

for (var i = 0; i < parts.length; i++) {
var part = parts[i];
var $image;
var $link = $('<a></a>').attr('href', this.getCrumbUrl(part, i));
$link.text(part.name);
var $link = $('<a></a>');
$crumb = $('<div class="crumb svg"></div>');
if(part.dir) {
$link.attr('href', this.getCrumbUrl(part, i));
}
if(part.name) {
$link.text(part.name);
}
$link.addClass(part.linkclass);
$crumb.append($link);
$crumb.attr('data-dir', part.dir);
$crumb.data('dir', part.dir);
// Ignore menu button
$crumb.data('crumb-id', i - 1);
$crumb.addClass(part.class);

if (part.img) {
$image = $('<img class="svg"></img>');
Expand All @@ -132,12 +149,27 @@
}
this.breadcrumbs.push($crumb);
this.$el.append($crumb);
if (this.onClick) {
$crumb.on('click', this.onClick);
// Only add feedback if not menu
if (this.onClick && i !== 0) {
$link.on('click', this.onClick);
}
}
$crumb.addClass('last');

// Menu creation
this._createMenu();
for (var j = 0; j < parts.length; j++) {
var menuPart = parts[j];
if(menuPart.dir) {
$menuItem = $('<li class="crumblist"><a><span class="icon-folder"></span><span></span></a></li>');
$menuItem.data('dir', menuPart.dir);
$menuItem.find('a').attr('href', this.getCrumbUrl(part, j));
$menuItem.find('span:eq(1)').text(menuPart.name);
this.$menu.children('ul').append($menuItem);
if (this.onClick) {
$menuItem.on('click', this.onClick);
}
}
}
_.each(this._detailViews, function(view) {
view.render({
dirInfo: this.dirInfo
Expand All @@ -152,16 +184,20 @@

// setup drag and drop
if (this.onDrop) {
this.$el.find('.crumb:not(.last)').droppable({
this.$el.find('.crumb:not(:last-child):not(.crumbmenu), .crumblist:not(:last-child)').droppable({
drop: this.onDrop,
over: this.onOver,
out: this.onOut,
tolerance: 'pointer',
hoverClass: 'canDrop'
hoverClass: 'canDrop',
greedy: true
});
}

this._updateTotalWidth();
// Menu is destroyed on every change, we need to init it
OC.registerMenu($('.crumbmenu'), $('.crumbmenu > .popovermenu'));

this._resize();
},

/**
Expand All @@ -179,12 +215,17 @@
if (dir === '') {
parts = [];
}
// menu part
crumbs.push({
class: 'crumbmenu hidden',
linkclass: 'icon-more'
});
// root part
crumbs.push({
name: t('core', 'Home'),
dir: '/',
name: '',
alt: t('files', 'Home'),
img: OC.imagePath('core', 'places/home.svg')
class: 'crumbhome',
linkclass: 'icon-home'
});
for (var i = 0; i < parts.length; i++) {
var part = parts[i];
Expand All @@ -197,23 +238,10 @@
return crumbs;
},

/**
* Calculate the total breadcrumb width when
* all crumbs are expanded
*/
_updateTotalWidth: function () {
this.totalWidth = 0;
for (var i = 0; i < this.breadcrumbs.length; i++ ) {
var $crumb = $(this.breadcrumbs[i]);
$crumb.data('real-width', $crumb.width());
this.totalWidth += $crumb.width();
}
this._resize();
},

/**
* Show/hide breadcrumbs to fit the given width
*
* Mostly used by tests
*
* @param {int} availableWidth available width
*/
setMaxWidth: function (availableWidth) {
Expand All @@ -223,74 +251,107 @@
}
},

_resize: function() {
var i, $crumb, $ellipsisCrumb;

if (!this.availableWidth) {
this.availableWidth = this.$el.width();
/**
* Calculate real width based on individual crumbs
* More accurate and works with tests
*
* @param {boolean} ignoreHidden ignore hidden crumbs
*/
getTotalWidth: function(ignoreHidden) {
var totalWidth = 0;
for (var i = 0; i < this.breadcrumbs.length; i++ ) {
var $crumb = $(this.breadcrumbs[i]);
if(!$crumb.hasClass('hidden') || ignoreHidden === true) {
totalWidth += $crumb.outerWidth();
}
}
return totalWidth;
},

if (this.breadcrumbs.length <= 1) {
return;
/**
* Hide the middle crumb
*/
_hideCrumb: function() {
var length = this.$el.find(this.crumbSelector).length;
// Get the middle one floored down
var elmt = Math.floor(length / 2 - 0.5);
this.$el.find(this.crumbSelector+':eq('+elmt+')').addClass('hidden');
},

/**
* Get the crumb to show
*/
_getCrumbElement: function() {
var hidden = this.$el.find('.crumb.hidden').length;
var shown = this.$el.find(this.crumbSelector).length;
// Get the outer one with priority to the highest
var elmt = (1 - shown % 2) * (hidden - 1);
return this.$el.find('.crumb.hidden:eq('+elmt+')');
},

/**
* Show the middle crumb
*/
_showCrumb: function() {
if(this.$el.find('.crumb.hidden').length === 1) {
this.$el.find('.crumb.hidden').removeClass('hidden');
}
this._getCrumbElement().removeClass('hidden');
},

/**
* Create and append the popovermenu
*/
_createMenu: function() {
this.$el.find('.crumbmenu').append(this.$menu);
this.$menu.children('ul').empty();
},

/**
* Update the popovermenu
*/
_updateMenu: function() {
var menuItems = this.$el.find('.crumb.hidden');
// Hide the crumb menu if no elements
this.$el.find('.crumbmenu').toggleClass('hidden', menuItems.length === 0);

// reset crumbs
this.$el.find('.crumb.ellipsized').remove();
this.$menu.find('li').addClass('in-breadcrumb');
for (var i = 0; i < menuItems.length; i++) {
var crumbId = $(menuItems[i]).data('crumb-id');
this.$menu.find('li:eq('+crumbId+')').removeClass('in-breadcrumb');
}
},

// unhide all
this.$el.find('.crumb.hidden').removeClass('hidden');
_resize: function() {

if (this.totalWidth <= this.availableWidth) {
// no need to compute breadcrumbs, there is enough space
if (this.breadcrumbs.length <= 2) {
// home & menu
return;
}

// running width, considering the hidden crumbs
var currentTotalWidth = $(this.breadcrumbs[0]).data('real-width');
var firstHidden = true;

// insert ellipsis after root part (root part is always visible)
$ellipsisCrumb = $('<div class="crumb ellipsized svg"><span class="ellipsis">...</span></div>');
$(this.breadcrumbs[0]).after($ellipsisCrumb);
currentTotalWidth += $ellipsisCrumb.width();

i = this.breadcrumbs.length - 1;

// find the first section that would cause the overflow
// then hide everything in front of that
//
// this ensures that the last crumb section stays visible
// for most of the cases and is always the last one to be
// hidden when the screen becomes very narrow
while (i > 0) {
$crumb = $(this.breadcrumbs[i]);
// if the current breadcrumb would cause overflow
if (!firstHidden || currentTotalWidth + $crumb.data('real-width') > this.availableWidth) {
// hide it
$crumb.addClass('hidden');
if (firstHidden) {
// set the path of this one as title for the ellipsis
this.$el.find('.crumb.ellipsized')
.attr('title', $crumb.attr('data-dir'))
.tooltip();
this.$el.find('.ellipsis')
.wrap('<a class="ellipsislink" href="' + encodeURI(OC.generateUrl('apps/files/?dir=' + $crumb.attr('data-dir'))) + '"></a>');
}
// and all the previous ones (going backwards)
firstHidden = false;
} else {
// add to total width
currentTotalWidth += $crumb.data('real-width');
}
i--;
// Used for testing since this.$el.parent fails
if (!this.availableWidth) {
this.usedWidth = this.$el.parent().width() - (this.$el.parent().find('.button').length + 1) * 44;
} else {
this.usedWidth = this.availableWidth;
}

if (!OC.Util.hasSVGSupport()) {
OC.Util.replaceSVG(this.$el);
// If container is smaller than content
// AND if there are crumbs left to hide
while (this.getTotalWidth() > this.usedWidth
&& this.$el.find(this.crumbSelector).length > 0) {
this._hideCrumb();
}
// If container is bigger than content + element to be shown
// AND if there is at least one hidden crumb
while (this.$el.find('.crumb.hidden').length > 0
&& this.getTotalWidth() + this._getCrumbElement().width() < this.usedWidth) {
this._showCrumb();
}

this._updateMenu();
}
};

OCA.Files.BreadCrumb = BreadCrumb;
})();

Loading

0 comments on commit ff2d443

Please sign in to comment.