Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

On touchscreens add content overlay for opened menu #3088

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions client/src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,8 @@ export class AppComponent implements OnInit, AfterViewInit {

eventsObs.pipe(
filter((e: Event): e is GuardsCheckStart => e instanceof GuardsCheckStart),
filter(() => this.screenService.isInSmallView())
).subscribe(() => this.menu.isMenuDisplayed = false) // User clicked on a link in the menu, change the page
filter(() => this.screenService.isInSmallView() || !!this.screenService.isInTouchScreen())
).subscribe(() => this.menu.setMenuDisplay(false)) // User clicked on a link in the menu, change the page
}

private injectBroadcastMessage () {
Expand Down
37 changes: 30 additions & 7 deletions client/src/app/core/menu/menu.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,45 @@ export class MenuService {
constructor (
private screenService: ScreenService
) {
// Do not display menu on small screens
if (this.screenService.isInSmallView()) {
this.isMenuDisplayed = false
// Do not display menu on small or touch screens
if (this.screenService.isInSmallView() || this.screenService.isInTouchScreen()) {
this.setMenuDisplay(false)
}

fromEvent(window, 'resize')
.pipe(debounceTime(200))
.subscribe(() => this.onResize())
this.handleWindowResize()
}

toggleMenu () {
this.isMenuDisplayed = !this.isMenuDisplayed
this.setMenuDisplay(!this.isMenuDisplayed)
this.isMenuChangedByUser = true
}

setMenuDisplay (display: boolean) {
this.isMenuDisplayed = display

// On touch screens, lock body scroll and display content overlay when memu is opened
if (this.screenService.isInTouchScreen()) {
if (this.isMenuDisplayed) {
document.body.classList.add('menu-open')
this.screenService.onFingerSwipe('left', () => { this.setMenuDisplay(false) })
} else {
document.body.classList.remove('menu-open')
}
}
}

onResize () {
this.isMenuDisplayed = window.innerWidth >= 800 && !this.isMenuChangedByUser
}

private handleWindowResize () {
// On touch screens, do not handle window resize event since opened menu is handled with a content overlay
if (this.screenService.isInTouchScreen()) {
return
}

fromEvent(window, 'resize')
.pipe(debounceTime(200))
.subscribe(() => this.onResize())
}
}
2 changes: 1 addition & 1 deletion client/src/app/core/routing/menu-guard.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ abstract class MenuGuard implements CanActivate, CanDeactivate<any> {
// small screens already have the site-wide onResize from screenService
// > medium screens have enough space to fit the administrative menus
if (!this.screen.isInMobileView() && this.screen.isInMediumView()) {
this.menu.isMenuDisplayed = this.display
this.menu.setMenuDisplay(this.display)
}
return true
}
Expand Down
58 changes: 58 additions & 0 deletions client/src/app/core/wrappers/screen.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,64 @@ export class ScreenService {
return this.windowInnerWidth
}

// https://stackoverflow.com/questions/2264072/detect-a-finger-swipe-through-javascript-on-the-iphone-and-android
onFingerSwipe (direction: 'left' | 'right' | 'up' | 'down', action: () => void, removeEventOnEnd = true) {
let touchDownClientX: number
let touchDownClientY: number

const onTouchStart = (event: TouchEvent) => {
const firstTouch = event.touches[0]
touchDownClientX = firstTouch.clientX
touchDownClientY = firstTouch.clientY
}

const onTouchMove = (event: TouchEvent) => {
if (!touchDownClientX || !touchDownClientY) {
return
}

const touchUpClientX = event.touches[0].clientX
const touchUpClientY = event.touches[0].clientY

const touchClientX = Math.abs(touchDownClientX - touchUpClientX)
const touchClientY = Math.abs(touchDownClientY - touchUpClientY)

if (touchClientX > touchClientY) {
if (touchClientX > 0) {
if (direction === 'left') {
if (removeEventOnEnd) this.removeFingerSwipeEventListener(onTouchStart, onTouchMove)
action()
}
} else {
if (direction === 'right') {
if (removeEventOnEnd) this.removeFingerSwipeEventListener(onTouchStart, onTouchMove)
action()
}
}
} else {
if (touchClientY > 0) {
if (direction === 'up') {
if (removeEventOnEnd) this.removeFingerSwipeEventListener(onTouchStart, onTouchMove)
action()
}
} else {
if (direction === 'down') {
if (removeEventOnEnd) this.removeFingerSwipeEventListener(onTouchStart, onTouchMove)
action()
}
}
}
}

document.addEventListener('touchstart', onTouchStart, false)
document.addEventListener('touchmove', onTouchMove, false)
}

private removeFingerSwipeEventListener (onTouchStart: (event: TouchEvent) => void, onTouchMove: (event: TouchEvent) => void) {
document.removeEventListener('touchstart', onTouchStart)
document.removeEventListener('touchmove', onTouchMove)
}

private refreshWindowInnerWidth () {
this.lastFunctionCallTime = new Date().getTime()

Expand Down
23 changes: 23 additions & 0 deletions client/src/sass/bootstrap.scss
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,29 @@ $icon-font-path: '~@neos21/bootstrap3-glyphicons/assets/fonts/';
width: 100vw; // Make sure the content fits all the available width
}

// On touchscreen devices, simply overflow: hidden to avoid detached overlay on scroll
@media (hover: none) and (pointer: coarse) {
.modal-open, .menu-open {
overflow: hidden !important;
}

// On touchscreen devices display content overlay when opened menu
.menu-open {
.main-col {
&::before {
background-color: black;
width: 100vw;
height: 100vh;
opacity: 0.75;
content: '';
display: block;
position: fixed;
z-index: z('header') - 1;
}
}
}
}

// Nav customizations
.nav .nav-link {
display: flex !important;
Expand Down