View | Details | Raw Unified | Return to bug 39794 | Differences between
and this patch

Collapse All | Expand All

(-)debian/univention-management-console-module-appcenter.install (+2 lines)
 Lines 12-14    Link Here 
12
umc/js/appcenter/star.svg usr/share/univention-management-console-frontend/js/umc/modules/appcenter
12
umc/js/appcenter/star.svg usr/share/univention-management-console-frontend/js/umc/modules/appcenter
13
umc/js/appcenter/carouselArrowRight.svg usr/share/univention-management-console-frontend/js/umc/modules/appcenter
13
umc/js/appcenter/carouselArrowRight.svg usr/share/univention-management-console-frontend/js/umc/modules/appcenter
14
umc/js/appcenter/statusIcons.svg usr/share/univention-management-console-frontend/js/umc/modules/appcenter
14
umc/js/appcenter/statusIcons.svg usr/share/univention-management-console-frontend/js/umc/modules/appcenter
15
umc/js/appcenter/thumbnailError.svg usr/share/univention-management-console-frontend/js/umc/modules/appcenter
16
umc/js/appcenter/videoPlayButton.svg usr/share/univention-management-console-frontend/js/umc/modules/appcenter
(-)umc/js/appcenter/AppDetailsPage.js (-4 / +20 lines)
 Lines 63-71    Link Here 
63
	"umc/widgets/Grid",
63
	"umc/widgets/Grid",
64
	"umc/modules/appcenter/AppCenterGallery",
64
	"umc/modules/appcenter/AppCenterGallery",
65
	"umc/modules/appcenter/App",
65
	"umc/modules/appcenter/App",
66
	"umc/modules/appcenter/Carousel",
66
	"umc/modules/appcenter/ThumbnailGallery",
67
	"umc/i18n!umc/modules/appcenter"
67
	"umc/i18n!umc/modules/appcenter"
68
], function(declare, lang, kernel, array, dojoEvent, all, json, when, query, ioQuery, topic, Deferred, domConstruct, domClass, on, domStyle, Memory, Observable, Tooltip, Lightbox, entities, UMCApplication, tools, dialog, TitlePane, ContainerWidget, ProgressBar, Page, Text, Button, CheckBox, Grid, AppCenterGallery, App, Carousel, _) {
68
], function(declare, lang, kernel, array, dojoEvent, all, json, when, query, ioQuery, topic, Deferred, domConstruct, domClass, on, domStyle, Memory, Observable, Tooltip, Lightbox, entities, UMCApplication, tools, dialog, TitlePane, ContainerWidget, ProgressBar, Page, Text, Button, CheckBox, Grid, AppCenterGallery, App, ThumbnailGallery, _) {
69
69
70
	var adaptedGrid = declare([Grid], {
70
	var adaptedGrid = declare([Grid], {
71
		_updateContextActions: function() {
71
		_updateContextActions: function() {
 Lines 554-563    Link Here 
554
						src: ithumb
554
						src: ithumb
555
					};
555
					};
556
				});
556
				});
557
				this.carousel = new Carousel({
557
				this.thumbnailGallery = new ThumbnailGallery({
558
					items: urls
558
					items: urls
559
				});
559
				});
560
				styleContainer.addChild(this.carousel);
560
				styleContainer.addChild(this.thumbnailGallery);
561
				this._detailsContainer.addChild(styleContainer);
561
				this._detailsContainer.addChild(styleContainer);
562
			}
562
			}
563
563
 Lines 568-573    Link Here 
568
				content: this._detailsContainer,
568
				content: this._detailsContainer,
569
				'class': 'appDetailsPane'
569
				'class': 'appDetailsPane'
570
			});
570
			});
571
572
			//handle behaviour of the thumbnailGallery based on wether
573
			//the titlepane is closed or not
574
			if (!detailsPane.open) {
575
				this.thumbnailGallery._stopFirstResize = true;
576
			}
577
			detailsPane.watch('open', lang.hitch(this, function(variable, oldVal, titlePaneIsOpen) {
578
				if (titlePaneIsOpen) {
579
					this.thumbnailGallery._handleResize();
580
				} else {
581
					if (this.thumbnailGallery.isBigThumbnails) {
582
						this.thumbnailGallery.toggleThumbSize();
583
					}
584
					this.thumbnailGallery.pauseAllVideos();
585
				}
586
			}));
571
			this._mainRegionContainer.addChild(detailsPane, isAppInstalled ? null : 0);
587
			this._mainRegionContainer.addChild(detailsPane, isAppInstalled ? null : 0);
572
		},
588
		},
573
589
(-)umc/js/appcenter/Carousel.js (-490 lines)
 Lines 1-490    Link Here 
1
/*
2
 * Copyright 2011-2016 Univention GmbH
3
 *
4
 * http://www.univention.de/
5
 *
6
 * All rights reserved.
7
 *
8
 * The source code of this program is made available
9
 * under the terms of the GNU Affero General Public License version 3
10
 * (GNU AGPL V3) as published by the Free Software Foundation.
11
 *
12
 * Binary versions of this program provided by Univention to you as
13
 * well as other copyrighted, protected or trademarked materials like
14
 * Logos, graphics, fonts, specific documentations and configurations,
15
 * cryptographic keys etc. are subject to a license agreement between
16
 * you and Univention and not subject to the GNU AGPL V3.
17
 *
18
 * In the case you use this program under the terms of the GNU AGPL V3,
19
 * the program is provided in the hope that it will be useful,
20
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
 * GNU Affero General Public License for more details.
23
 *
24
 * You should have received a copy of the GNU Affero General Public
25
 * License with the Debian GNU/Linux or Univention distribution in file
26
 * /usr/share/common-licenses/AGPL-3; if not, see
27
 * <http://www.gnu.org/licenses/>.
28
 */
