Initial commit
@ -0,0 +1,3 @@
|
||||
node_modules
|
||||
example/website
|
||||
.DS_Store
|
@ -0,0 +1,80 @@
|
||||
# thumbsup
|
||||
|
||||
Static HTML galleries from a list of photos & videos.
|
||||
|
||||
- creates thumbnails for fast previews
|
||||
- uses relative paths so you can deploy the pages anywhere
|
||||
- supports custom CSS for styling
|
||||
- works great with Amazon S3 for static hosting
|
||||
|
||||
![screenshot](https://raw.github.com/rprieto/thumbsup/master/screenshot.jpg)
|
||||
|
||||
*Note: `thumbsup` keeps generated content separate from the original media. This means you're free to upload the media anywhere, and never have to worry about deleting the output folder*
|
||||
|
||||
## Requirements
|
||||
|
||||
- [Node.js](http://nodejs.org/): `brew install Node`
|
||||
- [GraphicsMagick](http://www.graphicsmagick.org/): `brew install graphicsmagick`
|
||||
|
||||
## Input
|
||||
|
||||
Any folder with photos and videos.
|
||||
`thumbsup` supports 1 level of subfolders:
|
||||
|
||||
```
|
||||
input
|
||||
|
|
||||
|__ paris
|
||||
| |__ img001.jpg
|
||||
| |__ img002.jpg
|
||||
|
|
||||
|__ sydney
|
||||
|__ vid001.mp4
|
||||
|__ img003.png
|
||||
```
|
||||
|
||||
## Generating the galleries
|
||||
|
||||
![npm install thumbsup](https://nodei.co/npm/thumbsup.png)
|
||||
|
||||
```
|
||||
thumbsup <args>
|
||||
```
|
||||
|
||||
The following args are required:
|
||||
|
||||
- `--input <path>` path to the folder with photos / videos
|
||||
- `--output <path>` target output folder
|
||||
- `--media-prefix <url>` prefix for the photos / videos URLS (can be relative or absolute)
|
||||
|
||||
And you can optionally specify:
|
||||
|
||||
- `--size <pixels>` size of the thumbnails
|
||||
- `--css <path>` use the given CSS file instead of the default style
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
thumbsup --input "/media/photos" --output "./website" --media-prefix "http://my.photo.bucket.s3.amazon.com" --css "custom.css" --size 200
|
||||
```
|
||||
|
||||
## Deployment
|
||||
|
||||
The simplest is to deploy the media and generated pages to S3 buckets on AWS using the [AWS CLI tools](http://aws.amazon.com/cli/).
|
||||
|
||||
- `aws s3 sync /media/photos s3://my.photo.bucket --delete`
|
||||
- `aws s3 sync /generated/website s3://my.website.bucket --delete`
|
||||
|
||||
## Password protection
|
||||
|
||||
Amazon S3 buckets do not offer any type of authentication. However you can choose to deploy to another web server that offers password protection, such as HTTP Basic Auth.
|
||||
|
||||
An alternative is to deploy the galleries to UUID-based locations, like Dropbox shared galleries.
|
||||
|
||||
## Dev notes
|
||||
|
||||
To create the sample gallery locally:
|
||||
|
||||
```
|
||||
npm run example
|
||||
```
|
@ -0,0 +1,19 @@
|
||||
var thumbsup = require('../src/index');
|
||||
|
||||
|
||||
thumbsup.build({
|
||||
|
||||
// the input folder
|
||||
// with all photos/videos
|
||||
input: 'example/media',
|
||||
|
||||
// the output folder
|
||||
// for the thumbnails and static pages
|
||||
output: 'example/website',
|
||||
|
||||
// relative path to the media
|
||||
// a local path for testing
|
||||
// but this could be the URL to an S3 bucket
|
||||
mediaPrefix: '../media'
|
||||
|
||||
});
|
After Width: | Height: | Size: 56 KiB |
After Width: | Height: | Size: 91 KiB |
After Width: | Height: | Size: 119 KiB |
After Width: | Height: | Size: 66 KiB |
After Width: | Height: | Size: 112 KiB |
After Width: | Height: | Size: 133 KiB |
@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "static-gallery",
|
||||
"version": "0.0.1",
|
||||
"description": "Photo / video gallery generator",
|
||||
"author": "Romain Prieto",
|
||||
"license": "BSD",
|
||||
"keywords": [
|
||||
"photo", "video", "gallery", "thumbnails", "portfolio",
|
||||
"website", "s3", "generator"
|
||||
],
|
||||
"scripts": {
|
||||
"example": "node example/build.js"
|
||||
},
|
||||
"bin": {
|
||||
"static-gallery": "./bin/generate.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"gulp": "~3.6.0",
|
||||
"handlebars": "~2.0.0-alpha.2",
|
||||
"gulp-newer": "~0.3.0",
|
||||
"concurrent-transform": "~1.0.0",
|
||||
"gulp-image-resize": "~0.5.0",
|
||||
"lodash": "~2.4.1",
|
||||
"wrench": "~1.5.8",
|
||||
"fs-extra": "~0.8.1"
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
@charset "UTF-8";
|
||||
/*
|
||||
* blueimp Gallery Indicator CSS 1.1.0
|
||||
* https://github.com/blueimp/Gallery
|
||||
*
|
||||
* Copyright 2013, Sebastian Tschan
|
||||
* https://blueimp.net
|
||||
*
|
||||
* Licensed under the MIT license:
|
||||
* http://www.opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
.blueimp-gallery > .indicator {
|
||||
position: absolute;
|
||||
top: auto;
|
||||
right: 15px;
|
||||
bottom: 15px;
|
||||
left: 15px;
|
||||
margin: 0 40px;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
text-align: center;
|
||||
line-height: 10px;
|
||||
display: none;
|
||||
}
|
||||
.blueimp-gallery > .indicator > li {
|
||||
display: inline-block;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
margin: 6px 3px 0 3px;
|
||||
-webkit-box-sizing: content-box;
|
||||
-moz-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
border: 1px solid transparent;
|
||||
background: #ccc;
|
||||
background: rgba(255, 255, 255, 0.25) center no-repeat;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 2px #000;
|
||||
opacity: 0.5;
|
||||
cursor: pointer;
|
||||
}
|
||||
.blueimp-gallery > .indicator > li:hover,
|
||||
.blueimp-gallery > .indicator > .active {
|
||||
background-color: #fff;
|
||||
border-color: #fff;
|
||||
opacity: 1;
|
||||
}
|
||||
.blueimp-gallery-controls > .indicator {
|
||||
display: block;
|
||||
/* Fix z-index issues (controls behind slide element) on Android: */
|
||||
-webkit-transform: translateZ(0);
|
||||
-moz-transform: translateZ(0);
|
||||
-ms-transform: translateZ(0);
|
||||
-o-transform: translateZ(0);
|
||||
transform: translateZ(0);
|
||||
}
|
||||
.blueimp-gallery-single > .indicator {
|
||||
display: none;
|
||||
}
|
||||
.blueimp-gallery > .indicator {
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
/* IE7 fixes */
|
||||
*+html .blueimp-gallery > .indicator > li {
|
||||
display: inline;
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
@charset "UTF-8";
|
||||
/*
|
||||
* blueimp Gallery Video Factory CSS 1.3.0
|
||||
* https://github.com/blueimp/Gallery
|
||||
*
|
||||
* Copyright 2013, Sebastian Tschan
|
||||
* https://blueimp.net
|
||||
*
|
||||
* Licensed under the MIT license:
|
||||
* http://www.opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
.blueimp-gallery > .slides > .slide > .video-content > img {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
margin: auto;
|
||||
width: auto;
|
||||
height: auto;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
/* Prevent artifacts in Mozilla Firefox: */
|
||||
-moz-backface-visibility: hidden;
|
||||
}
|
||||
.blueimp-gallery > .slides > .slide > .video-content > video {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.blueimp-gallery > .slides > .slide > .video-content > iframe {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
}
|
||||
.blueimp-gallery > .slides > .slide > .video-playing > iframe {
|
||||
top: 0;
|
||||
}
|
||||
.blueimp-gallery > .slides > .slide > .video-content > a {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
left: 0;
|
||||
margin: -64px auto 0;
|
||||
width: 128px;
|
||||
height: 128px;
|
||||
background: url(../img/video-play.png) center no-repeat;
|
||||
opacity: 0.8;
|
||||
cursor: pointer;
|
||||
}
|
||||
.blueimp-gallery > .slides > .slide > .video-content > a:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
.blueimp-gallery > .slides > .slide > .video-playing > a,
|
||||
.blueimp-gallery > .slides > .slide > .video-playing > img {
|
||||
display: none;
|
||||
}
|
||||
.blueimp-gallery > .slides > .slide > .video-content > video {
|
||||
display: none;
|
||||
}
|
||||
.blueimp-gallery > .slides > .slide > .video-playing > video {
|
||||
display: block;
|
||||
}
|
||||
.blueimp-gallery > .slides > .slide > .video-loading > a {
|
||||
background: url(../img/loading.gif) center no-repeat;
|
||||
background-size: 64px 64px;
|
||||
}
|
||||
|
||||
/* Replace PNGs with SVGs for capable browsers (excluding IE<9) */
|
||||
body:last-child .blueimp-gallery > .slides > .slide > .video-content:not(.video-loading) > a {
|
||||
background-image: url(../img/video-play.svg);
|
||||
}
|
||||
|
||||
/* IE7 fixes */
|
||||
*+html .blueimp-gallery > .slides > .slide > .video-content {
|
||||
height: 100%;
|
||||
}
|
||||
*+html .blueimp-gallery > .slides > .slide > .video-content > a {
|
||||
left: 50%;
|
||||
margin-left: -64px;
|
||||
}
|
After Width: | Height: | Size: 2.2 KiB |
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="64" height="64">
|
||||
<circle cx="32" cy="32" r="25" stroke="red" stroke-width="7" fill="black" fill-opacity="0.2"/>
|
||||
<rect x="28" y="7" width="8" height="50" fill="red" transform="rotate(45, 32, 32)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 306 B |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 606 B |
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="30" height="15">
|
||||
<polygon points="2,1 2,14 13,7" stroke="black" stroke-width="1" fill="white"/>
|
||||
<rect x="17" y="2" width="4" height="11" stroke="black" stroke-width="1" fill="white"/>
|
||||
<rect x="24" y="2" width="4" height="11" stroke="black" stroke-width="1" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 382 B |
After Width: | Height: | Size: 2.7 KiB |
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="64" height="64">
|
||||
<circle cx="32" cy="32" r="25" stroke="white" stroke-width="7" fill="black" fill-opacity="0.2"/>
|
||||
<polygon points="26,22 26,42 43,32" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 274 B |
@ -0,0 +1,153 @@
|
||||
/*
|
||||
* blueimp Gallery Indicator JS 1.1.0
|
||||
* https://github.com/blueimp/Gallery
|
||||
*
|
||||
* Copyright 2013, Sebastian Tschan
|
||||
* https://blueimp.net
|
||||
*
|
||||
* Licensed under the MIT license:
|
||||
* http://www.opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
/* global define, window, document */
|
||||
|
||||
(function (factory) {
|
||||
'use strict';
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// Register as an anonymous AMD module:
|
||||
define([
|
||||
'./blueimp-helper',
|
||||
'./blueimp-gallery'
|
||||
], factory);
|
||||
} else {
|
||||
// Browser globals:
|
||||
factory(
|
||||
window.blueimp.helper || window.jQuery,
|
||||
window.blueimp.Gallery
|
||||
);
|
||||
}
|
||||
}(function ($, Gallery) {
|
||||
'use strict';
|
||||
|
||||
$.extend(Gallery.prototype.options, {
|
||||
// The tag name, Id, element or querySelector of the indicator container:
|
||||
indicatorContainer: 'ol',
|
||||
// The class for the active indicator:
|
||||
activeIndicatorClass: 'active',
|
||||
// The list object property (or data attribute) with the thumbnail URL,
|
||||
// used as alternative to a thumbnail child element:
|
||||
thumbnailProperty: 'thumbnail',
|
||||
// Defines if the gallery indicators should display a thumbnail:
|
||||
thumbnailIndicators: true
|
||||
});
|
||||
|
||||
var initSlides = Gallery.prototype.initSlides,
|
||||
addSlide = Gallery.prototype.addSlide,
|
||||
resetSlides = Gallery.prototype.resetSlides,
|
||||
handleClick = Gallery.prototype.handleClick,
|
||||
handleSlide = Gallery.prototype.handleSlide,
|
||||
handleClose = Gallery.prototype.handleClose;
|
||||
|
||||
$.extend(Gallery.prototype, {
|
||||
|
||||
createIndicator: function (obj) {
|
||||
var indicator = this.indicatorPrototype.cloneNode(false),
|
||||
title = this.getItemProperty(obj, this.options.titleProperty),
|
||||
thumbnailProperty = this.options.thumbnailProperty,
|
||||
thumbnailUrl,
|
||||
thumbnail;
|
||||
if (this.options.thumbnailIndicators) {
|
||||
thumbnail = obj.getElementsByTagName && $(obj).find('img')[0];
|
||||
if (thumbnail) {
|
||||
thumbnailUrl = thumbnail.src;
|
||||
} else if (thumbnailProperty) {
|
||||
thumbnailUrl = this.getItemProperty(obj, thumbnailProperty);
|
||||
}
|
||||
if (thumbnailUrl) {
|
||||
indicator.style.backgroundImage = 'url("' + thumbnailUrl + '")';
|
||||
}
|
||||
}
|
||||
if (title) {
|
||||
indicator.title = title;
|
||||
}
|
||||
return indicator;
|
||||
},
|
||||
|
||||
addIndicator: function (index) {
|
||||
if (this.indicatorContainer.length) {
|
||||
var indicator = this.createIndicator(this.list[index]);
|
||||
indicator.setAttribute('data-index', index);
|
||||
this.indicatorContainer[0].appendChild(indicator);
|
||||
this.indicators.push(indicator);
|
||||
}
|
||||
},
|
||||
|
||||
setActiveIndicator: function (index) {
|
||||
if (this.indicators) {
|
||||
if (this.activeIndicator) {
|
||||
this.activeIndicator
|
||||
.removeClass(this.options.activeIndicatorClass);
|
||||
}
|
||||
this.activeIndicator = $(this.indicators[index]);
|
||||
this.activeIndicator
|
||||
.addClass(this.options.activeIndicatorClass);
|
||||
}
|
||||
},
|
||||
|
||||
initSlides: function (reload) {
|
||||
if (!reload) {
|
||||
this.indicatorContainer = this.container.find(
|
||||
this.options.indicatorContainer
|
||||
);
|
||||
if (this.indicatorContainer.length) {
|
||||
this.indicatorPrototype = document.createElement('li');
|
||||
this.indicators = this.indicatorContainer[0].children;
|
||||
}
|
||||
}
|
||||
initSlides.call(this, reload);
|
||||
},
|
||||
|
||||
addSlide: function (index) {
|
||||
addSlide.call(this, index);
|
||||
this.addIndicator(index);
|
||||
},
|
||||
|
||||
resetSlides: function () {
|
||||
resetSlides.call(this);
|
||||
this.indicatorContainer.empty();
|
||||
this.indicators = [];
|
||||
},
|
||||
|
||||
handleClick: function (event) {
|
||||
var target = event.target || event.srcElement,
|
||||
parent = target.parentNode;
|
||||
if (parent === this.indicatorContainer[0]) {
|
||||
// Click on indicator element
|
||||
this.preventDefault(event);
|
||||
this.slide(this.getNodeIndex(target));
|
||||
} else if (parent.parentNode === this.indicatorContainer[0]) {
|
||||
// Click on indicator child element
|
||||
this.preventDefault(event);
|
||||
this.slide(this.getNodeIndex(parent));
|
||||
} else {
|
||||
return handleClick.call(this, event);
|
||||
}
|
||||
},
|
||||
|
||||
handleSlide: function (index) {
|
||||
handleSlide.call(this, index);
|
||||
this.setActiveIndicator(index);
|
||||
},
|
||||
|
||||
handleClose: function () {
|
||||
if (this.activeIndicator) {
|
||||
this.activeIndicator
|
||||
.removeClass(this.options.activeIndicatorClass);
|
||||
}
|
||||
handleClose.call(this);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return Gallery;
|
||||
}));
|
@ -0,0 +1,171 @@
|
||||
/*
|
||||
* blueimp Gallery Video Factory JS 1.1.1
|
||||
* https://github.com/blueimp/Gallery
|
||||
*
|
||||
* Copyright 2013, Sebastian Tschan
|
||||
* https://blueimp.net
|
||||
*
|
||||
* Licensed under the MIT license:
|
||||
* http://www.opensource.org/licenses/MIT
|
||||
*/
|
||||
|
||||
/* global define, window, document */
|
||||
|
||||
(function (factory) {
|
||||
'use strict';
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// Register as an anonymous AMD module:
|
||||
define([
|
||||
'./blueimp-helper',
|
||||
'./blueimp-gallery'
|
||||
], factory);
|
||||
} else {
|
||||
// Browser globals:
|
||||
factory(
|
||||
window.blueimp.helper || window.jQuery,
|
||||
window.blueimp.Gallery
|
||||
);
|
||||
}
|
||||
}(function ($, Gallery) {
|
||||
'use strict';
|
||||
|
||||
$.extend(Gallery.prototype.options, {
|
||||
// The class for video content elements:
|
||||
videoContentClass: 'video-content',
|
||||
// The class for video when it is loading:
|
||||
videoLoadingClass: 'video-loading',
|
||||
// The class for video when it is playing:
|
||||
videoPlayingClass: 'video-playing',
|
||||
// The list object property (or data attribute) for the video poster URL:
|
||||
videoPosterProperty: 'poster',
|
||||
// The list object property (or data attribute) for the video sources array:
|
||||
videoSourcesProperty: 'sources'
|
||||
});
|
||||
|
||||
var handleSlide = Gallery.prototype.handleSlide;
|
||||
|
||||
$.extend(Gallery.prototype, {
|
||||
|
||||
handleSlide: function (index) {
|
||||
handleSlide.call(this, index);
|
||||
if (this.playingVideo) {
|
||||
this.playingVideo.pause();
|
||||
}
|
||||
},
|
||||
|
||||
videoFactory: function (obj, callback, videoInterface) {
|
||||
var that = this,
|
||||
options = this.options,
|
||||
videoContainerNode = this.elementPrototype.cloneNode(false),
|
||||
videoContainer = $(videoContainerNode),
|
||||
errorArgs = [{
|
||||
type: 'error',
|
||||
target: videoContainerNode
|
||||
}],
|
||||
video = videoInterface || document.createElement('video'),
|
||||
url = this.getItemProperty(obj, options.urlProperty),
|
||||
type = this.getItemProperty(obj, options.typeProperty),
|
||||
title = this.getItemProperty(obj, options.titleProperty),
|
||||
posterUrl = this.getItemProperty(obj, options.videoPosterProperty),
|
||||
posterImage,
|
||||
sources = this.getItemProperty(
|
||||
obj,
|
||||
options.videoSourcesProperty
|
||||
),
|
||||
source,
|
||||
playMediaControl,
|
||||
isLoading,
|
||||
hasControls;
|
||||
videoContainer.addClass(options.videoContentClass);
|
||||
if (title) {
|
||||
videoContainerNode.title = title;
|
||||
}
|
||||
if (video.canPlayType) {
|
||||
if (url && type && video.canPlayType(type)) {
|
||||
video.src = url;
|
||||
} else {
|
||||
while (sources && sources.length) {
|
||||
source = sources.shift();
|
||||
url = this.getItemProperty(source, options.urlProperty);
|
||||
type = this.getItemProperty(source, options.typeProperty);
|
||||
if (url && type && video.canPlayType(type)) {
|
||||
video.src = url;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (posterUrl) {
|
||||
video.poster = posterUrl;
|
||||
posterImage = this.imagePrototype.cloneNode(false);
|
||||
$(posterImage).addClass(options.toggleClass);
|
||||
posterImage.src = posterUrl;
|
||||
posterImage.draggable = false;
|
||||
videoContainerNode.appendChild(posterImage);
|
||||
}
|
||||
playMediaControl = document.createElement('a');
|
||||
playMediaControl.setAttribute('target', '_blank');
|
||||
if (!videoInterface) {
|
||||
playMediaControl.setAttribute('download', title);
|
||||
}
|
||||
playMediaControl.href = url;
|
||||
if (video.src) {
|
||||
video.controls = true;
|
||||
(videoInterface || $(video))
|
||||
.on('error', function () {
|
||||
that.setTimeout(callback, errorArgs);
|
||||
})
|
||||
.on('pause', function () {
|
||||
isLoading = false;
|
||||
videoContainer
|
||||
.removeClass(that.options.videoLoadingClass)
|
||||
.removeClass(that.options.videoPlayingClass);
|
||||
if (hasControls) {
|
||||
that.container.addClass(that.options.controlsClass);
|
||||
}
|
||||
delete that.playingVideo;
|
||||
if (that.interval) {
|
||||
that.play();
|
||||
}
|
||||
})
|
||||
.on('playing', function () {
|
||||
isLoading = false;
|
||||
videoContainer
|
||||
.removeClass(that.options.videoLoadingClass)
|
||||
.addClass(that.options.videoPlayingClass);
|
||||
if (that.container.hasClass(that.options.controlsClass)) {
|
||||
hasControls = true;
|
||||
that.container.removeClass(that.options.controlsClass);
|
||||
} else {
|
||||
hasControls = false;
|
||||
}
|
||||
})
|
||||
.on('play', function () {
|
||||
window.clearTimeout(that.timeout);
|
||||
isLoading = true;
|
||||
videoContainer.addClass(that.options.videoLoadingClass);
|
||||
that.playingVideo = video;
|
||||
});
|
||||
$(playMediaControl).on('click', function (event) {
|
||||
that.preventDefault(event);
|
||||
if (isLoading) {
|
||||
video.pause();
|
||||
} else {
|
||||
video.play();
|
||||
}
|
||||
});
|
||||
videoContainerNode.appendChild(
|
||||
(videoInterface && videoInterface.element) || video
|
||||
);
|
||||
}
|
||||
videoContainerNode.appendChild(playMediaControl);
|
||||
this.setTimeout(callback, [{
|
||||
type: 'load',
|
||||
target: videoContainerNode
|
||||
}]);
|
||||
return videoContainerNode;
|
||||
}
|
||||
});
|
||||
|
||||
return Gallery;
|
||||
}));
|
@ -0,0 +1,48 @@
|
||||
/* http://meyerweb.com/eric/tools/css/reset/
|
||||
v2.0 | 20110126
|
||||
License: none (public domain)
|
||||
*/
|
||||
|
||||
html, body, div, span, applet, object, iframe,
|
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||
a, abbr, acronym, address, big, cite, code,
|
||||
del, dfn, em, img, ins, kbd, q, s, samp,
|
||||
small, strike, strong, sub, sup, tt, var,
|
||||
b, u, i, center,
|
||||
dl, dt, dd, ol, ul, li,
|
||||
fieldset, form, label, legend,
|
||||
table, caption, tbody, tfoot, thead, tr, th, td,
|
||||
article, aside, canvas, details, embed,
|
||||
figure, figcaption, footer, header, hgroup,
|
||||
menu, nav, output, ruby, section, summary,
|
||||
time, mark, audio, video {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
/* HTML5 display-role reset for older browsers */
|
||||
article, aside, details, figcaption, figure,
|
||||
footer, header, hgroup, menu, nav, section {
|
||||
display: block;
|
||||
}
|
||||
body {
|
||||
line-height: 1;
|
||||
}
|
||||
ol, ul {
|
||||
list-style: none;
|
||||
}
|
||||
blockquote, q {
|
||||
quotes: none;
|
||||
}
|
||||
blockquote:before, blockquote:after,
|
||||
q:before, q:after {
|
||||
content: '';
|
||||
content: none;
|
||||
}
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
@ -0,0 +1,87 @@
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
}
|
||||
|
||||
html, body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
border-top: 5px solid #33609c;
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: lighter;
|
||||
margin: 0 1em;
|
||||
padding: 0 1em;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
header {
|
||||
font-size: 2em;
|
||||
padding: 1em 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #33609c;
|
||||
display: inline-block;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #666;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
nav {
|
||||
color: #33609c;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
nav li {
|
||||
border-radius: 2px;
|
||||
display: inline-block;
|
||||
padding: 0.4em 1em;
|
||||
}
|
||||
|
||||
nav a {
|
||||
color: #33609c;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
nav li.active {
|
||||
background-color: #33609c;
|
||||
}
|
||||
|
||||
nav li.active a {
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
nav li:not(.active):hover {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.gallery li {
|
||||
display: inline-block;
|
||||
margin-right: 0.5em;
|
||||
margin-bottom: 0.5em;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.gallery img {
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.gallery .play-overlay {
|
||||
color: #fff;
|
||||
font-size: 2em;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
line-height: 100%;
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
}
|
After Width: | Height: | Size: 48 KiB |
@ -0,0 +1,41 @@
|
||||
var _ = require('lodash');
|
||||
var path = require('path');
|
||||
var wrench = require('wrench');
|
||||
|
||||
exports.fromDisk = function(mediaPath, mediaPrefix) {
|
||||
|
||||
function fileInfo(file) {
|
||||
return {
|
||||
// read file date
|
||||
date: 2340930845,
|
||||
path: file,
|
||||
url: mediaPrefix + '/' + file,
|
||||
thumbnail: 'thumbs/' + file,
|
||||
video: file.match(/\.mp4$/)
|
||||
}
|
||||
}
|
||||
|
||||
function byFolder(file) {
|
||||
return path.dirname(file.path);
|
||||
}
|
||||
|
||||
function byExtension(file) {
|
||||
return file.match(/\.(jpg|jpeg|png|mp4)$/)
|
||||
}
|
||||
|
||||
function folderInfo(files, name) {
|
||||
return {
|
||||
name: name,
|
||||
media: files,
|
||||
url: name + '.html'
|
||||
};
|
||||
}
|
||||
|
||||
var files = wrench.readdirSyncRecursive(mediaPath);
|
||||
return _(files).filter(byExtension)
|
||||
.map(fileInfo)
|
||||
.groupBy(byFolder)
|
||||
.map(folderInfo)
|
||||
.value();
|
||||
|
||||
};
|
@ -0,0 +1,53 @@
|
||||
var _ = require('lodash');
|
||||
var fs = require('fs-extra');
|
||||
var os = require('os');
|
||||
var path = require('path');
|
||||
var gulp = require('gulp');
|
||||
var newer = require('gulp-newer');
|
||||
var imageResize = require('gulp-image-resize');
|
||||
var parallel = require('concurrent-transform');
|
||||
|
||||
var galleries = require('./galleries');
|
||||
var render = require('./render');
|
||||
|
||||
exports.build = function(opts) {
|
||||
|
||||
fs.mkdirp(opts.output);
|
||||
var list = galleries.fromDisk(opts.input, opts.mediaPrefix);
|
||||
console.log(require('util').inspect(list, {depth:6, color:true}))
|
||||
|
||||
gulp.task('thumbs', function () {
|
||||
var dest = opts.output + '/thumbs';
|
||||
gulp
|
||||
.src(opts.input + '/**/*.{jpg,png}')
|
||||
.pipe(newer(dest))
|
||||
.pipe(parallel(imageResize({width: 100, height: 100, crop: true}), os.cpus().length))
|
||||
.pipe(gulp.dest(dest));
|
||||
});
|
||||
|
||||
gulp.task('index', function() {
|
||||
// render('index.hbs', {salt: SALT}, 'index.html');
|
||||
});
|
||||
|
||||
gulp.task('public', function() {
|
||||
var dest = opts.output + '/public';
|
||||
gulp
|
||||
.src('public/**')
|
||||
.pipe(newer(dest))
|
||||
.pipe(gulp.dest(dest));
|
||||
});
|
||||
|
||||
gulp.task('galleries', function() {
|
||||
// var dest = opts.output + '/galleries';
|
||||
// wrench.rmdirSyncRecursive(dest, true);
|
||||
// fs.mkdirp(dest);
|
||||
list.forEach(function(folder) {
|
||||
var rendered = render.gallery(list, folder);
|
||||
var outputPath = path.join(opts.output, folder.url);
|
||||
fs.writeFileSync(outputPath, rendered);
|
||||
});
|
||||
});
|
||||
|
||||
gulp.run('thumbs', 'public', 'index', 'galleries');
|
||||
|
||||
};
|
@ -0,0 +1,32 @@
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var handlebars = require('handlebars');
|
||||
|
||||
function compileTemplate(hbsFile) {
|
||||
var src = fs.readFileSync(path.join('templates', hbsFile));
|
||||
return handlebars.compile(src.toString());
|
||||
}
|
||||
|
||||
var indexTemplate = compileTemplate('index.hbs');
|
||||
var galleryTemplate = compileTemplate('gallery.hbs');
|
||||
|
||||
|
||||
exports.index = function(list) {
|
||||
};
|
||||
|
||||
exports.gallery = function(list, active) {
|
||||
|
||||
var links = list.map(function(item) {
|
||||
return {
|
||||
name: item.name,
|
||||
url: item.name + '.html',
|
||||
active: (item === active)
|
||||
};
|
||||
});
|
||||
|
||||
return galleryTemplate({
|
||||
links: links,
|
||||
gallery: active
|
||||
});
|
||||
|
||||
};
|
@ -0,0 +1,30 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>{{folder.name}}</title>
|
||||
<link rel="stylesheet" href="../public/blueimp/css/reset.css" />
|
||||
<link rel="stylesheet" href="../public/{{css}}.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<h1>{{title}}</h1>
|
||||
<h2>{{subtitle}}</h2>
|
||||
</header>
|
||||
|
||||
<nav>
|
||||
<ul>
|
||||
{{#each links}}
|
||||
<li>
|
||||
<a href="{{url}}">{{name}}</a>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|