]> BookStack Code Mirror - bookstack/blob - resources/js/components/tri-layout.js
Layout: Added scroll fade to the sidebars
[bookstack] / resources / js / components / tri-layout.js
1 import {Component} from './component';
2
3 export class TriLayout extends Component {
4
5     setup() {
6         this.container = this.$refs.container;
7         this.tabs = this.$manyRefs.tab;
8         this.sidebarScrollContainers = this.$manyRefs.sidebarScrollContainer;
9
10         this.lastLayoutType = 'none';
11         this.onDestroy = null;
12         this.scrollCache = {
13             content: 0,
14             info: 0,
15         };
16         this.lastTabShown = 'content';
17
18         // Bind any listeners
19         this.mobileTabClick = this.mobileTabClick.bind(this);
20
21         // Watch layout changes
22         this.updateLayout();
23         window.addEventListener('resize', () => {
24             this.updateLayout();
25         }, {passive: true});
26
27         this.setupSidebarScrollHandlers();
28     }
29
30     updateLayout() {
31         let newLayout = 'tablet';
32         if (window.innerWidth <= 1000) newLayout = 'mobile';
33         if (window.innerWidth > 1400) newLayout = 'desktop';
34         if (newLayout === this.lastLayoutType) return;
35
36         if (this.onDestroy) {
37             this.onDestroy();
38             this.onDestroy = null;
39         }
40
41         if (newLayout === 'desktop') {
42             this.setupDesktop();
43         } else if (newLayout === 'mobile') {
44             this.setupMobile();
45         }
46
47         this.lastLayoutType = newLayout;
48     }
49
50     setupMobile() {
51         for (const tab of this.tabs) {
52             tab.addEventListener('click', this.mobileTabClick);
53         }
54
55         this.onDestroy = () => {
56             for (const tab of this.tabs) {
57                 tab.removeEventListener('click', this.mobileTabClick);
58             }
59         };
60     }
61
62     setupDesktop() {
63         //
64     }
65
66     /**
67      * Action to run when the mobile info toggle bar is clicked/tapped
68      * @param event
69      */
70     mobileTabClick(event) {
71         const {tab} = event.target.dataset;
72         this.showTab(tab);
73     }
74
75     /**
76      * Show the content tab.
77      * Used by the page-display component.
78      */
79     showContent() {
80         this.showTab('content', false);
81     }
82
83     /**
84      * Show the given tab
85      * @param {String} tabName
86      * @param {Boolean }scroll
87      */
88     showTab(tabName, scroll = true) {
89         this.scrollCache[this.lastTabShown] = document.documentElement.scrollTop;
90
91         // Set tab status
92         for (const tab of this.tabs) {
93             const isActive = (tab.dataset.tab === tabName);
94             tab.setAttribute('aria-selected', isActive ? 'true' : 'false');
95         }
96
97         // Toggle section
98         const showInfo = (tabName === 'info');
99         this.container.classList.toggle('show-info', showInfo);
100
101         // Set the scroll position from cache
102         if (scroll) {
103             const pageHeader = document.querySelector('header');
104             const defaultScrollTop = pageHeader.getBoundingClientRect().bottom;
105             document.documentElement.scrollTop = this.scrollCache[tabName] || defaultScrollTop;
106             setTimeout(() => {
107                 document.documentElement.scrollTop = this.scrollCache[tabName] || defaultScrollTop;
108             }, 50);
109         }
110
111         this.lastTabShown = tabName;
112     }
113
114     setupSidebarScrollHandlers() {
115         for (const sidebar of this.sidebarScrollContainers) {
116             sidebar.addEventListener('scroll', () => this.handleSidebarScroll(sidebar), {
117                 passive: true,
118             });
119             this.handleSidebarScroll(sidebar);
120         }
121
122         window.addEventListener('resize', () => {
123             for (const sidebar of this.sidebarScrollContainers) {
124                 this.handleSidebarScroll(sidebar);
125             }
126         });
127     }
128
129     handleSidebarScroll(sidebar) {
130         const scrollable = sidebar.clientHeight !== sidebar.scrollHeight;
131         const atTop = sidebar.scrollTop === 0;
132         const atBottom = (sidebar.scrollTop + sidebar.clientHeight) === sidebar.scrollHeight;
133
134         sidebar.parentElement.classList.toggle('scroll-away-from-top', !atTop && scrollable);
135         sidebar.parentElement.classList.toggle('scroll-away-from-bottom', !atBottom && scrollable);
136     }
137
138 }