Skip to content
Snippets Groups Projects
Commit f6b1acb3 authored by Thomas Hackl's avatar Thomas Hackl Committed by Jan-Hendrik Willms
Browse files

Resolve "Polishing für die neue HTML-Struktur und die responsive Ansicht" Teil 2

Closes #1858, #1906, #1905, #1881, #1884, and #1882

Merge request studip/studip!1234
parent b64e995d
No related branches found
No related tags found
No related merge requests found
......@@ -77,6 +77,31 @@ class ResponsiveHelper
return [$navigation, $activated];
}
/**
* Returns the navigation object required for the Vue.js component.
*
* The object will always contain the currently selected navigation path.
* Besides that, the object may contain the whole navigation and a hash
* for that navigation. If a hash is passed and it matches the currently
* genereated hash, the navigation and hash will be omitted from the
* response for performance reasons. We don't want to include the large
* navigation object in every response.
*
* @return array
*/
public static function getNavigationObject(string $stored_hash = null): array
{
[$navigation, $activated] = self::getNavigationArray();
$hash = md5(json_encode($navigation));
$response = compact('activated');
if ($stored_hash !== $hash) {
$response = array_merge($response, compact('navigation', 'hash'));
}
return $response;
}
/**
* Recursively build a navigation array from the subnavigation/children
* of a navigation object.
......
// Build responsive menu on domready or resize
STUDIP.domReady(() => {
const cache = STUDIP.Cache.getInstance('responsive.');
if (STUDIP.Navigation.navigation !== undefined) {
cache.set('navigation', STUDIP.Navigation.navigation);
STUDIP.Cookie.set('responsive-navigation-hash', STUDIP.Navigation.hash);
} else {
STUDIP.Navigation.navigation = cache.get('navigation');
}
STUDIP.Responsive.engage();
if (STUDIP.Responsive.isFullscreen()) {
......
......@@ -3,7 +3,7 @@
* @type {[type]}
*/
function determineBreakpoint(element) {
return $(element).closest('.ui-dialog-content').length > 0 ? '.ui-dialog-content' : '#content';
return $(element).closest('.ui-dialog-content').length > 0 ? '.ui-dialog-content' : 'body';
}
/**
......
......@@ -288,6 +288,7 @@ body {
justify-content: space-between;
background-color: $dark-gray-color-10;
font-size: 0.9em;
height: 2.3em;
border-bottom: 1px solid $dark-gray-color-40;
}
......
......@@ -75,6 +75,7 @@ $sidebarOut: -330px;
#responsive-navigation-items {
background-color: $base-color;
left: 0;
max-height: calc(100vh - $header-bar-container-height - 5px);
max-width: $responsive-menu-width;
overflow-y: auto;
padding-bottom: 5px;
......@@ -308,6 +309,8 @@ $sidebarOut: -330px;
#sidebar {
background-color: $white;
max-height: calc(100vh - 100px);
overflow-y: auto;
position: absolute;
transform: translateX($sidebarOut);
z-index: 100;
......@@ -510,13 +513,9 @@ $sidebarOut: -330px;
display: none;
}
#sidebar {
flex: 0;
&.responsive-hide {
#sidebar.responsive-hide {
top: 110px;
}
}
#content-wrapper {
flex: 1;
......@@ -528,7 +527,9 @@ $sidebarOut: -330px;
.consuming_mode {
display: unset;
#responsive-contentbar {
#main-header,
#sidebar,
#main-footer {
display: none;
}
}
......@@ -712,10 +713,6 @@ html:not(.responsive-display):not(.fullscreen-mode) {
#login,
#request_new_password,
#web_migrate {
.messagebox {
margin: 0;
}
#background-desktop,
#background-mobile {
position: fixed;
......@@ -737,6 +734,11 @@ html:not(.responsive-display):not(.fullscreen-mode) {
margin: 0;
padding: 0;
width: calc(100% - 10px);
.messagebox {
margin: 0;
width: calc(100vw - 74px);
}
}
}
......
......@@ -85,6 +85,14 @@ a.wiki-restricted {
overflow: auto;
}
.wiki {
padding: 0 !important;
section {
padding: 0 10px;
}
}
$authors: (
0: $dark-gray-color-20,
1: $red-20,
......
......@@ -81,8 +81,11 @@ export default {
sidebar.classList.add('responsive-show');
sidebar.classList.remove('responsive-hide');
if (html.classList.contains('responsive-display') && !html.classList.contains('fullscreen-mode')) {
// Set a timeout here so that the content "disappears" after slide-in aninmation is finished.
setTimeout(() => {
content.style.display = 'none';
pageTitle.style.display = 'none';
}, 300);
}
this.sidebarOpen = true;
}
......
<template>
<div role="navigation">
<div class="responsive-navigation-header">
<transition name="slide" appear>
<button v-if="menuNeeded"
id="responsive-navigation-button" class="styleless"
:title="showMenu ? $gettext('Navigation schließen') : $gettext('Navigation öffnen')"
......@@ -13,6 +14,7 @@
:size="iconSize" :class="showMenu ? 'menu-open' : 'menu-closed'">
</studip-icon>
</button>
</transition>
<toggle-fullscreen v-if="!isResponsive && !isFocusMode && me.username != 'nobody'"
:is-fullscreen="isFullscreen"></toggle-fullscreen>
</div>
......@@ -43,7 +45,7 @@
</section>
</template>
<template v-else>
<focus-trap active="true" :return-focus-on-deactivate="true"
<focus-trap :active="true" :return-focus-on-deactivate="true"
:click-outside-deactivates="true">
<div>
<div class="close-avatarmenu">
......@@ -127,23 +129,30 @@ export default {
hasSidebar: {
type: Boolean,
default: true
},
navigation: {
type: Object,
required: true,
}
},
data() {
let studipNavigation = this.sanitizeNavigation(this.navigation);
return {
studipNavigation,
isResponsive: false,
isFullscreen: false,
isFocusMode: false,
headerMagic: false,
iconSize: 28,
showMenu: false,
activeItem: STUDIP.Navigation.activated.at(-1) ?? 'start',
currentNavigation: this.findItem(STUDIP.Navigation.activated.at(0) ?? 'start'),
activeItem: this.navigation.activated.at(-1) ?? 'start',
currentNavigation: this.findItem(this.navigation.activated.at(0) ?? 'start', studipNavigation),
initialNavigation: {},
initialTitle: '',
isAdmin: ['root','admin'].includes(this.me.perm),
courses: [],
avatarNavigation: STUDIP.Navigation.navigation.avatar,
avatarNavigation: studipNavigation.avatar,
avatarMenuOpen: false,
observer: null,
hasSkiplinks: document.querySelector('#skiplink_list') !== null
......@@ -152,22 +161,13 @@ export default {
computed: {
// Current navigation title, supplemented by context title if available
currentTitle() {
return this.context != '' && this.currentNavigation.path.indexOf('my_courses/') != -1 ?
return this.context !== '' && this.currentNavigation.path.indexOf('my_courses/') !== -1 ?
this.context : '';
},
// The parent element of the current navigation item
currentParent() {
return this.currentNavigation.parent != null
? this.findItem(this.currentNavigation.parent)
: null;
},
/*
* The parent element of the current navigation item parent
* which is used to provide a link up
*/
currentGrandparent() {
return this.currentParent != null && this.currentParent.parent != null
? this.findItem(this.currentParent.parent)
return this.currentNavigation.parent
? this.findItem(this.currentNavigation.parent, this.studipNavigation)
: null;
},
/*
......@@ -210,53 +210,44 @@ export default {
* @returns {{parent: null, path: string, visible: boolean, children, icon: null, active: boolean, title, url}|null}
*/
findItem(path, navigation) {
// No navigation given, use full Stud.IP navigation hierarchy.
if (!navigation) {
const nav = STUDIP.Navigation.navigation;
// Some "pseudo" navigation directly at root level.
if (path === '/' || path === 'start') {
return {
active: true,
children: nav,
children: navigation,
icon: null,
parent: null,
path: '/',
title: nav.start.title,
url: nav.start.url,
title: navigation.start.title,
url: navigation.start.url,
visible: true
};
// Direct hit in sub navigation items.
} else if (nav[path]) {
return nav[path];
// Recurse through sub navigation items.
} else {
// Found requested item at current level.
if (navigation[path]) {
return navigation[path];
} else {
// Special handling for first navigation level, we have no "children" attribute here.
if (navigation.start) {
let found = null;
for (const sub in nav) {
found = this.findItem(path, nav[sub]);
for (const sub in navigation) {
found = this.findItem(path, navigation[sub]);
if (found) {
break;
}
}
return found;
}
} else {
} else if (navigation.children) {
// Found requested item at current level.
if (navigation[path]) {
return navigation[path];
} else {
if (navigation.children) {
// Found requested item as child of current one.
if (navigation.children[path]) {
return navigation.children[path];
// Recurse deeper.
} else {
let found = null;
for (const sub in navigation.children) {
found = this.findItem(path, navigation.children[sub]);
......@@ -274,11 +265,9 @@ export default {
}
}
},
/**
* Open or close the navigation menu
* @param event
*/
toggleMenu() {
......@@ -299,7 +288,7 @@ export default {
},
/**
* Turn fullscreen mode on or off
* @param event
* @param state
*/
setFullscreen(state) {
const html = document.querySelector('html');
......@@ -327,11 +316,11 @@ export default {
},
/**
* Move to another item in navigation structure, specified by path
* @param string path
* @param path
*/
moveTo(path) {
this.avatarMenuOpen = false;
this.currentNavigation = this.findItem(path ? path : '/');
this.currentNavigation = this.findItem(path ? path : '/', this.studipNavigation);
this.$nextTick(() => {
const current = document.querySelector('.navigation-current') ?? document.querySelector('.navigation-item');
if (current) {
......@@ -384,6 +373,7 @@ export default {
if (classList.includes('consuming_mode')) {
this.isFocusMode = true;
STUDIP.Vue.emit('consuming-mode-enabled');
this.setFullscreen(false);
} else {
this.isFocusMode = false;
STUDIP.Vue.emit('consuming-mode-disabled');
......@@ -427,12 +417,22 @@ export default {
}
break;
case 'HEADER':
if (classList.includes('cw-ribbon-consume')) {
this.isFocusMode = true;
} else {
this.isFocusMode = false;
this.isFocusMode = classList.includes('cw-ribbon-consume');
}
},
sanitizeNavigation(navigation) {
const cache = STUDIP.Cache.getInstance('responsive.');
// No navigation object was sent, read from cache
if (navigation.navigation === undefined) {
return cache.get('navigation');
}
// Navigation object was sent, store in cache
cache.set('navigation', navigation.navigation);
STUDIP.Cookie.set('responsive-navigation-hash', navigation.hash);
return navigation.navigation;
}
},
mounted() {
......@@ -503,7 +503,6 @@ export default {
attributeFilter: ['class']
})
})
},
beforeDestroy() {
this.observer.disconnect();
......@@ -513,13 +512,40 @@ export default {
</script>
<style lang="scss">
@media not prefers-reduced-motion {
.slide-enter-active,
.slide-leave-active {
transition: all .3s ease;
}
.slide-enter-to,
.slide-leave-from,
.slide-leave {
margin-left: 0;
}
.slide-enter,
.slide-enter-from,
.slide-leave-to {
margin-left: -50px;
}
.appear-enter-active,
.appear-leave-active {
transition: opacity .3s ease;
}
.appear-leave,
.appear-leave-from,
.appear-enter-to {
opacity: 1;
}
.appear-enter,
.appear-enter-from,
.appear-leave-to {
opacity: 0;
}
}
</style>
......@@ -58,7 +58,6 @@ if ($navigation) {
<!-- Top bar with site title, quick search and avatar menu -->
<div id="top-bar" role="banner">
<?= $this->render_partial('responsive-navigation.php') ?>
<div id="responsive-menu">
<?
$user = User::findCurrent();
......@@ -71,15 +70,19 @@ if ($navigation) {
'perm' => $GLOBALS['perm']->get_perm()
];
$hasSidebar = Sidebar::get()->countWidgets(NavigationWidget::class) > 0;
$navWidget = Sidebar::get()->countWidgets(NavigationWidget::class);
$allWidgets = Sidebar::get()->countWidgets();
$hasSidebar = $allWidgets - $navWidget > 0;
?>
<? } else {
$me = ['username' => 'nobody'];
$hasSidebar = false;
} ?>
<responsive-navigation :me="<?= htmlReady(json_encode($me)) ?>" context="<?= htmlReady(Context::get() ?
Context::get()->getFullname() : '') ?>" :has-sidebar="<?= $hasSidebar ? 'true' : 'false' ?>">
</responsive-navigation>
<responsive-navigation :me="<?= htmlReady(json_encode($me)) ?>"
context="<?= htmlReady(Context::get() ? Context::get()->getFullname() : '') ?>"
:has-sidebar="<?= $hasSidebar ? 'true' : 'false' ?>"
:navigation="<?= htmlReady(json_encode(ResponsiveHelper::getNavigationObject($_COOKIE['responsive-navigation-hash'] ?? null))) ?>"
></responsive-navigation>
</div>
<div id="site-title">
<?= htmlReady(Config::get()->UNI_NAME_CLEAN) ?>
......
<?php
[$navigation, $activated] = ResponsiveHelper::getNavigationArray();
$hash = md5(json_encode($navigation));
$response = compact('activated');
if (!isset($_COOKIE['responsive-navigation-hash']) || $_COOKIE['responsive-navigation-hash'] !== $hash) {
$response = array_merge($response, compact('navigation', 'hash'));
}
?>
<script>
STUDIP.Navigation = <?= json_encode($response, JSON_PARTIAL_OUTPUT_ON_ERROR) ?>;
</script>
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment