diff options
-rw-r--r-- | client/src/app/app.component.ts | 4 | ||||
-rw-r--r-- | client/src/app/core/menu/menu.service.ts | 37 | ||||
-rw-r--r-- | client/src/app/core/routing/menu-guard.service.ts | 2 | ||||
-rw-r--r-- | client/src/app/core/wrappers/screen.service.ts | 58 | ||||
-rw-r--r-- | client/src/sass/bootstrap.scss | 23 |
5 files changed, 114 insertions, 10 deletions
diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts index b8af4e2c7..5b0439e6b 100644 --- a/client/src/app/app.component.ts +++ b/client/src/app/app.component.ts | |||
@@ -180,8 +180,8 @@ export class AppComponent implements OnInit, AfterViewInit { | |||
180 | 180 | ||
181 | eventsObs.pipe( | 181 | eventsObs.pipe( |
182 | filter((e: Event): e is GuardsCheckStart => e instanceof GuardsCheckStart), | 182 | filter((e: Event): e is GuardsCheckStart => e instanceof GuardsCheckStart), |
183 | filter(() => this.screenService.isInSmallView()) | 183 | filter(() => this.screenService.isInSmallView() || !!this.screenService.isInTouchScreen()) |
184 | ).subscribe(() => this.menu.isMenuDisplayed = false) // User clicked on a link in the menu, change the page | 184 | ).subscribe(() => this.menu.setMenuDisplay(false)) // User clicked on a link in the menu, change the page |
185 | } | 185 | } |
186 | 186 | ||
187 | private injectBroadcastMessage () { | 187 | private injectBroadcastMessage () { |
diff --git a/client/src/app/core/menu/menu.service.ts b/client/src/app/core/menu/menu.service.ts index ef5271f97..671ee3e4f 100644 --- a/client/src/app/core/menu/menu.service.ts +++ b/client/src/app/core/menu/menu.service.ts | |||
@@ -12,22 +12,45 @@ export class MenuService { | |||
12 | constructor ( | 12 | constructor ( |
13 | private screenService: ScreenService | 13 | private screenService: ScreenService |
14 | ) { | 14 | ) { |
15 | // Do not display menu on small screens | 15 | // Do not display menu on small or touch screens |
16 | if (this.screenService.isInSmallView()) { | 16 | if (this.screenService.isInSmallView() || this.screenService.isInTouchScreen()) { |
17 | this.isMenuDisplayed = false | 17 | this.setMenuDisplay(false) |
18 | } | 18 | } |
19 | 19 | ||
20 | fromEvent(window, 'resize') | 20 | this.handleWindowResize() |
21 | .pipe(debounceTime(200)) | ||
22 | .subscribe(() => this.onResize()) | ||
23 | } | 21 | } |
24 | 22 | ||
25 | toggleMenu () { | 23 | toggleMenu () { |
26 | this.isMenuDisplayed = !this.isMenuDisplayed | 24 | this.setMenuDisplay(!this.isMenuDisplayed) |
27 | this.isMenuChangedByUser = true | 25 | this.isMenuChangedByUser = true |
28 | } | 26 | } |
29 | 27 | ||
28 | setMenuDisplay (display: boolean) { | ||
29 | this.isMenuDisplayed = display | ||
30 | |||
31 | // On touch screens, lock body scroll and display content overlay when memu is opened | ||
32 | if (this.screenService.isInTouchScreen()) { | ||
33 | if (this.isMenuDisplayed) { | ||
34 | document.body.classList.add('menu-open') | ||
35 | this.screenService.onFingerSwipe('left', () => { this.setMenuDisplay(false) }) | ||
36 | } else { | ||
37 | document.body.classList.remove('menu-open') | ||
38 | } | ||
39 | } | ||
40 | } | ||
41 | |||
30 | onResize () { | 42 | onResize () { |
31 | this.isMenuDisplayed = window.innerWidth >= 800 && !this.isMenuChangedByUser | 43 | this.isMenuDisplayed = window.innerWidth >= 800 && !this.isMenuChangedByUser |
32 | } | 44 | } |
45 | |||
46 | private handleWindowResize () { | ||
47 | // On touch screens, do not handle window resize event since opened menu is handled with a content overlay | ||
48 | if (this.screenService.isInTouchScreen()) { | ||
49 | return | ||
50 | } | ||
51 | |||
52 | fromEvent(window, 'resize') | ||
53 | .pipe(debounceTime(200)) | ||
54 | .subscribe(() => this.onResize()) | ||
55 | } | ||
33 | } | 56 | } |
diff --git a/client/src/app/core/routing/menu-guard.service.ts b/client/src/app/core/routing/menu-guard.service.ts index 9df285635..501e009c0 100644 --- a/client/src/app/core/routing/menu-guard.service.ts +++ b/client/src/app/core/routing/menu-guard.service.ts | |||
@@ -15,7 +15,7 @@ abstract class MenuGuard implements CanActivate, CanDeactivate<any> { | |||
15 | // small screens already have the site-wide onResize from screenService | 15 | // small screens already have the site-wide onResize from screenService |
16 | // > medium screens have enough space to fit the administrative menus | 16 | // > medium screens have enough space to fit the administrative menus |
17 | if (!this.screen.isInMobileView() && this.screen.isInMediumView()) { | 17 | if (!this.screen.isInMobileView() && this.screen.isInMediumView()) { |
18 | this.menu.isMenuDisplayed = this.display | 18 | this.menu.setMenuDisplay(this.display) |
19 | } | 19 | } |
20 | return true | 20 | return true |
21 | } | 21 | } |
diff --git a/client/src/app/core/wrappers/screen.service.ts b/client/src/app/core/wrappers/screen.service.ts index 96fb7ae57..88cf662b3 100644 --- a/client/src/app/core/wrappers/screen.service.ts +++ b/client/src/app/core/wrappers/screen.service.ts | |||
@@ -54,6 +54,64 @@ export class ScreenService { | |||
54 | return this.windowInnerWidth | 54 | return this.windowInnerWidth |
55 | } | 55 | } |
56 | 56 | ||
57 | // https://stackoverflow.com/questions/2264072/detect-a-finger-swipe-through-javascript-on-the-iphone-and-android | ||
58 | onFingerSwipe (direction: 'left' | 'right' | 'up' | 'down', action: () => void, removeEventOnEnd = true) { | ||
59 | let touchDownClientX: number | ||
60 | let touchDownClientY: number | ||
61 | |||
62 | const onTouchStart = (event: TouchEvent) => { | ||
63 | const firstTouch = event.touches[0] | ||
64 | touchDownClientX = firstTouch.clientX | ||
65 | touchDownClientY = firstTouch.clientY | ||
66 | } | ||
67 | |||
68 | const onTouchMove = (event: TouchEvent) => { | ||
69 | if (!touchDownClientX || !touchDownClientY) { | ||
70 | return | ||
71 | } | ||
72 | |||
73 | const touchUpClientX = event.touches[0].clientX | ||
74 | const touchUpClientY = event.touches[0].clientY | ||
75 | |||
76 | const touchClientX = Math.abs(touchDownClientX - touchUpClientX) | ||
77 | const touchClientY = Math.abs(touchDownClientY - touchUpClientY) | ||
78 | |||
79 | if (touchClientX > touchClientY) { | ||
80 | if (touchClientX > 0) { | ||
81 | if (direction === 'left') { | ||
82 | if (removeEventOnEnd) this.removeFingerSwipeEventListener(onTouchStart, onTouchMove) | ||
83 | action() | ||
84 | } | ||
85 | } else { | ||
86 | if (direction === 'right') { | ||
87 | if (removeEventOnEnd) this.removeFingerSwipeEventListener(onTouchStart, onTouchMove) | ||
88 | action() | ||
89 | } | ||
90 | } | ||
91 | } else { | ||
92 | if (touchClientY > 0) { | ||
93 | if (direction === 'up') { | ||
94 | if (removeEventOnEnd) this.removeFingerSwipeEventListener(onTouchStart, onTouchMove) | ||
95 | action() | ||
96 | } | ||
97 | } else { | ||
98 | if (direction === 'down') { | ||
99 | if (removeEventOnEnd) this.removeFingerSwipeEventListener(onTouchStart, onTouchMove) | ||
100 | action() | ||
101 | } | ||
102 | } | ||
103 | } | ||
104 | } | ||
105 | |||
106 | document.addEventListener('touchstart', onTouchStart, false) | ||
107 | document.addEventListener('touchmove', onTouchMove, false) | ||
108 | } | ||
109 | |||
110 | private removeFingerSwipeEventListener (onTouchStart: (event: TouchEvent) => void, onTouchMove: (event: TouchEvent) => void) { | ||
111 | document.removeEventListener('touchstart', onTouchStart) | ||
112 | document.removeEventListener('touchmove', onTouchMove) | ||
113 | } | ||
114 | |||
57 | private refreshWindowInnerWidth () { | 115 | private refreshWindowInnerWidth () { |
58 | this.lastFunctionCallTime = new Date().getTime() | 116 | this.lastFunctionCallTime = new Date().getTime() |
59 | 117 | ||
diff --git a/client/src/sass/bootstrap.scss b/client/src/sass/bootstrap.scss index 308a28658..a3b60198c 100644 --- a/client/src/sass/bootstrap.scss +++ b/client/src/sass/bootstrap.scss | |||
@@ -150,6 +150,29 @@ $icon-font-path: '~@neos21/bootstrap3-glyphicons/assets/fonts/'; | |||
150 | width: 100vw; // Make sure the content fits all the available width | 150 | width: 100vw; // Make sure the content fits all the available width |
151 | } | 151 | } |
152 | 152 | ||
153 | // On touchscreen devices, simply overflow: hidden to avoid detached overlay on scroll | ||
154 | @media (hover: none) and (pointer: coarse) { | ||
155 | .modal-open, .menu-open { | ||
156 | overflow: hidden !important; | ||
157 | } | ||
158 | |||
159 | // On touchscreen devices display content overlay when opened menu | ||
160 | .menu-open { | ||
161 | .main-col { | ||
162 | &::before { | ||
163 | background-color: black; | ||
164 | width: 100vw; | ||
165 | height: 100vh; | ||
166 | opacity: 0.75; | ||
167 | content: ''; | ||
168 | display: block; | ||
169 | position: fixed; | ||
170 | z-index: z('header') - 1; | ||
171 | } | ||
172 | } | ||
173 | } | ||
174 | } | ||
175 | |||
153 | // Nav customizations | 176 | // Nav customizations |
154 | .nav .nav-link { | 177 | .nav .nav-link { |
155 | display: flex !important; | 178 | display: flex !important; |