diff --git a/cypress/integration/layout.spec.js b/cypress/integration/layout.spec.js index f57cb3ed..14b95be7 100644 --- a/cypress/integration/layout.spec.js +++ b/cypress/integration/layout.spec.js @@ -1,55 +1,109 @@ /** * Copyright (C) 2019 The Software Heritage developers * See the AUTHORS file at the top-level directory of this distribution * License: GNU Affero General Public License version 3, or any later version * See top-level LICENSE file for more information */ const url = '/'; describe('Test top-bar', function() { - it('should should contain all navigation links', function() { + beforeEach(function() { + cy.clearLocalStorage(); cy.visit(url); + }); + it('should should contain all navigation links', function() { cy.get('.swh-top-bar a') .should('have.length.of.at.least', 4) .and('be.visible') .and('have.attr', 'href'); }); it('should show donate button on lg screen', function() { - cy.visit(url); cy.get('.swh-donate-link') .should('be.visible'); }); it('should hide donate button on sm screen', function() { cy.viewport(600, 800); - cy.visit(url); cy.get('.swh-donate-link') .should('not.be.visible'); }); + it('should change container width when toggling Full width switch', function() { + cy.get('#swh-web-content') + .should('have.class', 'container') + .should('not.have.class', 'container-fluid'); + + cy.should(() => { + expect(JSON.parse(localStorage.getItem('swh-web-full-width'))).to.be.null; + }); + + cy.get('#swh-full-width-switch') + .click({force: true}); + + cy.get('#swh-web-content') + .should('not.have.class', 'container') + .should('have.class', 'container-fluid'); + + cy.should(() => { + expect(JSON.parse(localStorage.getItem('swh-web-full-width'))).to.be.true; + }); + + cy.get('#swh-full-width-switch') + .click({force: true}); + + cy.get('#swh-web-content') + .should('have.class', 'container') + .should('not.have.class', 'container-fluid'); + + cy.should(() => { + expect(JSON.parse(localStorage.getItem('swh-web-full-width'))).to.be.false; + }); + }); + it('should restore container width when loading page again', function() { + cy.visit(url) + .get('#swh-web-content') + .should('have.class', 'container') + .should('not.have.class', 'container-fluid'); + + cy.get('#swh-full-width-switch') + .click({force: true}); + + cy.visit(url) + .get('#swh-web-content') + .should('not.have.class', 'container') + .should('have.class', 'container-fluid'); + + cy.get('#swh-full-width-switch') + .click({force: true}); + + cy.visit(url) + .get('#swh-web-content') + .should('have.class', 'container') + .should('not.have.class', 'container-fluid'); + }); }); describe('Test footer', function() { beforeEach(function() { cy.visit(url); }); it('should be visible', function() { cy.get('footer') .should('be.visible'); }); it('should have correct copyright years', function() { const currentYear = new Date().getFullYear(); const copyrightText = '(C) 2015–' + currentYear.toString(); cy.get('footer') .should('contain', copyrightText); }); it('should contain link to Web API', function() { cy.get('footer') .get(`a[href="${this.Urls.api_1_homepage()}"]`) .should('contain', 'Web API'); }); }); diff --git a/swh/web/assets/src/bundles/webapp/webapp-utils.js b/swh/web/assets/src/bundles/webapp/webapp-utils.js index d175f1d9..985ac6a1 100644 --- a/swh/web/assets/src/bundles/webapp/webapp-utils.js +++ b/swh/web/assets/src/bundles/webapp/webapp-utils.js @@ -1,315 +1,338 @@ /** * Copyright (C) 2018-2020 The Software Heritage developers * See the AUTHORS file at the top-level directory of this distribution * License: GNU Affero General Public License version 3, or any later version * See top-level LICENSE file for more information */ import objectFitImages from 'object-fit-images'; import {selectText} from 'utils/functions'; import {BREAKPOINT_MD} from 'utils/constants'; let collapseSidebar = false; let previousSidebarState = localStorage.getItem('remember.lte.pushmenu'); if (previousSidebarState !== undefined) { collapseSidebar = previousSidebarState === 'sidebar-collapse'; } $(document).on('DOMContentLoaded', () => { // set state to collapsed on smaller devices if ($(window).width() < BREAKPOINT_MD) { collapseSidebar = true; } // restore previous sidebar state (collapsed/expanded) if (collapseSidebar) { // hack to avoid animated transition for collapsing sidebar // when loading a page let sidebarTransition = $('.main-sidebar, .main-sidebar:before').css('transition'); let sidebarEltsTransition = $('.sidebar .nav-link p, .main-sidebar .brand-text, .sidebar .user-panel .info').css('transition'); $('.main-sidebar, .main-sidebar:before').css('transition', 'none'); $('.sidebar .nav-link p, .main-sidebar .brand-text, .sidebar .user-panel .info').css('transition', 'none'); $('body').addClass('sidebar-collapse'); $('.swh-words-logo-swh').css('visibility', 'visible'); // restore transitions for user navigation setTimeout(() => { $('.main-sidebar, .main-sidebar:before').css('transition', sidebarTransition); $('.sidebar .nav-link p, .main-sidebar .brand-text, .sidebar .user-panel .info').css('transition', sidebarEltsTransition); }); } }); $(document).on('collapsed.lte.pushmenu', event => { if ($('body').width() >= BREAKPOINT_MD) { $('.swh-words-logo-swh').css('visibility', 'visible'); } }); $(document).on('shown.lte.pushmenu', event => { $('.swh-words-logo-swh').css('visibility', 'hidden'); }); function ensureNoFooterOverflow() { $('body').css('padding-bottom', $('footer').outerHeight() + 'px'); } $(document).ready(() => { // redirect to last browse page if any when clicking on the 'Browse' entry // in the sidebar $(`.swh-browse-link`).click(event => { let lastBrowsePage = sessionStorage.getItem('last-browse-page'); if (lastBrowsePage) { event.preventDefault(); window.location = lastBrowsePage; } }); const mainSideBar = $('.main-sidebar'); function updateSidebarState() { const body = $('body'); if (body.hasClass('sidebar-collapse') && !mainSideBar.hasClass('swh-sidebar-collapsed')) { mainSideBar.removeClass('swh-sidebar-expanded'); mainSideBar.addClass('swh-sidebar-collapsed'); $('.swh-words-logo-swh').css('visibility', 'visible'); } else if (!body.hasClass('sidebar-collapse') && !mainSideBar.hasClass('swh-sidebar-expanded')) { mainSideBar.removeClass('swh-sidebar-collapsed'); mainSideBar.addClass('swh-sidebar-expanded'); $('.swh-words-logo-swh').css('visibility', 'hidden'); } // ensure correct sidebar state when loading a page if (body.hasClass('hold-transition')) { setTimeout(() => { updateSidebarState(); }); } } // set sidebar state after collapse / expand animation mainSideBar.on('transitionend', evt => { updateSidebarState(); }); updateSidebarState(); // ensure footer do not overflow main content for mobile devices // or after resizing the browser window ensureNoFooterOverflow(); $(window).resize(function() { ensureNoFooterOverflow(); if ($('body').hasClass('sidebar-collapse') && $('body').width() >= BREAKPOINT_MD) { $('.swh-words-logo-swh').css('visibility', 'visible'); } }); // activate css polyfill 'object-fit: contain' in old browsers objectFitImages(); // reparent the modals to the top navigation div in order to be able // to display them $('.swh-browse-top-navigation').append($('.modal')); let selectedCode = null; function getCodeOrPreEltUnderPointer(e) { let elts = document.elementsFromPoint(e.clientX, e.clientY); for (let elt of elts) { if (elt.nodeName === 'CODE' || elt.nodeName === 'PRE') { return elt; } } return null; } // click handler to set focus on code block for copy $(document).click(e => { selectedCode = getCodeOrPreEltUnderPointer(e); }); function selectCode(event, selectedCode) { if (selectedCode) { let hljsLnCodeElts = $(selectedCode).find('.hljs-ln-code'); if (hljsLnCodeElts.length) { selectText(hljsLnCodeElts[0], hljsLnCodeElts[hljsLnCodeElts.length - 1]); } else { selectText(selectedCode.firstChild, selectedCode.lastChild); } event.preventDefault(); } } // select the whole text of focused code block when user // double clicks or hits Ctrl+A $(document).dblclick(e => { if ((e.ctrlKey || e.metaKey)) { selectCode(e, getCodeOrPreEltUnderPointer(e)); } }); $(document).keydown(e => { if ((e.ctrlKey || e.metaKey) && e.key === 'a') { selectCode(e, selectedCode); } }); // show/hide back-to-top button let scrollThreshold = 0; scrollThreshold += $('.swh-top-bar').height() || 0; scrollThreshold += $('.navbar').height() || 0; $(window).scroll(() => { if ($(window).scrollTop() > scrollThreshold) { $('#back-to-top').css('display', 'block'); } else { $('#back-to-top').css('display', 'none'); } }); }); export function initPage(page) { $(document).ready(() => { // set relevant sidebar link to page active $(`.swh-${page}-item`).addClass('active'); $(`.swh-${page}-link`).addClass('active'); // triggered when unloading the current page $(window).on('unload', () => { // backup current browse page if (page === 'browse') { sessionStorage.setItem('last-browse-page', window.location); } }); }); } export function initHomePage() { $(document).ready(() => { $('.swh-coverage-list').iFrameResize({heightCalculationMethod: 'taggedElement'}); fetch(Urls.stat_counters()) .then(response => response.json()) .then(data => { if (data.stat_counters.content) { $('#swh-contents-count').html(data.stat_counters.content.toLocaleString()); $('#swh-revisions-count').html(data.stat_counters.revision.toLocaleString()); $('#swh-origins-count').html(data.stat_counters.origin.toLocaleString()); $('#swh-directories-count').html(data.stat_counters.directory.toLocaleString()); $('#swh-persons-count').html(data.stat_counters.person.toLocaleString()); $('#swh-releases-count').html(data.stat_counters.release.toLocaleString()); } if (data.stat_counters_history.content) { swh.webapp.drawHistoryCounterGraph('#swh-contents-count-history', data.stat_counters_history.content); swh.webapp.drawHistoryCounterGraph('#swh-revisions-count-history', data.stat_counters_history.revision); swh.webapp.drawHistoryCounterGraph('#swh-origins-count-history', data.stat_counters_history.origin); } else { $('#swh-contents-count-history').hide(); $('#swh-revisions-count-history').hide(); $('#swh-origins-count-history').hide(); } }); }); initPage('home'); } export function showModalMessage(title, message) { $('#swh-web-modal-message .modal-title').text(title); $('#swh-web-modal-message .modal-content p').text(message); $('#swh-web-modal-message').modal('show'); } export function showModalConfirm(title, message, callback) { $('#swh-web-modal-confirm .modal-title').text(title); $('#swh-web-modal-confirm .modal-content p').text(message); $('#swh-web-modal-confirm #swh-web-modal-confirm-ok-btn').bind('click', () => { callback(); $('#swh-web-modal-confirm').modal('hide'); $('#swh-web-modal-confirm #swh-web-modal-confirm-ok-btn').unbind('click'); }); $('#swh-web-modal-confirm').modal('show'); } export function showModalHtml(title, html) { $('#swh-web-modal-html .modal-title').text(title); $('#swh-web-modal-html .modal-body').html(html); $('#swh-web-modal-html').modal('show'); } export function addJumpToPagePopoverToDataTable(dataTableElt) { dataTableElt.on('draw.dt', function() { $('.paginate_button.disabled').css('cursor', 'pointer'); $('.paginate_button.disabled').on('click', event => { const pageInfo = dataTableElt.page.info(); let content = ' / ${pageInfo.pages}`; $(event.target).popover({ 'title': 'Jump to page', 'content': content, 'html': true, 'placement': 'top', 'sanitizeFn': swh.webapp.filterXSS }); $(event.target).popover('show'); $('.jump-to-page').on('change', function() { $('.paginate_button.disabled').popover('hide'); const pageNumber = parseInt($(this).val()) - 1; dataTableElt.page(pageNumber).draw('page'); }); }); }); dataTableElt.on('preXhr.dt', () => { $('.paginate_button.disabled').popover('hide'); }); } let swhObjectIcons; export function setSwhObjectIcons(icons) { swhObjectIcons = icons; } export function getSwhObjectIcon(swhObjectType) { return swhObjectIcons[swhObjectType]; } let browsedSwhObjectMetadata = {}; export function setBrowsedSwhObjectMetadata(metadata) { browsedSwhObjectMetadata = metadata; } export function getBrowsedSwhObjectMetadata() { return browsedSwhObjectMetadata; } // This will contain a mapping between an archived object type // and its related SWHID metadata for each object reachable from // the current browse view. // SWHID metadata contain the following keys: // * object_type: type of archived object // * object_id: sha1 object identifier // * swhid: SWH persistent identifier without contextual info // * swhid_url: URL to resolve SWH persistent identifier without contextual info // * context: object describing SWHID context // * swhid_with_context: SWH persistent identifier with contextual info // * swhid_with_context_url: URL to resolve SWH persistent identifier with contextual info let swhidsContext_ = {}; export function setSwhIdsContext(swhidsContext) { swhidsContext_ = {}; for (let swhidContext of swhidsContext) { swhidsContext_[swhidContext.object_type] = swhidContext; } } export function getSwhIdsContext() { return swhidsContext_; } + +function setFullWidth(fullWidth) { + if (fullWidth) { + $('#swh-web-content').removeClass('container'); + $('#swh-web-content').addClass('container-fluid'); + } else { + $('#swh-web-content').removeClass('container-fluid'); + $('#swh-web-content').addClass('container'); + } + localStorage.setItem('swh-web-full-width', JSON.stringify(fullWidth)); + $('#swh-full-width-switch').prop('checked', fullWidth); +} + +export function fullWidthToggled(event) { + setFullWidth($(event.target).prop('checked')); +} + +export function setContainerFullWidth() { + let previousFullWidthState = JSON.parse(localStorage.getItem('swh-web-full-width')); + if (previousFullWidthState !== null) { + setFullWidth(previousFullWidthState); + } +} diff --git a/swh/web/templates/layout.html b/swh/web/templates/layout.html index 160dc9f9..527d754b 100644 --- a/swh/web/templates/layout.html +++ b/swh/web/templates/layout.html @@ -1,220 +1,225 @@ {% comment %} Copyright (C) 2015-2019 The Software Heritage developers See the AUTHORS file at the top-level directory of this distribution License: GNU Affero General Public License version 3, or any later version See top-level LICENSE file for more information {% endcomment %} {% load js_reverse %} {% load static %} {% load render_bundle from webpack_loader %} {% load swh_templatetags %} {% block title %}{% endblock %} {% render_bundle 'vendors' %} {% render_bundle 'webapp' %} {% block header %}{% endblock %}
-
+
{% block content %}{% endblock %}
{% include "includes/global-modals.html" %}
back to top
+