View Javadoc
1   /**
2    * This file Copyright (c) 2018 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.admincentral;
35  
36  import static info.magnolia.admincentral.ViewportLayout.View.*;
37  
38  import info.magnolia.event.EventBus;
39  import info.magnolia.ui.api.event.AdmincentralEventBus;
40  import info.magnolia.ui.api.ioc.AdmincentralScoped;
41  import info.magnolia.ui.api.location.Location;
42  import info.magnolia.ui.api.location.LocationController;
43  import info.magnolia.ui.api.shell.CloseAppEvent;
44  
45  import java.util.Arrays;
46  import java.util.Optional;
47  import java.util.stream.Stream;
48  
49  import javax.inject.Inject;
50  import javax.inject.Named;
51  
52  import org.slf4j.Logger;
53  import org.slf4j.LoggerFactory;
54  
55  import com.vaadin.ui.Component;
56  import com.vaadin.ui.CssLayout;
57  import com.vaadin.ui.Layout;
58  import com.vaadin.ui.Panel;
59  import com.vaadin.ui.VerticalLayout;
60  
61  import lombok.Getter;
62  
63  /**
64   * View holding all available views and making transitions between them.
65   */
66  @AdmincentralScoped
67  public class ViewportLayout extends CssLayout {
68  
69      private static final Logger log = LoggerFactory.getLogger(ViewportLayout.class);
70  
71      private static final String APP_VIEW_VISIBLE = "app-view-visible";
72      private static final String APP_VIEW_SLIDE_DOWN = "app-view-slide-down";
73      private static final String APP_VIEW_MOVE_DOWN = "app-view-move-down";
74      private static final String APP_VIEW_SLIDE_UP = "app-view-slide-up";
75      private static final String APP_VIEW_TO_FRONT = "app-view-to-front";
76  
77      private static final String FIND_BAR_PANEL_SLIDE_DOWN = "find-bar-panel-slide-down";
78      private static final String FIND_BAR_PANEL_OPEN = "find-bar-panel-open";
79      private static final String FIND_BAR_PANEL_HIDDEN = "find-bar-panel-hidden";
80  
81      private static final String STYLE_APPS_VIEWPORT = "apps-viewport";
82      private static final String STYLE_HEADER_APPS_VIEWPORT = "header-apps-viewport";
83  
84      @Getter
85      private View activeView;
86  
87      private final CssLayout appView;
88  
89      private final CssLayout headerAppView;
90  
91      @Getter
92      private final VerticalLayout appLauncherLayout;
93  
94      @Getter
95      private final Layout findBarResultsLayout;
96      @Getter
97      private final Panel appLayout;
98  
99      private final Panel tasksAppLayout;
100 
101     private final Panel notificationAppLayout;
102 
103     private final LocationController locationController;
104     private final EventBus eventBus;
105     private Location currentActiveAppLocation;
106 
107     @Inject
108     public ViewportLayout(LocationController locationController, @Named(AdmincentralEventBus.NAME) EventBus eventBus) {
109 
110         this.locationController = locationController;
111         this.eventBus = eventBus;
112 
113         findBarResultsLayout = new CssLayout();
114         findBarResultsLayout.setWidth(100, Unit.PERCENTAGE);
115         findBarResultsLayout.addStyleName("findbar-results");
116 
117         appLauncherLayout = new VerticalLayout();
118         appLauncherLayout.addStyleName("v-app-launcher");
119         appLauncherLayout.setSpacing(false);
120         appLauncherLayout.setMargin(false);
121         appLauncherLayout.setSizeFull();
122         Component findBarAndAppLauncher = new CssLayout(findBarResultsLayout, appLauncherLayout);
123         findBarAndAppLauncher.setWidth(100, Unit.PERCENTAGE);
124         findBarAndAppLauncher.addStyleName("findbarAndApplauncher");
125 
126         appLayout = new Panel() {
127             @Override
128             public void setContent(Component content) {
129                 super.setContent(content);
130                 if (content != null) {
131                     Optional.ofNullable(appView).ifPresent(v -> v.addStyleName(APP_VIEW_TO_FRONT));
132                 } else {
133                     Optional.ofNullable(appView).ifPresent(v -> v.removeStyleName(APP_VIEW_TO_FRONT));
134                 }
135             }
136         };
137         appLayout.setSizeFull();
138         appLayout.addStyleName(STYLE_APPS_VIEWPORT);
139 
140         CssLayout appOverlay = new CssLayout();
141         appOverlay.addStyleName("app-overlay");
142         appOverlay.setSizeFull();
143         appOverlay.addLayoutClickListener(event -> {
144             if (appLayout.getContent() != null) {
145                 final View from = activeView != null ? activeView : APP_LAUNCHER;
146                 updateView(from, APP);
147             }
148         });
149 
150         appView = new CssLayout(appLayout, appOverlay) {
151             @Override
152             protected String getCss(Component c) {
153                 return "z-index: " + (c == appLayout && (activeView == APP || appLayout.getContent() == null) ? 2 : 1);
154             }
155         };
156         appView.addStyleNames(STYLE_APPS_VIEWPORT);
157         appView.setSizeFull();
158 
159         tasksAppLayout = new Panel();
160         tasksAppLayout.setSizeFull();
161         tasksAppLayout.addStyleName(STYLE_HEADER_APPS_VIEWPORT);
162 
163         notificationAppLayout = new Panel();
164         notificationAppLayout.setSizeFull();
165         notificationAppLayout.addStyleName(STYLE_HEADER_APPS_VIEWPORT);
166 
167         headerAppView = new CssLayout(tasksAppLayout, notificationAppLayout);
168         headerAppView.addStyleNames(STYLE_HEADER_APPS_VIEWPORT);
169         headerAppView.setSizeFull();
170 
171         addComponents(findBarAndAppLauncher, appView, headerAppView);
172         addStyleName("viewportLayout");
173         setSizeFull();
174 
175         updateView(NONE, FIND_BAR);
176     }
177 
178     public void hideApp(String appName) {
179         View to;
180         View from;
181         Optional<View> appView = View.fromName(appName);
182         if (appView.isPresent() && appView.get().isHeaderView()) {
183             if (activeView.isFindBarActionView()) { // hide header apps and show app_launcher or find_bar
184                 to = activeView;
185             } else if (currentActiveAppLocation != null) { // with normal app under
186                 to = APP;
187             } else {
188                 to = FIND_BAR;
189             }
190             from = appView.get();
191         } else {
192             setAppViewTransparent(true);
193             this.currentActiveAppLocation = null;
194             to = appLauncherLayout.getStyleName().contains(FIND_BAR_PANEL_OPEN) ? APP_LAUNCHER : FIND_BAR;
195             from = activeView;
196         }
197         if (to != from) {
198             updateView(from, to);
199         }
200     }
201 
202     public void toggleAppLauncher() {
203         if (activeView == APP_LAUNCHER) {
204             if (appLayout.getContent() != null) {
205                 updateView(activeView, APP);
206             }
207         } else {
208             if (activeView.isHeaderView()) { // Try to close header app
209 
210                 activeView = APP_LAUNCHER;
211 
212                 eventBus.fireEvent(new CloseAppEvent());
213             } else {
214                 updateView(activeView, APP_LAUNCHER);
215             }
216         }
217     }
218 
219     public void toggleFindBar() {
220         if (activeView == FIND_BAR) {
221             if (appLayout.getContent() != null) {
222                 updateView(activeView, APP);
223             }
224         } else {
225             if (activeView.isHeaderView()) { // Try to close header app
226 
227                 activeView = FIND_BAR;
228 
229                 eventBus.fireEvent(new CloseAppEvent());
230             } else {
231                 showFindBar();
232             }
233         }
234     }
235 
236     public void showFindBar() {
237         if (activeView != FIND_BAR) {
238             updateView(activeView, FIND_BAR);
239         }
240     }
241 
242     public void showApp(String appName) {
243         // show header apps
244         Optional<View> to = View.fromName(appName);
245         if (to.isPresent() && to.get().isHeaderView()) {
246             if (activeView != to.get()) {
247                 updateView(activeView, to.get());
248             }
249         } else { // otherwise show normal app
250             currentActiveAppLocation = locationController.getWhere();
251             showApp();
252         }
253     }
254 
255     protected void showApp() {
256         if (activeView != APP) {
257             updateView(activeView, APP);
258         }
259     }
260 
261     protected void disableAnimations() {
262         disableFindBarAnimation();
263         appView.removeStyleNames(APP_VIEW_SLIDE_UP, APP_VIEW_SLIDE_DOWN, APP_VIEW_MOVE_DOWN);
264     }
265 
266     protected void disableFindBarAnimation() {
267         Arrays.asList(appLauncherLayout, findBarResultsLayout, headerAppView, notificationAppLayout, tasksAppLayout)
268                 .forEach(v -> v.removeStyleNames(FIND_BAR_PANEL_SLIDE_DOWN, FIND_BAR_PANEL_OPEN, FIND_BAR_PANEL_HIDDEN));
269     }
270 
271     public Panel getActiveAppView(Location location) {
272         if (location == null) { // close app, need to current active to clear content
273 
274             return Arrays.asList(this.appLayout, tasksAppLayout, notificationAppLayout).stream()
275                     .filter(layout -> layout.getContent() != null)
276                     .findFirst().orElse(null);
277 
278         } else { // navigate away, need the current active to fill content
279 
280             Optional<View> currentAppView = View.fromName(location.getAppName());
281             if (currentAppView.isPresent() && currentAppView.get().isHeaderView()) {
282                 switch (currentAppView.get()) {
283                 case NOTIFICATIONS_APP:
284                     return notificationAppLayout;
285                 case TASK_APP:
286                     return tasksAppLayout;
287                 default:
288                     return null;
289                 }
290             } else {
291                 this.currentActiveAppLocation = location;
292                 return appLayout;
293             }
294         }
295     }
296 
297     private void updateView(View from, View to) {
298 
299         log.debug("switch from {} to {}", Optional.ofNullable(from).orElse(NONE), Optional.ofNullable(to).orElse(NONE));
300         disableAnimations();
301 
302         boolean resetCurrentApp = false;
303 
304         if (from.isFindBarActionView()) {
305 
306             resetCurrentApp = switchFromFindBarAction(to);
307 
308         } else if (from.isHeaderView()) {
309 
310             resetCurrentApp = switchFromHeaderApp(to);
311 
312         } else if (from == APP) {
313 
314             switchFromApp(to);
315 
316         } else {
317 
318             hideHeaderAppView();
319 
320             appView.addStyleNames(APP_VIEW_SLIDE_DOWN);
321 
322             setAppViewTransparent(true);
323         }
324 
325         activeView = to;
326 
327         if (resetCurrentApp) {
328             Optional.ofNullable(currentActiveAppLocation).ifPresent(locationController::goTo);
329         }
330 
331     }
332 
333     private void switchFromApp(View to) {
334         if (to.isFindBarActionView()) {
335 
336             openFindBarActionFromApp(to);
337 
338         } else if (to.isHeaderView()) {
339 
340             hideFindBarActionViews();
341 
342             slideDownHeaderAppView(to);
343 
344             appView.addStyleName(APP_VIEW_SLIDE_DOWN);
345             setAppViewTransparent(isAppViewHidden());
346         }
347     }
348 
349     private boolean switchFromHeaderApp(View to) {
350         boolean resetCurrentApp = false;
351         if (to.isFindBarActionView()) {
352 
353             openFindBarActionFromHeaderApp(to);
354 
355         } else if (to == APP) {
356 
357             hideHeaderAppView();
358 
359             hideFindBarActionViews();
360 
361             appView.addStyleName(APP_VIEW_SLIDE_UP);
362             setAppViewTransparent(false);
363 
364             resetCurrentApp = true;
365 
366         } else {
367 
368             hideFindBarActionViews();
369 
370             appView.addStyleName(APP_VIEW_MOVE_DOWN);
371             setAppViewTransparent(isAppViewHidden());
372 
373             headerAppView.addStyleName(FIND_BAR_PANEL_OPEN);
374 
375             swapHeaderAppViews(to);
376 
377         }
378         return resetCurrentApp;
379     }
380 
381     private boolean switchFromFindBarAction(View to) {
382         boolean resetCurrentApp = false;
383         if (to == APP) {
384 
385             openAppFromFindBarActions(appLauncherLayout, findBarResultsLayout);
386 
387             // reset to home/current application when close findbar/app launcher
388             // doesn't apply when navigating to other application while current application still opening
389             resetCurrentApp = currentActiveAppLocation != null &&
390                     locationController.getWhere().getAppName().equals(currentActiveAppLocation.getAppName());
391 
392         } else if (to.isFindBarActionView()) {
393 
394             swapFindBarActionViews(to);
395 
396             hideHeaderAppView();
397 
398             appView.addStyleName(APP_VIEW_MOVE_DOWN);
399             setAppViewTransparent(isAppViewHidden());
400 
401         } else if (to.isHeaderView()) {
402 
403             hideFindBarActionViews();
404 
405             slideDownHeaderAppView(to);
406 
407             appView.addStyleName(APP_VIEW_MOVE_DOWN);
408             setAppViewTransparent(isAppViewHidden());
409 
410         }
411         return resetCurrentApp;
412     }
413 
414     private void openFindBarActionFromApp(View view) {
415 
416         swapFindBarActionViews(view);
417 
418         hideHeaderAppView();
419 
420         appView.addStyleName(APP_VIEW_SLIDE_DOWN);
421 
422         // this is the case app close and it go view findbar
423         if (view == FIND_BAR) {
424             setAppViewTransparent(currentActiveAppLocation == null || isAppViewHidden());
425         }
426 
427     }
428 
429     private void openFindBarActionFromHeaderApp(View view) {
430 
431         swapFindBarActionViews(view);
432 
433         hideHeaderAppView();
434 
435         appView.addStyleName(APP_VIEW_MOVE_DOWN);
436         setAppViewTransparent(isAppViewHidden());
437     }
438 
439     private void openAppFromFindBarActions(Layout findbarLayoutToSlideUp, Layout findbarLayoutToHide) {
440 
441         appView.addStyleName(APP_VIEW_SLIDE_UP);
442         setAppViewTransparent(false);
443 
444         findbarLayoutToHide.addStyleName(FIND_BAR_PANEL_HIDDEN);
445 
446         findbarLayoutToSlideUp.addStyleNames(FIND_BAR_PANEL_HIDDEN);
447 
448         hideHeaderAppView();
449 
450     }
451 
452     private void swapFindBarActionViews(View activeFindBarActionView) {
453         appLauncherLayout.addStyleNames(activeFindBarActionView == APP_LAUNCHER ? new String[]{FIND_BAR_PANEL_OPEN, FIND_BAR_PANEL_SLIDE_DOWN} :
454                 new String[]{FIND_BAR_PANEL_HIDDEN});
455 
456         findBarResultsLayout.addStyleNames(activeFindBarActionView == FIND_BAR ? new String[]{FIND_BAR_PANEL_OPEN, FIND_BAR_PANEL_SLIDE_DOWN} :
457                 new String[]{FIND_BAR_PANEL_HIDDEN});
458     }
459 
460     private void hideFindBarActionViews() {
461         appLauncherLayout.addStyleName(FIND_BAR_PANEL_HIDDEN);
462 
463         findBarResultsLayout.addStyleName(FIND_BAR_PANEL_HIDDEN);
464     }
465 
466     private void swapHeaderAppViews(View activeHeaderAppView) {
467 
468         tasksAppLayout.addStyleNames(activeHeaderAppView == TASK_APP ? new String[]{FIND_BAR_PANEL_SLIDE_DOWN, FIND_BAR_PANEL_OPEN} :
469                 new String[]{FIND_BAR_PANEL_HIDDEN});
470 
471         notificationAppLayout.addStyleNames(activeHeaderAppView == NOTIFICATIONS_APP ? new String[]{FIND_BAR_PANEL_SLIDE_DOWN, FIND_BAR_PANEL_OPEN} :
472                 new String[]{FIND_BAR_PANEL_HIDDEN});
473     }
474 
475     private void slideDownHeaderAppView(View view) {
476         headerAppView.addStyleNames(FIND_BAR_PANEL_SLIDE_DOWN, FIND_BAR_PANEL_OPEN);
477         swapHeaderAppViews(view);
478     }
479 
480     private void hideHeaderAppView() {
481         headerAppView.addStyleName(FIND_BAR_PANEL_HIDDEN);
482     }
483 
484     private boolean isAppViewHidden() {
485         return currentActiveAppLocation == null;
486     }
487 
488     private void setAppViewTransparent(boolean transparent) {
489         if (transparent) {
490             appView.removeStyleName(APP_VIEW_VISIBLE);
491         } else {
492             appView.addStyleName(APP_VIEW_VISIBLE);
493         }
494     }
495 
496     /**
497      * Type of view.
498      */
499     public enum View {
500 
501         NONE("none"),
502         APP("app"),
503         FIND_BAR("find-bar"),
504         APP_LAUNCHER("app-launcher"),
505         TASK_APP("tasks-app"),
506         NOTIFICATIONS_APP("notifications");
507 
508         String name;
509 
510         View(String name) {
511             this.name = name;
512         }
513 
514         static Optional<View> fromName(String name) {
515             return Stream.of(View.values())
516                     .filter(v -> v.name.equals(name))
517                     .findFirst();
518         }
519 
520         boolean isFindBarActionView() {
521             return this == APP_LAUNCHER || this == FIND_BAR;
522         }
523 
524         boolean isHeaderView() {
525             return this == TASK_APP || this == NOTIFICATIONS_APP;
526         }
527     }
528 }