├── .gitignore
├── README.md
├── debug
└── index.html
├── example
└── src
│ ├── actions
│ ├── api
│ │ ├── flickr.js
│ │ └── yandex.js
│ ├── constants.js
│ └── photos.js
│ ├── app.css
│ ├── app.js
│ ├── components
│ ├── Container.css
│ ├── Container.js
│ ├── FeedView.css
│ └── FeedView.js
│ ├── constants
│ └── flickr.js
│ ├── dispatcher
│ └── index.js
│ ├── stores
│ ├── PhotoStore.js
│ ├── SimpleStore.js
│ └── index.js
│ └── utils
│ └── url.js
├── index.html
├── index.js
├── library
├── photogrid.js
└── style.css
├── package.json
├── src
└── components
│ ├── DefaultInfoElement.css
│ ├── DefaultInfoElement.js
│ ├── PhotoGrid.css
│ ├── PhotoGrid.js
│ ├── RadioButtonGroup.css
│ └── RadioButtonGroup.js
├── static
├── app.min.js
├── styles.css
└── vendors.min.js
├── webpack.config.debug.js
├── webpack.config.library.js
└── webpack.config.production.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # TypeScript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # parcel-bundler cache (https://parceljs.org/)
61 | .cache
62 |
63 | # next.js build output
64 | .next
65 |
66 | # nuxt.js build output
67 | .nuxt
68 |
69 | # vuepress build output
70 | .vuepress/dist
71 |
72 | # Serverless directories
73 | .serverless
74 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-photo-feed
2 | Photo gallery, example with public photos feed from Flickr, Yandex
3 |
4 | Simple example of photo gallery with responsive image grid, columns customizing, one-column view with description, fullscreen preview with
5 | one click. Pure CSS for that.
6 |
7 |
8 | ## Installation
9 | You can use PhotoGrid in your app, just install it from npm
10 |
11 | `npm install react-photo-feed`
12 |
13 | ## Usage
14 |
15 | ````js
16 | import React from "react";
17 | import ReactDOM from "react-dom";
18 | import PhotoGrid from "react-photo-feed";
19 | import "react-photo-feed/library/style.css";
20 |
21 | const demoPhotos = [
22 | {
23 | id : 1, src : "https://farm5.staticflickr.com/4077/34824083444_f5f050e31c_n.jpg",
24 | bigSrc : "https://farm5.staticflickr.com/4077/34824083444_f5f050e31c_b.jpg"
25 | },
26 | {
27 | id : 2, src : "https://farm5.staticflickr.com/4240/35527849422_25a0a67df6_n.jpg",
28 | bigSrc : "https://farm5.staticflickr.com/4240/35527849422_25a0a67df6_b.jpg"
29 | },
30 | {
31 | id : 3, src : "https://farm5.staticflickr.com/4077/34824083444_f5f050e31c_n.jpg",
32 | bigSrc : "https://farm5.staticflickr.com/4077/34824083444_f5f050e31c_b.jpg"
33 | },
34 | {
35 | id : 4, src : "https://farm5.staticflickr.com/4240/35527849422_25a0a67df6_n.jpg",
36 | bigSrc : "https://farm5.staticflickr.com/4240/35527849422_25a0a67df6_b.jpg"
37 | },
38 | {
39 | id : 5, src : "https://farm5.staticflickr.com/4077/34824083444_f5f050e31c_n.jpg",
40 | bigSrc : "https://farm5.staticflickr.com/4077/34824083444_f5f050e31c_b.jpg"
41 | },
42 | {
43 | id : 6, src : "https://farm5.staticflickr.com/4240/35527849422_25a0a67df6_n.jpg",
44 | bigSrc : "https://farm5.staticflickr.com/4240/35527849422_25a0a67df6_b.jpg"
45 | },
46 | {
47 | id : 7, src : "https://farm5.staticflickr.com/4077/34824083444_f5f050e31c_n.jpg",
48 | bigSrc : "https://farm5.staticflickr.com/4077/34824083444_f5f050e31c_b.jpg"
49 | }
50 | ];
51 | ReactDOM.render(
52 |
,
55 | document.getElementById('root')
56 | );
57 | `````
58 |
59 | ### Prop Types
60 | | Property | Type | Required? | Description |
61 | |:---|:---|:---:|:---|
62 | | photos | Array | ✓ | Array of objects, like `[{id: 1, src: 'http://url_to_small_image', bigSrc: 'http://url_to_big_image', title: 'Caption of photo'}]` |
63 | | columns | Number | ✓ | Grid columns, like `columns={1}`, also can be 2,3,5 |
64 | | InformationElement | Function | | Component used for one-column view |
65 |
66 |
67 |
68 | Also you can see toggle|radio button group.
69 | ```javascript
70 |
71 | ```
72 | ## [Demo](http://lkazberova.github.io/react-photo-feed/)
73 |
74 | 
75 |
76 | Some ideas were inspired by [react-rpg](https://github.com/James-Oldfield/react-rpg)
77 |
--------------------------------------------------------------------------------
/debug/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Flickr/Yandex Public Feed
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/example/src/actions/api/flickr.js:
--------------------------------------------------------------------------------
1 | import $ from 'jquery';
2 | import { LOAD_PUBLIC_FEED, _SUCCESS, _FAIL, _START } from './../constants';
3 | import {FLICKR_PUBLIC_FEED_URL, SUFFIX_SMALL_240, SUFFIX_LARGE_1024,SUFFIX_SMALL_320} from '../../constants/flickr';
4 | import {getLastPartOfUrl} from '../../utils/url';
5 | import AppDispatcher from '../../dispatcher';
6 |
7 |
8 | export function loadPublicFeed() {
9 |
10 | AppDispatcher.dispatch({
11 | type : LOAD_PUBLIC_FEED + _START
12 | });
13 |
14 | $.getJSON(FLICKR_PUBLIC_FEED_URL)
15 | .done(response => {
16 | let photos = response.items.map(item => ({
17 | title : item.title,
18 | id : getLastPartOfUrl(item.link),
19 | link : item.link,
20 | src : item.media.m.replace(SUFFIX_SMALL_240, SUFFIX_SMALL_320),
21 | bigSrc : item.media.m.replace(SUFFIX_SMALL_240, SUFFIX_LARGE_1024),
22 | author : item.author,
23 | created : Date.parse(item.published),
24 | tags : item.tags
25 | }));
26 |
27 | AppDispatcher.dispatch({
28 | type : LOAD_PUBLIC_FEED + _SUCCESS,
29 | response : photos
30 | });
31 | })
32 | .fail((error) => {
33 | AppDispatcher.dispatch({
34 | type : LOAD_PUBLIC_FEED + _FAIL,
35 | error
36 | })
37 | });
38 |
39 | }
--------------------------------------------------------------------------------
/example/src/actions/api/yandex.js:
--------------------------------------------------------------------------------
1 | import $ from 'jquery';
2 | import { LOAD_PUBLIC_FEED, _SUCCESS, _FAIL, _START } from './../constants';
3 | import AppDispatcher from '../../dispatcher';
4 |
5 |
6 | export function loadPublicFeed() {
7 | AppDispatcher.dispatch({
8 | type : LOAD_PUBLIC_FEED + _START
9 | });
10 |
11 | $.getJSON('http://api-fotki.yandex.ru/api/podhistory/poddate/?format=json&callback=?')
12 | .done(response => {
13 | console.log(response.entries);
14 |
15 | let photos = response.entries.map(item => ({
16 | title : item.title,
17 | id : item.id,
18 | link : item.links.alternate,
19 | src : item.img.L.href,
20 | bigSrc : item.img.XXL.href,
21 | author : item.author,
22 | created : Date.parse(item.published),
23 | tags : Object.keys(item.tags).join(',')
24 | }));
25 | console.log(photos);
26 |
27 | AppDispatcher.dispatch({
28 | type : LOAD_PUBLIC_FEED + _SUCCESS,
29 | response : photos
30 | });
31 | })
32 | .fail((error) => {
33 | AppDispatcher.dispatch({
34 | type : LOAD_PUBLIC_FEED + _FAIL,
35 | error
36 | })
37 | });
38 | }
39 |
--------------------------------------------------------------------------------
/example/src/actions/constants.js:
--------------------------------------------------------------------------------
1 | export const LOAD_PUBLIC_FEED = 'LOAD_PUBLIC_FEED';
2 |
3 | export const _START = '_START';
4 | export const _SUCCESS = '_SUCCESS';
5 | export const _FAIL = '_FAIL';
--------------------------------------------------------------------------------
/example/src/actions/photos.js:
--------------------------------------------------------------------------------
1 | import AppDispatcher from '../dispatcher'
2 | import { LOAD_PUBLIC_FEED } from './constants'
3 | import { loadPublicFeed as loadFlickrPF} from './api/flickr';
4 | import { loadPublicFeed as loadYandexPF} from './api/yandex';
5 |
6 | export const loadFlickrPublicFeed = loadFlickrPF;
7 | export const loadYandexPublicFeed = loadYandexPF;
8 |
--------------------------------------------------------------------------------
/example/src/app.css:
--------------------------------------------------------------------------------
1 | @import url(https://fonts.googleapis.com/css?family=Roboto:400,300,500);
2 | html {
3 | font-family: 'Roboto', sans-serif;
4 | -webkit-font-smoothing: antialiased;
5 | }
6 | body {
7 | width: 80%;
8 | margin: 30px auto;
9 | }
--------------------------------------------------------------------------------
/example/src/app.js:
--------------------------------------------------------------------------------
1 | import ReactDOM from 'react-dom';
2 | import React from 'react';
3 | import Container from './components/Container';
4 | import './app.css';
5 |
6 | ReactDOM.render(, document.getElementById('container'));
--------------------------------------------------------------------------------
/example/src/components/Container.css:
--------------------------------------------------------------------------------
1 | h1 small {
2 | font-size:65%;
3 | color:#777;
4 | font-weight: 400;
5 | }
--------------------------------------------------------------------------------
/example/src/components/Container.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {loadFlickrPublicFeed, loadYandexPublicFeed} from '../actions/photos';
3 | import {photoStore} from '../stores';
4 | import FeedView from './FeedView';
5 | import styles from './Container.css';
6 |
7 |
8 | class Container extends React.Component {
9 | constructor() {
10 | super();
11 |
12 | this.state = {
13 | photos : photoStore.getAll()
14 | };
15 | }
16 |
17 | componentDidMount() {
18 | photoStore.addChangeListener(this.change);
19 |
20 | loadYandexPublicFeed();
21 | loadFlickrPublicFeed();
22 | }
23 |
24 | componentWillUnmount() {
25 | photoStore.removeChangeListener(this.change);
26 | }
27 |
28 | change = () => {
29 | this.setState({
30 | photos : photoStore.getAll()
31 | })
32 | };
33 |
34 |
35 | render() {
36 | const {photos} = this.state;
37 | return (
38 |
39 |
PUBLIC FEED from Flickr, Yandex
40 |
41 |
42 | );
43 | }
44 | }
45 |
46 | export default Container;
--------------------------------------------------------------------------------
/example/src/components/FeedView.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lkazberova/react-photo-feed/6119b53182717d964f758972cdcb76df4fb75c37/example/src/components/FeedView.css
--------------------------------------------------------------------------------
/example/src/components/FeedView.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 | import PhotoGrid from './../../../src/components/PhotoGrid';
4 | import RadioButtonGroup from './../../../src/components/RadioButtonGroup';
5 | import styles from './FeedView.css';
6 |
7 | const columnsData = [
8 | {value : 1, label : 'x1'},
9 | {value : 2, label : 'x2'},
10 | {value : 3, label : 'x3'},
11 | {value : 5, label : 'x5'}];
12 | const sortParams = [
13 | {label : 'oldest', value : 'created-asc'},
14 | {label : 'newest', value : 'created-desc'},
15 | {label : 'title asc', value : 'title-asc'},
16 | {label : 'title desc', value : 'title-desc'}];
17 |
18 |
19 | class Feed extends React.Component {
20 | static propTypes = {
21 | photos : PropTypes.array
22 | };
23 | constructor () {
24 | super ();
25 | this.state = {
26 | columns : 2,
27 | order : null
28 | }
29 | }
30 |
31 | render() {
32 | const {photos} = this.props;
33 | const { columns, order } = this.state;
34 | const sortedPhotos = order ? this.getSorted() : photos;
35 |
36 | return (
37 |
45 | );
46 | }
47 |
48 | getSorted() {
49 | const {photos} = this.props;
50 |
51 | const {order} = this.state;
52 | const [field, type] = order.split('-');
53 | const sign = type == 'asc' ? 1 : -1;
54 | return photos.slice().sort((a, b) => (+(a[field] > b[field]) || +(a[field] === b[field]) - 1) * sign);
55 | }
56 |
57 | onSortClick(item) {
58 | this.setState({
59 | order : item == this.state.order ? null : item
60 | });
61 | }
62 |
63 | onClick(value) {
64 | this.setState({
65 | columns : value
66 | });
67 |
68 | }
69 | }
70 |
71 | export default Feed;
72 |
--------------------------------------------------------------------------------
/example/src/constants/flickr.js:
--------------------------------------------------------------------------------
1 | export const FLICKR_PUBLIC_FEED_URL = 'https://api.flickr.com/services/feeds/photos_public.gne?format=json&jsoncallback=?';
2 |
3 | // see https://www.flickr.com/services/api/misc.urls.html
4 | export const SUFFIX_MEDIUM_640x640 = "_z";
5 | export const SUFFIX_SMALL_240 = "_m";
6 | export const SUFFIX_SMALL_320 = "_n";
7 | export const SUFFIX_MEDIUM_500 = '';
8 | export const SUFFIX_LARGE_1024 = '_b';
9 |
--------------------------------------------------------------------------------
/example/src/dispatcher/index.js:
--------------------------------------------------------------------------------
1 | import { Dispatcher } from 'flux';
2 |
3 | const AppDispatcher = new Dispatcher;
4 |
5 | AppDispatcher.register(console.log.bind(console));
6 |
7 | export default AppDispatcher;
--------------------------------------------------------------------------------
/example/src/stores/PhotoStore.js:
--------------------------------------------------------------------------------
1 | import SimpleStore from './SimpleStore';
2 | import { LOAD_PUBLIC_FEED, _SUCCESS, _FAIL, _START } from '../actions/constants';
3 | import AppDispatcher from '../dispatcher';
4 |
5 | class PhotoStore extends SimpleStore {
6 | constructor(...args) {
7 | super(...args)
8 | this.dispatchToken = AppDispatcher.register((action) => {
9 | const { type, data, response } = action;
10 |
11 | switch (type) {
12 | case LOAD_PUBLIC_FEED + _SUCCESS:
13 | response.forEach(this.add);
14 | this.emitChange();
15 |
16 | break;
17 | default: return;
18 | }
19 | })
20 | }
21 | }
22 |
23 | export default PhotoStore;
--------------------------------------------------------------------------------
/example/src/stores/SimpleStore.js:
--------------------------------------------------------------------------------
1 | import { EventEmitter } from 'events';
2 | const CHANGE_EVENT = 'CHANGE_EVENT';
3 |
4 | class SimpleStore extends EventEmitter {
5 | constructor() {
6 | super();
7 | this.__items = [];
8 | }
9 |
10 | emitChange() {
11 | this.emit(CHANGE_EVENT);
12 | }
13 |
14 | addChangeListener(callback) {
15 | this.on(CHANGE_EVENT, callback);
16 | }
17 |
18 | removeChangeListener(callback) {
19 | this.removeListener(CHANGE_EVENT, callback);
20 | }
21 |
22 | getAll() {
23 | return this.__items.slice();
24 | }
25 |
26 | getById = (id) => {
27 | let result = this.__items.filter((item) => item.id == id);
28 | return result && result.length > 0 ? result[0] : null;
29 | };
30 |
31 | add = (item) => {
32 | this.delete(item.id);
33 | this.__items.push(item);
34 | };
35 |
36 | delete = (id) => {
37 | this.__items = this.__items.filter(item => item.id != id);
38 | };
39 |
40 |
41 | }
42 |
43 | export default SimpleStore;
--------------------------------------------------------------------------------
/example/src/stores/index.js:
--------------------------------------------------------------------------------
1 | import PhotoStore from './PhotoStore';
2 |
3 | export const photoStore = new PhotoStore();
4 |
--------------------------------------------------------------------------------
/example/src/utils/url.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export function getLastPartOfUrl (url) {
4 | let matches = url.match(/\/([^/]*)([/]*)$/);
5 | return matches && matches.length > 1 ? matches [1] : null;
6 | }
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Flickr/Yandex Public Feed
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 |
4 | module.exports = require('./src/components/PhotoGrid.js');
--------------------------------------------------------------------------------
/library/photogrid.js:
--------------------------------------------------------------------------------
1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("react")):"function"==typeof define&&define.amd?define(["react"],t):"object"==typeof exports?exports.PhotoFeed=t(require("react")):e.PhotoFeed=t(e.React)}(this,function(e){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var n={};return t.m=e,t.c=n,t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=6)}([function(e,t){function n(){throw new Error("setTimeout has not been defined")}function r(){throw new Error("clearTimeout has not been defined")}function o(e){if(f===setTimeout)return setTimeout(e,0);if((f===n||!f)&&setTimeout)return f=setTimeout,setTimeout(e,0);try{return f(e,0)}catch(t){try{return f.call(null,e,0)}catch(t){return f.call(this,e,0)}}}function i(e){if(s===clearTimeout)return clearTimeout(e);if((s===r||!s)&&clearTimeout)return s=clearTimeout,clearTimeout(e);try{return s(e)}catch(t){try{return s.call(null,e)}catch(t){return s.call(this,e)}}}function u(){h&&d&&(h=!1,d.length?m=d.concat(m):y=-1,m.length&&a())}function a(){if(!h){var e=o(u);h=!0;for(var t=m.length;t;){for(d=m,m=[];++y1)for(var n=1;n1?t-1:0),r=1;r2?r-2:0),i=2;it+1?t+1:0},e.getPreviousPhotoIndex=function(t){return t-1>=0?t-1:e.props.photos.length-1},e.getPhoto=function(t){return e.props.photos.length>t?e.props.photos[t]:null},e.state={fullScreenImage:null,fullScreenImageIndex:null},e}return u(t,e),a(t,[{key:"render",value:function(){return s.default.createElement("div",null,this.getGridElements(),this.getFullScreenImage(this.state.fullScreenImage))}},{key:"getGridElements",value:function(){var e=this,t=this.props.photos,n=this.isShowInfo()?[d.default.imageGridItem,d.default.column1]:[d.default.imageGridItem],r=this.isShowInfo()?{}:{width:this.getPercentWidth()+"%"};return t.map(function(t,o){return s.default.createElement("div",{className:n.join(" "),style:r,key:t.id},e.getImageElement(t,o))})}}]),t}(s.default.Component);h.propTypes={photos:l.default.array,columns:l.default.number,InformationElement:l.default.func},t.default=h},function(e,t,n){(function(t){if("production"!==t.env.NODE_ENV){var r="function"==typeof Symbol&&Symbol.for&&Symbol.for("react.element")||60103,o=function(e){return"object"==typeof e&&null!==e&&e.$$typeof===r};e.exports=n(10)(o,!0)}else e.exports=n(12)()}).call(t,n(0))},function(e,t,n){"use strict";(function(t){var r=n(1),o=n(2),i=n(4),u=n(3),a=n(11);e.exports=function(e,n){function c(e){var t=e&&(T&&e[T]||e[O]);if("function"==typeof t)return t}function l(e,t){return e===t?0!==e||1/e==1/t:e!==e&&t!==t}function f(e){this.message=e,this.stack=""}function s(e){function r(r,l,s,p,d,m,h){if(p=p||P,m=m||s,h!==u)if(n)o(!1,"Calling PropTypes validators directly is not supported by the `prop-types` package. Use `PropTypes.checkPropTypes()` to call them. Read more at http://fb.me/use-check-prop-types");else if("production"!==t.env.NODE_ENV&&"undefined"!=typeof console){var y=p+":"+s;!a[y]&&c<3&&(i(!1,"You are manually calling a React.PropTypes validation function for the `%s` prop on `%s`. This is deprecated and will throw in the standalone `prop-types` package. You may be seeing this warning due to a third-party PropTypes library. See https://fb.me/react-warning-dont-call-proptypes for details.",m,p),a[y]=!0,c++)}return null==l[s]?r?new f(null===l[s]?"The "+d+" `"+m+"` is marked as required in `"+p+"`, but its value is `null`.":"The "+d+" `"+m+"` is marked as required in `"+p+"`, but its value is `undefined`."):null:e(l,s,p,d,m)}if("production"!==t.env.NODE_ENV)var a={},c=0;var l=r.bind(null,!1);return l.isRequired=r.bind(null,!0),l}function p(e){function t(t,n,r,o,i,u){var a=t[n];if(E(a)!==e)return new f("Invalid "+o+" `"+i+"` of type `"+x(a)+"` supplied to `"+r+"`, expected `"+e+"`.");return null}return s(t)}function d(e){function t(t,n,r,o,i){if("function"!=typeof e)return new f("Property `"+i+"` of component `"+r+"` has invalid PropType notation inside arrayOf.");var a=t[n];if(!Array.isArray(a)){return new f("Invalid "+o+" `"+i+"` of type `"+E(a)+"` supplied to `"+r+"`, expected an array.")}for(var c=0;c>",S={array:p("array"),bool:p("boolean"),func:p("function"),number:p("number"),object:p("object"),string:p("string"),symbol:p("symbol"),any:function(){return s(r.thatReturnsNull)}(),arrayOf:d,element:function(){function t(t,n,r,o,i){var u=t[n];if(!e(u)){return new f("Invalid "+o+" `"+i+"` of type `"+E(u)+"` supplied to `"+r+"`, expected a single ReactElement.")}return null}return s(t)}(),instanceOf:m,node:function(){function e(e,t,n,r,o){return b(e[t])?null:new f("Invalid "+r+" `"+o+"` supplied to `"+n+"`, expected a ReactNode.")}return s(e)}(),objectOf:y,oneOf:h,oneOfType:g,shape:v};return f.prototype=Error.prototype,S.checkPropTypes=a,S.PropTypes=S,S}}).call(t,n(0))},function(e,t,n){"use strict";(function(t){function r(e,n,r,c,l){if("production"!==t.env.NODE_ENV)for(var f in e)if(e.hasOwnProperty(f)){var s;try{o("function"==typeof e[f],"%s: %s type `%s` is invalid; it must be a function, usually from React.PropTypes.",c||"React class",r,f),s=e[f](n,f,c,r,null,u)}catch(e){s=e}if(i(!s||s instanceof Error,"%s: type specification of %s `%s` is invalid; the type checker function must return `null` or an `Error` but returned a %s. You may have forgotten to pass an argument to the type checker creator (arrayOf, instanceOf, objectOf, oneOf, oneOfType, and shape all require an argument).",c||"React class",r,f,typeof s),s instanceof Error&&!(s.message in a)){a[s.message]=!0;var p=l?l():"";i(!1,"Failed %s type: %s%s",r,s.message,null!=p?p:"")}}}if("production"!==t.env.NODE_ENV)var o=n(2),i=n(4),u=n(3),a={};e.exports=r}).call(t,n(0))},function(e,t,n){"use strict";var r=n(1),o=n(2),i=n(3);e.exports=function(){function e(e,t,n,r,u,a){a!==i&&o(!1,"Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types")}function t(){return e}e.isRequired=e;var n={array:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t};return n.checkPropTypes=r,n.PropTypes=n,n}},function(e,t){e.exports={imageGridItem:"PhotoGrid__imageGridItem___hFxCm",imageWrapper:"PhotoGrid__imageWrapper___1YTK9",column1:"PhotoGrid__column1___3xTvr",column1Image:"PhotoGrid__column1Image___2KDMR",lightbox:"PhotoGrid__lightbox___2hnqF",hide:"PhotoGrid__hide___3-4dl"}},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0}),t.DefaultInfoElement=void 0;var o=n(5),i=r(o),u=n(15),a=r(u);t.DefaultInfoElement=function(e){var t=e.photo,n=new Date(t.created).toLocaleString();return i.default.createElement("div",{className:a.default.info},i.default.createElement("h3",null,t.title),i.default.createElement("p",null,n),i.default.createElement("p",null,i.default.createElement("a",{href:t.link},t.link)),i.default.createElement("p",null,"tags: ",t.tags))}},function(e,t){e.exports={info:"DefaultInfoElement__info___OKuK5"}}])});
--------------------------------------------------------------------------------
/library/style.css:
--------------------------------------------------------------------------------
1 | .PhotoGrid__imageGridItem___hFxCm {
2 | display: inline-block;
3 | box-sizing: border-box;
4 | float: left;
5 | padding: 10px;
6 | overflow: hidden;
7 | position: relative;
8 | transition: all .5s linear;
9 | transition-delay: .1s;
10 | }
11 | .PhotoGrid__imageWrapper___1YTK9 {
12 | position: relative;
13 | width: 100%;
14 | padding-bottom: 100%;
15 | background-size: cover;
16 | background-position: center center;
17 | background-repeat: no-repeat;
18 | transition: all .5s linear;
19 |
20 | cursor: pointer;
21 | box-shadow:0 10px 6px -6px rgba(0, 0, 0, 0.3), 0 0 40px rgba(0, 0, 0, 0.1) inset;
22 | }
23 | .PhotoGrid__imageGridItem___hFxCm:hover .PhotoGrid__imageWrapper___1YTK9{
24 | background-position: right;
25 | }
26 |
27 | .PhotoGrid__imageGridItem___hFxCm a {
28 | display: none;
29 | font-size: 100%;
30 | color: #ffffff !important;
31 | text-align: center;
32 | margin: auto;
33 | position: absolute;
34 | top: 0;
35 | left: 0;
36 | bottom: 0;
37 | right: 0;
38 | height: 50px;
39 | cursor: pointer;
40 | text-decoration: none;
41 | }
42 |
43 | .PhotoGrid__imageGridItem___hFxCm:hover a, .PhotoGrid__imageGridItem___hFxCm:focus a {
44 | display: block;
45 | }
46 |
47 | .PhotoGrid__imageGridItem___hFxCm:hover .PhotoGrid__imageWrapper___1YTK9:before, .PhotoGrid__imageGridItem___hFxCm:focus .PhotoGrid__imageWrapper___1YTK9:before {
48 | display: block;
49 | }
50 |
51 | .PhotoGrid__imageWrapper___1YTK9:before {
52 | content: "";
53 | display: none;
54 | height: 100%;
55 | width: 100%;
56 | position: absolute;
57 | top: 0;
58 | left: 0;
59 | background-color: rgba(29, 38, 41, 0.65);
60 | }
61 | .PhotoGrid__column1___3xTvr {
62 | width: 100%;
63 | transition: all .5s linear;
64 | }
65 | .PhotoGrid__column1Image___2KDMR {
66 | float:left;
67 | width:30%;
68 | margin-right: 10px;
69 | padding-bottom: 30%;
70 | transition: all .5s linear;
71 | }
72 |
73 |
74 | .PhotoGrid__lightbox___2hnqF {
75 | /** Default lightbox to hidden */
76 | /*display: block;*/
77 | opacity: 1;
78 | /** Position and style */
79 | position: fixed;
80 | z-index: 999;
81 | width: 100%;
82 | height: 100%;
83 | text-align: center;
84 | top: 0;
85 | left: 0;
86 | background: rgba(0,0,0,0.88);
87 | visibility: visible;
88 | /*transition: visibility .3s, opacity .3s linear;*/
89 | transition: all .5s ;
90 | }
91 |
92 | .PhotoGrid__lightbox___2hnqF img {
93 | /** Pad the lightbox image */
94 | max-width: 100%;
95 | max-height: 100%;
96 | position: absolute;
97 | top: 50%;
98 | left: 50%;
99 | margin-right: -50%;
100 | transform: translate(-50%, -50%);
101 | box-shadow:0 1px 4px rgba(0, 0, 0, 0.3), 0 0 40px rgba(0, 0, 0, 0.1) inset;
102 | /*padding: 10px;*/
103 | -webkit-transition: opacity 1s ease-in-out;
104 | -moz-transition: opacity 1s ease-in-out;
105 | -o-transition: opacity 1s ease-in-out;
106 | transition: opacity 1s ease-in-out;
107 | opacity:0;
108 | -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
109 | filter: alpha(opacity=0);
110 | pointer-events:none;
111 |
112 | }
113 |
114 | .PhotoGrid__lightbox___2hnqF img.opaque {
115 | opacity:1;
116 | -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
117 | filter: alpha(opacity=1);
118 | pointer-events: auto;
119 |
120 | }
121 |
122 | .PhotoGrid__hide___3-4dl {
123 | /*display:none;*/
124 | opacity: 0;
125 | visibility: hidden;
126 | transition: visibility .3s, opacity .3s linear;
127 | /*transition: all .5s;*/
128 |
129 | }
130 | .DefaultInfoElement__info___OKuK5 {
131 | transition: all 5s;
132 | transition-delay: 2s;
133 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-photo-feed",
3 | "version": "1.0.10",
4 | "description": "",
5 | "main": "./library/photogrid.js",
6 | "scripts": {
7 | "start": "webpack-dev-server --port 8081 --hot --inline --config webpack.config.debug.js --content-base debug/ ",
8 | "build:example": "webpack --config webpack.config.production.js ",
9 | "build:lib": "webpack --progress --colors --config webpack.config.library.js ",
10 | "analyze": "webpack --json --config webpack.config.library.js | webpack-bundle-size-analyzer"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/lkazberova/react-photo-feed.git"
15 | },
16 | "author": "",
17 | "license": "ISC",
18 | "devDependencies": {
19 | "babel-core": "^6.25.0",
20 | "babel-loader": "^7.1.1",
21 | "babel-preset-es2015": "^6.24.1",
22 | "babel-preset-react": "^6.24.1",
23 | "babel-preset-react-hmre": "^1.1.0",
24 | "babel-preset-stage-0": "^6.24.1",
25 | "css-loader": "^0.23.1",
26 | "events": "^1.1.0",
27 | "extract-text-webpack-plugin": "^3.0.0-beta.3",
28 | "flux": "^2.1.1",
29 | "jquery": "^2.2.2",
30 | "style-loader": "^0.13.0",
31 | "webpack": "^3.0.0",
32 | "webpack-dev-server": "^2.5.0"
33 | },
34 | "dependencies": {
35 | "null-loader": "^0.1.1",
36 | "prop-types": "^15.5.10",
37 | "react": "^15.0.0-rc.2",
38 | "react-dom": "^15.0.0-rc.2"
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/components/DefaultInfoElement.css:
--------------------------------------------------------------------------------
1 | .info {
2 | transition: all 5s;
3 | transition-delay: 2s;
4 | }
--------------------------------------------------------------------------------
/src/components/DefaultInfoElement.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styles from './DefaultInfoElement.css';
3 | export const DefaultInfoElement = (props) => {
4 | const {photo} = props;
5 | const date = new Date(photo.created).toLocaleString();
6 | return (
7 |
8 |
{photo.title}
9 |
{date}
10 |
{photo.link}
11 |
tags: {photo.tags}
12 |
13 | );
14 | }
15 | ;
--------------------------------------------------------------------------------
/src/components/PhotoGrid.css:
--------------------------------------------------------------------------------
1 | .imageGridItem {
2 | display: inline-block;
3 | box-sizing: border-box;
4 | float: left;
5 | padding: 10px;
6 | overflow: hidden;
7 | position: relative;
8 | transition: all .5s linear;
9 | transition-delay: .1s;
10 | }
11 | .imageWrapper {
12 | position: relative;
13 | width: 100%;
14 | padding-bottom: 100%;
15 | background-size: cover;
16 | background-position: center center;
17 | background-repeat: no-repeat;
18 | transition: all .5s linear;
19 |
20 | cursor: pointer;
21 | box-shadow:0 10px 6px -6px rgba(0, 0, 0, 0.3), 0 0 40px rgba(0, 0, 0, 0.1) inset;
22 | }
23 | .imageGridItem:hover .imageWrapper{
24 | background-position: right;
25 | }
26 |
27 | .imageGridItem a {
28 | display: none;
29 | font-size: 100%;
30 | color: #ffffff !important;
31 | text-align: center;
32 | margin: auto;
33 | position: absolute;
34 | top: 0;
35 | left: 0;
36 | bottom: 0;
37 | right: 0;
38 | height: 50px;
39 | cursor: pointer;
40 | text-decoration: none;
41 | }
42 |
43 | .imageGridItem:hover a, .imageGridItem:focus a {
44 | display: block;
45 | }
46 |
47 | .imageGridItem:hover .imageWrapper:before, .imageGridItem:focus .imageWrapper:before {
48 | display: block;
49 | }
50 |
51 | .imageWrapper:before {
52 | content: "";
53 | display: none;
54 | height: 100%;
55 | width: 100%;
56 | position: absolute;
57 | top: 0;
58 | left: 0;
59 | background-color: rgba(29, 38, 41, 0.65);
60 | }
61 | .column1 {
62 | width: 100%;
63 | transition: all .5s linear;
64 | }
65 | .column1Image {
66 | float:left;
67 | width:30%;
68 | margin-right: 10px;
69 | padding-bottom: 30%;
70 | transition: all .5s linear;
71 | }
72 |
73 |
74 | .lightbox {
75 | /** Default lightbox to hidden */
76 | /*display: block;*/
77 | opacity: 1;
78 | /** Position and style */
79 | position: fixed;
80 | z-index: 999;
81 | width: 100%;
82 | height: 100%;
83 | text-align: center;
84 | top: 0;
85 | left: 0;
86 | background: rgba(0,0,0,0.88);
87 | visibility: visible;
88 | /*transition: visibility .3s, opacity .3s linear;*/
89 | transition: all .5s ;
90 | }
91 |
92 | .lightbox img {
93 | /** Pad the lightbox image */
94 | max-width: 100%;
95 | max-height: 100%;
96 | position: absolute;
97 | top: 50%;
98 | left: 50%;
99 | margin-right: -50%;
100 | transform: translate(-50%, -50%);
101 | box-shadow:0 1px 4px rgba(0, 0, 0, 0.3), 0 0 40px rgba(0, 0, 0, 0.1) inset;
102 | /*padding: 10px;*/
103 | -webkit-transition: opacity 1s ease-in-out;
104 | -moz-transition: opacity 1s ease-in-out;
105 | -o-transition: opacity 1s ease-in-out;
106 | transition: opacity 1s ease-in-out;
107 | opacity:0;
108 | -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
109 | filter: alpha(opacity=0);
110 | pointer-events:none;
111 |
112 | }
113 |
114 | .lightbox img:global(.opaque) {
115 | opacity:1;
116 | -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
117 | filter: alpha(opacity=1);
118 | pointer-events: auto;
119 |
120 | }
121 |
122 | .hide {
123 | /*display:none;*/
124 | opacity: 0;
125 | visibility: hidden;
126 | transition: visibility .3s, opacity .3s linear;
127 | /*transition: all .5s;*/
128 |
129 | }
130 |
--------------------------------------------------------------------------------
/src/components/PhotoGrid.js:
--------------------------------------------------------------------------------
1 | import PropTypes from "prop-types";
2 | import React from "react";
3 | import styles from "./PhotoGrid.css";
4 | import {DefaultInfoElement} from "./DefaultInfoElement";
5 |
6 | class PhotoGrid extends React.Component {
7 | static propTypes = {
8 | photos : PropTypes.array /* { src, id, bigSrc}*/,
9 | columns : PropTypes.number,
10 | InformationElement : PropTypes.func
11 | };
12 |
13 | constructor() {
14 | super();
15 | this.state = {
16 | fullScreenImage : null,
17 | fullScreenImageIndex : null
18 | }
19 | }
20 |
21 | render() {
22 | return (
23 |
24 | {this.getGridElements()}
25 | {this.getFullScreenImage(this.state.fullScreenImage)}
26 |
27 | );
28 | }
29 |
30 | getGridElements() {
31 | const {photos} = this.props;
32 | const classNames = this.isShowInfo() ? [styles.imageGridItem, styles.column1] : [styles.imageGridItem];
33 | const style = this.isShowInfo() ? {} : {width : this.getPercentWidth() + '%'};
34 |
35 | return photos.map((photo, index) => (
36 |
39 | {this.getImageElement(photo, index)}
40 |
41 |
42 | ));
43 | }
44 |
45 | getImageElement = (photo, index) => {
46 | const InformationElement = this.props.InformationElement ? this.props.InformationElement : DefaultInfoElement;
47 | const classNames = this.isShowInfo() ? [styles.imageWrapper, styles.column1Image] : [styles.imageWrapper];
48 | const style = {backgroundImage : `url(${photo.src})`};
49 |
50 | return (
51 |
52 |
57 | {this.isShowInfo() ?
: null }
58 |
59 |
60 | );
61 | };
62 |
63 | getFullScreenImage = src => {
64 | const classNames = src ? [styles.lightbox] : [styles.hide, styles.lightbox ];
65 | const {photos} = this.props;
66 | return (
67 |
68 | {photos.map((photo, index) => (
69 |
71 | ))}
72 | );
73 | };
74 |
75 |
76 | image_clickHandler = (photo, index) => () => {
77 | this.setState({
78 | fullScreenImage : photo.bigSrc,
79 | fullScreenImageIndex : index
80 | })
81 | };
82 |
83 | lightBox_clickHandler = e => {
84 | if (e.target.tagName.toUpperCase() == 'IMG') return;
85 | this.setState({
86 | fullScreenImage : null,
87 | fullScreenImageIndex : null
88 | })
89 | };
90 |
91 | fullScreenImage_clickHandler = e => {
92 | e.stopPropagation();
93 | const {fullScreenImageIndex} = this.state;
94 | const nextPhotoIndex = this.getNextPhotoIndex(fullScreenImageIndex);
95 | const nextPhoto = this.getPhoto(nextPhotoIndex);
96 |
97 | this.setState({
98 | fullScreenImage : nextPhoto ? nextPhoto.bigSrc : null,
99 | fullScreenImageIndex : nextPhotoIndex
100 | })
101 | };
102 |
103 | isShowInfo = () => this.props.columns == 1;
104 | getPercentWidth = () => 100 / this.props.columns - 1;
105 | getNextPhotoIndex = currentIndex => this.props.photos.length > currentIndex + 1 ? currentIndex + 1 : 0;
106 | getPreviousPhotoIndex = currentIndex => currentIndex - 1 >= 0 ? currentIndex - 1 : this.props.photos.length - 1;
107 | getPhoto = index => this.props.photos.length > index ? this.props.photos[index] : null;
108 | }
109 |
110 |
111 | export default PhotoGrid
--------------------------------------------------------------------------------
/src/components/RadioButtonGroup.css:
--------------------------------------------------------------------------------
1 | .buttons {
2 | margin: 0 auto;
3 | width: 100%;
4 | display:inline;
5 | }
6 |
7 |
8 |
9 | .buttons a {
10 | margin: 10px;
11 | /*box-shadow:0 0 10px rgba(0, 0, 0, 0.5);;*/
12 | box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3), 0 0 40px rgba(0, 0, 0, 0.1) inset;
13 | transition: all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms;
14 | }
15 |
16 | .buttons a:link, .buttons a:visited {
17 | background-color: white;
18 | color: black;
19 | /*border: 2px solid green;*/
20 | padding: 10px 20px;
21 | text-align: center;
22 | text-decoration: none;
23 | display: inline-block;
24 | border-radius: 2px;
25 | }
26 |
27 | .buttons a:hover, .buttons a:active {
28 | background-color: rgba(0, 0, 0, 0.078);
29 | box-shadow:0 10px 6px -6px rgba(0, 0, 0, 0.3), 0 0 40px rgba(0, 0, 0, 0.1) inset;
30 |
31 | /*color: white;*/
32 | }
33 |
34 | .secondaryButton a:link, .secondaryButton a:visited {
35 | background-color: #00bcd4;
36 | color: white
37 | }
38 |
39 | .secondaryButton a:hover, .secondaryButton a:active {
40 | background-color: rgb(0, 161, 183);
41 | box-shadow:0 10px 6px -6px rgba(0, 0, 0, 0.3), 0 0 40px rgba(0, 0, 0, 0.1) inset ;
42 | }
43 |
44 | .defaultButton a:hover, .defaultButton a:active {
45 | background-color: rgba(0, 0, 0, 0.078) ;
46 | box-shadow:0 10px 6px -6px rgba(0, 0, 0, 0.3), 0 0 40px rgba(0, 0, 0, 0.1) inset;
47 | }
48 | .defaultButtonSelected {
49 | background-color: rgba(0, 0, 0, 0.2) !important;
50 |
51 | }
52 | .secondaryButtonSelected {
53 | background-color: rgb(0, 139, 161) !important;
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/src/components/RadioButtonGroup.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 | import styles from './RadioButtonGroup.css';
4 |
5 | class RadioButtonGroup extends React.Component {
6 | static propTypes = {
7 | items : PropTypes.array,
8 | value : PropTypes.string,
9 | onClick : PropTypes.func,
10 | type : PropTypes.string
11 | };
12 |
13 | render() {
14 | const {items,type} = this.props;
15 | const itemElements = items.map(this.getItemElement);
16 | const typeStyle = type == 'default' ? '' : styles.secondaryButton;
17 | return (
18 |
19 | {itemElements}
20 |
21 | );
22 | }
23 |
24 | getItemElement = (item) => {
25 | const {value} = this.props;
26 | const className = value == item.value ? this.getSelectedClassName() : '';
27 | return (
28 | {item.label}
30 | );
31 | };
32 |
33 | getSelectedClassName() {
34 | const {type} = this.props;
35 | switch (type) {
36 | case 'default' :
37 | return styles.defaultButtonSelected;
38 | case 'secondary' :
39 | return styles.secondaryButtonSelected;
40 | }
41 | }
42 |
43 | onClick(value) {
44 | return function () {
45 | this.props.onClick(value);
46 | };
47 | }
48 | }
49 |
50 | export default RadioButtonGroup;
--------------------------------------------------------------------------------
/static/app.min.js:
--------------------------------------------------------------------------------
1 | webpackJsonp([0],{0:function(e,t,n){e.exports=n(1)},1:function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}var o=n(2),u=r(o),a=n(161),l=r(a),i=n(172),c=r(i);n(204),u.default.render(l.default.createElement(c.default,null),document.getElementById("container"))},172:function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function u(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var l=function(){function e(e,t){for(var n=0;n1?t[1]:null}Object.defineProperty(t,"__esModule",{value:!0}),t.getLastPartOfUrl=o;var u=n(161);r(u)},183:function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}function o(){c.default.dispatch({type:l.LOAD_PUBLIC_FEED+l._START}),a.default.getJSON("http://api-fotki.yandex.ru/api/podhistory/poddate/?format=json&callback=?").done(function(e){console.log(e.entries);var t=e.entries.map(function(e){return{title:e.title,id:e.id,link:e.links.alternate,src:e.img.L.href,bigSrc:e.img.XXL.href,author:e.author,created:Date.parse(e.published),tags:Object.keys(e.tags).join(",")}});console.log(t),c.default.dispatch({type:l.LOAD_PUBLIC_FEED+l._SUCCESS,response:t})}).fail(function(e){c.default.dispatch({type:l.LOAD_PUBLIC_FEED+l._FAIL,error:e})})}Object.defineProperty(t,"__esModule",{value:!0}),t.loadPublicFeed=o;var u=n(180),a=r(u),l=n(178),i=n(174),c=r(i)},184:function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}Object.defineProperty(t,"__esModule",{value:!0}),t.photoStore=void 0;var o=n(185),u=r(o);t.photoStore=new u.default},185:function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function u(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var l=n(186),i=r(l),c=n(178),f=n(174),s=r(f),d=function(e){function t(){var e;o(this,t);for(var n=arguments.length,r=Array(n),a=0;n>a;a++)r[a]=arguments[a];var l=u(this,(e=Object.getPrototypeOf(t)).call.apply(e,[this].concat(r)));return l.dispatchToken=s.default.register(function(e){var t=e.type,n=(e.data,e.response);switch(t){case c.LOAD_PUBLIC_FEED+c._SUCCESS:n.forEach(l.add),l.emitChange();break;default:return}}),l}return a(t,e),t}(i.default);t.default=d},186:function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function u(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var a=function(){function e(e,t){for(var n=0;n0?n[0]:null},e.add=function(t){e.delete(t.id),e.__items.push(t)},e.delete=function(t){e.__items=e.__items.filter(function(e){return e.id!=t})},e.__items=[],e}return u(t,e),a(t,[{key:"emitChange",value:function(){this.emit(i)}},{key:"addChangeListener",value:function(e){this.on(i,e)}},{key:"removeChangeListener",value:function(e){this.removeListener(i,e)}},{key:"getAll",value:function(){return this.__items.slice()}}]),t}(l.EventEmitter);t.default=c},188:function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function u(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var l=function(){function e(e,t){var n=[],r=!0,o=!1,u=void 0;try{for(var a,l=e[Symbol.iterator]();!(r=(a=l.next()).done)&&(n.push(a.value),!t||n.length!==t);r=!0);}catch(i){o=!0,u=i}finally{try{!r&&l.return&&l.return()}finally{if(o)throw u}}return n}return function(t,n){if(Array.isArray(t))return t;if(Symbol.iterator in Object(t))return e(t,n);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}(),i=function(){function e(e,t){for(var n=0;nt[o])||+(e[o]===t[o])-1)*a})}},{key:"onSortClick",value:function(e){this.setState({order:e==this.state.order?null:e})}},{key:"onClick",value:function(e){this.setState({columns:e})}}]),t}(f.default.Component);v.propTypes={photos:c.PropTypes.array},t.default=v},189:function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{"default":e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function u(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var l=function(){function e(e,t){for(var n=0;nf;f++)i[f]=arguments[f];return n=r=u(this,(e=Object.getPrototypeOf(t)).call.apply(e,[this].concat(i))),r.getItemElement=function(e){var t=r.props.value,n=t==e.value?r.getSelectedClassName():"";return c.default.createElement("a",{key:e.value+e.label,href:"#",className:n,onClick:r.onClick(e.value).bind(r)},e.label)},a=n,u(r,a)}return a(t,e),l(t,[{key:"render",value:function(){var e=this.props,t=e.items,n=e.type,r=t.map(this.getItemElement),o="default"==n?"":s.default.secondaryButton;return c.default.createElement("div",{className:[s.default.buttons,o].join(" ")},r)}},{key:"getSelectedClassName",value:function(){var e=this.props.type;switch(e){case"default":return s.default.defaultButtonSelected;case"secondary":return s.default.secondaryButtonSelected}}},{key:"onClick",value:function(e){return function(){this.props.onClick(e)}}}]),t}(c.default.Component);d.propTypes={items:i.PropTypes.array,value:i.PropTypes.string,onClick:i.PropTypes.func,type:i.PropTypes.string},t.default=d},198:function(e,t){e.exports={buttons:"RadioButtonGroup__buttons___38Ovq",secondaryButton:"RadioButtonGroup__secondaryButton___2g6PC",defaultButton:"RadioButtonGroup__defaultButton___V_J7Q",defaultButtonSelected:"RadioButtonGroup__defaultButtonSelected___3Ds5j",secondaryButtonSelected:"RadioButtonGroup__secondaryButtonSelected___Bayht"}},200:function(e,t){},202:function(e,t){},204:function(e,t){}});
--------------------------------------------------------------------------------
/static/styles.css:
--------------------------------------------------------------------------------
1 | @import url(https://fonts.googleapis.com/css?family=Roboto:400,300,500);.PhotoGrid__imageGridItem___hFxCm{display:inline-block;box-sizing:border-box;float:left;padding:10px;overflow:hidden;position:relative;transition:all .5s linear;transition-delay:.1s}.PhotoGrid__imageWrapper___1YTK9{position:relative;width:100%;padding-bottom:100%;background-size:cover;background-position:50%;background-repeat:no-repeat;transition:all .5s linear;cursor:pointer;box-shadow:0 10px 6px -6px rgba(0,0,0,.3),inset 0 0 40px rgba(0,0,0,.1)}.PhotoGrid__imageGridItem___hFxCm:hover .PhotoGrid__imageWrapper___1YTK9{background-position:100%}.PhotoGrid__column1___3xTvr{width:100%;transition:all .5s linear}.PhotoGrid__column1Image___2KDMR{float:left;width:30%;margin-right:10px;padding-bottom:30%;transition:all .5s linear}.PhotoGrid__lightbox___2hnqF{opacity:1;position:fixed;z-index:999;width:100%;height:100%;text-align:center;top:0;left:0;background:rgba(0,0,0,.88);visibility:visible;transition:all .5s}.PhotoGrid__lightbox___2hnqF img{max-width:100%;max-height:100%;position:absolute;top:50%;left:50%;margin-right:-50%;transform:translate(-50%,-50%);box-shadow:0 1px 4px rgba(0,0,0,.3),inset 0 0 40px rgba(0,0,0,.1)}.PhotoGrid__hide___3-4dl{opacity:0;visibility:hidden;transition:visibility .3s,opacity .3s linear}.DefaultInfoElement__info___OKuK5{transition:all 5s;transition-delay:2s}.RadioButtonGroup__buttons___38Ovq{margin:0 auto;width:100%;display:inline}.RadioButtonGroup__buttons___38Ovq a{margin:10px;box-shadow:0 1px 4px rgba(0,0,0,.3),inset 0 0 40px rgba(0,0,0,.1);transition:all .45s cubic-bezier(.23,1,.32,1) 0ms}.RadioButtonGroup__buttons___38Ovq a:link,.RadioButtonGroup__buttons___38Ovq a:visited{background-color:#fff;color:#000;padding:10px 20px;text-align:center;text-decoration:none;display:inline-block;border-radius:2px}.RadioButtonGroup__buttons___38Ovq a:active,.RadioButtonGroup__buttons___38Ovq a:hover{background-color:rgba(0,0,0,.078);box-shadow:0 10px 6px -6px rgba(0,0,0,.3),inset 0 0 40px rgba(0,0,0,.1)}.RadioButtonGroup__secondaryButton___2g6PC a:link,.RadioButtonGroup__secondaryButton___2g6PC a:visited{background-color:#00bcd4;color:#fff}.RadioButtonGroup__secondaryButton___2g6PC a:active,.RadioButtonGroup__secondaryButton___2g6PC a:hover{background-color:#00a1b7;box-shadow:0 10px 6px -6px rgba(0,0,0,.3),inset 0 0 40px rgba(0,0,0,.1)}.RadioButtonGroup__defaultButton___V_J7Q a:active,.RadioButtonGroup__defaultButton___V_J7Q a:hover{background-color:rgba(0,0,0,.078);box-shadow:0 10px 6px -6px rgba(0,0,0,.3),inset 0 0 40px rgba(0,0,0,.1)}.RadioButtonGroup__defaultButtonSelected___3Ds5j{background-color:rgba(0,0,0,.2)!important}.RadioButtonGroup__secondaryButtonSelected___Bayht{background-color:#008ba1!important}h1 small{font-size:65%;color:#777;font-weight:400}html{font-family:Roboto,sans-serif;-webkit-font-smoothing:antialiased}body{width:80%;margin:30px auto}
--------------------------------------------------------------------------------
/webpack.config.debug.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 |
4 |
5 | module.exports = {
6 | devtool : 'eval',
7 | entry : [
8 | './example/src/app.js'
9 | ],
10 | output : {
11 | path : path.join(__dirname, 'debug'),
12 | filename : 'bundle.js',
13 | publicPath : '/'
14 | },
15 | module : {
16 | rules : [
17 | {
18 | test : /\.jsx?/,
19 |
20 | use : {
21 | loader : 'babel-loader',
22 | options : {
23 | presets : ['react', 'es2015', 'stage-0', "react-hmre"],
24 |
25 | },
26 | },
27 | exclude : /(node_modules|bower_components)/,
28 | }, {
29 | test : /\.css/,
30 | use : [
31 | {
32 | loader : 'style-loader'
33 | },
34 | {
35 | loader : 'css-loader',
36 | options : {
37 | modules : true,
38 | localIdentName : '[name]__[local]___[hash:base64:5]'
39 | }
40 | }
41 | ]
42 | }
43 | ]
44 | }
45 | }
--------------------------------------------------------------------------------
/webpack.config.library.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 | var ExtractTextPlugin = require("extract-text-webpack-plugin");
4 |
5 | module.exports = {
6 |
7 | entry: {
8 | photogrid: ["./index.js"],
9 | },
10 | output: {
11 | path: path.join(__dirname, '/library'),
12 | filename: '[name].js',
13 | library: 'PhotoFeed',
14 | libraryTarget: 'umd',
15 | },
16 | module: {
17 | rules: [
18 | // {
19 | // test: /prop-types/,
20 | // use: 'null-loader',
21 | // },
22 | {
23 | test: /\.js$/,
24 | exclude: /(node_modules|bower_components)/,
25 | use: {
26 | loader: 'babel-loader',
27 | options: {
28 | presets: ['react', 'es2015', 'stage-0'],
29 |
30 | },
31 | },
32 | },
33 | {
34 | test: /\.css$/,
35 | use: ExtractTextPlugin.extract({
36 | fallback: "style-loader",
37 | use: "css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]"
38 | })
39 | }
40 | ],
41 | // loaders: [
42 | // {
43 | // test: /\.jsx?/,
44 | // loader: 'babel',
45 | // query: {
46 | // presets: ["react", "es2015", "stage-0"]
47 | // },
48 | // include: path.join(__dirname, 'src')
49 | // }
50 | // , {
51 | // test: /\.css/,
52 | // include: path.join(__dirname, 'src'),
53 | // loader: ExtractTextPlugin.extract("style-loader", "css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]")
54 | // }
55 | // ]
56 | },
57 | externals: {
58 | react: {
59 | root: 'React',
60 | commonjs2: 'react',
61 | commonjs: 'react',
62 | amd: 'react',
63 | },
64 |
65 | },
66 | plugins: [
67 | new webpack.optimize.UglifyJsPlugin({
68 | compress: {
69 | screw_ie8: true,
70 | warnings: false
71 | },
72 | sourceMap: false,
73 | minimize: true
74 | }),
75 | new ExtractTextPlugin("style.css"),
76 | ]
77 | };
--------------------------------------------------------------------------------
/webpack.config.production.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 | var webpack = require('webpack');
3 | var ExtractTextPlugin = require("extract-text-webpack-plugin");
4 |
5 | module.exports = {
6 | devtool: 'cheap-module-source-map',
7 | cache: false,
8 |
9 | entry : {
10 | app : ["./example/src/app.js"],
11 | vendors : ['react', 'react-dom', 'jquery', 'flux', 'events']
12 | },
13 | output : {
14 | path : path.join(__dirname, 'static'),
15 | filename : '[name].min.js',
16 | publicPath : '/static/'
17 | },
18 | module : {
19 | rules : [
20 | {
21 | test : /\.js$/,
22 | exclude : /(node_modules|bower_components)/,
23 | use : {
24 | loader : 'babel-loader',
25 | options : {
26 | presets : ['react', 'es2015', 'stage-0'],
27 |
28 | },
29 | },
30 | // include: path.join(__dirname, 'src')
31 | }, {
32 | test : /\.css$/,
33 | use : ExtractTextPlugin.extract({
34 | fallback : "style-loader",
35 | use : "css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]"
36 | })
37 | }
38 | ]
39 | },
40 | plugins : [
41 | new webpack.DefinePlugin({
42 | 'process.env' : {
43 | NODE_ENV : JSON.stringify('production')
44 | }
45 | }),
46 | new webpack.optimize.UglifyJsPlugin({
47 | mangle: true,
48 | compress: {
49 | warnings: false, // Suppress uglification warnings
50 | pure_getters: true,
51 | unsafe: true,
52 | unsafe_comps: true,
53 | screw_ie8: true
54 | },
55 | output: {
56 | comments: false,
57 | },
58 | exclude: [/\.min\.js$/gi] // skip pre-minified libs
59 | }),
60 | new webpack.optimize.CommonsChunkPlugin({name : 'vendors', fileName : 'vendors.min.js', minChunks : Infinity}),
61 | new ExtractTextPlugin("styles.css"),
62 | ]
63 | };
--------------------------------------------------------------------------------