29
/*global define,console,require*/
30
31
define([
32
	"dojo/_base/declare",
33
	"dojo/_base/array",
34
	"dojo/_base/lang",
35
	"dojo/_base/kernel",
36
	"dojo/_base/window",
37
	"dojo/on",
38
	"dojo/query",
39
	"dojo/dom-construct",
40
	"dojo/dom-style",
41
	"dojo/dom-geometry",
42
	"dojo/dom-class",
43
	"dojox/html/styles",
44
	"umc/tools",
45
	"umc/widgets/ContainerWidget",
46
	"umc/widgets/_RegisterOnShowMixin",
47
	"dojo/domReady!"
48
], function(declare, array, lang, kernel, baseWin, on, query, domConstruct, domStyle, domGeometry, domClass, styles, tools, ContainerWidget, _RegisterOnShowMixin) {
49
	return declare("umc.modules.appcenter.Carousel_new", [ContainerWidget, _RegisterOnShowMixin], {
50
		baseClass: 'umcCarouselWidget',
51
52
		outerContainer: null,
53
		contentSlider: null,
54
		contentSliderOffset: null,
55
56
		shownItemIndex: null,
57
58
		itemHeight: null,
59
		heighestImg: null,
60
61
		// items: Array
62
		//     array of objects({src: <src-string>})
63
		//     where <src-string> is either an image or a youtube video url 
64
		items: null,
65
		itemNodes: null,
66
67
		allItemsLoaded: null,
68
69
		bigThumbnails: null,
70
71
		_resizeDeferred: null,
72
73
		postMixInProperties: function() {
74
			this.itemHeight = this.itemHeight || 200;
75
			this.itemNodes = [];
76
			this.contentSliderOffset = 0;
77
			this.shownItemIndex = 0;
78
			this.allItemsLoaded = false;
79
			this.bigThumbnails = false;
80
		},
81
82
		buildRendering: function() {
83
			this.inherited(arguments);
84
85
			this.outerContainer = new ContainerWidget({
86
				'class': 'carouselOuterContainer'
87
			});
88
89
			this.addChild(this.outerContainer);
90
			this.own(this.outerContainer);
91
92
			this.renderContentSlider();
93
			this.renderNavButtons();
94
			this.renderScaleButton();
95
			
96
		},
97
98
		postCreate: function() {
99
			if (window.YT && window.YT.loaded) {
100
				setTimeout(lang.hitch(this, function() {
101
					this._renderYoutubeVideos();
102
				}), 500);
103
			} else {
104
				//load youtube api
105
				var tag = document.createElement('script');
106
				tag = domConstruct.create('script', {
107
					id: "youtubeAPI",
108
					src: "https://www.youtube.com/iframe_api"
109
				});
110
				var firstScriptTag = document.getElementsByTagName('script')[0];
111
				firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
112
113
				window.onYouTubeIframeAPIReady = lang.hitch(this, function() {
114
					this._renderYoutubeVideos();
115
				});
116
			}
117
			onPlayerReady = lang.hitch(this, function() {
118
				this.imagesLoaded();
119
			});
120
		},
121
122
		renderContentSlider: function() {
123
			this.contentSliderWrapper = new ContainerWidget({
124
				'class': 'contentSliderWrapper'
125
			});
126
			this.contentSlider = new ContainerWidget({
127
				'class': 'contentSlider'
128
			});
129
			this.contentSliderWrapper.addChild(this.contentSlider);
130
			this.outerContainer.addChild(this.contentSliderWrapper);
131
132
			this._renderThumbs();
133
		},
134
135
		_renderThumbs: function() {
136
			this.loadedImagesCount = 0;
137
			var index = 0;
138
			var uniqueYTVideoIds = [];
139
140
			array.forEach(this.items, lang.hitch(this, function(item) {
141
				if (this._srcIsYoutubeVideo(item.src)) {
142
					var videoId = this._getYoutubeUrlVideoId(item.src);
143
					if (uniqueYTVideoIds.indexOf(videoId) !== -1) {
144
						return;
145
					}
146
					uniqueYTVideoIds.push(videoId);
147
148
					var videoWrapper = domConstruct.create('div', {'class': 'galleryVideo'}, this.contentSlider.domNode);
149
					var div = domConstruct.create('div', {
150
						id: videoId
151
					}, videoWrapper);
152
153
					this.itemNodes.push(videoWrapper);
154
				} else {
155
					var imgWrapper = domConstruct.create('div', {}, this.contentSlider.domNode);
156
					var img = domConstruct.create('img', {
157
						src: item.src,
158
						index: index,
159
						'class': 'carouselScreenshot',
160
						onload: lang.hitch(this, function() {
161
							this._updateHeighestImage(img);
162
							this.imagesLoaded();
163
						}),
164
						onclick: lang.hitch(this, function(evt) {
165
							this.togglePreviewSize(parseInt(evt.target.getAttribute('index')));
166
						})
167
					}, imgWrapper);
168
					this.itemNodes.push(imgWrapper);
169
				}
170
				index++;
171
			}));
172
		},
173
174
		_renderYoutubeVideos: function() {
175
			query('.galleryVideo', this.contentSlider.domNode).forEach(lang.hitch(this, function(videoWrapper) {
176
				var player;
177
				var videoId = videoWrapper.firstChild.id;
178
				player = new YT.Player(videoId, {
179
					height: this.itemHeight.toString(),
180
					width: 'auto',
181
					videoId: videoId,
182
					events: {
183
						'onReady': onPlayerReady
184
					}
185
				});
186
				this.own(player);
187
			}));
188
		},
189
190
		_srcIsYoutubeVideo: function(src) {
191
			//taken from http://stackoverflow.com/questions/28735459/how-to-validate-youtube-url-in-client-side-in-text-box
192
			var p = /^(?:https?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((\w|-){11})(?:\S+)?$/;
193
			if(src.match(p)){
194
				return true;
195
			}
196
			return false;
197
		},
198
199
		_getYoutubeUrlVideoId: function(src) {
200
			var p = /^(?:https?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((\w|-){11})(?:\S+)?$/;
201
			return src.match(p)[1];
202
		},
203
		
204
		_setDefaultThumbsHeight: function() {
205
			if (!styles.getStyleSheet('defaultItemHeightImage')) {
206
				styles.insertCssRule('.contentSlider .carouselScreenshot', lang.replace('max-height: {0}px', [this.itemHeight]), 'defaultItemHeightImage');
207
			}
208
			if (!styles.getStyleSheet('defaultItemHeightWrapper')) {
209
				styles.insertCssRule('.contentSlider div', lang.replace('height: {0}px', [this.itemHeight]), 'defaultItemHeightWrapper');
210
			}
211
		},
212
213
		renderNavButtons: function() {
214
			this.leftButton = domConstruct.create('div', {
215
				'class': 'carouselButton leftCarouselButton',
216
				onclick: lang.hitch(this, function() {
217
					this.showItem(this.shownItemIndex - 1);
218
				})
219
			}, this.contentSliderWrapper.domNode, 'before');
220
			domConstruct.create('div', {
221
				'class': 'carouselButtonImage'
222
			}, this.leftButton);
223
224
			this.rightButton = domConstruct.create('div', {
225
				'class': 'carouselButton rightCarouselButton',
226
				onclick: lang.hitch(this, function() {
227
					this.showItem(this.shownItemIndex + 1);
228
				})
229
			}, this.contentSliderWrapper.domNode, 'after');
230
			domConstruct.create('div', {
231
				'class': 'carouselButtonImage'
232
			}, this.rightButton);
233
		},
234
235
		renderScaleButton: function() {
236
			this.scaleButton = domConstruct.create('div', {
237
				'class': 'scaleButton dijitHidden',
238
				onclick: lang.hitch(this, function() {
239
					this.togglePreviewSize(this.shownItemIndex);
240
				})
241
			}, this.domNode);
242
		},
243
244
		imagesLoaded: function() {
245
			this.loadedImagesCount++;
246
			if (this.loadedImagesCount === this.itemNodes.length) {
247
				this.allItemsLoaded = true;
248
				this.heighestImg = this.heighestImg.cloneNode();
249
				domConstruct.place(this.heighestImg, this.domNode);
250
				domClass.add(this.heighestImg, 'dijitHidden');
251
				domClass.remove(this.scaleButton, 'dijitHidden');
252
				
253
				this._setDefaultThumbsHeight();
254
				this.resizeCarousel();
255
				this.calcDefaultSliderWidth();
256
				this.calcOffsets();
257
			}
258
		},
259
260
		_updateHeighestImage: function(img) {
261
			if (!this.heighestImg) {
262
				this.heighestImg = img;
263
			}
264
			if ((img.height > img.width) && (img.height > this.heighestImg.height)) {
265
				this.heighestImg = img;
266
			}
267
		},
268
269
		calcDefaultSliderWidth: function() {
270
			this.defaultSliderWidth = domGeometry.getMarginBox(this.contentSlider.domNode).w;
271
			this.defaultItemWidths = [];
272
			array.forEach(this.itemNodes, lang.hitch(this, function(itemNode) {
273
				this.defaultItemWidths.push(domGeometry.getMarginBox(itemNode).w);
274
			}));
275
		},
276
277
		calcOffsets: function() {
278
			this.offsets = [0];
279
			var maxOffset = Math.max(0, this.defaultSliderWidth - domGeometry.getMarginBox(this.contentSliderWrapper.domNode).w);
280
281
			for (var i = 1; i < this.itemNodes.length; i++) {
282
				var offset = 0;
283
				for (var c = 0; c < i; c++) {
284
					offset += this.defaultItemWidths[c];
285
				}
286
				if (offset >= maxOffset) {
287
					this.offsets.push(maxOffset);
288
				} else {
289
					this.offsets.push(offset);
290
				}
291
			}
292
		},
293
294
		resizeCarousel: function() {
295
			if (this.bigThumbnails) {
296
				this._resizeBigThumbnails();
297
			}
298
			var totalItemsWidth = 0;
299
			array.forEach(this.itemNodes, function(imgWrapper) {
300
				var imgWrapperWidth = domGeometry.getMarginBox(imgWrapper).w;
301
				totalItemsWidth += imgWrapperWidth;
302
			});
303
			
304
			var contentSliderHeight = domGeometry.getMarginBox(this.contentSlider.domNode).h;
305
			domStyle.set(this.outerContainer.domNode, 'height', contentSliderHeight + 'px');
306
307
			var availableWidth = domGeometry.getMarginBox(this.domNode).w;
308
309
			if (availableWidth >= totalItemsWidth) {
310
				domClass.add(this.leftButton, 'dijitHidden');
311
				domClass.add(this.rightButton, 'dijitHidden');
312
				domStyle.set(this.outerContainer.domNode, 'width', totalItemsWidth + 'px');
313
				domStyle.set(this.contentSliderWrapper.domNode, 'width', totalItemsWidth + 'px');
314
			} else {
315
				domClass.remove(this.leftButton, 'dijitHidden');
316
				domClass.remove(this.rightButton, 'dijitHidden');
317
318
				var widthForThumbs = availableWidth - 
319
					  (domGeometry.getMarginBox(this.leftButton).w  + domGeometry.getMarginBox(this.rightButton).w);
320
				domStyle.set(this.outerContainer.domNode, 'width', availableWidth + 'px');
321
				domStyle.set(this.contentSliderWrapper.domNode, 'width', widthForThumbs + 'px');
322
			}
323
			this._toggleNavButtonsVisibility();
324
		},
325
326
		_resizeBigThumbnails: function(newIndex) {
327
			var maxHeight = dojo.window.getBox().h - 200;
328
			maxHeight = (maxHeight < this.itemHeight) ? this.itemHeight : maxHeight;
329
330
			var marginOfThumbs = domGeometry.getMarginExtents(this.itemNodes[0]).w;
331
			domClass.remove(this.leftButton, 'dijitHidden'); //make sure navButton is visible for sizing calculations
332
			domClass.remove(this.rightButton, 'dijitHidden');
333
			var maxWidth = domGeometry.getMarginBox(this.domNode).w - (domGeometry.getMarginBox(this.leftButton).w * 2) - marginOfThumbs;
334
			domStyle.set(this.outerContainer.domNode, 'width', '');
335
			domStyle.set(this.contentSliderWrapper.domNode, 'width', maxWidth + 'px');
336
337
			styles.disableStyleSheet('defaultItemHeightWrapper');
338
			
339
			domStyle.set(this.heighestImg, 'position', 'absolute');
340
			domStyle.set(this.heighestImg, 'right', '1000000px');
341
			domClass.toggle(this.heighestImg, 'dijitHidden');
342
343
344
			domStyle.set(this.heighestImg, 'max-height', maxHeight + 'px');
345
			domStyle.set(this.heighestImg, 'max-width', maxWidth + 'px');
346
			var heighestImgHeight = domGeometry.getMarginBox(this.heighestImg).h;
347
			domClass.toggle(this.heighestImg, 'dijitHidden');
348
349
			array.forEach(this.itemNodes, lang.hitch(this, function(imgWrapper) {
350
				domStyle.set(imgWrapper.firstChild, 'max-width', maxWidth + 'px');
351
				domStyle.set(imgWrapper.firstChild, 'max-height', maxHeight + 'px');
352
				domStyle.set(imgWrapper, 'height', heighestImgHeight + 'px');
353
				domStyle.set(imgWrapper, 'width', maxWidth + 'px');
354
			}));
355
			query('.galleryVideo', this.contentSlider.domNode).forEach(lang.hitch(this, function(videoWrapper) {
356
				domStyle.set(videoWrapper.firstChild, 'height', '100%');
357
				domStyle.set(videoWrapper.firstChild, 'width', '100%');
358
			}));
359
			domStyle.set(this.outerContainer.domNode, 'height', heighestImgHeight + 'px');
360
361
			var newOffset = newIndex * (maxWidth + marginOfThumbs);
362
			domStyle.set(this.contentSlider.domNode, 'left', (newOffset * (-1)) + 'px');
363
		},
364
365
		_toggleNavButtonsVisibility: function(newOffset) {
366
			var _contentSliderOffset;
367
			if (newOffset === undefined) {
368
				_contentSliderOffset = Math.abs(domStyle.get(this.contentSlider.domNode, 'left'));
369
			} else {
370
				_contentSliderOffset = newOffset;
371
			}
372
			
373
			var maxOffset = domGeometry.getMarginBox(this.contentSlider.domNode).w - domGeometry.getMarginBox(this.contentSliderWrapper.domNode).w;
374
375
			domClass.toggle(this.leftButton, 'disabled', (_contentSliderOffset === 0 || this.shownItemIndex === 0));
376
			domClass.toggle(this.rightButton, 'disabled', (_contentSliderOffset === maxOffset || this.shownItemIndex === this.items.length-1));
377
		},
378
379
		togglePreviewSize: function(newIndex) {
380
			if (this.bigThumbnails) {
381
				styles.enableStyleSheet('defaultItemHeightWrapper');
382
				var availableWidth = domGeometry.getMarginBox(this.domNode).w;
383
384
				if (availableWidth >= this.defaultSliderWidth) {
385
					domClass.add(this.leftButton, 'dijitHidden');
386
					domClass.add(this.rightButton, 'dijitHidden');
387
					domStyle.set(this.outerContainer.domNode, 'transition', 'height 0.5s, width 0.5s');
388
					domStyle.set(this.contentSliderWrapper.domNode, 'transition', 'height 0.5s, width 0.5s');
389
					domStyle.set(this.outerContainer.domNode, 'width', this.defaultSliderWidth + 'px');
390
					domStyle.set(this.contentSliderWrapper.domNode, 'width', this.defaultSliderWidth + 'px');
391
				}
392
393
394
				query('.carouselScreenshot', this.contentSlider.domNode).forEach(lang.hitch(this, function(imgNode) {
395
					domStyle.set(imgNode, 'max-height', '');
396
					domStyle.set(imgNode, 'max-width', '');
397
					domStyle.set(imgNode.parentNode, 'width', '');
398
					domStyle.set(imgNode.parentNode, 'height', '');
399
				}));
400
				query('.galleryVideo', this.contentSlider.domNode).forEach(lang.hitch(this, function(videoWrapper) {
401
					domStyle.set(videoWrapper, 'height', '');
402
					domStyle.set(videoWrapper, 'width', '');
403
					domStyle.set(videoWrapper.firstChild, 'height', '');
404
					domStyle.set(videoWrapper.firstChild, 'width', '');
405
				}));
406
				domStyle.set(this.outerContainer.domNode, 'height', this.itemHeight + 'px');
407
				domStyle.set(this.contentSlider.domNode, 'left', Math.abs(this.offsets[newIndex]) * (-1) + 'px');
408
				
409
			} else {
410
				domStyle.set(this.outerContainer.domNode, 'transition', '');
411
				domStyle.set(this.contentSliderWrapper.domNode, 'transition', '');
412
				this._resizeBigThumbnails(newIndex);
413
			}
414
			this.bigThumbnails = !this.bigThumbnails;
415
			domClass.toggle(this.scaleButton, 'minimize');
416
			this.shownItemIndex = newIndex;
417
418
			setTimeout(lang.hitch(this, function() {
419
				this.resizeCarousel();
420
			}), 600);
421
422
			//scroll to the top of the image
423
			//var scrollTarget = domGeometry.position(this.domNode, true).y - 50;
424
			//window.scrollTo(0, scrollTarget);
425
		},
426
427
		showItem: function(newIndex) {
428
			if (newIndex < 0 || newIndex >= this.itemNodes.length) {
429
				return;
430
			}
431
432
			var newOffset = 0;
433
434
			var neededOffset = 0;
435
			for (var i = 0; i < newIndex; i++) {
436
				var itemWidth = domGeometry.getMarginBox(this.itemNodes[i]).w;
437
				neededOffset += itemWidth;
438
			}
439
			
440
			var maxOffset = domGeometry.getMarginBox(this.contentSlider.domNode).w - domGeometry.getMarginBox(this.contentSliderWrapper.domNode).w;
441
			if (neededOffset >= maxOffset) {
442
				newOffset = maxOffset;
443
			} else {
444
				newOffset = neededOffset;
445
			}
446
447
			var oldOffset = Math.abs(domStyle.get(this.contentSlider.domNode, 'left'));
448
			
449
			//if the new index would not change the offset
450
			//take the next lower index till a new offset is found
451
			if (oldOffset === newOffset && newIndex < this.shownItemIndex) {
452
				this.showItem(newIndex -1);
453
				return;
454
			}
455
456
			this.shownItemIndex = newIndex;
457
			domStyle.set(this.contentSlider.domNode, 'left', (newOffset * (-1)) + 'px');
458
459
			this._toggleNavButtonsVisibility(newOffset);
460
		},
461
462
		_handleResize: function() {
463
			if (this._resizeDeferred && !this._resizeDeferred.isFulfilled()) {
464
				this._resizeDeferred.cancel();
465
			}
466
			this._resizeDeferred = tools.defer(lang.hitch(this, function() {
467
				this.resizeCarousel();
468
				this.calcOffsets();
469
				this.showItem(this.shownItemIndex);
470
			}), 200);
471
			this._resizeDeferred.otherwise(function() { /* prevent logging of exception */ });
472
		},
473
474
		startup: function() {
475
			this.inherited(arguments);
476
			this._registerAtParentOnShowEvents(lang.hitch(this, function() {
477
				if (this.defaultSliderWidth === 0 && !this.bigThumbnails) {
478
					this.calcDefaultSliderWidth();
479
				}
480
				if (this.allItemsLoaded) {
481
					this.resizeCarousel();
482
					this.showItem(this.shownItemIndex);
483
				}
484
			}));
485
			this.own(on(baseWin.doc, 'resize', lang.hitch(this, '_handleResize')));
486
			this.own(on(kernel.global, 'resize', lang.hitch(this, '_handleResize')));
487
			styles.enableStyleSheet('defaultItemHeightWrapper');
488
		}
489
	});
490
});
(-)umc/js/appcenter/ThumbnailGallery.js (+1052 lines)
Line 0    Link Here 
1
/*
2
 * Copyright 2011-2016 Univention GmbH
3
 *
4
 * http://www.univention.de/
5
 *
6
 * All rights reserved.
7
 *
8
 * The source code of this program is made available
9
 * under the terms of the GNU Affero General Public License version 3
10
 * (GNU AGPL V3) as published by the Free Software Foundation.
11
 *
12
 * Binary versions of this program provided by Univention to you as
13
 * well as other copyrighted, protected or trademarked materials like
14
 * Logos, graphics, fonts, specific documentations and configurations,
15
 * cryptographic keys etc. are subject to a license agreement between
16
 * you and Univention and not subject to the GNU AGPL V3.
17
 *
18
 * In the case you use this program under the terms of the GNU AGPL V3,
19
 * the program is provided in the hope that it will be useful,
20
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
 * GNU Affero General Public License for more details.
23
 *
24
 * You should have received a copy of the GNU Affero General Public
25
 * License with the Debian GNU/Linux or Univention distribution in file
26
 * /usr/share/common-licenses/AGPL-3; if not, see
27
 * <http://www.gnu.org/licenses/>.
28
 */
29
/*global define,console,require*/
30
31
define([
32
	"dojo/_base/declare",
33
	"dojo/_base/array",
34
	"dojo/_base/lang",
35
	"dojo/_base/kernel",
36
	"dojo/_base/window",
37
	"dojo/on",
38
	"dojo/query",
39
	"dojo/dom-construct",
40
	"dojo/dom-style",
41
	"dojo/dom-geometry",
42
	"dojo/dom-class",
43
	"dojox/html/styles",
44
	"umc/tools",
45
	"umc/widgets/ContainerWidget"
46
], function(declare, array, lang, kernel, baseWin, on, query, domConstruct, domStyle, domGeometry, domClass, styles, tools, ContainerWidget) {
47
	return declare("umc.modules.appcenter.ThumbnailGallery", [ContainerWidget], {
48
		baseClass: 'umcThumbnailGallery',
49
50
		outerContainer: null,
51
52
		//is the viewport for the contentSlider
53
		contentSliderWrapper: null,
54
55
		//the contentSlider contains all thumbs
56
		//the offset of the contentSlider gets changed to show different thumbs
57
		contentSlider: null,
58
59
		//the current offset off the contentSlider as absolute value ignoring navButtons
60
		//used for calculations
61
		contentSliderOffset: null,
62
63
		//the currently displayed offset as absolute value
64
		//the navButtons lay on top of the contentSlider but the calculations ignore that fact. so the width of one or 
65
		//both navButtons have to be accounted after the fact
66
		_visibleOffset: null,
67
68
		//toggles thumb size
69
		scaleButton: null,
70
71
		//all ThumbNodes that are in the contentSlider
72
		itemNodes: null,
73
74
		//shownItemIndex: int
75
		//     The currently visible thumb (itemNodes[shownItemIndex]).
76
		//     In the small view the shownItemIndex is either the leftmost or rightmost thumb
77
		//     based on the last call of showPrevThumbs or showNextThumbs.
78
		//     In the big view it is the visible thumb.
79
		shownItemIndex: null,
80
81
		//the default height for gallery thumbs in the small view
82
		defaultThumbHeight: null,
83
84
		//heighestImg: {w: number, h: number}
85
		//     the natural width and height of the heighest img in the contentSlider
86
		heighestImg: null,
87
88
		//heighestImgRatio = heighestImg.w / heighestImg.h
89
		//     If there are only videos in the contentSlider
90
		//     the heighestImgRatio defaults to the _youtubeIframeRatio
91
		//     to determine the height for big Thumbnails
92
		heighestImgRatio: null,
93
94
		//used to check if the contentSliderWrapper is to small for showNextThumbs and showPrevThumbs to work correctly
95
		//and change behaviour accordingly
96
		widestDefaultThumbWidth: null,
97
98
		//_youtubeIframeRatio: defaults to 16/9
99
		_youtubeIframeRatio: null,
100
101
		// items: Array
102
		//     array of received objects({src: <src-string>})
103
		//     where <src-string> is either an image or a youtube video url 
104
		items: null,
105
		
106
		//preparedItems: Array
107
		//     array of objects derived from items.
108
		//     Duplicate youtube videos are removed and
109
		//     objects are marked as either 'img' or 'video'
110
		preparedItems: null,
111
112
		//allItemsLoaded: bool
113
		allItemsLoaded: null,
114
115
		_loadedThumbsCount: null,
116
117
		//isBigThumbnails: bool
118
		isBigThumbnails: null,
119
120
		//the margin each thumb has on either side
121
		_galleryThumbLeftRightMargin: null,
122
		//the whole left and right margin for thumbs
123
		_galleryThumbMarginExtents: null,
124
125
		//naturalThumbDimensions: object{<thumbIndex>: {w: <width>, h: <height>}}
126
		//    stores the unscaled width and height of the images.
127
		//    defaults to w: 1600, h: 900 for videos
128
		naturalThumbDimensions: null,
129
130
		//defaultThumbWidths: object{<thumbIndex>: <width + this._galleryThumbMarginExtents>}
131
		//    The default width for all thumbs in the small view including this._galleryThumbMarginExtents
132
		defaultThumbWidths: null,
133
134
		//defaultThumbOffsets: array
135
		//    contains the offset for every thumb so it is the leftmost first visible thumb
136
		defaultThumbOffsets: null,
137
138
		//ytPlayers: Array
139
		//     array of youtube Player objects for every video in the Gallery
140
		//     ( https://developers.google.com/youtube/iframe_api_reference?hl=de#Loading_a_Video_Player )
141
		//     used to pause videos if they are no longer visible
142
		ytPlayers: null,
143
144
		//thumbIndexToVideoId: object
145
		//     maps the id of the videoThumb to its youtube video_id
146
		thumbIndexToVideoId: null,
147
		videoIdToThumbIndex: null,
148
149
		playingVideos: null,
150
151
		//_insertedCssRules: Array
152
		//     contains array with objects with the selector and declaration inserted via styles.insertCssRule.
153
		//     {selector: <selector>, declaration: <declaration>}
154
		_insertedCssRules: null,
155
156
		_resizeDeferred: null,
157
158
		_firstResizeInterval: null,
159
160
		_stopFirstResize: null,
161
162
		//_baseTransitionDuration: int in ms
163
		_baseTransitionDuration: null,
164
165
		postMixInProperties: function() {
166
			this.preparedItems = [];
167
			this.defaultThumbHeight = this.defaultThumbHeight || 200;
168
			this.itemNodes = [];
169
			this.contentSliderOffset = 0;
170
			this._visibleOffset = 0;
171
			this.shownItemIndex = 0;
172
			this.allItemsLoaded = false;
173
			this.isBigThumbnails = false;
174
			this._youtubeIframeRatio = 16 / 9;
175
			//if there are only youtube videos in the contentslider
176
			//the height for big thumbnails is determined by the _youtubeIframeRatio
177
			this.heighestImgRatio = this._youtubeIframeRatio;
178
			this.heighestImg = {w: 0, h: 0};
179
			this.widestDefaultThumbWidth = 0;
180
			this._galleryThumbLeftRightMargin = 10;
181
			this._galleryThumbMarginExtents = 2 * this._galleryThumbLeftRightMargin;
182
			this.naturalThumbDimensions = {};
183
			this.defaultThumbWidths = {};
184
			this.ytPlayers = {};
185
			this.thumbIndexToVideoId = {};
186
			this.videoIdToThumbIndex = {};
187
			this.playingVideos = {};
188
			this._insertedCssRules = [];
189
			this._totalWidthOfSmallThumbs = 0;
190
			this._bigThumbDimensions = {};
191
			this._smallMaxOffset = 0;
192
			this._widthForThumbs = 0;
193
			this.loaded = false;
194
			this._smallViewHasNavButtons = false;
195
			this._stopFirstResize = false;
196
			this._baseTransitionDuration = 500;
197
		},
198
199
		postCreate: function() {
200
			//check if youtube iframe api is already loaded
201
			if (window.YT && window.YT.loaded) {
202
				//stub
203
			} else {
204
				//load youtube iframe api
205
				var tag = document.createElement('script');
206
				tag = domConstruct.create('script', {
207
					id: "youtubeAPI",
208
					src: "https://www.youtube.com/iframe_api"
209
				});
210
				var firstScriptTag = document.getElementsByTagName('script')[0];
211
				firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
212
			}
213
		},
214
215
		buildRendering: function() {
216
			this.inherited(arguments);
217
218
			this._insertGalleryVideoDefaultDimensions();
219
			this._insertGalleryThumbCssRules();
220
			this._insertTransitionDurations();
221
222
			//style needed while thumbs are getting loaded
223
			domStyle.set(this.domNode, 'height', this.defaultThumbHeight + 'px');
224
			domStyle.set(this.domNode, 'overflow', 'hidden');
225
226
			this.galleryLoadingOverlay = domConstruct.create('div', {
227
				'class': 'galleryLoadingOverlay'
228
			}, this.domNode);
229
230
			this.outerContainer = new ContainerWidget({
231
				'class': 'galleryOuterContainer'
232
			});
233
			this.contentSliderWrapper = new ContainerWidget({
234
				'class': 'contentSliderWrapper'
235
			});
236
237
			this.outerContainer.addChild(this.contentSliderWrapper);
238
			this.addChild(this.outerContainer);
239
			this.own(this.outerContainer);
240
241
			this.renderNavButtons();
242
			this.renderScaleButton();
243
			this.renderContentSlider();
244
		},
245
246
		renderNavButtons: function() {
247
			//construct left and right navButtons
248
			this.leftButton = domConstruct.create('div', {
249
				'class': 'galleryNavButton leftGalleryNavButton disabled'
250
			}, this.contentSliderWrapper.domNode, 'before');
251
			on(this.leftButton, 'click', lang.hitch(this, function() {
252
				this.showPrevThumbs();
253
			}));
254
			domConstruct.create('div', {
255
				'class': 'galleryNavButtonImage'
256
			}, this.leftButton);
257
258
			this.rightButton = domConstruct.create('div', {
259
				'class': 'galleryNavButton rightGalleryNavButton disabled'
260
			}, this.contentSliderWrapper.domNode, 'after');
261
			on(this.rightButton, 'click', lang.hitch(this, function() {
262
				this.showNextThumbs();
263
			}));
264
			domConstruct.create('div', {
265
				'class': 'galleryNavButtonImage'
266
			}, this.rightButton);
267
268
			//insert Css Rule
269
			var selector = lang.replace('#{0} .galleryNavButton', [this.id]);
270
			var declaration = lang.replace('height: {0}px; transition-duration: {1}ms', [this.defaultThumbHeight, this._baseTransitionDuration]);
271
			styles.insertCssRule(selector, declaration);
272
			this._insertedCssRules.push({selector: selector, declaration: declaration});
273
		},
274
275
		renderScaleButton: function() {
276
			this.scaleButton = domConstruct.create('div', {
277
				'class': 'scaleButton dijitHidden',
278
				onclick: lang.hitch(this, function() {
279
					this.toggleThumbSize(this.shownItemIndex);
280
				})
281
			}, this.domNode);
282
		},
283
284
		renderContentSlider: function() {
285
			this.contentSlider = new ContainerWidget({
286
				'class': 'contentSlider'
287
			});
288
289
			this.galleryThumbVerticalAlignHelper = domConstruct.create('div', {
290
				'class': 'galleryThumb verticalAlignHelper'
291
			}, this.contentSlider.domNode);
292
293
			this.contentSliderWrapper.addChild(this.contentSlider);
294
295
			this._renderThumbs();
296
		},
297
298
		_renderThumbs: function() {
299
			this._prepareItems();
300
			this._loadedThumbsCount = 0;
301
302
			array.forEach(this.preparedItems, lang.hitch(this, function(item, index) {
303
				if (item.type === 'video') {
304
					this._createVideoThumb(item, index);
305
				} else {
306
					this._createImgThumb(item, index);
307
				}
308
			}));
309
		},
310
311
		_prepareItems: function() {
312
			var uniqueYTVideoIds = [];
313
314
			array.forEach(this.items, lang.hitch(this, function(item, index) {
315
				if (this._srcIsYoutubeVideo(item.src)) {
316
					var videoId = this._getYoutubeUrlVideoId(item.src);
317
					if (uniqueYTVideoIds.indexOf(videoId) !== -1) {
318
						return;
319
					}
320
					uniqueYTVideoIds.push(videoId);
321
					this.preparedItems.push({type: 'video', videoId: videoId});
322
				} else {
323
					this.preparedItems.push({type: 'img', src: item.src});
324
				}
325
			}));
326
		},
327
328
		_createVideoThumb: function(item, index) {
329
			var videoId = item.videoId;
330
331
			//get the thumbnail for the youtube video via the unique id
332
			var ytVideoThumbnailURL = lang.replace('https://img.youtube.com/vi/{0}/hqdefault.jpg', [videoId]);
333
334
			var galleryVideoWrapper = domConstruct.create('div', {
335
				id: 'galleryThumb_' + index,
336
				'class': 'galleryThumb galleryVideoThumb galleryVideoWrapper'
337
			}, this.contentSlider.domNode);
338
339
			//show the thumbnail of the video with a playbutton
340
			var thumbNailWrapper = domConstruct.create('div', {
341
				'class': 'galleryVideoThumbnailWrapper'
342
			}, galleryVideoWrapper);
343
344
			var thumbNail = domConstruct.create('div', {
345
				'class': 'galleryVideoThumbnail'
346
			}, thumbNailWrapper);
347
			var playButtonImg = domConstruct.create('div', {
348
				'class': 'galleryVideoPlayButtonIcon'
349
			}, thumbNailWrapper);
350
351
			//this div gets replaced by the youtube iframe
352
			var galleryVideo = domConstruct.create('div', {
353
				'class': 'galleryVideo dijitHidden',
354
				id: videoId
355
			}, galleryVideoWrapper);
356
357
			//set the thumbnail of the ytVideo as Background
358
			var selector = lang.replace('#{0} #galleryThumb_{1} .galleryVideoThumbnail', [this.id, index]);
359
			var declaration = lang.replace('background-image: url({0})', [ytVideoThumbnailURL]);
360
			styles.insertCssRule(selector, declaration);
361
			this._insertedCssRules.push({selector: selector, declaration: declaration});
362
363
			//load youtube video
364
			on(galleryVideoWrapper, 'click', lang.hitch(this, function() {
365
				this.shownItemIndex = index;
366
				if (window.YT && window.YT.loaded) {
367
					this.showThumb(this.shownItemIndex);
368
					domClass.add(galleryVideoWrapper, 'loaded');
369
					domClass.remove(galleryVideo, 'dijitHidden');
370
					domClass.add(playButtonImg, 'dijitHidden');
371
					domClass.add(thumbNailWrapper, 'dijitHidden');
372
					setTimeout(lang.hitch(this, function() {
373
						this._renderYoutubeVideo(galleryVideo, index);
374
					}), 0);
375
				}
376
			}));
377
378
			this.naturalThumbDimensions[index] = {w: 1600, h: 900};
379
			var w = Math.round(this.defaultThumbHeight * this._youtubeIframeRatio);
380
			var h = this.defaultThumbHeight;
381
			this.defaultThumbWidths[index] = w + this._galleryThumbMarginExtents;
382
			this._updateWidestThumb(w);
383
384
			this.itemNodes.push(galleryVideoWrapper);
385
			item.domNode = galleryVideoWrapper;
386
			this.thumbLoaded();
387
		},
388
389
		_createImgThumb: function(item, index) {
390
			var imgUrl = item.src;
391
			var galleryImgThumb = domConstruct.create('div', {
392
				'class': 'galleryThumb galleryImgThumb',
393
				id: 'galleryThumb_' + index
394
			}, this.contentSlider.domNode);
395
396
			var img = domConstruct.create('img', {
397
				src: imgUrl,
398
				onload: lang.hitch(this, function() {
399
					this._updateHeighestImage(img);
400
					this.naturalThumbDimensions[index] = {w: img.naturalWidth, h: img.naturalHeight};
401
402
					//calc the width and height for the thumb in the small view
403
					var w = Math.round(this.defaultThumbHeight * (img.naturalWidth / img.naturalHeight));
404
					var h = this.defaultThumbHeight;
405
					this.defaultThumbWidths[index] = w + this._galleryThumbMarginExtents;
406
					this._updateWidestThumb(w);
407
408
					//insert img as background
409
					var selector = lang.replace('#{0} #{1}', [this.id, galleryImgThumb.id]);
410
					var declaration = lang.replace('width: {0}px; height: {1}px; background-image: url({2})', [w, h, img.src]);
411
					styles.insertCssRule(selector, declaration);
412
					this._insertedCssRules.push({selector: selector, declaration: declaration});
413
414
					this.thumbLoaded();
415
				}),
416
				onerror: lang.hitch(this, function() {
417
					//calc the width and height for the thumb in the small view
418
					var w = Math.round(this.defaultThumbHeight * (3/4));
419
					var h = this.defaultThumbHeight;
420
					this.naturalThumbDimensions[index] = {w: w, h: h};
421
					this.defaultThumbWidths[index] = w + this._galleryThumbMarginExtents;
422
					this._updateWidestThumb(w);
423
424
					//insert img as background
425
					var selector = lang.replace('#{0} #{1}', [this.id, galleryImgThumb.id]);
426
					var declaration = lang.replace('width: {0}px; height: {1}px;', [w, h]);
427
					styles.insertCssRule(selector, declaration);
428
					this._insertedCssRules.push({selector: selector, declaration: declaration});
429
					domClass.add(galleryImgThumb, 'brokenImg');
430
431
					this.thumbLoaded();
432
				})
433
			});
434
435
			on(galleryImgThumb, 'click', lang.hitch(this, function() {
436
				this.toggleThumbSize(index);
437
			}));
438
			this.itemNodes.push(galleryImgThumb);
439
			item.domNode = galleryImgThumb;
440
		},
441
442
		_srcIsYoutubeVideo: function(src) {
443
			//taken from http://stackoverflow.com/questions/28735459/how-to-validate-youtube-url-in-client-side-in-text-box
444
			var p = /^(?:https?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((\w|-){11})(?:\S+)?$/;
445
			if(src.match(p)){
446
				return true;
447
			}
448
			return false;
449
		},
450
451
		_getYoutubeUrlVideoId: function(src) {
452
			var p = /^(?:https?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((\w|-){11})(?:\S+)?$/;
453
			return src.match(p)[1];
454
		},
455
456
		_insertGalleryVideoDefaultDimensions: function() {
457
			//calc default width and height
458
			var width = Math.round(this.defaultThumbHeight * this._youtubeIframeRatio);
459
			var height = this.defaultThumbHeight;
460
461
			//insert Css Rule
462
			var selector = lang.replace('#{0}.{1} .contentSlider .galleryVideoWrapper', [this.id, this.baseClass]);
463
			var declaration = lang.replace('width: {0}px; height: {1}px', [width, height]);
464
			styles.insertCssRule(selector, declaration);
465
			this._insertedCssRules.push({selector: selector, declaration: declaration});
466
		},
467
468
		_insertGalleryThumbCssRules: function() {
469
			var selector = lang.replace('#{0} .contentSlider .galleryThumb', [this.id]);
470
			var declaration = lang.replace('margin: 0 {0}px; transition-duration: {1}ms', [this._galleryThumbLeftRightMargin, this._baseTransitionDuration]);
471
			styles.insertCssRule(selector, declaration);
472
			this._insertedCssRules.push({ selector: selector, declaration: declaration });
473
		},
474
475
		_insertTransitionDurations: function() {
476
			var selector;
477
			var declaration;
478
			//contentSlider
479
			selector = lang.replace('#{0} .contentSlider', [this.id]);
480
			declaration = lang.replace('transition-duration: {0}ms', [this._baseTransitionDuration]);
481
			styles.insertCssRule(selector, declaration);
482
			this._insertedCssRules.push({ selector: selector, declaration: declaration });
483
		},
484
485
		_renderYoutubeVideo: function(galleryVideo, index) {
486
			var player;
487
			var videoId = galleryVideo.id;
488
			//add the youtube video via the youtube iframe api
489
			player = new YT.Player(videoId, {
490
				videoId: videoId,
491
				events: {
492
					'onReady': lang.hitch(this, this.onPlayerReady),
493
					'onStateChange': lang.hitch(this, this.onPlayerStateChange)
494
				}
495
			});
496
			this.own(player);
497
			
498
			//safe a reference to the video id accessible through the thumbIndex and vice versa
499
			this.thumbIndexToVideoId[index] = videoId;
500
			this.videoIdToThumbIndex[videoId] = index;
501
			//safe the youtube player object
502
			this.ytPlayers[videoId] = {playerIndex: index};
503
		},
504
505
		onPlayerReady: function(evt) {
506
			evt.target.playVideo();
507
			var videoId = evt.target.getVideoData().video_id;
508
			this.ytPlayers[videoId].player = evt.target;
509
		},
510
511
		onPlayerStateChange: function(evt) {
512
			var videoId;
513
			if (evt.data && evt.data === YT.PlayerState.PLAYING) {
514
				//safe clicked video as playing video and center it
515
				videoId = evt.target.getVideoData().video_id;
516
				if (this.playingVideos[videoId]) {
517
					return;
518
				}
519
				this.playingVideos[videoId] = evt.target;
520
				var index = this.videoIdToThumbIndex[videoId];
521
				if (index !== this.shownItemIndex) {
522
					this.showThumb(index);
523
				}
524
			} else if (evt.data === YT.PlayerState.PAUSED || evt.data === YT.PlayerState.ENDED) {
525
				//remove paused video from playing videos
526
				videoId = evt.target.getVideoData().video_id;
527
				delete this.playingVideos[videoId];
528
			}
529
		},
530
531
		//checks if all divs in the contentSlider are ready
532
		thumbLoaded: function() {
533
			this._loadedThumbsCount++;
534
			if (this._loadedThumbsCount === this.preparedItems.length) {
535
				this.onAllThumbsLoaded();
536
			}
537
		},
538
539
		onAllThumbsLoaded: function() {
540
			this.allItemsLoaded = true;
541
542
			//calc offsets for all thumbs in the small view
543
			this.defaultThumbOffsets = [];
544
			var offset = 0;
545
			for (var i = 0; i < this.itemNodes.length; i++) {
546
				this.defaultThumbOffsets.push(offset);
547
				offset += this.defaultThumbWidths[i];
548
			}
549
550
			this._handleFirstResize();
551
		},
552
553
		//waits till the gallery is visible in the domTree
554
		//can be cancelled with _stopFirstResize=true (added for use in TitlePane)
555
		_handleFirstResize: function() {
556
			//avoid the 200ms interval if possible
557
			if (domGeometry.getMarginBox(this.domNode).w !== 0) {
558
				this._handleResize();
559
			} else {
560
				this._firstResizeInterval = setInterval(lang.hitch(this, function() {
561
					if (this._stopFirstResize) {
562
						clearInterval(this._firstResizeInterval);
563
					} else if (domGeometry.getMarginBox(this.domNode).w !== 0) {
564
						clearInterval(this._firstResizeInterval);
565
						this._handleResize();
566
					}
567
				}), 200);
568
			}
569
		},
570
571
		_updateHeighestImage: function(img) {
572
			if (img.naturalHeight > this.heighestImg.h) {
573
				this.heighestImg.w = img.naturalWidth;
574
				this.heighestImg.h = img.naturalHeight;
575
				this.heighestImgRatio = img.naturalWidth / img.naturalHeight;
576
			}
577
		},
578
579
		_updateWidestThumb: function(width) {
580
			if (width > this.widestDefaultThumbWidth) {
581
				this.widestDefaultThumbWidth = width;
582
			}
583
		},
584
585
		resizeCarousel: function() {
586
			if (domGeometry.getMarginBox(this.domNode).w === 0) {
587
				return;
588
			}
589
590
			//calc all values needed for toggling thumbsize and showing items
591
			this._galleryWidth = domGeometry.getMarginBox(this.domNode).w;
592
			this._leftNavButtonWidth = domGeometry.getMarginBox(this.leftButton).w;
593
			this._rightNavButtonWidth = domGeometry.getMarginBox(this.rightButton).w;
594
			this._widthForThumbs = this._galleryWidth - (this._leftNavButtonWidth + this._rightNavButtonWidth);
595
596
			this._totalWidthOfSmallThumbs = 0;
597
			for (var i = 0; i < this.itemNodes.length; i++) {
598
				thumbWidth = this.defaultThumbWidths[i];
599
				this._totalWidthOfSmallThumbs += thumbWidth;
600
			}
601
602
			this._smallMaxOffset = Math.max(this._totalWidthOfSmallThumbs - this._widthForThumbs, 0);
603
			this._bigThumbDimensions = this._getMaxWidthHeightForBigThumbs();
604
			var _bigContentSliderWidth = this.itemNodes.length * (this._bigThumbDimensions.maxWidthForThumb + this._galleryThumbMarginExtents);
605
			this._bigMaxOffset = _bigContentSliderWidth - this._widthForThumbs;
606
			this._smallViewHasNavButtons = this._totalWidthOfSmallThumbs > this._galleryWidth;
607
608
609
			if (this.isBigThumbnails) {
610
				this._scrollToTopOfGalleryWidget();
611
			}
612
613
			//reshow the current thumb with new dimensions
614
			setTimeout(lang.hitch(this, function() {
615
				this.showThumb(this.shownItemIndex);
616
			}), 0);
617
		},
618
619
		_toggleNavButtonsVisibility: function(newOffset) {
620
			if (this.isBigThumbnails || this._smallViewHasNavButtons) {
621
				domClass.toggle(this.leftButton, 'disabled', (this.contentSliderOffset === 0 || this.shownItemIndex === 0));
622
				domClass.toggle(this.rightButton, 'disabled', ((this.isBigThumbnails && this.contentSliderOffset === this.defaultThumbOffsets[this.itemNodes.length-1]) || (!this.isBigThumbnails && this.contentSliderOffset === this._smallMaxOffset) || this.shownItemIndex === this.itemNodes.length - 1));
623
			} else {
624
				domClass.add(this.leftButton, 'disabled');
625
				domClass.add(this.rightButton, 'disabled');
626
			}
627
		},
628
629
		showThumb: function(thumbIndex) {
630
			if (thumbIndex < 0 || thumbIndex >= this.itemNodes.length) {
631
				return;
632
			}
633
634
			var newOffsetIgnoringNavButtons = 0;
635
			var newOffsetWithNavButtons = 0;
636
637
			if (this.isBigThumbnails) {
638
				////for big thumbnails
639
				this._hideVideosTemporarily(this._baseTransitionDuration + 50);
640
641
				var targetThumb = this.itemNodes[thumbIndex];
642
643
				//revert previously enlarged thumb and enlarge new thumb
644
				query('.galleryThumb.enlarged', this.contentSlider.domNode).style('width', '').style('height', '').removeClass('enlarged');
645
				////_width
646
				domStyle.set(targetThumb, 'width', lang.replace('{0}px', [this._bigThumbDimensions.maxWidthForThumb]));
647
				////_height
648
				var targetThumbIsVideo = domClass.contains(targetThumb, 'galleryVideoThumb');
649
				var maxHeight = targetThumbIsVideo ? this._bigThumbDimensions.maxHeightForVideoThumb : this._bigThumbDimensions.maxHeightForImgThumb;
650
				domClass.add(targetThumb, 'enlarged');
651
				domStyle.set(targetThumb, 'height', maxHeight + 'px');
652
653
				//get new offset
654
				newOffsetIgnoringNavButtons = this.defaultThumbOffsets[thumbIndex];
655
				newOffsetWithNavButtons = newOffsetIgnoringNavButtons - this._leftNavButtonWidth;
656
			} else { 
657
				////for small thumbnails
658
				if (!this._smallViewHasNavButtons) {
659
					newOffsetIgnoringNavButtons = -((this._widthForThumbs - this._totalWidthOfSmallThumbs) / 2) - domGeometry.getMarginBox(this.leftButton).w;
660
					newOffsetWithNavButtons = newOffsetIgnoringNavButtons;
661
				} else {
662
					//offset for target thumb so that it is just visible
663
					newOffsetIgnoringNavButtons = this.defaultThumbOffsets[thumbIndex];
664
665
					//calc offset so that targetThumb is centered
666
					var targetThumbWidth = this.defaultThumbWidths[thumbIndex];
667
					var newOffsetCentered = newOffsetIgnoringNavButtons - ((this._widthForThumbs - targetThumbWidth) / 2);
668
669
					//if the centered offset would show blank parts of the contentSlider revert to 0 or maxOffset
670
					//if the centerd thumb would only be 50px away from 0 or maxOffset set them to 0 or maxOffset
671
					if (newOffsetCentered <= (this._galleryThumbMarginExtents + 30) ) {
672
						newOffsetIgnoringNavButtons = 0;
673
						newOffsetWithNavButtons = newOffsetIgnoringNavButtons;
674
					} else if (newOffsetCentered + (this._galleryThumbMarginExtents + 30) >= this._smallMaxOffset) {
675
						newOffsetIgnoringNavButtons = this._smallMaxOffset;
676
						newOffsetWithNavButtons = this._smallMaxOffset - this._leftNavButtonWidth - this._rightNavButtonWidth;
677
					} else {
678
						newOffsetIgnoringNavButtons = newOffsetCentered;
679
						newOffsetWithNavButtons = newOffsetIgnoringNavButtons - this._leftNavButtonWidth;
680
					}
681
				}
682
			}
683
684
			//set new offset and thumbIndex
685
			this.shownItemIndex = thumbIndex || 0;
686
			domStyle.set(this.contentSlider.domNode, 'transform', lang.replace('translate3d({0}px, 0, 0)', [(newOffsetWithNavButtons * (-1))]));
687
			domStyle.set(this.contentSlider.domNode, '-webkit-transform', lang.replace('translate3d({0}px, 0, 0)', [(newOffsetWithNavButtons * (-1))]));
688
			this.contentSliderOffset = newOffsetIgnoringNavButtons;
689
			this._visibleOffset = newOffsetWithNavButtons;
690
691
			this._toggleNavButtonsVisibility();
692
		},
693
694
		showNextThumbs: function() {
695
			maxOffset = this.isBigThumbnails ? this._bigMaxOffset : this._smallMaxOffset;
696
			if (this.contentSliderOffset === maxOffset || this.shownItemIndex === this.itemNodes.length - 1) {
697
				return;
698
			}
699
700
			//define functions
701
			_calcCurrentFullyVisibleThumbs = lang.hitch(this, function() {
702
				var lastFullyVisibleThumbIndex = 0;
703
				var totalThumbsWidth = 0;
704
705
				var tmpWidth = 0;
706
				var _widthForThumbs = this.shownItemIndex === 0 ? this._widthForThumbs + this._leftNavButtonWidth : this._widthForThumbs;
707
				for (var tmpIndex = 0; tmpIndex <= this.itemNodes.length -1; ++tmpIndex) {
708
					tmpWidth += this.defaultThumbWidths[tmpIndex];
709
710
					isCurrentThumbFullyVisible = (tmpWidth - this._galleryThumbLeftRightMargin) <= (_widthForThumbs + this.contentSliderOffset);
711
					if (!isCurrentThumbFullyVisible) {
712
						break;
713
					}
714
					totalThumbsWidth = tmpWidth;
715
					lastFullyVisibleThumbIndex = tmpIndex;
716
				}
717
				
718
				return {
719
					lastIndex: lastFullyVisibleThumbIndex,
720
					width: totalThumbsWidth
721
				};
722
			});
723
724
			_calcNewFullyVisibleThumbs = lang.hitch(this, function(currThumbs) {
725
				var lastFullyVisibleThumbIndex = currThumbs.lastIndex + 1;
726
				var totalThumbsWidth = 0;
727
				
728
				var tmpWidth = 0;
729
				var _widthForThumbs = this._widthForThumbs;
730
				for (var tmpIndex = lastFullyVisibleThumbIndex; tmpIndex <= this.itemNodes.length -1; ++tmpIndex) {
731
					tmpWidth += this.defaultThumbWidths[tmpIndex];
732
733
					if (tmpIndex === this.itemNodes.length-1) {
734
						_widthForThumbs += this._leftNavButtonWidth;
735
					}
736
					isCurrentThumbFullyVisible = tmpWidth < _widthForThumbs;
737
					if (!isCurrentThumbFullyVisible) {
738
						break;
739
					}
740
					totalThumbsWidth = tmpWidth;
741
					lastFullyVisibleThumbIndex = tmpIndex;
742
				}
743
744
				return {
745
					lastIndex: lastFullyVisibleThumbIndex,
746
					width: totalThumbsWidth
747
				};
748
			});
749
750
			_calcCenteringOffset = lang.hitch(this, function(currentThumbs, newThumbs) {
751
				//calc new offset so that all new thumbs are centered
752
				var offsetCentered = currentThumbs.width - ((this._widthForThumbs - newThumbs.width) / 2);
753
754
				//make sure to not exceed the maxOffset
755
				var offset = Math.min(offsetCentered, maxOffset);
756
757
				return offset;
758
			});
759
760
761
			//start
762
			this.pauseAllVideos();
763
764
			if (this.isBigThumbnails) {
765
				////for big thumbs
766
				this.showThumb(this.shownItemIndex + 1);
767
			} else {
768
				////for small thumbs
769
				var newOffsetIgnoringNavButtons = 0;
770
				var newIndex;
771
772
				//calc new offset and index
773
				var isGalleryTooSmall = !this.isBigThumbnails && this.widestDefaultThumbWidth + this._galleryThumbMarginExtents >= this._widthForThumbs;
774
				if (isGalleryTooSmall) {
775
					newIndex = this.shownItemIndex + 1;
776
					var thumbOffset = this.defaultThumbOffsets[newIndex];
777
					newOffsetIgnoringNavButtons = Math.min(thumbOffset, this._smallMaxOffset);
778
				} else {
779
					var isCurrentThumbFullyVisible;
780
					var currentFullyVisibleThumbs = _calcCurrentFullyVisibleThumbs();
781
					var newFullyVisibleThumbs = _calcNewFullyVisibleThumbs(currentFullyVisibleThumbs);
782
783
					newOffsetIgnoringNavButtons = _calcCenteringOffset(currentFullyVisibleThumbs, newFullyVisibleThumbs);
784
					newIndex = newFullyVisibleThumbs.lastIndex;
785
				}
786
787
				//set the new offset and index
788
				var newOffsetWithNavButtons = newIndex === this.itemNodes.length -1 ? newOffsetIgnoringNavButtons - ( this._leftNavButtonWidth  * 2) : newOffsetIgnoringNavButtons - this._leftNavButtonWidth;
789
				var _oldOffset = this._visibleOffset;
790
791
				//set transition duration based on distance
792
				this._setSliderTransitionDuration(newOffsetWithNavButtons, _oldOffset);
793
794
				domStyle.set(this.contentSlider.domNode, 'transform', lang.replace('translate3d({0}px, 0, 0)', [newOffsetWithNavButtons * (-1)]));
795
				domStyle.set(this.contentSlider.domNode, '-webkit-transform', lang.replace('translate3d({0}px, 0, 0)', [(newOffsetWithNavButtons * (-1))]));
796
				this.contentSliderOffset = newOffsetIgnoringNavButtons;
797
				this._visibleOffset = newOffsetWithNavButtons;
798
				this.shownItemIndex = newIndex;
799
800
				this._toggleNavButtonsVisibility();
801
			}
802
		},
803
804
		showPrevThumbs: function() {
805
			if (this.contentSliderOffset ===  0 || this.shownItemIndex === 0) {
806
				return;
807
			}
808
809
			//define functions
810
			var _findFirstFullyVisibleThumb = lang.hitch(this, function() {
811
				var totalWidth = 0;
812
				for (var ithumb = 0; ithumb <= this.itemNodes.length -1; ++ithumb) {
813
					totalWidth += this.defaultThumbWidths[ithumb];
814
815
					var isThumbFullyVisible = totalWidth - this.contentSliderOffset >= this.defaultThumbWidths[ithumb] - this._galleryThumbMarginExtents;
816
					if (isThumbFullyVisible) {
817
						break;
818
					}
819
				}
820
				return ithumb;
821
			});
822
823
			var _findNextFullyVisibleThumbs = lang.hitch(this, function(ifirstThumb) {
824
				var firstFullyVisibleThumbIndex = ifirstThumb -1;
825
				var totalThumbsWidth = 0;
826
827
				var tmpWidth = 0;
828
				var _widthForThumbs = this._widthForThumbs;
829
				for (var ithumb = firstFullyVisibleThumbIndex; ithumb >= 0; --ithumb) {
830
					tmpWidth += this.defaultThumbWidths[ithumb];
831
832
					if (ithumb === 0) {
833
						_widthForThumbs += this._leftNavButtonWidth;
834
					}
835
					isCurrentThumbFullyVisible = tmpWidth < _widthForThumbs;
836
					if (!isCurrentThumbFullyVisible) {
837
						break;
838
					}
839
					totalThumbsWidth = tmpWidth;
840
					firstFullyVisibleThumbIndex = ithumb;
841
				}
842
				return {
843
					width: totalThumbsWidth,
844
					firstIndex: firstFullyVisibleThumbIndex
845
				};
846
			});
847
848
			var _calcCenteringOffset = lang.hitch(this, function(newThumbs) {
849
				var offsetUntilNewThumbs = this.defaultThumbOffsets[newThumbs.firstIndex];
850
				var newOffsetCentered = offsetUntilNewThumbs - ((this._widthForThumbs - newThumbs.width) / 2);
851
852
				//make sure to not go lower than 0
853
				return Math.max(newOffsetCentered, 0);
854
			});
855
856
857
			//start
858
			this.pauseAllVideos();
859
860
			if (this.isBigThumbnails) {
861
				this.showThumb(this.shownItemIndex - 1);
862
			} else {
863
				////for small thumbs
864
				var newOffsetIgnoringNavButtons;
865
				var newIndex;
866
867
				//calc new offset and index
868
				var isGalleryTooSmall = !this.isBigThumbnails && this.widestDefaultThumbWidth + this._galleryThumbMarginExtents >= this._widthForThumbs;
869
				if (isGalleryTooSmall) {
870
					newIndex = this.shownItemIndex-1;
871
					newOffsetIgnoringNavButtons = this.defaultThumbOffsets[newIndex];
872
				} else {
873
					var isCurrentThumbFullyVisible;
874
					var currFirstFullyVisibleThumbIndex = _findFirstFullyVisibleThumb();
875
					var newFullyVisibleThumbs = _findNextFullyVisibleThumbs(currFirstFullyVisibleThumbIndex);
876
877
					newOffsetIgnoringNavButtons = _calcCenteringOffset(newFullyVisibleThumbs);
878
					newIndex = newFullyVisibleThumbs.firstIndex;
879
				}
880
881
				//set the new offset and index
882
				var newOffsetWithNavButtons = newIndex === 0 ? newOffsetIgnoringNavButtons : newOffsetIgnoringNavButtons - this._leftNavButtonWidth;
883
				var _oldOffset = this._visibleOffset;
884
885
				//set transition duration based on distance
886
				this._setSliderTransitionDuration(newOffsetWithNavButtons, _oldOffset);
887
888
				domStyle.set(this.contentSlider.domNode, 'transform', lang.replace('translate3d({0}px, 0, 0)', [(newOffsetWithNavButtons * (-1))]));
889
				domStyle.set(this.contentSlider.domNode, '-webkit-transform', lang.replace('translate3d({0}px, 0, 0)', [(newOffsetWithNavButtons * (-1))]));
890
				this.contentSliderOffset = newOffsetIgnoringNavButtons;
891
				this._visibleOffset = newOffsetWithNavButtons;
892
				this.shownItemIndex = newIndex;
893
894
				this._toggleNavButtonsVisibility();
895
			}
896
		},
897
898
		_setSliderTransitionDuration: function(newOffset, oldOffset) {
899
			var offsetDistance = Math.abs(oldOffset - newOffset);
900
			var isNewTransitionDuration = offsetDistance > this._baseTransitionDuration;
901
			if (isNewTransitionDuration) {
902
				domStyle.set(this.contentSlider.domNode, 'transition-duration', lang.replace('{0}ms', [offsetDistance]));
903
				setTimeout(lang.hitch(this, function() {
904
					domStyle.set(this.contentSlider.domNode, 'transition-duration', '');
905
				}), offsetDistance);
906
			}
907
		},
908
909
		_hideVideosTemporarily: function(duration) {
910
			//hide loaded youtube iframes during resize for performance reasons
911
			query('.galleryVideoThumb.loaded .galleryVideoThumbnailWrapper', this.contentSlider.domNode).removeClass('dijitHidden');
912
			query('.galleryVideoThumb.loaded .galleryVideo', this.contentSlider.domNode).addClass('dijitHidden');
913
			setTimeout(lang.hitch(this, function() {
914
				query('.galleryVideoThumb.loaded .galleryVideo', this.contentSlider.domNode).removeClass('dijitHidden');
915
				query('.galleryVideoThumb.loaded .galleryVideoThumbnailWrapper', this.contentSlider.domNode).addClass('dijitHidden');
916
			}), duration);
917
		},
918
919
		toggleThumbSize: function(newIndex) {
920
			newIndex = (newIndex === undefined) ? this.shownItemIndex : newIndex;
921
922
			if (this.isBigThumbnails) {
923
				this._toggleToSmallThumbs();
924
			} else {
925
				this._toggleToBigThumbs(newIndex);
926
			}
927
			domClass.toggle(this.scaleButton, 'minimize');
928
929
			this.isBigThumbnails = !this.isBigThumbnails;
930
			this.showThumb(newIndex);
931
		},
932
933
		_toggleToSmallThumbs: function() {
934
			this._hideVideosTemporarily(this._baseTransitionDuration + 50);
935
			//remove height and width for thumbs and navButtons
936
			query('.galleryNavButton', this.outerContainer.domNode).style('height', '');
937
			query('.galleryThumb', this.contentSlider.domNode).style('width', '').style('height', '');
938
		},
939
940
		_toggleToBigThumbs: function(newIndex) {
941
			this.pauseAllVideos(newIndex);
942
943
			this._scrollToTopOfGalleryWidget();
944
945
			//enlarge navButtons and VerticalAlignHelperThumb
946
			var maxHeight = Math.max(this._bigThumbDimensions.maxHeightForImgThumb, this._bigThumbDimensions.maxHeightForVideoThumb);
947
			query('.galleryNavButton', this.outerContainer.domNode).style('height', lang.replace('{0}px', [maxHeight]));
948
			domStyle.set(this.galleryThumbVerticalAlignHelper, 'height', lang.replace('{0}px', [maxHeight]));
949
		},
950
951
		_scrollToTopOfGalleryWidget: function() {
952
			//##scroll to the top of the galleryWidget
953
			var scrollTarget = domGeometry.position(this.domNode, true).y - 50;
954
			var scrollInterval = setInterval(lang.hitch(this, function() {
955
				window.scrollTo(0, scrollTarget);
956
			}), 1);
957
958
			//wait for the transition to finish
959
			setTimeout(function() {
960
				clearInterval(scrollInterval);
961
			}, this._baseTransitionDuration + 100);
962
		},
963
964
		_getMaxWidthHeightForBigThumbs: function() {
965
			//##get max width for a big thumb
966
			var maxWidthForThumb = this._widthForThumbs - this._galleryThumbMarginExtents;
967
968
			//##get the max height for a big thumb
969
			var maxHeight = dojo.window.getBox().h - 100;
970
			maxHeight = (maxHeight < this.defaultThumbHeight) ? this.defaultThumbHeight : maxHeight;
971
			var maxHeightForImgThumb = Math.min(maxWidthForThumb / this.heighestImgRatio, maxHeight, this.heighestImg.h);
972
			var maxHeightForVideoThumb = maxWidthForThumb / (this._youtubeIframeRatio);
973
974
			return {
975
				maxWidthForThumb: maxWidthForThumb, 
976
				maxHeightForImgThumb: maxHeightForImgThumb, 
977
				maxHeightForVideoThumb: maxHeightForVideoThumb
978
			};
979
		},
980
981
		_handleResize: function() {
982
			if (domGeometry.getMarginBox(this.domNode).w === 0) {
983
				return;
984
			}
985
			if (this._resizeDeferred && !this._resizeDeferred.isFulfilled()) {
986
				this._resizeDeferred.cancel();
987
			}
988
989
			this._resizeDeferred = tools.defer(lang.hitch(this, function() {
990
				this.resizeCarousel();
991
992
				this._removeLoadingScreen();
993
			}), 200);
994
995
			this._resizeDeferred.otherwise(function() { /* prevent logging of exception */ });
996
		},
997
998
		_removeLoadingScreen: function() {
999
			if (!this.loaded) {
1000
				//fade the loadingAnimationOverlay out over given transition duration
1001
				var transitionDuration = 600;
1002
				domStyle.set(this.galleryLoadingOverlay, 'transition', lang.replace('opacity {0}ms', [transitionDuration]));
1003
				domClass.add(this.galleryLoadingOverlay, 'loaded');
1004
				domClass.add(this.contentSlider.domNode, 'noTransition');
1005
				domStyle.set(this.contentSlider.domNode, 'transform', lang.replace('translate3d({0}px, 0, 0)', [this._leftNavButtonWidth]));
1006
				domStyle.set(this.contentSlider.domNode, '-webkit-transform', lang.replace('translate3d({0}px, 0, 0)', [this._leftNavButtonWidth]));
1007
1008
				//hide the loading overlay after transition duration
1009
				tools.defer(lang.hitch(this, function() {
1010
					domClass.add(this.galleryLoadingOverlay, 'dijitHidden');
1011
					domClass.remove(this.contentSlider.domNode, 'noTransition');
1012
				}), transitionDuration);
1013
1014
				//remove styles needed for loading overlay
1015
				domStyle.set(this.domNode, 'height', '');
1016
				domStyle.set(this.domNode, 'overflow', '');
1017
1018
				domClass.remove(this.scaleButton, 'dijitHidden');
1019
				this.loaded = true;
1020
			}
1021
		},
1022
1023
		startup: function() {
1024
			this.inherited(arguments);
1025
			this.own(on(baseWin.doc, 'resize', lang.hitch(this, '_handleResize')));
1026
			this.own(on(kernel.global, 'resize', lang.hitch(this, '_handleResize')));
1027
		},
1028
1029
		destroy: function() {
1030
			this.inherited(arguments);
1031
1032
			//remove all inserted CssRules
1033
			array.forEach(this._insertedCssRules, function(rule) {
1034
				styles.removeCssRule(rule.selector, rule.declaration);
1035
			});
1036
		},
1037
1038
		pauseAllVideos: function(pauseExceptionIndex) {
1039
			if (Object.keys(this.playingVideos).length === 0) {
1040
				return;
1041
			}
1042
			
1043
			//pause all playing videos
1044
			//if a pauseExceptionIndex is given do not pause that video if it is playing
1045
			tools.forIn(this.playingVideos, lang.hitch(this, function(_videoId, player) {
1046
				if (this.videoIdToThumbIndex[_videoId] !== pauseExceptionIndex) {
1047
					player.pauseVideo();
1048
				}
1049
			}));
1050
		}, 
1051
	});
1052
});
(-)umc/js/appcenter/thumbnailError.svg (+1 lines)
Line 0    Link Here 
1
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M16.142 2L22 7.858v8.284L16.142 22H7.858L2 16.142V7.858L7.858 2h8.284zm.83-2H7.028L0 7.03v9.94L7.03 24h9.94L24 16.97V7.03L16.97 0zM11 6h2v8h-2V6zm1 12.25a1.25 1.25 0 1 1 0-2.5 1.25 1.25 0 0 1 0 2.5z"/></svg>
(-)umc/js/appcenter/videoPlayButton.svg (+63 lines)
Line 0    Link Here 
1
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
<svg
3
   xmlns:dc="http://purl.org/dc/elements/1.1/"
4
   xmlns:cc="http://creativecommons.org/ns#"
5
   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
6
   xmlns:svg="http://www.w3.org/2000/svg"
7
   xmlns="http://www.w3.org/2000/svg"
8
   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
9
   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
10
   width="24"
11
   height="24"
12
   viewBox="0 0 24 24"
13
   id="svg2"
14
   version="1.1"
15
   inkscape:version="0.48.4 r9939"
16
   sodipodi:docname="iconmonstr-video-15.svg">
17
  <metadata
18
     id="metadata10">
19
    <rdf:RDF>
20
      <cc:Work
21
         rdf:about="">
22
        <dc:format>image/svg+xml</dc:format>
23
        <dc:type
24
           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
25
        <dc:title></dc:title>
26
      </cc:Work>
27
    </rdf:RDF>
28
  </metadata>
29
  <defs
30
     id="defs8" />
31
  <sodipodi:namedview
32
     pagecolor="#ffffff"
33
     bordercolor="#666666"
34
     borderopacity="1"
35
     objecttolerance="10"
36
     gridtolerance="10"
37
     guidetolerance="10"
38
     inkscape:pageopacity="0"
39
     inkscape:pageshadow="2"
40
     inkscape:window-width="1920"
41
     inkscape:window-height="1025"
42
     id="namedview6"
43
     showgrid="false"
44
     inkscape:zoom="9.8333333"
45
     inkscape:cx="-8.644706"
46
     inkscape:cy="11.996192"
47
     inkscape:window-x="-2"
48
     inkscape:window-y="-3"
49
     inkscape:window-maximized="1"
50
     inkscape:current-layer="svg2" />
51
  <path
52
     d="M 12,0 C 5.373,0 0,5.373 0,12 0,18.627 5.373,24 12,24 18.627,24 24,18.627 24,12 24,5.373 18.627,0 12,0 z"
53
     id="path4"
54
     style="fill:#353535;stroke:none;fill-opacity:1"
55
     inkscape:connector-curvature="0"
56
     sodipodi:nodetypes="sssss" />
57
  <path
58
     inkscape:connector-curvature="0"
59
     d="M 9,17 9,7 18,12.146 z"
60
     id="path4-1"
61
     style="fill:#ffffff;stroke:none"
62
     sodipodi:nodetypes="cccc" />
63
</svg>
(-)umc/js/appcenter.styl (-33 / +90 lines)
 Lines 445-505    Link Here 
445
		@media screen and (max-width: 549px)
445
		@media screen and (max-width: 549px)
446
			width: 100%
446
			width: 100%
447
447
448
	.umcCarouselWidget
448
	.umcThumbnailGallery
449
		position: relative
449
		position: relative
450
		line-height: 0
450
451
451
		.carouselOuterContainer
452
		.galleryLoadingOverlay
452
			margin: auto
453
			position: absolute
454
			width: 100%
455
			height: 100%
456
			z-index: 1
457
			background-image: url(../../dijit/themes/umc/images/standbyAnimation.svg)
458
			background-repeat: no-repeat
459
			background-position: center center
460
			background-color: #f0f0f0
461
			opacity: 1
462
463
			&.loaded
464
				opacity: 0
465
				pointer-events: none
466
467
		.galleryOuterContainer
453
			position: relative
468
			position: relative
454
			transition: height 0.5s
455
469
456
			.contentSliderWrapper
470
			.contentSliderWrapper
457
				float: left
471
				display: inline-block
472
458
				white-space: nowrap
473
				white-space: nowrap
459
				overflow: hidden
474
				overflow: hidden
460
				position: relative
461
				height: 100%
462
475
476
				width: 100%
463
				.contentSlider
477
				.contentSlider
464
					position: absolute
478
					display: inline-block
465
					left: 0
479
					transform: translate3d(0, 0, 0)
466
					transition: left 0.5s
480
					transition-property: transform
467
481
468
					div
482
					&.noTransition
469
						margin: 0 10px
483
						transition: none
484
485
					.galleryThumb
470
						display: inline-block
486
						display: inline-block
471
						/*border: 2px solid darkgrey;*/
487
						vertical-align: middle
472
						/*box-sizing: content-box;*/
488
						transition-property: width, height
473
489
490
						&.galleryImgThumb
491
							background-size: contain
492
							background-position: center center
493
							background-repeat: no-repeat
494
							cursor: pointer
474
495
475
					&.noTransition
496
						&.brokenImg
476
						transition: none !important
497
							width: 150px
498
							box-shadow: inset 0px 0px 6px #5a5a5a
499
							background: url("appcenter/thumbnailError.svg") no-repeat, #e6e6e6
500
							background-position: center center
501
							background-size: 30% auto
477
502
478
					.carouselScreenshot
503
						&.verticalAlignHelper
479
						cursor: pointer
504
							margin: 0 !important
480
						display: block
505
							height: 0
481
						margin: auto
506
507
					.galleryVideoThumb
482
						position: relative
508
						position: relative
483
						top: 50%
484
						transform: translateY(-50%)
485
						-ms-transform: translateY(-50%)
486
						-webkit-transform: translateY(-50%)
487
						transition: max-height 0.5s, max-width 0.5s
488
509
489
		.carouselButton
510
						.galleryVideo
490
			float: left
511
							width: 100%
512
							height: 100%
513
							background-color: black
491
514
515
						.galleryVideoThumbnailWrapper
516
								width: 100%
517
								height: 100%
518
519
							.galleryVideoThumbnail
520
								width: 100%
521
								height: 100%
522
								background-size: cover
523
								background-position: center center
524
								background-repeat: no-repeat
525
								cursor: pointer
526
527
							.galleryVideoPlayButtonIcon
528
								width: 100%
529
								height: 100%
530
								background: url("appcenter/videoPlayButton.svg") no-repeat;
531
								background-size: auto 30%
532
								background-position: center center
533
								position: absolute
534
								top: 0
535
536
						&:after
537
							content: ''
538
							height: 100%
539
							display: inline-block
540
							vertical-align: middle
541
542
		.galleryNavButton
492
			cursor: pointer
543
			cursor: pointer
493
			width: 40px
544
			width: 40px
494
			height: 100%
495
			opacity: 1
545
			opacity: 1
496
			transition: opacity 0.5s
546
			transition-property: opacity, height
547
			position: absolute
548
			z-index: 1
549
			background-color: #f0f0f0
497
550
498
			&.disabled
551
			&.disabled
499
				opacity: 0
552
				opacity: 0
500
				cursor: default
553
				cursor: default
554
				pointer-events: none
555
				height: 0
501
556
502
			.carouselButtonImage
557
			.galleryNavButtonImage
503
				background-image: url("appcenter/carouselArrowRight.svg")
558
				background-image: url("appcenter/carouselArrowRight.svg")
504
				background-size: contain
559
				background-size: contain
505
				background-repeat: no-repeat
560
				background-repeat: no-repeat
 Lines 511-523    Link Here 
511
				&:hover
566
				&:hover
512
					opacity: 1
567
					opacity: 1
513
568
514
			&.rightCarouselButton
569
			&.rightGalleryNavButton
515
				margin-left: 5px
570
				margin-left: 5px
571
				right: 0
572
				top: 0
516
573
517
			&.leftCarouselButton
574
			&.leftGalleryNavButton
518
				margin-right: 5px
575
				margin-right: 5px
519
576
520
				.carouselButtonImage
577
				.galleryNavButtonImage
521
					transform: rotateY(180deg)
578
					transform: rotateY(180deg)
522
					-ms-transform: rotateY(180deg)
579
					-ms-transform: rotateY(180deg)
523
					-webkit-transform: rotateY(180deg)
580
					-webkit-transform: rotateY(180deg)

Return to bug 39794