blob: 4e146b6cf7ed3ff6f844e6835269d21938638c9d [file] [log] [blame]
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +02001import { SLIDES_SELECTOR } from '../utils/constants.js'
2import { extend, queryAll, transformElement } from '../utils/util.js'
3
4/**
5 * Handles all logic related to the overview mode
6 * (birds-eye view of all slides).
7 */
8export default class Overview {
9
10 constructor( Reveal ) {
11
12 this.Reveal = Reveal;
13
14 this.active = false;
15
16 this.onSlideClicked = this.onSlideClicked.bind( this );
17
18 }
19
20 /**
21 * Displays the overview of slides (quick nav) by scaling
22 * down and arranging all slide elements.
23 */
24 activate() {
25
26 // Only proceed if enabled in config
Marc Kupietz9c036a42024-05-14 13:17:25 +020027 if( this.Reveal.getConfig().overview && !this.Reveal.isScrollView() && !this.isActive() ) {
Christophe Dervieuxe1893ae2021-10-07 17:09:02 +020028
29 this.active = true;
30
31 this.Reveal.getRevealElement().classList.add( 'overview' );
32
33 // Don't auto-slide while in overview mode
34 this.Reveal.cancelAutoSlide();
35
36 // Move the backgrounds element into the slide container to
37 // that the same scaling is applied
38 this.Reveal.getSlidesElement().appendChild( this.Reveal.getBackgroundsElement() );
39
40 // Clicking on an overview slide navigates to it
41 queryAll( this.Reveal.getRevealElement(), SLIDES_SELECTOR ).forEach( slide => {
42 if( !slide.classList.contains( 'stack' ) ) {
43 slide.addEventListener( 'click', this.onSlideClicked, true );
44 }
45 } );
46
47 // Calculate slide sizes
48 const margin = 70;
49 const slideSize = this.Reveal.getComputedSlideSize();
50 this.overviewSlideWidth = slideSize.width + margin;
51 this.overviewSlideHeight = slideSize.height + margin;
52
53 // Reverse in RTL mode
54 if( this.Reveal.getConfig().rtl ) {
55 this.overviewSlideWidth = -this.overviewSlideWidth;
56 }
57
58 this.Reveal.updateSlidesVisibility();
59
60 this.layout();
61 this.update();
62
63 this.Reveal.layout();
64
65 const indices = this.Reveal.getIndices();
66
67 // Notify observers of the overview showing
68 this.Reveal.dispatchEvent({
69 type: 'overviewshown',
70 data: {
71 'indexh': indices.h,
72 'indexv': indices.v,
73 'currentSlide': this.Reveal.getCurrentSlide()
74 }
75 });
76
77 }
78
79 }
80
81 /**
82 * Uses CSS transforms to position all slides in a grid for
83 * display inside of the overview mode.
84 */
85 layout() {
86
87 // Layout slides
88 this.Reveal.getHorizontalSlides().forEach( ( hslide, h ) => {
89 hslide.setAttribute( 'data-index-h', h );
90 transformElement( hslide, 'translate3d(' + ( h * this.overviewSlideWidth ) + 'px, 0, 0)' );
91
92 if( hslide.classList.contains( 'stack' ) ) {
93
94 queryAll( hslide, 'section' ).forEach( ( vslide, v ) => {
95 vslide.setAttribute( 'data-index-h', h );
96 vslide.setAttribute( 'data-index-v', v );
97
98 transformElement( vslide, 'translate3d(0, ' + ( v * this.overviewSlideHeight ) + 'px, 0)' );
99 } );
100
101 }
102 } );
103
104 // Layout slide backgrounds
105 Array.from( this.Reveal.getBackgroundsElement().childNodes ).forEach( ( hbackground, h ) => {
106 transformElement( hbackground, 'translate3d(' + ( h * this.overviewSlideWidth ) + 'px, 0, 0)' );
107
108 queryAll( hbackground, '.slide-background' ).forEach( ( vbackground, v ) => {
109 transformElement( vbackground, 'translate3d(0, ' + ( v * this.overviewSlideHeight ) + 'px, 0)' );
110 } );
111 } );
112
113 }
114
115 /**
116 * Moves the overview viewport to the current slides.
117 * Called each time the current slide changes.
118 */
119 update() {
120
121 const vmin = Math.min( window.innerWidth, window.innerHeight );
122 const scale = Math.max( vmin / 5, 150 ) / vmin;
123 const indices = this.Reveal.getIndices();
124
125 this.Reveal.transformSlides( {
126 overview: [
127 'scale('+ scale +')',
128 'translateX('+ ( -indices.h * this.overviewSlideWidth ) +'px)',
129 'translateY('+ ( -indices.v * this.overviewSlideHeight ) +'px)'
130 ].join( ' ' )
131 } );
132
133 }
134
135 /**
136 * Exits the slide overview and enters the currently
137 * active slide.
138 */
139 deactivate() {
140
141 // Only proceed if enabled in config
142 if( this.Reveal.getConfig().overview ) {
143
144 this.active = false;
145
146 this.Reveal.getRevealElement().classList.remove( 'overview' );
147
148 // Temporarily add a class so that transitions can do different things
149 // depending on whether they are exiting/entering overview, or just
150 // moving from slide to slide
151 this.Reveal.getRevealElement().classList.add( 'overview-deactivating' );
152
153 setTimeout( () => {
154 this.Reveal.getRevealElement().classList.remove( 'overview-deactivating' );
155 }, 1 );
156
157 // Move the background element back out
158 this.Reveal.getRevealElement().appendChild( this.Reveal.getBackgroundsElement() );
159
160 // Clean up changes made to slides
161 queryAll( this.Reveal.getRevealElement(), SLIDES_SELECTOR ).forEach( slide => {
162 transformElement( slide, '' );
163
164 slide.removeEventListener( 'click', this.onSlideClicked, true );
165 } );
166
167 // Clean up changes made to backgrounds
168 queryAll( this.Reveal.getBackgroundsElement(), '.slide-background' ).forEach( background => {
169 transformElement( background, '' );
170 } );
171
172 this.Reveal.transformSlides( { overview: '' } );
173
174 const indices = this.Reveal.getIndices();
175
176 this.Reveal.slide( indices.h, indices.v );
177 this.Reveal.layout();
178 this.Reveal.cueAutoSlide();
179
180 // Notify observers of the overview hiding
181 this.Reveal.dispatchEvent({
182 type: 'overviewhidden',
183 data: {
184 'indexh': indices.h,
185 'indexv': indices.v,
186 'currentSlide': this.Reveal.getCurrentSlide()
187 }
188 });
189
190 }
191 }
192
193 /**
194 * Toggles the slide overview mode on and off.
195 *
196 * @param {Boolean} [override] Flag which overrides the
197 * toggle logic and forcibly sets the desired state. True means
198 * overview is open, false means it's closed.
199 */
200 toggle( override ) {
201
202 if( typeof override === 'boolean' ) {
203 override ? this.activate() : this.deactivate();
204 }
205 else {
206 this.isActive() ? this.deactivate() : this.activate();
207 }
208
209 }
210
211 /**
212 * Checks if the overview is currently active.
213 *
214 * @return {Boolean} true if the overview is active,
215 * false otherwise
216 */
217 isActive() {
218
219 return this.active;
220
221 }
222
223 /**
224 * Invoked when a slide is and we're in the overview.
225 *
226 * @param {object} event
227 */
228 onSlideClicked( event ) {
229
230 if( this.isActive() ) {
231 event.preventDefault();
232
233 let element = event.target;
234
235 while( element && !element.nodeName.match( /section/gi ) ) {
236 element = element.parentNode;
237 }
238
239 if( element && !element.classList.contains( 'disabled' ) ) {
240
241 this.deactivate();
242
243 if( element.nodeName.match( /section/gi ) ) {
244 let h = parseInt( element.getAttribute( 'data-index-h' ), 10 ),
245 v = parseInt( element.getAttribute( 'data-index-v' ), 10 );
246
247 this.Reveal.slide( h, v );
248 }
249
250 }
251 }
252
253 }
254
255}