diff --git a/apps/files/js/breadcrumb.js b/apps/files/js/breadcrumb.js index c017d710d6..7381fa8e43 100644 --- a/apps/files/js/breadcrumb.js +++ b/apps/files/js/breadcrumb.js @@ -41,8 +41,9 @@ $el: null, dir: null, - lastWidth: 0, - hiddenBreadcrumbs: 0, + /** + * Total width of all breadcrumbs + */ totalWidth: 0, breadcrumbs: [], onClick: null, @@ -116,7 +117,6 @@ } this._updateTotalWidth(); - this.resize($(window).width(), true); }, /** @@ -150,93 +150,93 @@ return crumbs; }, + /** + * Calculate the total breadcrumb width when + * all crumbs are expanded + */ _updateTotalWidth: function () { - var self = this; - - this.lastWidth = 0; - - // initialize with some extra space - this.totalWidth = 64; - // FIXME: this class should not know about global elements - if ( $('#navigation').length ) { - this.totalWidth += $('#navigation').outerWidth(); - } - - if ( $('#app-navigation').length && !$('#app-navigation').hasClass('hidden')) { - this.totalWidth += $('#app-navigation').outerWidth(); - } - this.hiddenBreadcrumbs = 0; - + this.totalWidth = 0; for (var i = 0; i < this.breadcrumbs.length; i++ ) { - this.totalWidth += $(this.breadcrumbs[i]).get(0).offsetWidth; + var $crumb = $(this.breadcrumbs[i]); + $crumb.data('real-width', $crumb.width()); + this.totalWidth += $crumb.width(); } - - $.each($('#controls .actions'), function(index, action) { - self.totalWidth += $(action).outerWidth(); - }); - + this._resize(); }, /** * Show/hide breadcrumbs to fit the given width */ - resize: function (width, firstRun) { - var i, $crumb; + setMaxWidth: function (availableWidth) { + if (this.availableWidth !== availableWidth) { + this.availableWidth = availableWidth; + this._resize(); + } + }, - if (width === this.lastWidth) { + _resize: function() { + var i, $crumb, $ellipsisCrumb; + + if (!this.availableWidth) { + this.availableWidth = this.$el.width(); + } + + if (this.breadcrumbs.length <= 1) { return; } - // window was shrinked since last time or first run ? - if ((width < this.lastWidth || firstRun) && width < this.totalWidth) { - if (this.hiddenBreadcrumbs === 0 && this.breadcrumbs.length > 1) { - // start by hiding the first breadcrumb after home, - // that one will have extra three dots displayed - $crumb = this.breadcrumbs[1]; - this.totalWidth -= $crumb.get(0).offsetWidth; - $crumb.find('a').addClass('hidden'); - $crumb.append('...'); - this.totalWidth += $crumb.get(0).offsetWidth; - this.hiddenBreadcrumbs = 2; - } - i = this.hiddenBreadcrumbs; - // hide subsequent breadcrumbs if the space is still not enough - while (width < this.totalWidth && i > 1 && i < this.breadcrumbs.length - 1) { - $crumb = this.breadcrumbs[i]; - this.totalWidth -= $crumb.get(0).offsetWidth; - $crumb.addClass('hidden'); - this.hiddenBreadcrumbs = i; - i++; - } - // window is bigger than last time - } else if (width > this.lastWidth && this.hiddenBreadcrumbs > 0) { - i = this.hiddenBreadcrumbs; - while (width > this.totalWidth && i > 0) { - if (this.hiddenBreadcrumbs === 1) { - // special handling for last one as it has the three dots - $crumb = this.breadcrumbs[1]; - if ($crumb) { - this.totalWidth -= $crumb.get(0).offsetWidth; - $crumb.find('.ellipsis').remove(); - $crumb.find('a').removeClass('hidden'); - this.totalWidth += $crumb.get(0).offsetWidth; - } - } else { - $crumb = this.breadcrumbs[i]; - $crumb.removeClass('hidden'); - this.totalWidth += $crumb.get(0).offsetWidth; - if (this.totalWidth > width) { - this.totalWidth -= $crumb.get(0).offsetWidth; - $crumb.addClass('hidden'); - break; - } - } - i--; - this.hiddenBreadcrumbs = i; - } + // reset crumbs + this.$el.find('.crumb.ellipsized').remove(); + + // unhide all + this.$el.find('.crumb.hidden').removeClass('hidden'); + + if (this.totalWidth <= this.availableWidth) { + // no need to compute breadcrumbs, there is enough space + return; } - this.lastWidth = width; + // 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 = $('
...
'); + $(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')) + .tipsy(); + } + // and all the previous ones (going backwards) + firstHidden = false; + } else { + // add to total width + currentTotalWidth += $crumb.data('real-width'); + } + i--; + } + + if (!OC.Util.hasSVGSupport()) { + OC.Util.replaceSVG(this.$el); + } } }; diff --git a/apps/files/js/file-upload.js b/apps/files/js/file-upload.js index da58e1c31b..2637d13f9b 100644 --- a/apps/files/js/file-upload.js +++ b/apps/files/js/file-upload.js @@ -179,9 +179,20 @@ OC.Upload = { callbacks.onNoConflicts(selection); }, + _hideProgressBar: function() { + $('#uploadprogresswrapper input.stop').fadeOut(); + $('#uploadprogressbar').fadeOut(function() { + $('#file_upload_start').trigger(new $.Event('resized')); + }); + }, + + _showProgressBar: function() { + $('#uploadprogressbar').fadeIn(); + $('#file_upload_start').trigger(new $.Event('resized')); + }, + init: function() { if ( $('#file_upload_start').exists() ) { - var file_upload_param = { dropZone: $('#content'), // restrict dropZone to content div autoUpload: false, @@ -444,7 +455,7 @@ OC.Upload = { OC.Upload.log('progress handle fileuploadstart', e, data); $('#uploadprogresswrapper input.stop').show(); $('#uploadprogressbar').progressbar({value: 0}); - $('#uploadprogressbar').fadeIn(); + OC.Upload._showProgressBar(); }); fileupload.on('fileuploadprogress', function(e, data) { OC.Upload.log('progress handle fileuploadprogress', e, data); @@ -458,15 +469,13 @@ OC.Upload = { fileupload.on('fileuploadstop', function(e, data) { OC.Upload.log('progress handle fileuploadstop', e, data); - $('#uploadprogresswrapper input.stop').fadeOut(); - $('#uploadprogressbar').fadeOut(); + OC.Upload._hideProgressBar(); }); fileupload.on('fileuploadfail', function(e, data) { OC.Upload.log('progress handle fileuploadfail', e, data); //if user pressed cancel hide upload progress bar and cancel button if (data.errorThrown === 'abort') { - $('#uploadprogresswrapper input.stop').fadeOut(); - $('#uploadprogressbar').fadeOut(); + OC.Upload._hideProgressBar(); } }); @@ -649,7 +658,7 @@ OC.Upload = { //IE < 10 does not fire the necessary events for the progress bar. if ($('html.lte9').length === 0) { $('#uploadprogressbar').progressbar({value: 0}); - $('#uploadprogressbar').fadeIn(); + OC.Upload._showProgressBar(); } var eventSource = new OC.EventSource( @@ -668,12 +677,12 @@ OC.Upload = { }); eventSource.listen('success', function(data) { var file = data; - $('#uploadprogressbar').fadeOut(); + OC.Upload._hideProgressBar(); FileList.add(file, {hidden: hidden, animate: true}); }); eventSource.listen('error', function(error) { - $('#uploadprogressbar').fadeOut(); + OC.Upload._hideProgressBar(); var message = (error && error.message) || t('core', 'Error fetching URL'); OC.Notification.show(message); //hide notification after 10 sec diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index 55afedb206..31c4c11183 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -150,11 +150,10 @@ this.$el.find('thead th .columntitle').click(_.bind(this._onClickHeader, this)); - $(window).resize(function() { - // TODO: debounce this ? - var width = $(this).width(); - self.breadcrumb.resize(width, false); - }); + this._onResize = _.debounce(_.bind(this._onResize, this), 100); + $(window).resize(this._onResize); + + this.$el.on('show', this._onResize); this.$fileList.on('click','td.filename>a.name', _.bind(this._onClickFile, this)); this.$fileList.on('change', 'td.filename>input:checkbox', _.bind(this._onClickFileCheckbox, this)); @@ -176,6 +175,22 @@ } }, + /** + * Event handler for when the window size changed + */ + _onResize: function() { + var containerWidth = this.$el.width(); + var actionsWidth = 0; + $.each(this.$el.find('#controls .actions'), function(index, action) { + actionsWidth += $(action).outerWidth(); + }); + + // substract app navigation toggle when visible + containerWidth -= $('#app-navigation-toggle').width(); + + this.breadcrumb.setMaxWidth(containerWidth - actionsWidth - 10); + }, + /** * Event handler for when the URL changed */ @@ -1538,6 +1553,9 @@ // handle upload events var fileUploadStart = this.$el.find('#file_upload_start'); + // detect the progress bar resize + fileUploadStart.on('resized', this._onResize); + fileUploadStart.on('fileuploaddrop', function(e, data) { OC.Upload.log('filelist handle fileuploaddrop', e, data); diff --git a/apps/files/tests/js/breadcrumbSpec.js b/apps/files/tests/js/breadcrumbSpec.js index e3d9c757a7..30784fd70a 100644 --- a/apps/files/tests/js/breadcrumbSpec.js +++ b/apps/files/tests/js/breadcrumbSpec.js @@ -19,7 +19,6 @@ * */ -/* global BreadCrumb */ describe('OCA.Files.BreadCrumb tests', function() { var BreadCrumb = OCA.Files.BreadCrumb; @@ -131,48 +130,42 @@ describe('OCA.Files.BreadCrumb tests', function() { }); }); describe('Resizing', function() { - var bc, widthStub, dummyDir, - oldUpdateTotalWidth; + var bc, dummyDir, widths, oldUpdateTotalWidth; beforeEach(function() { - dummyDir = '/short name/longer name/looooooooooooonger/even longer long long long longer long/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/last one'; + dummyDir = '/short name/longer name/looooooooooooonger/' + + 'even longer long long long longer long/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/last one'; + + // using hard-coded widths (pre-measured) to avoid getting different + // results on different browsers due to font engine differences + widths = [41, 106, 112, 160, 257, 251, 91]; oldUpdateTotalWidth = BreadCrumb.prototype._updateTotalWidth; BreadCrumb.prototype._updateTotalWidth = function() { - // need to set display:block for correct offsetWidth (no CSS loaded here) - $('div.crumb').css({ - 'display': 'block', - 'float': 'left' + // pre-set a width to simulate consistent measurement + $('div.crumb').each(function(index){ + $(this).css('width', widths[index]); }); return oldUpdateTotalWidth.apply(this, arguments); }; bc = new BreadCrumb(); - widthStub = sinon.stub($.fn, 'width'); // append dummy navigation and controls // as they are currently used for measurements $('#testArea').append( - '', '
' ); - - // make sure we know the test screen width - $('#testArea').css('width', 1280); - - // use test area as we need it for measurements $('#controls').append(bc.$el); - $('#controls').append('
Dummy action with a given width
'); }); afterEach(function() { BreadCrumb.prototype._updateTotalWidth = oldUpdateTotalWidth; - widthStub.restore(); bc = null; }); - it('Hides breadcrumbs to fit window', function() { + it('Hides breadcrumbs to fit max allowed width', function() { var $crumbs; - widthStub.returns(500); + bc.setMaxWidth(500); // triggers resize implicitly bc.setDirectory(dummyDir); $crumbs = bc.$el.find('.crumb'); @@ -190,19 +183,23 @@ describe('OCA.Files.BreadCrumb tests', function() { expect($crumbs.eq(4).hasClass('hidden')).toEqual(true); expect($crumbs.eq(5).hasClass('hidden')).toEqual(true); expect($crumbs.eq(6).hasClass('hidden')).toEqual(false); + expect($crumbs.eq(7).hasClass('hidden')).toEqual(false); }); - it('Updates ellipsis on window size increase', function() { + it('Updates the breadcrumbs when reducing max allowed width', function() { var $crumbs; - widthStub.returns(500); + // enough space + bc.setMaxWidth(1800); + + expect(bc.$el.find('.ellipsis').length).toEqual(0); + // triggers resize implicitly bc.setDirectory(dummyDir); - $crumbs = bc.$el.find('.crumb'); // simulate increase - $('#testArea').css('width', 1800); - bc.resize(1800); + bc.setMaxWidth(950); + $crumbs = bc.$el.find('.crumb'); // first one is always visible expect($crumbs.eq(0).hasClass('hidden')).toEqual(false); // second one has ellipsis @@ -213,37 +210,35 @@ describe('OCA.Files.BreadCrumb tests', function() { // subsequent elements are hidden expect($crumbs.eq(2).hasClass('hidden')).toEqual(true); expect($crumbs.eq(3).hasClass('hidden')).toEqual(true); - expect($crumbs.eq(4).hasClass('hidden')).toEqual(true); // the rest is visible + expect($crumbs.eq(4).hasClass('hidden')).toEqual(false); expect($crumbs.eq(5).hasClass('hidden')).toEqual(false); expect($crumbs.eq(6).hasClass('hidden')).toEqual(false); }); - it('Updates ellipsis on window size decrease', function() { + it('Removes the ellipsis when there is enough space', function() { var $crumbs; - $('#testArea').css('width', 2000); - widthStub.returns(2000); + bc.setMaxWidth(500); // triggers resize implicitly bc.setDirectory(dummyDir); $crumbs = bc.$el.find('.crumb'); - // simulate decrease - bc.resize(500); - $('#testArea').css('width', 500); + // ellipsis + expect(bc.$el.find('.ellipsis').length).toEqual(1); - // first one is always visible + // simulate increase + bc.setMaxWidth(1800); + + // no ellipsis + expect(bc.$el.find('.ellipsis').length).toEqual(0); + + // all are visible expect($crumbs.eq(0).hasClass('hidden')).toEqual(false); - // second one has ellipsis expect($crumbs.eq(1).hasClass('hidden')).toEqual(false); - expect($crumbs.eq(1).find('.ellipsis').length).toEqual(1); - // there is only one ellipsis in total - expect($crumbs.find('.ellipsis').length).toEqual(1); - // subsequent elements are hidden - expect($crumbs.eq(2).hasClass('hidden')).toEqual(true); - expect($crumbs.eq(3).hasClass('hidden')).toEqual(true); - expect($crumbs.eq(4).hasClass('hidden')).toEqual(true); - // the rest is visible - expect($crumbs.eq(5).hasClass('hidden')).toEqual(true); + expect($crumbs.eq(2).hasClass('hidden')).toEqual(false); + expect($crumbs.eq(3).hasClass('hidden')).toEqual(false); + expect($crumbs.eq(4).hasClass('hidden')).toEqual(false); + expect($crumbs.eq(5).hasClass('hidden')).toEqual(false); expect($crumbs.eq(6).hasClass('hidden')).toEqual(false); }); }); diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js index dea7c48e05..318f66825a 100644 --- a/apps/files/tests/js/filelistSpec.js +++ b/apps/files/tests/js/filelistSpec.js @@ -21,6 +21,7 @@ describe('OCA.Files.FileList tests', function() { var testFiles, alertStub, notificationStub, fileList; + var bcResizeStub; /** * Generate test file data @@ -52,6 +53,9 @@ describe('OCA.Files.FileList tests', function() { beforeEach(function() { alertStub = sinon.stub(OC.dialogs, 'alert'); notificationStub = sinon.stub(OC.Notification, 'show'); + // prevent resize algo to mess up breadcrumb order while + // testing + bcResizeStub = sinon.stub(OCA.Files.BreadCrumb.prototype, '_resize'); // init parameters and test table elements $('#testArea').append( @@ -125,6 +129,7 @@ describe('OCA.Files.FileList tests', function() { notificationStub.restore(); alertStub.restore(); + bcResizeStub.restore(); }); describe('Getters', function() { it('Returns the current directory', function() { diff --git a/core/css/mobile.css b/core/css/mobile.css index d840cdafa2..2003e0a7f4 100644 --- a/core/css/mobile.css +++ b/core/css/mobile.css @@ -110,7 +110,6 @@ min-width: initial !important; left: 0 !important; padding-left: 0; - padding-right: 0 !important; } /* position controls for apps with app-navigation */ #app-navigation+#app-content #controls { diff --git a/core/css/styles.css b/core/css/styles.css index c1f42b457e..66af01ae3c 100644 --- a/core/css/styles.css +++ b/core/css/styles.css @@ -261,11 +261,9 @@ input[type="submit"].enabled { /* position controls for apps with app-navigation */ #app-navigation+#app-content #controls { left: 250px; - padding-right: 250px; } .viewer-mode #app-navigation+#app-content #controls { left: 0; - padding-right: 0; } #controls .button, diff --git a/core/js/tests/specHelper.js b/core/js/tests/specHelper.js index 2af3497051..3d208d9ef3 100644 --- a/core/js/tests/specHelper.js +++ b/core/js/tests/specHelper.js @@ -85,7 +85,7 @@ window.Snap.prototype = { beforeEach(function() { // test area for elements that need absolute selector access or measure widths/heights // which wouldn't work for detached or hidden elements - $testArea = $('
'); + $testArea = $('
'); $('body').append($testArea); // enforce fake XHR, tests should not depend on the server and // must use fake responses for expected calls diff --git a/tests/karma.config.js b/tests/karma.config.js index 290790686b..14a0d7e846 100644 --- a/tests/karma.config.js +++ b/tests/karma.config.js @@ -110,15 +110,16 @@ module.exports = function(config) { // core mocks files.push(corePath + 'tests/specHelper.js'); + var srcFile, i; // add core library files - for ( var i = 0; i < coreModule.libraries.length; i++ ) { - var srcFile = corePath + coreModule.libraries[i]; + for ( i = 0; i < coreModule.libraries.length; i++ ) { + srcFile = corePath + coreModule.libraries[i]; files.push(srcFile); } // add core modules files - for ( var i = 0; i < coreModule.modules.length; i++ ) { - var srcFile = corePath + coreModule.modules[i]; + for ( i = 0; i < coreModule.modules.length; i++ ) { + srcFile = corePath + coreModule.modules[i]; files.push(srcFile); if (enableCoverage) { preprocessors[srcFile] = 'coverage'; @@ -155,12 +156,15 @@ module.exports = function(config) { } // add source files for apps to test - for ( var i = 0; i < appsToTest.length; i++ ) { + for ( i = 0; i < appsToTest.length; i++ ) { addApp(appsToTest[i]); } // serve images to avoid warnings files.push({pattern: 'core/img/**/*', watched: false, included: false, served: true}); + + // include core CSS + files.push({pattern: 'core/css/*.css', watched: true, included: true, served: true}); config.set({ @@ -180,7 +184,9 @@ module.exports = function(config) { proxies: { // prevent warnings for images - '/context.html//core/img/': 'http://localhost:9876/base/core/img/' + '/context.html//core/img/': 'http://localhost:9876/base/core/img/', + '/context.html//core/css/': 'http://localhost:9876/base/core/css/', + '/context.html//core/fonts/': 'http://localhost:9876/base/core/fonts/' }, // test results reporter to use