├── .gitignore ├── LICENSE ├── README.md ├── bower.json ├── dist ├── ion-gallery.css ├── ion-gallery.js └── ion-gallery.min.js ├── gulpfile.js ├── package.json └── src ├── js ├── gallery.js ├── galleryConfig.js ├── galleryHelper.js ├── imageScale.js ├── rowHeight.js ├── slideAction.js ├── slider.js └── sliderHelper.js ├── scss └── ion-gallery.scss └── templates ├── gallery.html └── slider.html /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store 3 | bower_components/ 4 | tests/ 5 | .tmp/ 6 | .idea 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Pedro Abreu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ion-gallery 2 | Ionic gallery with slider 3 | 4 | Demo availabe in Ionic View with id 150745FE 5 | 6 | $ bower install --save ion-gallery 7 | 8 | # Features 9 | 10 | - Define number of collums to present (1 to array length) 11 | - Pinch and double tap do zoom on picture 12 | 13 | # Usage 14 | 15 | Load script and css on the html 16 | 17 | 18 | ... 19 | 20 | 21 | Add ion-gallery as dependency to your project 22 | 23 | angular 24 | .module('starter', ['ionic','ion-gallery']) 25 | 26 | Add gallery directive with array of photos: 27 | 28 | 29 | 30 | Data source example 31 | 32 | $scope.items = [ 33 | { 34 | src:'http://www.wired.com/images_blogs/rawfile/2013/11/offset_WaterHouseMarineImages_62652-2-660x440.jpg', 35 | sub: 'This is a subtitle' 36 | }, 37 | { 38 | src:'http://www.gettyimages.co.uk/CMS/StaticContent/1391099215267_hero2.jpg', 39 | sub: '' /* Not showed */ 40 | }, 41 | { 42 | src:'http://www.hdwallpapersimages.com/wp-content/uploads/2014/01/Winter-Tiger-Wild-Cat-Images.jpg', 43 | thumb:'http://www.gettyimages.co.uk/CMS/StaticContent/1391099215267_hero2.jpg' 44 | } 45 | ] 46 | 47 | Thumbnail property is also optional. If no thumbnail, the source content will be used 48 | 49 | Subtitle property is optional. If no property present, nothing is showed (Same for empty string). 50 | Supports html tags. 51 | 52 | UI will reflect changes on the content object passed to the directive. Example of adding and removing pictures can be seen in the ionic view app. 53 | 54 | # Config 55 | 56 | - Via provider: 57 | 58 | Default values in example. 59 | 60 | ``` 61 | app.config(function(ionGalleryConfigProvider) { 62 | ionGalleryConfigProvider.setGalleryConfig({ 63 | action_label: 'Close', 64 | template_gallery: 'gallery.html', 65 | template_slider: 'slider.html', 66 | toggle: false, 67 | row_size: 3, 68 | fixed_row_size: true 69 | }); 70 | }); 71 | ``` 72 | 73 | ``` 74 | Default values 75 | action_label - 'Close' (String) 76 | template_gallery - 'gallery.html' (String) 77 | template_slider - 'slider.html' (String) 78 | toggle - false (Boolean) 79 | row_size - 3 (Int) 80 | fixed_row_size - true (boolean). If true, thumbnails in gallery will always be sized as if there are "row_size" number of images in a row (even if there aren't). If set to false, the row_size will be dynamic until it reaches the set row_size (Ex: if only 1 image it will be rendered in the entire row, if 2 images, both will be rendered in the entire row) 81 | zoom_events - true (Boolean) 82 | ``` 83 | 84 | - Via markup: 85 | 86 | Markup overrides provider definitions 87 | 88 | - ion-gallery-row: Defines size of the row. Default to 3 images per row 89 | 90 | 91 | 92 | - ion-gallery-toggle: Sets one tap action on slideshow to hide/show subtitles and "Done" button. Default: true 93 | 94 | 95 | 96 | - ion-item-action: Overrides the default action when a gallery item is tapped. Default: opens the slider modal 97 | 98 | 99 | 100 | - ion-zoom-events: Enable/Disable all zoom events in slider (pinchToZoom, tap and double tap). Default: true 101 | 102 | 103 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ion-gallery", 3 | "version": "0.2", 4 | "description": "Ionic gallery directive", 5 | "main": "dist/ion-gallery.min.js", 6 | "keywords": [ 7 | "ionic", 8 | "gallery", 9 | "slider" 10 | ], 11 | "authors": [ 12 | "Pedro Abreu " 13 | ], 14 | "license": "MIT", 15 | "ignore": [ 16 | "**/.*", 17 | "node_modules", 18 | "bower_components", 19 | "test", 20 | "tests" 21 | ], 22 | "devDependencies": { 23 | "angular-mocks": "~1.4.0", 24 | "ionic": "~1.1.0" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /dist/ion-gallery.css: -------------------------------------------------------------------------------- 1 | .gallery-view .image-container{position:relative;overflow:hidden;border:2px solid white}.gallery-view .image-container img{position:absolute;top:-9999px;bottom:-9999px;left:-9999px;right:-9999px;margin:auto}.imageView .has-no-header{top:0px !important}.imageView .close-btn{font-weight:900;border:2px solid;position:absolute;right:5px;border-radius:5px}.imageView .headerView{background-image:none;background-color:black}.imageView .gallery-slide-view{width:98%;background-color:transparent}.imageView .image-subtitle{color:white;position:absolute;bottom:0px;left:10px;width:95%;height:15%;z-index:100}.imageView .listContainer{width:100%;height:100%;background-color:black}.imageView .hideAll{display:none}.imageView img{display:block;width:100%;height:auto}.imageView .scroll-view{position:absolute;width:100%;height:100%}.imageView .scroll-view .scroll{min-height:100%;display:-webkit-box;display:-moz-box;display:-ms-flexbox;display:-webkit-flex;display:flex;-webkit-box-direction:normal;-moz-box-direction:normal;-webkit-box-orient:horizontal;-moz-box-orient:horizontal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap;-webkit-box-pack:center;-moz-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-align-content:stretch;-ms-flex-line-pack:stretch;align-content:stretch;-webkit-box-align:center;-moz-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center} 2 | -------------------------------------------------------------------------------- /dist/ion-gallery.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('ion-gallery', ['templates']) 6 | .directive('ionGallery', ionGallery); 7 | 8 | ionGallery.$inject = ['$ionicPlatform', 'ionGalleryHelper', 'ionGalleryConfig']; 9 | 10 | function ionGallery($ionicPlatform, ionGalleryHelper, ionGalleryConfig) { 11 | controller.$inject = ["$scope"]; 12 | return { 13 | restrict: 'AE', 14 | scope: { 15 | ionGalleryItems: '=ionGalleryItems', 16 | ionGalleryRowSize: '=?ionGalleryRow', 17 | ionItemAction: '&?ionItemAction', 18 | ionZoomEvents: '=?ionZoomEvents' 19 | }, 20 | controller: controller, 21 | link: link, 22 | replace: true, 23 | templateUrl: ionGalleryConfig.template_gallery 24 | }; 25 | 26 | function controller($scope) { 27 | var _rowSize = parseInt($scope.ionGalleryRowSize); 28 | 29 | var _drawGallery = function () { 30 | $scope.ionGalleryRowSize = ionGalleryHelper.getRowSize(_rowSize || ionGalleryConfig.row_size, $scope.ionGalleryItems.length); 31 | $scope.actionLabel = ionGalleryConfig.action_label; 32 | $scope.items = ionGalleryHelper.buildGallery($scope.ionGalleryItems, $scope.ionGalleryRowSize); 33 | $scope.responsiveGrid = parseInt((1 / $scope.ionGalleryRowSize) * 100); 34 | }; 35 | 36 | _drawGallery(); 37 | 38 | (function () { 39 | $scope.$watch(function () { 40 | return $scope.ionGalleryItems.length; 41 | }, function (newVal, oldVal) { 42 | if (newVal !== oldVal) { 43 | _drawGallery(); 44 | } 45 | }); 46 | }()); 47 | 48 | } 49 | 50 | function link(scope, element, attrs) { 51 | scope.customItemAction = angular.isFunction(scope.ionItemAction) && attrs.hasOwnProperty('ionItemAction'); 52 | scope.ionSliderToggle = attrs.ionGalleryToggle === 'false' ? false : ionGalleryConfig.toggle; 53 | } 54 | } 55 | })(); 56 | 57 | (function(){ 58 | 'use strict'; 59 | 60 | angular 61 | .module('ion-gallery') 62 | .provider('ionGalleryConfig',ionGalleryConfig); 63 | 64 | ionGalleryConfig.$inject = []; 65 | 66 | function ionGalleryConfig(){ 67 | this.config = { 68 | action_label: 'Done', 69 | template_gallery: 'gallery.html', 70 | template_slider: 'slider.html', 71 | toggle: true, 72 | row_size: 3, 73 | fixed_row_size: true, 74 | zoom_events: true 75 | }; 76 | 77 | this.$get = function() { 78 | return this.config; 79 | }; 80 | 81 | this.setGalleryConfig = function(config) { 82 | angular.extend(this.config, this.config, config); 83 | }; 84 | } 85 | 86 | })(); 87 | 88 | (function(){ 89 | 'use strict'; 90 | 91 | angular 92 | .module('ion-gallery') 93 | .service('ionGalleryHelper',ionGalleryHelper); 94 | 95 | ionGalleryHelper.$inject = ['ionGalleryConfig']; 96 | 97 | function ionGalleryHelper(ionGalleryConfig) { 98 | 99 | this.getRowSize = function(size,length){ 100 | var rowSize; 101 | 102 | if(isNaN(size) === true || size <= 0){ 103 | rowSize = ionGalleryConfig.row_size; 104 | } 105 | else if(size > length && !ionGalleryConfig.fixed_row_size){ 106 | rowSize = length; 107 | } 108 | else{ 109 | rowSize = size; 110 | } 111 | 112 | return rowSize; 113 | 114 | }; 115 | 116 | this.buildGallery = function(items,rowSize){ 117 | var _gallery = []; 118 | var row = -1; 119 | var col = 0; 120 | 121 | for(var i=0;i0){ 160 | if(context.naturalHeight >= context.naturalWidth){ 161 | element.attr('width','100%'); 162 | } 163 | else{ 164 | element.attr('height',element.parent()[0].offsetHeight+'px'); 165 | } 166 | } 167 | }; 168 | 169 | element.bind("load" , function(e){ 170 | var _this = this; 171 | if(element.parent()[0].offsetHeight > 0){ 172 | scaleImage(this,element.parent()[0].offsetHeight); 173 | } 174 | 175 | scope.$watch(function(){ 176 | return element.parent()[0].offsetHeight; 177 | },function(newValue){ 178 | scaleImage(_this,newValue); 179 | }); 180 | }); 181 | } 182 | } 183 | })(); 184 | (function(){ 185 | 'use strict'; 186 | 187 | angular 188 | .module('ion-gallery') 189 | .directive('ionRowHeight',ionRowHeight); 190 | 191 | ionRowHeight.$inject = ['ionGalleryConfig']; 192 | 193 | function ionRowHeight(ionGalleryConfig){ 194 | 195 | return { 196 | restrict: 'A', 197 | link : link 198 | }; 199 | 200 | function link(scope, element, attrs) { 201 | scope.$watch( 202 | function(){ 203 | return scope.ionGalleryRowSize; 204 | }, 205 | function(newValue,oldValue){ 206 | if(newValue > 0){ 207 | element.css('height',element[0].offsetWidth * parseInt(scope.responsiveGrid)/100 + 'px'); 208 | } 209 | }); 210 | } 211 | } 212 | })(); 213 | (function(){ 214 | 'use strict'; 215 | 216 | angular 217 | .module('ion-gallery') 218 | .directive('ionSlideAction',ionSlideAction); 219 | 220 | ionSlideAction.$inject = ['$ionicGesture','$timeout']; 221 | 222 | function ionSlideAction($ionicGesture, $timeout){ 223 | 224 | return { 225 | restrict: 'A', 226 | link : link 227 | }; 228 | 229 | function link(scope, element, attrs) { 230 | var isDoubleTapAction = false; 231 | 232 | var pinchZoom = function pinchZoom(){ 233 | scope.$emit('ZoomStarted'); 234 | }; 235 | 236 | var imageDoubleTapGesture = function imageDoubleTapGesture(event) { 237 | 238 | isDoubleTapAction = true; 239 | 240 | $timeout(function(){ 241 | isDoubleTapAction = false; 242 | scope.$emit('DoubleTapEvent',{ 'x': event.gesture.touches[0].pageX, 'y': event.gesture.touches[0].pageY}); 243 | },200); 244 | }; 245 | 246 | var imageTapGesture = function imageTapGesture(event) { 247 | 248 | if(isDoubleTapAction === true){ 249 | return; 250 | } 251 | else{ 252 | $timeout(function(){ 253 | if(isDoubleTapAction === true){ 254 | return; 255 | } 256 | else{ 257 | scope.$emit('TapEvent'); 258 | } 259 | },200); 260 | } 261 | }; 262 | 263 | var pinchEvent = $ionicGesture.on('pinch',pinchZoom,element); 264 | var doubleTapEvent = $ionicGesture.on('doubletap', function(e){imageDoubleTapGesture(e);}, element); 265 | var tapEvent = $ionicGesture.on('tap', imageTapGesture, element); 266 | 267 | scope.$on('$destroy', function() { 268 | $ionicGesture.off(doubleTapEvent, 'doubletap', imageDoubleTapGesture); 269 | $ionicGesture.off(tapEvent, 'tap', imageTapGesture); 270 | $ionicGesture.off(pinchEvent, 'pinch', pinchZoom); 271 | }); 272 | } 273 | } 274 | })(); 275 | 276 | (function(){ 277 | 'use strict'; 278 | 279 | angular 280 | .module('ion-gallery') 281 | .directive('ionSlider',ionSlider); 282 | 283 | ionSlider.$inject = ['$ionicModal','$timeout','$ionicScrollDelegate','ionSliderHelper','ionGalleryConfig']; 284 | 285 | function ionSlider($ionicModal,$timeout,$ionicScrollDelegate,ionSliderHelper,ionGalleryConfig){ 286 | 287 | controller.$inject = ["$scope"]; 288 | return { 289 | restrict: 'A', 290 | controller: controller, 291 | link : link 292 | }; 293 | 294 | function controller($scope){ 295 | var lastSlideIndex; 296 | var currentImage; 297 | 298 | var rowSize = $scope.ionGalleryRowSize; 299 | var zoomStart = false; 300 | 301 | $scope.selectedSlide = 1; 302 | $scope.hideAll = false; 303 | $scope.ionZoomEvents = ionSliderHelper.setZoomEvents($scope.ionZoomEvents) 304 | 305 | $scope.openSlider = function(index) { 306 | $scope.slides = []; 307 | currentImage = index; 308 | 309 | var galleryLength = $scope.ionGalleryItems.length; 310 | var previndex = index - 1 < 0 ? galleryLength - 1 : index - 1; 311 | var nextindex = index + 1 >= galleryLength ? 0 : index + 1; 312 | 313 | $scope.slides[0] = $scope.ionGalleryItems[previndex]; 314 | $scope.slides[1] = $scope.ionGalleryItems[index]; 315 | $scope.slides[2] = $scope.ionGalleryItems[nextindex]; 316 | 317 | lastSlideIndex = 1; 318 | $scope.openModal(); 319 | }; 320 | 321 | $scope.slideChanged = function(currentSlideIndex) { 322 | 323 | if(currentSlideIndex === lastSlideIndex){ 324 | return; 325 | } 326 | 327 | var slideToLoad = $scope.slides.length - lastSlideIndex - currentSlideIndex; 328 | var galleryLength = $scope.ionGalleryItems.length; 329 | var imageToLoad; 330 | var slidePosition = lastSlideIndex + '>' + currentSlideIndex; 331 | 332 | if(slidePosition === '0>1' || slidePosition === '1>2' || slidePosition === '2>0'){ 333 | currentImage++; 334 | 335 | if(currentImage >= galleryLength){ 336 | currentImage = 0; 337 | } 338 | 339 | imageToLoad = currentImage + 1; 340 | 341 | if( imageToLoad >= galleryLength){ 342 | imageToLoad = 0; 343 | } 344 | } 345 | else if(slidePosition === '0>2' || slidePosition === '1>0' || slidePosition === '2>1'){ 346 | currentImage--; 347 | 348 | if(currentImage < 0){ 349 | currentImage = galleryLength - 1 ; 350 | } 351 | 352 | imageToLoad = currentImage - 1; 353 | 354 | if(imageToLoad < 0){ 355 | imageToLoad = galleryLength - 1; 356 | } 357 | } 358 | 359 | if($scope.ionZoomEvents === true){ 360 | //Clear zoom 361 | $ionicScrollDelegate.$getByHandle('slide-' + slideToLoad).zoomTo(1); 362 | } 363 | 364 | $scope.slides[slideToLoad] = $scope.ionGalleryItems[imageToLoad]; 365 | 366 | lastSlideIndex = currentSlideIndex; 367 | }; 368 | 369 | $scope.$on('ZoomStarted', function(e){ 370 | $timeout(function () { 371 | zoomStart = true; 372 | $scope.hideAll = true; 373 | }); 374 | 375 | }); 376 | 377 | $scope.$on('TapEvent', function(e){ 378 | $timeout(function () { 379 | _onTap(); 380 | }); 381 | 382 | }); 383 | 384 | $scope.$on('DoubleTapEvent', function(event,position){ 385 | $timeout(function () { 386 | _onDoubleTap(position); 387 | }); 388 | 389 | }); 390 | 391 | var _onTap = function _onTap(){ 392 | if(zoomStart === true){ 393 | if($scope.ionZoomEvents === true){ 394 | $ionicScrollDelegate.$getByHandle('slide-'+lastSlideIndex).zoomTo(1,true); 395 | } 396 | 397 | $timeout(function () { 398 | _isOriginalSize(); 399 | },300); 400 | 401 | return; 402 | } 403 | 404 | if(($scope.hasOwnProperty('ionSliderToggle') && $scope.ionSliderToggle === false && $scope.hideAll === false) || zoomStart === true){ 405 | return; 406 | } 407 | 408 | $scope.hideAll = !$scope.hideAll; 409 | }; 410 | 411 | var _onDoubleTap = function _onDoubleTap(position){ 412 | if(zoomStart === false){ 413 | if($scope.ionZoomEvents === true){ 414 | $ionicScrollDelegate.$getByHandle('slide-'+lastSlideIndex).zoomTo(3,true,position.x,position.y); 415 | } 416 | 417 | zoomStart = true; 418 | $scope.hideAll = true; 419 | } 420 | else{ 421 | _onTap(); 422 | } 423 | }; 424 | 425 | function _isOriginalSize(){ 426 | zoomStart = false; 427 | _onTap(); 428 | } 429 | 430 | } 431 | 432 | function link(scope, element, attrs) { 433 | var _modal; 434 | 435 | $ionicModal.fromTemplateUrl(ionGalleryConfig.template_slider, { 436 | scope: scope, 437 | animation: 'fade-in' 438 | }).then(function(modal){ 439 | _modal = modal; 440 | }); 441 | 442 | scope.openModal = function() { 443 | _modal.show(); 444 | }; 445 | 446 | scope.closeModal = function() { 447 | _modal.hide(); 448 | }; 449 | 450 | scope.$on('$destroy', function() { 451 | try{ 452 | _modal.remove(); 453 | } catch(err) { 454 | console.log(err.message); 455 | } 456 | }); 457 | } 458 | } 459 | })(); 460 | 461 | (function(){ 462 | 'use strict'; 463 | 464 | angular 465 | .module('ion-gallery') 466 | .service('ionSliderHelper',ionSliderHelper); 467 | 468 | ionSliderHelper.$inject = ['ionGalleryConfig']; 469 | 470 | function ionSliderHelper(ionGalleryConfig) { 471 | 472 | this.setZoomEvents = function setZoomEvents(zoomEvents){ 473 | if (zoomEvents === false){ 474 | ionGalleryConfig.zoom_events = false; 475 | } 476 | 477 | return ionGalleryConfig.zoom_events; 478 | } 479 | 480 | } 481 | })(); 482 | 483 | angular.module("templates", []).run(["$templateCache", function($templateCache) {$templateCache.put("gallery.html","
\n
\n
\n\n \n\n
\n
\n
\n
\n"); 484 | $templateCache.put("slider.html","\n \n \n \n\n \n \n \n \n
\n \n
\n
0\" class=\"image-subtitle\" ng-show=\"!hideAll\">\n \n
\n
\n
\n
\n
\n
\n");}]); -------------------------------------------------------------------------------- /dist/ion-gallery.min.js: -------------------------------------------------------------------------------- 1 | !function(){"use strict";function n(n,e,i){function o(n){var o=parseInt(n.ionGalleryRowSize),t=function(){n.ionGalleryRowSize=e.getRowSize(o||i.row_size,n.ionGalleryItems.length),n.actionLabel=i.action_label,n.items=e.buildGallery(n.ionGalleryItems,n.ionGalleryRowSize),n.responsiveGrid=parseInt(1/n.ionGalleryRowSize*100)};t(),function(){n.$watch(function(){return n.ionGalleryItems.length},function(n,e){n!==e&&t()})}()}function t(n,e,o){n.customItemAction=angular.isFunction(n.ionItemAction)&&o.hasOwnProperty("ionItemAction"),n.ionSliderToggle="false"===o.ionGalleryToggle?!1:i.toggle}return o.$inject=["$scope"],{restrict:"AE",scope:{ionGalleryItems:"=ionGalleryItems",ionGalleryRowSize:"=?ionGalleryRow",ionItemAction:"&?ionItemAction",ionZoomEvents:"=?ionZoomEvents"},controller:o,link:t,replace:!0,templateUrl:i.template_gallery}}angular.module("ion-gallery",["templates"]).directive("ionGallery",n),n.$inject=["$ionicPlatform","ionGalleryHelper","ionGalleryConfig"]}(),function(){"use strict";function n(){this.config={action_label:"Done",template_gallery:"gallery.html",template_slider:"slider.html",toggle:!0,row_size:3,fixed_row_size:!0,zoom_events:!0},this.$get=function(){return this.config},this.setGalleryConfig=function(n){angular.extend(this.config,this.config,n)}}angular.module("ion-gallery").provider("ionGalleryConfig",n),n.$inject=[]}(),function(){"use strict";function n(n){this.getRowSize=function(e,i){var o;return o=isNaN(e)===!0||0>=e?n.row_size:e>i&&!n.fixed_row_size?i:e},this.buildGallery=function(n,e){for(var i=[],o=-1,t=0,l=0;l0&&(n.naturalHeight>=n.naturalWidth?e.attr("width","100%"):e.attr("height",e.parent()[0].offsetHeight+"px"))};e.bind("load",function(i){var t=this;e.parent()[0].offsetHeight>0&&o(this,e.parent()[0].offsetHeight),n.$watch(function(){return e.parent()[0].offsetHeight},function(n){o(t,n)})})}return{restrict:"A",link:n}}angular.module("ion-gallery").directive("ionImageScale",n),n.$inject=[]}(),function(){"use strict";function n(n){function e(n,e,i){n.$watch(function(){return n.ionGalleryRowSize},function(i,o){i>0&&e.css("height",e[0].offsetWidth*parseInt(n.responsiveGrid)/100+"px")})}return{restrict:"A",link:e}}angular.module("ion-gallery").directive("ionRowHeight",n),n.$inject=["ionGalleryConfig"]}(),function(){"use strict";function n(n,e){function i(i,o,t){var l=!1,r=function(){i.$emit("ZoomStarted")},s=function(n){l=!0,e(function(){l=!1,i.$emit("DoubleTapEvent",{x:n.gesture.touches[0].pageX,y:n.gesture.touches[0].pageY})},200)},a=function(n){l!==!0&&e(function(){l!==!0&&i.$emit("TapEvent")},200)},c=n.on("pinch",r,o),u=n.on("doubletap",function(n){s(n)},o),d=n.on("tap",a,o);i.$on("$destroy",function(){n.off(u,"doubletap",s),n.off(d,"tap",a),n.off(c,"pinch",r)})}return{restrict:"A",link:i}}angular.module("ion-gallery").directive("ionSlideAction",n),n.$inject=["$ionicGesture","$timeout"]}(),function(){"use strict";function n(n,e,i,o,t){function l(n){function t(){s=!1,a()}var l,r,s=(n.ionGalleryRowSize,!1);n.selectedSlide=1,n.hideAll=!1,n.ionZoomEvents=o.setZoomEvents(n.ionZoomEvents),n.openSlider=function(e){n.slides=[],r=e;var i=n.ionGalleryItems.length,o=0>e-1?i-1:e-1,t=e+1>=i?0:e+1;n.slides[0]=n.ionGalleryItems[o],n.slides[1]=n.ionGalleryItems[e],n.slides[2]=n.ionGalleryItems[t],l=1,n.openModal()},n.slideChanged=function(e){if(e!==l){var o,t=n.slides.length-l-e,s=n.ionGalleryItems.length,a=l+">"+e;"0>1"===a||"1>2"===a||"2>0"===a?(r++,r>=s&&(r=0),o=r+1,o>=s&&(o=0)):"0>2"!==a&&"1>0"!==a&&"2>1"!==a||(r--,0>r&&(r=s-1),o=r-1,0>o&&(o=s-1)),n.ionZoomEvents===!0&&i.$getByHandle("slide-"+t).zoomTo(1),n.slides[t]=n.ionGalleryItems[o],l=e}},n.$on("ZoomStarted",function(i){e(function(){s=!0,n.hideAll=!0})}),n.$on("TapEvent",function(n){e(function(){a()})}),n.$on("DoubleTapEvent",function(n,i){e(function(){c(i)})});var a=function(){return s===!0?(n.ionZoomEvents===!0&&i.$getByHandle("slide-"+l).zoomTo(1,!0),void e(function(){t()},300)):void(n.hasOwnProperty("ionSliderToggle")&&n.ionSliderToggle===!1&&n.hideAll===!1||s===!0||(n.hideAll=!n.hideAll))},c=function(e){s===!1?(n.ionZoomEvents===!0&&i.$getByHandle("slide-"+l).zoomTo(3,!0,e.x,e.y),s=!0,n.hideAll=!0):a()}}function r(e,i,o){var l;n.fromTemplateUrl(t.template_slider,{scope:e,animation:"fade-in"}).then(function(n){l=n}),e.openModal=function(){l.show()},e.closeModal=function(){l.hide()},e.$on("$destroy",function(){try{l.remove()}catch(n){}})}return l.$inject=["$scope"],{restrict:"A",controller:l,link:r}}angular.module("ion-gallery").directive("ionSlider",n),n.$inject=["$ionicModal","$timeout","$ionicScrollDelegate","ionSliderHelper","ionGalleryConfig"]}(),function(){"use strict";function n(n){this.setZoomEvents=function(e){return e===!1&&(n.zoom_events=!1),n.zoom_events}}angular.module("ion-gallery").service("ionSliderHelper",n),n.$inject=["ionGalleryConfig"]}(),angular.module("templates",[]).run(["$templateCache",function(n){n.put("gallery.html",'\n'),n.put("slider.html",'\n \n \n \n\n \n \n \n \n \n
\n \n
\n
\n
\n
\n
\n
\n')}]); -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var jshint = require('gulp-jshint'); 3 | var templateCache = require('gulp-angular-templatecache'); 4 | var concat = require('gulp-concat'); 5 | var uglify = require('gulp-uglify'); 6 | var rename = require('gulp-rename'); 7 | var sass = require('gulp-sass'); 8 | var ngAnnotate = require('gulp-ng-annotate'); 9 | var stripDebug = require('gulp-strip-debug'); 10 | var del = require('del'); 11 | 12 | gulp.task('default', ['compress','sass'], function() { 13 | del(['.tmp/'], function (err, paths) { 14 | console.log('Deleted files/folders:\n', paths.join('\n')); 15 | }); 16 | }); 17 | 18 | gulp.task('lint', function() { 19 | return gulp.src('./src/js/*.js') 20 | .pipe(jshint()) 21 | .pipe(jshint.reporter('default')); 22 | }); 23 | 24 | gulp.task('templatecache',['lint'], function () { 25 | return gulp.src('./src/templates/*.html') 26 | .pipe(templateCache({standalone:true})) 27 | .pipe(gulp.dest('./.tmp/')); 28 | }); 29 | 30 | gulp.task('scripts', ['templatecache'], function() { 31 | return gulp.src('./src/js/*.js') 32 | .pipe(ngAnnotate()) 33 | .pipe(concat('ion-gallery.js')) 34 | .pipe(gulp.dest('./.tmp/')); 35 | }); 36 | 37 | gulp.task('compress',['scripts'], function() { 38 | return gulp.src('./.tmp/*.js') 39 | .pipe(concat('ion-gallery.js')) 40 | .pipe(gulp.dest('./dist')) 41 | .pipe(stripDebug()) 42 | .pipe(uglify()) 43 | .pipe(rename('ion-gallery.min.js')) 44 | .pipe(gulp.dest('./dist')); 45 | }); 46 | 47 | gulp.task('sass', function () { 48 | gulp.src('./src/scss/*.scss') 49 | .pipe(sass({outputStyle: 'compressed'})) 50 | .pipe(gulp.dest('./dist')); 51 | }); 52 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ion-gallery", 3 | "version": "0.2", 4 | "description": "Ionic gallery directive", 5 | "main": "gulpfile.js", 6 | "dependencies": {}, 7 | "devDependencies": { 8 | "del": "^1.2.0", 9 | "gulp": "^3.8.11", 10 | "gulp-angular-templatecache": "^1.6.0", 11 | "gulp-concat": "^2.5.2", 12 | "gulp-jshint": "^1.10.0", 13 | "gulp-uglify": "^1.2.0", 14 | "gulp-ng-annotate": "^0.5.3", 15 | "gulp-rename": "^1.2.2", 16 | "gulp-sass": "^2.0.1", 17 | "gulp-strip-debug": "^1.0.2" 18 | }, 19 | "keywords": [ 20 | "ionic", 21 | "gallery", 22 | "slider" 23 | ], 24 | "author": "Pedro Abreu ", 25 | "license": "MIT" 26 | } 27 | -------------------------------------------------------------------------------- /src/js/gallery.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular 5 | .module('ion-gallery', ['templates']) 6 | .directive('ionGallery', ionGallery); 7 | 8 | ionGallery.$inject = ['$ionicPlatform', 'ionGalleryHelper', 'ionGalleryConfig']; 9 | 10 | function ionGallery($ionicPlatform, ionGalleryHelper, ionGalleryConfig) { 11 | return { 12 | restrict: 'AE', 13 | scope: { 14 | ionGalleryItems: '=ionGalleryItems', 15 | ionGalleryRowSize: '=?ionGalleryRow', 16 | ionItemAction: '&?ionItemAction', 17 | ionZoomEvents: '=?ionZoomEvents' 18 | }, 19 | controller: controller, 20 | link: link, 21 | replace: true, 22 | templateUrl: ionGalleryConfig.template_gallery 23 | }; 24 | 25 | function controller($scope) { 26 | var _rowSize = parseInt($scope.ionGalleryRowSize); 27 | 28 | var _drawGallery = function () { 29 | $scope.ionGalleryRowSize = ionGalleryHelper.getRowSize(_rowSize || ionGalleryConfig.row_size, $scope.ionGalleryItems.length); 30 | $scope.actionLabel = ionGalleryConfig.action_label; 31 | $scope.items = ionGalleryHelper.buildGallery($scope.ionGalleryItems, $scope.ionGalleryRowSize); 32 | $scope.responsiveGrid = parseInt((1 / $scope.ionGalleryRowSize) * 100); 33 | }; 34 | 35 | _drawGallery(); 36 | 37 | (function () { 38 | $scope.$watch(function () { 39 | return $scope.ionGalleryItems.length; 40 | }, function (newVal, oldVal) { 41 | if (newVal !== oldVal) { 42 | _drawGallery(); 43 | } 44 | }); 45 | }()); 46 | 47 | } 48 | 49 | function link(scope, element, attrs) { 50 | scope.customItemAction = angular.isFunction(scope.ionItemAction) && attrs.hasOwnProperty('ionItemAction'); 51 | scope.ionSliderToggle = attrs.ionGalleryToggle === 'false' ? false : ionGalleryConfig.toggle; 52 | } 53 | } 54 | })(); 55 | -------------------------------------------------------------------------------- /src/js/galleryConfig.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | angular 5 | .module('ion-gallery') 6 | .provider('ionGalleryConfig',ionGalleryConfig); 7 | 8 | ionGalleryConfig.$inject = []; 9 | 10 | function ionGalleryConfig(){ 11 | this.config = { 12 | action_label: 'Done', 13 | template_gallery: 'gallery.html', 14 | template_slider: 'slider.html', 15 | toggle: true, 16 | row_size: 3, 17 | fixed_row_size: true, 18 | zoom_events: true 19 | }; 20 | 21 | this.$get = function() { 22 | return this.config; 23 | }; 24 | 25 | this.setGalleryConfig = function(config) { 26 | angular.extend(this.config, this.config, config); 27 | }; 28 | } 29 | 30 | })(); 31 | -------------------------------------------------------------------------------- /src/js/galleryHelper.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | angular 5 | .module('ion-gallery') 6 | .service('ionGalleryHelper',ionGalleryHelper); 7 | 8 | ionGalleryHelper.$inject = ['ionGalleryConfig']; 9 | 10 | function ionGalleryHelper(ionGalleryConfig) { 11 | 12 | this.getRowSize = function(size,length){ 13 | var rowSize; 14 | 15 | if(isNaN(size) === true || size <= 0){ 16 | rowSize = ionGalleryConfig.row_size; 17 | } 18 | else if(size > length && !ionGalleryConfig.fixed_row_size){ 19 | rowSize = length; 20 | } 21 | else{ 22 | rowSize = size; 23 | } 24 | 25 | return rowSize; 26 | 27 | }; 28 | 29 | this.buildGallery = function(items,rowSize){ 30 | var _gallery = []; 31 | var row = -1; 32 | var col = 0; 33 | 34 | for(var i=0;i0){ 21 | if(context.naturalHeight >= context.naturalWidth){ 22 | element.attr('width','100%'); 23 | } 24 | else{ 25 | element.attr('height',element.parent()[0].offsetHeight+'px'); 26 | } 27 | } 28 | }; 29 | 30 | element.bind("load" , function(e){ 31 | var _this = this; 32 | if(element.parent()[0].offsetHeight > 0){ 33 | scaleImage(this,element.parent()[0].offsetHeight); 34 | } 35 | 36 | scope.$watch(function(){ 37 | return element.parent()[0].offsetHeight; 38 | },function(newValue){ 39 | scaleImage(_this,newValue); 40 | }); 41 | }); 42 | } 43 | } 44 | })(); -------------------------------------------------------------------------------- /src/js/rowHeight.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | angular 5 | .module('ion-gallery') 6 | .directive('ionRowHeight',ionRowHeight); 7 | 8 | ionRowHeight.$inject = ['ionGalleryConfig']; 9 | 10 | function ionRowHeight(ionGalleryConfig){ 11 | 12 | return { 13 | restrict: 'A', 14 | link : link 15 | }; 16 | 17 | function link(scope, element, attrs) { 18 | scope.$watch( 19 | function(){ 20 | return scope.ionGalleryRowSize; 21 | }, 22 | function(newValue,oldValue){ 23 | if(newValue > 0){ 24 | element.css('height',element[0].offsetWidth * parseInt(scope.responsiveGrid)/100 + 'px'); 25 | } 26 | }); 27 | } 28 | } 29 | })(); -------------------------------------------------------------------------------- /src/js/slideAction.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | angular 5 | .module('ion-gallery') 6 | .directive('ionSlideAction',ionSlideAction); 7 | 8 | ionSlideAction.$inject = ['$ionicGesture','$timeout']; 9 | 10 | function ionSlideAction($ionicGesture, $timeout){ 11 | 12 | return { 13 | restrict: 'A', 14 | link : link 15 | }; 16 | 17 | function link(scope, element, attrs) { 18 | var isDoubleTapAction = false; 19 | 20 | var pinchZoom = function pinchZoom(){ 21 | scope.$emit('ZoomStarted'); 22 | }; 23 | 24 | var imageDoubleTapGesture = function imageDoubleTapGesture(event) { 25 | 26 | isDoubleTapAction = true; 27 | 28 | $timeout(function(){ 29 | isDoubleTapAction = false; 30 | scope.$emit('DoubleTapEvent',{ 'x': event.gesture.touches[0].pageX, 'y': event.gesture.touches[0].pageY}); 31 | },200); 32 | }; 33 | 34 | var imageTapGesture = function imageTapGesture(event) { 35 | 36 | if(isDoubleTapAction === true){ 37 | return; 38 | } 39 | else{ 40 | $timeout(function(){ 41 | if(isDoubleTapAction === true){ 42 | return; 43 | } 44 | else{ 45 | scope.$emit('TapEvent'); 46 | } 47 | },200); 48 | } 49 | }; 50 | 51 | var pinchEvent = $ionicGesture.on('pinch',pinchZoom,element); 52 | var doubleTapEvent = $ionicGesture.on('doubletap', function(e){imageDoubleTapGesture(e);}, element); 53 | var tapEvent = $ionicGesture.on('tap', imageTapGesture, element); 54 | 55 | scope.$on('$destroy', function() { 56 | $ionicGesture.off(doubleTapEvent, 'doubletap', imageDoubleTapGesture); 57 | $ionicGesture.off(tapEvent, 'tap', imageTapGesture); 58 | $ionicGesture.off(pinchEvent, 'pinch', pinchZoom); 59 | }); 60 | } 61 | } 62 | })(); 63 | -------------------------------------------------------------------------------- /src/js/slider.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | angular 5 | .module('ion-gallery') 6 | .directive('ionSlider',ionSlider); 7 | 8 | ionSlider.$inject = ['$ionicModal','$timeout','$ionicScrollDelegate','ionSliderHelper','ionGalleryConfig']; 9 | 10 | function ionSlider($ionicModal,$timeout,$ionicScrollDelegate,ionSliderHelper,ionGalleryConfig){ 11 | 12 | return { 13 | restrict: 'A', 14 | controller: controller, 15 | link : link 16 | }; 17 | 18 | function controller($scope){ 19 | var lastSlideIndex; 20 | var currentImage; 21 | 22 | var rowSize = $scope.ionGalleryRowSize; 23 | var zoomStart = false; 24 | 25 | $scope.selectedSlide = 1; 26 | $scope.hideAll = false; 27 | $scope.ionZoomEvents = ionSliderHelper.setZoomEvents($scope.ionZoomEvents) 28 | 29 | $scope.openSlider = function(index) { 30 | $scope.slides = []; 31 | currentImage = index; 32 | 33 | var galleryLength = $scope.ionGalleryItems.length; 34 | var previndex = index - 1 < 0 ? galleryLength - 1 : index - 1; 35 | var nextindex = index + 1 >= galleryLength ? 0 : index + 1; 36 | 37 | $scope.slides[0] = $scope.ionGalleryItems[previndex]; 38 | $scope.slides[1] = $scope.ionGalleryItems[index]; 39 | $scope.slides[2] = $scope.ionGalleryItems[nextindex]; 40 | 41 | lastSlideIndex = 1; 42 | $scope.openModal(); 43 | }; 44 | 45 | $scope.slideChanged = function(currentSlideIndex) { 46 | 47 | if(currentSlideIndex === lastSlideIndex){ 48 | return; 49 | } 50 | 51 | var slideToLoad = $scope.slides.length - lastSlideIndex - currentSlideIndex; 52 | var galleryLength = $scope.ionGalleryItems.length; 53 | var imageToLoad; 54 | var slidePosition = lastSlideIndex + '>' + currentSlideIndex; 55 | 56 | if(slidePosition === '0>1' || slidePosition === '1>2' || slidePosition === '2>0'){ 57 | currentImage++; 58 | 59 | if(currentImage >= galleryLength){ 60 | currentImage = 0; 61 | } 62 | 63 | imageToLoad = currentImage + 1; 64 | 65 | if( imageToLoad >= galleryLength){ 66 | imageToLoad = 0; 67 | } 68 | } 69 | else if(slidePosition === '0>2' || slidePosition === '1>0' || slidePosition === '2>1'){ 70 | currentImage--; 71 | 72 | if(currentImage < 0){ 73 | currentImage = galleryLength - 1 ; 74 | } 75 | 76 | imageToLoad = currentImage - 1; 77 | 78 | if(imageToLoad < 0){ 79 | imageToLoad = galleryLength - 1; 80 | } 81 | } 82 | 83 | if($scope.ionZoomEvents === true){ 84 | //Clear zoom 85 | $ionicScrollDelegate.$getByHandle('slide-' + slideToLoad).zoomTo(1); 86 | } 87 | 88 | $scope.slides[slideToLoad] = $scope.ionGalleryItems[imageToLoad]; 89 | 90 | lastSlideIndex = currentSlideIndex; 91 | }; 92 | 93 | $scope.$on('ZoomStarted', function(e){ 94 | $timeout(function () { 95 | zoomStart = true; 96 | $scope.hideAll = true; 97 | }); 98 | 99 | }); 100 | 101 | $scope.$on('TapEvent', function(e){ 102 | $timeout(function () { 103 | _onTap(); 104 | }); 105 | 106 | }); 107 | 108 | $scope.$on('DoubleTapEvent', function(event,position){ 109 | $timeout(function () { 110 | _onDoubleTap(position); 111 | }); 112 | 113 | }); 114 | 115 | var _onTap = function _onTap(){ 116 | if(zoomStart === true){ 117 | if($scope.ionZoomEvents === true){ 118 | $ionicScrollDelegate.$getByHandle('slide-'+lastSlideIndex).zoomTo(1,true); 119 | } 120 | 121 | $timeout(function () { 122 | _isOriginalSize(); 123 | },300); 124 | 125 | return; 126 | } 127 | 128 | if(($scope.hasOwnProperty('ionSliderToggle') && $scope.ionSliderToggle === false && $scope.hideAll === false) || zoomStart === true){ 129 | return; 130 | } 131 | 132 | $scope.hideAll = !$scope.hideAll; 133 | }; 134 | 135 | var _onDoubleTap = function _onDoubleTap(position){ 136 | if(zoomStart === false){ 137 | if($scope.ionZoomEvents === true){ 138 | $ionicScrollDelegate.$getByHandle('slide-'+lastSlideIndex).zoomTo(3,true,position.x,position.y); 139 | } 140 | 141 | zoomStart = true; 142 | $scope.hideAll = true; 143 | } 144 | else{ 145 | _onTap(); 146 | } 147 | }; 148 | 149 | function _isOriginalSize(){ 150 | zoomStart = false; 151 | _onTap(); 152 | } 153 | 154 | } 155 | 156 | function link(scope, element, attrs) { 157 | var _modal; 158 | 159 | $ionicModal.fromTemplateUrl(ionGalleryConfig.template_slider, { 160 | scope: scope, 161 | animation: 'fade-in' 162 | }).then(function(modal){ 163 | _modal = modal; 164 | }); 165 | 166 | scope.openModal = function() { 167 | _modal.show(); 168 | }; 169 | 170 | scope.closeModal = function() { 171 | _modal.hide(); 172 | }; 173 | 174 | scope.$on('$destroy', function() { 175 | try{ 176 | _modal.remove(); 177 | } catch(err) { 178 | console.log(err.message); 179 | } 180 | }); 181 | } 182 | } 183 | })(); 184 | -------------------------------------------------------------------------------- /src/js/sliderHelper.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | 'use strict'; 3 | 4 | angular 5 | .module('ion-gallery') 6 | .service('ionSliderHelper',ionSliderHelper); 7 | 8 | ionSliderHelper.$inject = ['ionGalleryConfig']; 9 | 10 | function ionSliderHelper(ionGalleryConfig) { 11 | 12 | this.setZoomEvents = function setZoomEvents(zoomEvents){ 13 | if (zoomEvents === false){ 14 | ionGalleryConfig.zoom_events = false; 15 | } 16 | 17 | return ionGalleryConfig.zoom_events; 18 | } 19 | 20 | } 21 | })(); 22 | -------------------------------------------------------------------------------- /src/scss/ion-gallery.scss: -------------------------------------------------------------------------------- 1 | .gallery-view{ 2 | 3 | .image-container{ 4 | position: relative; 5 | overflow: hidden; 6 | border: 2px solid white; 7 | 8 | img{ 9 | position: absolute; 10 | top: -9999px; 11 | bottom: -9999px; 12 | left: -9999px; 13 | right: -9999px; 14 | margin: auto; 15 | } 16 | } 17 | } 18 | 19 | .imageView{ 20 | 21 | .has-no-header{ 22 | top:0px !important; 23 | } 24 | 25 | .close-btn{ 26 | font-weight: 900; 27 | border:2px solid; 28 | position:absolute; 29 | right:5px; 30 | border-radius: 5px; 31 | } 32 | 33 | .headerView{ 34 | background-image:none; 35 | background-color: black; 36 | } 37 | 38 | .gallery-slide-view{ 39 | width: 98%; 40 | background-color: transparent; 41 | } 42 | 43 | .image-subtitle{ 44 | color: white; 45 | position: absolute; 46 | bottom: 0px; 47 | left: 10px; 48 | width: 95%; 49 | height:15%; 50 | z-index: 100; 51 | } 52 | 53 | .listContainer { 54 | width: 100%; 55 | height: 100%; 56 | background-color:black; 57 | } 58 | 59 | .hideAll{ 60 | display:none; 61 | } 62 | 63 | img { 64 | display: block; 65 | width: 100%; 66 | height: auto; 67 | } 68 | 69 | .scroll-view { 70 | position: absolute; 71 | width: 100%; 72 | height:100%; 73 | } 74 | 75 | .scroll-view .scroll { 76 | min-height: 100%; 77 | display: -webkit-box; 78 | display: -moz-box; 79 | display: -ms-flexbox; 80 | display: -webkit-flex; 81 | display: flex; 82 | -webkit-box-direction: normal; 83 | -moz-box-direction: normal; 84 | -webkit-box-orient: horizontal; 85 | -moz-box-orient: horizontal; 86 | -webkit-flex-direction: row; 87 | -ms-flex-direction: row; 88 | flex-direction: row; 89 | -webkit-flex-wrap: nowrap; 90 | -ms-flex-wrap: nowrap; 91 | flex-wrap: nowrap; 92 | -webkit-box-pack: center; 93 | -moz-box-pack: center; 94 | -webkit-justify-content: center; 95 | -ms-flex-pack: center; 96 | justify-content: center; 97 | -webkit-align-content: stretch; 98 | -ms-flex-line-pack: stretch; 99 | align-content: stretch; 100 | -webkit-box-align: center; 101 | -moz-box-align: center; 102 | -webkit-align-items: center; 103 | -ms-flex-align: center; 104 | align-items: center; 105 | } 106 | } -------------------------------------------------------------------------------- /src/templates/gallery.html: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /src/templates/slider.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 19 | 22 |
23 | 24 |
25 |
26 |
27 |
28 |
29 |
30 | --------------------------------------------------------------------------------