├── .editorconfig ├── .gitignore ├── .jscsrc ├── .jshintrc ├── LICENSE ├── README.md ├── dist └── .gitkeep ├── favicon.ico ├── frameworks.json ├── index.html ├── package.json ├── src ├── App.js ├── Flux.js ├── actions │ ├── FavoriteActions.js │ └── FrameworkActions.js ├── api │ └── FrameworkApi.js ├── client.js ├── components │ ├── FrameworkDetail.js │ ├── FrameworkListElement.js │ ├── TopBar.js │ └── handlers │ │ ├── Detail.js │ │ ├── List.js │ │ └── Search.js ├── css │ ├── main.css │ ├── ratchet.css │ └── ratchet.min.css ├── fonts │ ├── ratchicons.eot │ ├── ratchicons.svg │ ├── ratchicons.ttf │ └── ratchicons.woff ├── stores │ ├── FrameworkStore.js │ └── SearchFrameworkStore.js └── utils │ ├── ApiUtils.js │ ├── ArrayUtils.js │ ├── BaseContentStore.js │ ├── BaseLinkedListStore.js │ ├── BaseStore.js │ └── LocalStorageUtils.js ├── webpack.config.js └── webpack.config.production.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | root = true 5 | [*] 6 | # Change these settings to your own preference 7 | indent_style = space 8 | indent_size = 2 9 | # We recommend you to keep these unchanged 10 | end_of_line = lf 11 | charset = utf-8 12 | trim_trailing_whitespace = true 13 | insert_final_newline = true 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | dist/* 4 | !dist/.gitkeep 5 | -------------------------------------------------------------------------------- /.jscsrc: -------------------------------------------------------------------------------- 1 | { 2 | "esnext": true, 3 | "requireCurlyBraces": ["else", "for", "while", "do", "try", "catch", "case", "default"], 4 | "requireSpaceAfterKeywords": [ 5 | "if", 6 | "else", 7 | "for", "while", "do", "switch", "return", "try", "catch" 8 | ], 9 | "requireSpaceBeforeBlockStatements": true, 10 | "requireParenthesesAroundIIFE": true, 11 | "requireSpacesInConditionalExpression": true, 12 | "requireSpacesInFunctionExpression": { 13 | "beforeOpeningRoundBrace": true, 14 | "beforeOpeningCurlyBrace": true 15 | }, 16 | "requireSpacesInFunctionDeclaration": { 17 | "beforeOpeningRoundBrace": true, 18 | "beforeOpeningCurlyBrace": true 19 | }, 20 | "disallowMultipleVarDecl": "exceptUndefined", 21 | "requireBlocksOnNewline": 1, 22 | "disallowPaddingNewlinesInBlocks": true, 23 | "disallowEmptyBlocks": true, 24 | "disallowSpacesInsideObjectBrackets": "all", 25 | "disallowSpacesInsideArrayBrackets": "all", 26 | "disallowSpacesInsideParentheses": true, 27 | "disallowSpaceAfterObjectKeys": true, 28 | "requireCommaBeforeLineBreak": true, 29 | "disallowSpacesInCallExpression": true, 30 | "requireSpaceBeforeObjectValues": true, 31 | "requireOperatorBeforeLineBreak": [ 32 | "?", 33 | "+", 34 | "-", 35 | "/", 36 | "*", 37 | "=", 38 | "==", 39 | "===", 40 | "!=", 41 | "!==", 42 | ">", 43 | ">=", 44 | "<", 45 | "<=", 46 | "||", 47 | "&&" 48 | ], 49 | "disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"], 50 | "disallowSpaceBeforePostfixUnaryOperators": ["++", "--"], 51 | "requireSpaceBeforeBinaryOperators": [ 52 | "?" 53 | , "+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<=" 54 | ], 55 | "requireSpaceAfterBinaryOperators": [ 56 | "?" 57 | , "+", "/", "*", ":", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<=" 58 | ], 59 | "disallowSpaceAfterBinaryOperators": ["!"], 60 | "disallowImplicitTypeConversion": ["numeric", "boolean", "binary", "string"], 61 | "requireCamelCaseOrUpperCaseIdentifiers": "ignoreProperties", 62 | "disallowKeywords": ["with"], 63 | "disallowMultipleLineStrings": true, 64 | "disallowMultipleLineBreaks": true, 65 | "validateLineBreaks": "LF", 66 | "validateQuoteMarks": true, 67 | "validateIndentation": 2, 68 | "disallowMixedSpacesAndTabs": true, 69 | "disallowTrailingWhitespace": true, 70 | "disallowTrailingComma": true, 71 | "disallowKeywordsOnNewLine": ["else", "catch"], 72 | "requireLineFeedAtFileEnd": true, 73 | "maximumLineLength": { 74 | "value": 100, 75 | "allowUrlComments": true 76 | }, 77 | "requireCapitalizedConstructors": true, 78 | "safeContextKeyword": "_this", 79 | "requireDotNotation": true, 80 | "validateJSDoc": { 81 | "checkParamNames": true, 82 | "checkRedundantParams": true, 83 | "requireParamTypes": true 84 | }, 85 | "requireSpaceAfterLineComment": true, 86 | "requirePaddingNewlinesBeforeKeywords": [ 87 | "do", 88 | "for", 89 | "if", 90 | "switch", 91 | "try", 92 | "void", 93 | "while", 94 | "with", 95 | "return" 96 | ], 97 | "validateParameterSeparator": ", ", 98 | // "requireLineBreakAfterVariableAssignment": true, 99 | "disallowNewlineBeforeBlockStatements": true, 100 | "excludeFiles": [] 101 | } 102 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "browser": true, 4 | "bitwise": true, 5 | "camelcase": true, 6 | "curly": true, 7 | "immed": true, 8 | "latedef": true, 9 | "newcap": true, 10 | "trailing": true, 11 | "quotmark": "single", 12 | "strict": true, 13 | "multistr": true, 14 | "debug": false, 15 | "forin": true, 16 | "undef": true, 17 | "plusplus": true, 18 | "eqeqeq": true, 19 | "validthis": false, 20 | "unused": true, 21 | "jasmine": true, 22 | "esnext": true 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Alessandro Arnodo 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 | # flux-immutable-example 2 | 3 | 4 | **Brought to you by [Alessandro Arnodo](http://alessandro.arnodo.net) [[@vesparny](https://twitter.com/vesparny)]** 5 | 6 | [![Dependency status](https://david-dm.org/vesparny/flux-immutable-example 7 | /status.png)](https://david-dm.org/vesparny/flux-immutable-example 8 | "Dependency status") 9 | 10 | **This is just my attempt to dig into flux, React, webpack, es6 syntax and immutability** 11 | 12 | It's an app written in few days while learning those technologies. 13 | 14 | #### See a [working demo](http://vesparny.github.io/flux-immutable-example/). 15 | 16 | ![flux](https://cloud.githubusercontent.com/assets/82070/6288351/a64feb16-b918-11e4-970e-4d11bc7cfdbe.gif) 17 | 18 | #### Install dependencies 19 | 20 | ```shell 21 | npm install 22 | ``` 23 | 24 | #### Run 25 | 26 | ```shell 27 | npm start 28 | ``` 29 | 30 | then open http://localhost:3333 31 | 32 | #### Build for production with Webpack 33 | 34 | ```shell 35 | npm run build 36 | ``` 37 | 38 | #### Technology stack 39 | 40 | * [React](http://facebook.github.io/react/), of course 41 | * flux stuff handled by [flummox](https://github.com/acdlite/flummox) by [acdlite](https://github.com/acdlite/) (which is the best flux library I played with) 42 | * Stores classification based on [gaearon](https://github.com/gaearon/flux-react-router-example) example. 43 | * [webpack](https://github.com/webpack/webpack) 44 | * [Immutable.js](https://github.com/facebook/immutable-js) 45 | * [react-router](https://github.com/rackt/react-router) 46 | * [react-immutable-render-mixin](https://www.npmjs.com/package/react-immutable-render-mixin) 47 | 48 | #### Useful links 49 | 50 | * http://stackoverflow.com/questions/23591325/in-flux-architecture-how-do-you-manage-store-lifecycle 51 | * http://facebook.github.io/react/blog/2013/11/05/thinking-in-react.html 52 | * http://stackoverflow.com/questions/23931416/react-flux-state-and-stores 53 | 54 | 55 | ### License 56 | 57 | See LICENSE file 58 | -------------------------------------------------------------------------------- /dist/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesparny/flux-immutable-example/c25c73ec31533054c877990cbf2fbbe46c96c789/dist/.gitkeep -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesparny/flux-immutable-example/c25c73ec31533054c877990cbf2fbbe46c96c789/favicon.ico -------------------------------------------------------------------------------- /frameworks.json: -------------------------------------------------------------------------------- 1 | { 2 | "results": [{ 3 | "id": "FadFAdas", 4 | "createdAt": "2015-02-20T13:31:59.519Z", 5 | "description": "AngularJS is a toolset for building the framework most suited to your application development. It is fully extensible and works well with other libraries. Every feature can be modified or replaced to suit your unique development workflow and feature needs", 6 | "image": "https://cloud.githubusercontent.com/assets/82070/6286967/94f8f0ca-b90d-11e4-88e0-4f213058537d.jpg", 7 | "name": "angularjs", 8 | "nameDesc": "AngularJS", 9 | "objectId": "G5bhJdQW9u", 10 | "updatedAt": "2015-02-20T14:23:14.398Z" 11 | }, { 12 | "id": "hjMhjhjm", 13 | "createdAt": "2015-02-20T13:37:51.427Z", 14 | "description": "Ember.js is built for productivity. Designed with developer ergonomics in mind, its friendly APIs help you get your job done—fast.", 15 | "image": "https://cloud.githubusercontent.com/assets/82070/6287020/f5fc2c52-b90d-11e4-9823-4b9887176ac1.png", 16 | "name": "ember", 17 | "nameDesc": "Ember.js", 18 | "objectId": "WXg6HTrpQ8", 19 | "updatedAt": "2015-02-20T14:23:01.291Z" 20 | }, { 21 | "id": "ShkmshKm", 22 | "createdAt": "2015-02-20T13:35:47.372Z", 23 | "description": "Lots of people use React as the V in MVC. Since React makes no assumptions about the rest of your technology stack, it's easy to try it out on a small feature in an existing project.", 24 | "image": "https://cloud.githubusercontent.com/assets/82070/6287019/f3db3a3a-b90d-11e4-8df2-fc9fb4420ac4.png", 25 | "name": "react", 26 | "nameDesc": "React", 27 | "objectId": "jLVZHaZPTr", 28 | "updatedAt": "2015-02-20T14:23:07.561Z" 29 | }] 30 | } 31 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | flux-immutable-example 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "flux-immutable-example", 3 | "repository": "https://github.com/vesparny/flux-immutable-example", 4 | "description": "A trivial example app with flux, flummox, react-router, webpack and Immutable.js", 5 | "version": "0.0.0", 6 | "scripts": { 7 | "start": "npm run dev", 8 | "build": "./node_modules/.bin/httpster & ./node_modules/.bin/webpack --config ./webpack.config.production.js", 9 | "dev": "./node_modules/.bin/httpster & ./node_modules/.bin/webpack --config ./webpack.config.js" 10 | }, 11 | "devDependencies": { 12 | "babel-core": "~4.3.0", 13 | "babel-loader": "~4.0.0", 14 | "httpster": "~1.0.1", 15 | "webpack": "~1.5.3" 16 | }, 17 | "dependencies": { 18 | "es6-promise": "~2.0.1", 19 | "fastclick": "~1.0.6", 20 | "flummox": "~2.12.4", 21 | "immutable": "~3.6.2", 22 | "object-assign": "~2.0.0", 23 | "react": "~0.12.2", 24 | "react-immutable-render-mixin": "~0.8.0", 25 | "react-router": "~0.12.4", 26 | "superagent": "~0.21.0" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var { RouteHandler } = require('react-router'); 5 | var TopBar = require('./components/TopBar'); 6 | var { State } = require('react-router'); 7 | 8 | var App = React.createClass({ 9 | 10 | mixins: [ State ], 11 | 12 | render() { 13 | return ( 14 |
15 | 16 |
17 | 18 |
19 |
20 | ); 21 | } 22 | }); 23 | module.exports = App; 24 | -------------------------------------------------------------------------------- /src/Flux.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var { Flummox } = require('flummox'); 4 | 5 | var FrameworkActions = require('./actions/FrameworkActions'); 6 | var FavoriteActions = require('./actions/FavoriteActions'); 7 | 8 | var FrameworkStore = require('./stores/FrameworkStore'); 9 | var SearchFrameworkStore = require('./stores/SearchFrameworkStore'); 10 | 11 | class Flux extends Flummox { 12 | constructor() { 13 | super(); 14 | this.createActions('framework', FrameworkActions); 15 | this.createActions('favorite', FavoriteActions); 16 | 17 | this.createStore('searchFramework', SearchFrameworkStore, this); 18 | this.createStore('framework', FrameworkStore, this); 19 | 20 | this.on('dispatch', function (payload) { 21 | console.log('dispatching -> ', payload); 22 | }); 23 | this.on('error', function (err) { 24 | console.error(err.stack); 25 | }); 26 | } 27 | } 28 | 29 | module.exports = Flux; 30 | -------------------------------------------------------------------------------- /src/actions/FavoriteActions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var { Actions } = require('flummox'); 4 | var localStorageUtils = require('../utils/localStorageUtils'); 5 | 6 | class FavoriteActions extends Actions { 7 | 8 | addFavorite (id) { 9 | localStorageUtils.push('favorites', id); 10 | 11 | return id; 12 | } 13 | 14 | removeFavorite (id) { 15 | localStorageUtils.pop('favorites', id); 16 | 17 | return id; 18 | } 19 | 20 | } 21 | 22 | module.exports = FavoriteActions; 23 | -------------------------------------------------------------------------------- /src/actions/FrameworkActions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var { Actions } = require('flummox'); 4 | var FrameworkApi = require('../api/FrameworkApi'); 5 | 6 | class FrameworkActions extends Actions { 7 | 8 | searchFrameworks(q) { 9 | return FrameworkApi.getFrameworks(q); 10 | } 11 | 12 | newSearch(q) { 13 | return q; 14 | } 15 | 16 | getFrameworkById(id) { 17 | return FrameworkApi.getFrameworkById(id); 18 | } 19 | } 20 | 21 | module.exports = FrameworkActions; 22 | -------------------------------------------------------------------------------- /src/api/FrameworkApi.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Promise = require('es6-promise').Promise; // jshint ignore:line 4 | var assign = require('object-assign'); 5 | var localStorageUtils = require('../utils/localStorageUtils'); 6 | var ApiUtils = require('../utils/ApiUtils'); 7 | 8 | var FrameworkApi = { 9 | getFrameworks(q = '') { 10 | return new Promise(function (resolve, reject) { 11 | ApiUtils.get('frameworks.json', {q}).then(function (res) { 12 | var favs = localStorageUtils.getAll('favorites'); 13 | var results = res.results.filter(framework => 14 | framework.name.indexOf(q.toLowerCase()) > -1); 15 | resolve({ 16 | response: results.map(framework => { 17 | framework.isFavorited = favs.indexOf(framework.id) > -1; 18 | 19 | return assign({id: framework.id}, framework); 20 | }), 21 | query: q 22 | }); 23 | }).catch(function (err) { 24 | reject(err); 25 | }); 26 | }); 27 | }, 28 | 29 | getFrameworkById(id) { 30 | return new Promise(function (resolve, reject) { 31 | ApiUtils.get('frameworks.json').then(function (res) { 32 | var favs = localStorageUtils.getAll('favorites'); 33 | var framework = res.results.filter(framework => framework.id === id)[0] || {}; 34 | var result = {}; 35 | 36 | if (framework.id) { 37 | framework.isFavorited = favs.indexOf(framework.id) > -1; 38 | result = assign({id: framework.id}, framework); 39 | } 40 | resolve({ 41 | response: result 42 | }); 43 | }).catch(function (err) { 44 | reject(err); 45 | }); 46 | }); 47 | } 48 | }; 49 | 50 | module.exports = FrameworkApi; 51 | -------------------------------------------------------------------------------- /src/client.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var Router = require('react-router'); 5 | var { Route, DefaultRoute } = Router; 6 | var App = require('./App'); 7 | var List = require('./components/handlers/List'); 8 | var Detail = require('./components/handlers/Detail'); 9 | var Search = require('./components/handlers/Search'); 10 | var flux = new (require('./Flux'))(); 11 | 12 | // Expose globally 13 | window.React = React; 14 | 15 | var FastClick = require('fastclick'); 16 | FastClick.attach(document.body); 17 | 18 | var routes = ( 19 | 20 | 21 | 22 | 23 | 24 | ); 25 | 26 | Router.run(routes, function (Handler, state) { // jshint unused:false 27 | React.withContext({flux}, function() { 28 | React.render(, document.getElementById('app')); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /src/components/FrameworkDetail.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var { PropTypes } = React; 5 | var ImmutableRenderMixin = require('react-immutable-render-mixin'); 6 | 7 | var FrameworkDetailElement = React.createClass({ 8 | 9 | mixins: [ImmutableRenderMixin], 10 | 11 | propTypes: { 12 | framework: PropTypes.object.isRequired, 13 | onAddToFavoritesClick: PropTypes.func.isRequired, 14 | onRemoveFromFavoritesClick: PropTypes.func.isRequired 15 | }, 16 | 17 | render() { 18 | var framework = this.props.framework.toJS(); 19 | return ( 20 |
21 |
    22 |
  • 23 | 24 |
    25 |

    {framework.nameDesc}

    26 |
    27 |
  • 28 |
29 | 30 |
31 | 32 |

{framework.description}

33 |
34 | 35 | { 36 | framework.isFavorited ? 37 |
38 | 39 | 40 | Remove from Favs 41 | 42 |
: 43 |
44 | 45 | 46 | Add to Favs 47 | 48 |
49 | } 50 | 51 |
52 | ); 53 | } 54 | 55 | }); 56 | 57 | module.exports = FrameworkDetailElement; 58 | -------------------------------------------------------------------------------- /src/components/FrameworkListElement.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var { PropTypes } = React; 5 | var { Link } = require('react-router'); 6 | var ImmutableRenderMixin = require('react-immutable-render-mixin'); 7 | 8 | var FrameworkListElement = React.createClass({ 9 | 10 | mixins: [ImmutableRenderMixin], 11 | 12 | propTypes: { 13 | framework: PropTypes.object.isRequired 14 | }, 15 | 16 | render() { 17 | var { framework } = this.props; 18 | return ( 19 |
  • 20 | 21 | 22 |
    23 |

    {framework.nameDesc}

    24 |

    {framework.description.substring(0, 250) + '...'}

    25 |
    26 | 27 |
  • 28 | ); 29 | } 30 | }); 31 | 32 | module.exports = FrameworkListElement; 33 | -------------------------------------------------------------------------------- /src/components/TopBar.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var { PropTypes } = React; 5 | var { Link } = require('react-router'); 6 | 7 | var TopBar = React.createClass({ 8 | 9 | propTypes: { 10 | pathName: PropTypes.string.isRequired 11 | }, 12 | 13 | render() { 14 | var pathName = this.props.pathName; 15 | return ( 16 |
    17 | { 18 | pathName !== '/' ? 19 | Search : 20 | null 21 | } 22 | 23 | 24 |

    Flux Immutable Example

    25 |
    26 | ); 27 | } 28 | }); 29 | 30 | module.exports = TopBar; 31 | -------------------------------------------------------------------------------- /src/components/handlers/Detail.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var FluxMixin = require('flummox/mixin'); 5 | var FrameworkDetail = require('../FrameworkDetail'); 6 | 7 | var { State} = require('react-router'); 8 | 9 | //jshint newcap:false 10 | var Detail = React.createClass({ 11 | mixins: [State, FluxMixin({ 12 | framework:function(store){ 13 | return { 14 | framework: store.getById(this.getParams().id), 15 | isLoading: store.isLoading() 16 | }; 17 | } 18 | })], 19 | 20 | componentDidMount() { 21 | this.flux.getActions('framework').getFrameworkById(this.getParams().id); 22 | }, 23 | 24 | render() { 25 | var { framework, isLoading } = this.state; 26 | if (isLoading){ 27 | return
    Loading...
    ; 28 | } 29 | if (!framework){ 30 | return
    Nothing to display
    ; 31 | } 32 | return ( 33 |
    34 | 38 |
    39 | ); 40 | }, 41 | 42 | handleAddToFavorites (id) { 43 | this.context.flux.getActions('favorite').addFavorite(id); 44 | }, 45 | 46 | handleRemoveFromFavorites (id) { 47 | this.context.flux.getActions('favorite').removeFavorite(id); 48 | } 49 | }); 50 | 51 | module.exports = Detail; 52 | -------------------------------------------------------------------------------- /src/components/handlers/List.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var FrameworkListElement = require('../FrameworkListElement'); 5 | var FluxMixin = require('flummox/mixin'); 6 | var { State} = require('react-router'); 7 | 8 | var List = React.createClass({ 9 | //jshint newcap:false 10 | mixins: [State, FluxMixin({ 11 | searchFramework: function(store){ 12 | return { 13 | frameworks: store.getframeworks(this.getQuery().q), 14 | isLoading: store.isLoading() 15 | }; 16 | } 17 | })], 18 | 19 | componentDidMount() { 20 | this.flux.getActions('framework').searchFrameworks(this.getQuery().q); 21 | }, 22 | 23 | render() { 24 | var { frameworks, isLoading } = this.state; 25 | if (isLoading){ 26 | return
    Loading...
    ; 27 | } 28 | if (frameworks.size === 0){ 29 | return
    Try another search
    ; 30 | } 31 | return ( 32 |
    33 |
      34 | {this.state.frameworks.toJS().map(framework => 35 | ) 36 | } 37 |
    38 |
    39 | ); 40 | } 41 | }); 42 | module.exports = List; 43 | -------------------------------------------------------------------------------- /src/components/handlers/Search.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var React = require('react'); 4 | var { Navigation } = require('react-router'); 5 | var LinkedStateMixin = require('react/lib/LinkedStateMixin'); 6 | 7 | var Search = React.createClass({ 8 | mixins: [LinkedStateMixin, Navigation], 9 | 10 | getInitialState() { 11 | return { 12 | search: 'React' 13 | }; 14 | }, 15 | 16 | render() { 17 | return ( 18 |
    19 |
    20 | 21 | 22 |
    23 |
    24 | source code on GitHub 25 |
    26 | follow me on twitter 27 |
    28 |
    29 | ); 30 | }, 31 | 32 | handleSubmit(e) { 33 | e.preventDefault(); 34 | this.transitionTo('list', null, { 35 | q: this.state.search 36 | }); 37 | } 38 | }); 39 | module.exports = Search; 40 | -------------------------------------------------------------------------------- /src/css/main.css: -------------------------------------------------------------------------------- 1 | .small-img{ 2 | height: 80px; 3 | width:80px; 4 | } 5 | 6 | .btn.active, .btn:active:not(.btn-block){ 7 | background-color: inherit; 8 | } 9 | 10 | .padding { 11 | padding: 10px; 12 | } 13 | 14 | .list h2 { 15 | margin: 0px 0px 2px; 16 | font-size: 16px; 17 | font-weight: 400; 18 | line-height: 1.2; 19 | } 20 | 21 | .list h3 { 22 | margin: 0px 0px 4px; 23 | font-size: 14px; 24 | line-height: 1.2; 25 | } 26 | 27 | .now { 28 | overflow: hidden; 29 | text-overflow: ellipsis; 30 | white-space: nowrap; 31 | } 32 | 33 | 34 | .table-view-cell > a:not(.btn){ 35 | margin: -11px -100px -11px -15px; 36 | } 37 | 38 | .full-image { 39 | width: 100% 40 | } 41 | 42 | .media-body { 43 | padding: 18px; 44 | } 45 | 46 | .fav { 47 | text-align: center; 48 | } 49 | 50 | .detail .table-view-cell { 51 | padding: 11px 15px 11px 15px; 52 | } 53 | 54 | .favLink { 55 | cursor: pointer; 56 | } 57 | 58 | .text-center { 59 | text-align: center; 60 | } 61 | -------------------------------------------------------------------------------- /src/css/ratchet.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * ===================================================== 3 | * Ratchet v2.0.2 (http://goratchet.com) 4 | * Copyright 2014 Connor Sears 5 | * Licensed under MIT (https://github.com/twbs/ratchet/blob/master/LICENSE) 6 | * 7 | * v2.0.2 designed by @connors. 8 | * ===================================================== 9 | */ 10 | 11 | /*! normalize.css v3.0.1 | MIT License | git.io/normalize */ 12 | html { 13 | font-family: sans-serif; 14 | -webkit-text-size-adjust: 100%; 15 | -ms-text-size-adjust: 100%; 16 | } 17 | 18 | body { 19 | margin: 0; 20 | } 21 | 22 | article, 23 | aside, 24 | details, 25 | figcaption, 26 | figure, 27 | footer, 28 | header, 29 | hgroup, 30 | main, 31 | nav, 32 | section, 33 | summary { 34 | display: block; 35 | } 36 | 37 | audio, 38 | canvas, 39 | progress, 40 | video { 41 | display: inline-block; 42 | vertical-align: baseline; 43 | } 44 | 45 | audio:not([controls]) { 46 | display: none; 47 | height: 0; 48 | } 49 | 50 | [hidden], 51 | template { 52 | display: none; 53 | } 54 | 55 | a { 56 | background: transparent; 57 | } 58 | 59 | a:active, 60 | a:hover { 61 | outline: 0; 62 | } 63 | 64 | abbr[title] { 65 | border-bottom: 1px dotted; 66 | } 67 | 68 | b, 69 | strong { 70 | font-weight: bold; 71 | } 72 | 73 | dfn { 74 | font-style: italic; 75 | } 76 | 77 | h1 { 78 | margin: .67em 0; 79 | font-size: 2em; 80 | } 81 | 82 | mark { 83 | color: #000; 84 | background: #ff0; 85 | } 86 | 87 | small { 88 | font-size: 80%; 89 | } 90 | 91 | sub, 92 | sup { 93 | position: relative; 94 | font-size: 75%; 95 | line-height: 0; 96 | vertical-align: baseline; 97 | } 98 | 99 | sup { 100 | top: -.5em; 101 | } 102 | 103 | sub { 104 | bottom: -.25em; 105 | } 106 | 107 | img { 108 | border: 0; 109 | } 110 | 111 | svg:not(:root) { 112 | overflow: hidden; 113 | } 114 | 115 | figure { 116 | margin: 1em 40px; 117 | } 118 | 119 | hr { 120 | height: 0; 121 | -moz-box-sizing: content-box; 122 | box-sizing: content-box; 123 | } 124 | 125 | pre { 126 | overflow: auto; 127 | } 128 | 129 | code, 130 | kbd, 131 | pre, 132 | samp { 133 | font-family: monospace, monospace; 134 | font-size: 1em; 135 | } 136 | 137 | button, 138 | input, 139 | optgroup, 140 | select, 141 | textarea { 142 | margin: 0; 143 | font: inherit; 144 | color: inherit; 145 | } 146 | 147 | button { 148 | overflow: visible; 149 | } 150 | 151 | button, 152 | select { 153 | text-transform: none; 154 | } 155 | 156 | button, 157 | html input[type="button"], 158 | input[type="reset"], 159 | input[type="submit"] { 160 | -webkit-appearance: button; 161 | cursor: pointer; 162 | } 163 | 164 | button[disabled], 165 | html input[disabled] { 166 | cursor: default; 167 | } 168 | 169 | button::-moz-focus-inner, 170 | input::-moz-focus-inner { 171 | padding: 0; 172 | border: 0; 173 | } 174 | 175 | input { 176 | line-height: normal; 177 | } 178 | 179 | input[type="checkbox"], 180 | input[type="radio"] { 181 | box-sizing: border-box; 182 | padding: 0; 183 | } 184 | 185 | input[type="number"]::-webkit-inner-spin-button, 186 | input[type="number"]::-webkit-outer-spin-button { 187 | height: auto; 188 | } 189 | 190 | input[type="search"] { 191 | -webkit-box-sizing: content-box; 192 | -moz-box-sizing: content-box; 193 | box-sizing: content-box; 194 | -webkit-appearance: textfield; 195 | } 196 | 197 | input[type="search"]::-webkit-search-cancel-button, 198 | input[type="search"]::-webkit-search-decoration { 199 | -webkit-appearance: none; 200 | } 201 | 202 | fieldset { 203 | padding: .35em .625em .75em; 204 | margin: 0 2px; 205 | border: 1px solid #c0c0c0; 206 | } 207 | 208 | legend { 209 | padding: 0; 210 | border: 0; 211 | } 212 | 213 | textarea { 214 | overflow: auto; 215 | } 216 | 217 | optgroup { 218 | font-weight: bold; 219 | } 220 | 221 | table { 222 | border-spacing: 0; 223 | border-collapse: collapse; 224 | } 225 | 226 | td, 227 | th { 228 | padding: 0; 229 | } 230 | 231 | * { 232 | -webkit-box-sizing: border-box; 233 | -moz-box-sizing: border-box; 234 | box-sizing: border-box; 235 | } 236 | 237 | body { 238 | position: fixed; 239 | top: 0; 240 | right: 0; 241 | bottom: 0; 242 | left: 0; 243 | font-family: "Helvetica Neue", Helvetica, sans-serif; 244 | font-size: 17px; 245 | line-height: 21px; 246 | color: #000; 247 | background-color: #fff; 248 | } 249 | 250 | a { 251 | color: #428bca; 252 | text-decoration: none; 253 | 254 | -webkit-tap-highlight-color: transparent; 255 | } 256 | a:active { 257 | color: #3071a9; 258 | } 259 | 260 | .content { 261 | position: absolute; 262 | top: 0; 263 | right: 0; 264 | bottom: 0; 265 | left: 0; 266 | overflow: auto; 267 | -webkit-overflow-scrolling: touch; 268 | background-color: #fff; 269 | } 270 | 271 | .content > * { 272 | -webkit-transform: translateZ(0); 273 | -ms-transform: translateZ(0); 274 | transform: translateZ(0); 275 | } 276 | 277 | .bar-nav ~ .content { 278 | padding-top: 44px; 279 | } 280 | 281 | .bar-header-secondary ~ .content { 282 | padding-top: 88px; 283 | } 284 | 285 | .bar-footer ~ .content { 286 | padding-bottom: 44px; 287 | } 288 | 289 | .bar-footer-secondary ~ .content { 290 | padding-bottom: 88px; 291 | } 292 | 293 | .bar-tab ~ .content { 294 | padding-bottom: 50px; 295 | } 296 | 297 | .bar-footer-secondary-tab ~ .content { 298 | padding-bottom: 94px; 299 | } 300 | 301 | .content-padded { 302 | margin: 10px; 303 | } 304 | 305 | .pull-left { 306 | float: left; 307 | } 308 | 309 | .pull-right { 310 | float: right; 311 | } 312 | 313 | .clearfix:before, .clearfix:after { 314 | display: table; 315 | content: " "; 316 | } 317 | .clearfix:after { 318 | clear: both; 319 | } 320 | 321 | h1, h2, h3, h4, h5, h6 { 322 | margin-top: 0; 323 | margin-bottom: 10px; 324 | line-height: 1; 325 | } 326 | 327 | h1, .h1 { 328 | font-size: 36px; 329 | } 330 | 331 | h2, .h2 { 332 | font-size: 30px; 333 | } 334 | 335 | h3, .h3 { 336 | font-size: 24px; 337 | } 338 | 339 | h4, .h4 { 340 | font-size: 18px; 341 | } 342 | 343 | h5, .h5 { 344 | margin-top: 20px; 345 | font-size: 14px; 346 | } 347 | 348 | h6, .h6 { 349 | margin-top: 20px; 350 | font-size: 12px; 351 | } 352 | 353 | p { 354 | margin-top: 0; 355 | margin-bottom: 10px; 356 | font-size: 14px; 357 | color: #777; 358 | } 359 | 360 | .btn { 361 | position: relative; 362 | display: inline-block; 363 | padding: 6px 8px 7px; 364 | margin-bottom: 0; 365 | font-size: 12px; 366 | font-weight: 400; 367 | line-height: 1; 368 | color: #333; 369 | text-align: center; 370 | white-space: nowrap; 371 | vertical-align: top; 372 | cursor: pointer; 373 | background-color: white; 374 | border: 1px solid #ccc; 375 | border-radius: 3px; 376 | } 377 | .btn:active, .btn.active { 378 | color: inherit; 379 | background-color: #ccc; 380 | } 381 | .btn:disabled, .btn.disabled { 382 | opacity: .6; 383 | } 384 | 385 | .btn-primary { 386 | color: #fff; 387 | background-color: #428bca; 388 | border: 1px solid #428bca; 389 | } 390 | .btn-primary:active, .btn-primary.active { 391 | color: #fff; 392 | background-color: #3071a9; 393 | border: 1px solid #3071a9; 394 | } 395 | 396 | .btn-positive { 397 | color: #fff; 398 | background-color: #5cb85c; 399 | border: 1px solid #5cb85c; 400 | } 401 | .btn-positive:active, .btn-positive.active { 402 | color: #fff; 403 | background-color: #449d44; 404 | border: 1px solid #449d44; 405 | } 406 | 407 | .btn-negative { 408 | color: #fff; 409 | background-color: #d9534f; 410 | border: 1px solid #d9534f; 411 | } 412 | .btn-negative:active, .btn-negative.active { 413 | color: #fff; 414 | background-color: #c9302c; 415 | border: 1px solid #c9302c; 416 | } 417 | 418 | .btn-outlined { 419 | background-color: transparent; 420 | } 421 | .btn-outlined.btn-primary { 422 | color: #428bca; 423 | } 424 | .btn-outlined.btn-positive { 425 | color: #5cb85c; 426 | } 427 | .btn-outlined.btn-negative { 428 | color: #d9534f; 429 | } 430 | .btn-outlined.btn-primary:active, .btn-outlined.btn-positive:active, .btn-outlined.btn-negative:active { 431 | color: #fff; 432 | } 433 | 434 | .btn-link { 435 | padding-top: 6px; 436 | padding-bottom: 6px; 437 | color: #428bca; 438 | background-color: transparent; 439 | border: 0; 440 | } 441 | .btn-link:active, .btn-link.active { 442 | color: #3071a9; 443 | background-color: transparent; 444 | } 445 | 446 | .btn-block { 447 | display: block; 448 | width: 100%; 449 | padding: 15px 0; 450 | margin-bottom: 10px; 451 | font-size: 18px; 452 | } 453 | 454 | input[type="submit"], 455 | input[type="reset"], 456 | input[type="button"] { 457 | width: 100%; 458 | } 459 | 460 | .btn .badge { 461 | margin: -2px -4px -2px 4px; 462 | font-size: 12px; 463 | background-color: rgba(0, 0, 0, .15); 464 | } 465 | 466 | .btn .badge-inverted, 467 | .btn:active .badge-inverted { 468 | background-color: transparent; 469 | } 470 | 471 | .btn-primary:active .badge-inverted, 472 | .btn-positive:active .badge-inverted, 473 | .btn-negative:active .badge-inverted { 474 | color: #fff; 475 | } 476 | 477 | .btn-block .badge { 478 | position: absolute; 479 | right: 0; 480 | margin-right: 10px; 481 | } 482 | 483 | .btn .icon { 484 | font-size: inherit; 485 | } 486 | 487 | .bar { 488 | position: fixed; 489 | right: 0; 490 | left: 0; 491 | z-index: 10; 492 | height: 44px; 493 | padding-right: 10px; 494 | padding-left: 10px; 495 | background-color: white; 496 | border-bottom: 1px solid #ddd; 497 | 498 | -webkit-backface-visibility: hidden; 499 | backface-visibility: hidden; 500 | } 501 | 502 | .bar-header-secondary { 503 | top: 44px; 504 | } 505 | 506 | .bar-footer { 507 | bottom: 0; 508 | } 509 | 510 | .bar-footer-secondary { 511 | bottom: 44px; 512 | } 513 | 514 | .bar-footer-secondary-tab { 515 | bottom: 50px; 516 | } 517 | 518 | .bar-footer, 519 | .bar-footer-secondary, 520 | .bar-footer-secondary-tab { 521 | border-top: 1px solid #ddd; 522 | border-bottom: 0; 523 | } 524 | 525 | .bar-nav { 526 | top: 0; 527 | } 528 | 529 | .title { 530 | position: absolute; 531 | display: block; 532 | width: 100%; 533 | padding: 0; 534 | margin: 0 -10px; 535 | font-size: 17px; 536 | font-weight: 500; 537 | line-height: 44px; 538 | color: #000; 539 | text-align: center; 540 | white-space: nowrap; 541 | } 542 | 543 | .title a { 544 | color: inherit; 545 | } 546 | 547 | .bar-tab { 548 | bottom: 0; 549 | display: table; 550 | width: 100%; 551 | height: 50px; 552 | padding: 0; 553 | table-layout: fixed; 554 | border-top: 1px solid #ddd; 555 | border-bottom: 0; 556 | } 557 | .bar-tab .tab-item { 558 | display: table-cell; 559 | width: 1%; 560 | height: 50px; 561 | color: #929292; 562 | text-align: center; 563 | vertical-align: middle; 564 | } 565 | .bar-tab .tab-item.active, .bar-tab .tab-item:active { 566 | color: #428bca; 567 | } 568 | .bar-tab .tab-item .icon { 569 | top: 3px; 570 | width: 24px; 571 | height: 24px; 572 | padding-top: 0; 573 | padding-bottom: 0; 574 | } 575 | .bar-tab .tab-item .icon ~ .tab-label { 576 | display: block; 577 | font-size: 11px; 578 | } 579 | 580 | .bar .btn { 581 | position: relative; 582 | top: 7px; 583 | z-index: 20; 584 | padding: 6px 12px 7px; 585 | margin-top: 0; 586 | font-weight: 400; 587 | } 588 | .bar .btn.pull-right { 589 | margin-left: 10px; 590 | } 591 | .bar .btn.pull-left { 592 | margin-right: 10px; 593 | } 594 | 595 | .bar .btn-link { 596 | top: 0; 597 | padding: 0; 598 | font-size: 16px; 599 | line-height: 44px; 600 | color: #428bca; 601 | border: 0; 602 | } 603 | .bar .btn-link:active, .bar .btn-link.active { 604 | color: #3071a9; 605 | } 606 | 607 | .bar .btn-block { 608 | top: 6px; 609 | padding: 7px 0; 610 | margin-bottom: 0; 611 | font-size: 16px; 612 | } 613 | 614 | .bar .btn-nav.pull-left { 615 | margin-left: -5px; 616 | } 617 | .bar .btn-nav.pull-left .icon-left-nav { 618 | margin-right: -3px; 619 | } 620 | .bar .btn-nav.pull-right { 621 | margin-right: -5px; 622 | } 623 | .bar .btn-nav.pull-right .icon-right-nav { 624 | margin-left: -3px; 625 | } 626 | 627 | .bar .icon { 628 | position: relative; 629 | z-index: 20; 630 | padding-top: 10px; 631 | padding-bottom: 10px; 632 | font-size: 24px; 633 | } 634 | .bar .btn .icon { 635 | top: 3px; 636 | padding: 0; 637 | } 638 | .bar .title .icon { 639 | padding: 0; 640 | } 641 | .bar .title .icon.icon-caret { 642 | top: 4px; 643 | margin-left: -5px; 644 | } 645 | 646 | .bar input[type="search"] { 647 | height: 29px; 648 | margin: 6px 0; 649 | } 650 | 651 | .bar .segmented-control { 652 | top: 7px; 653 | margin: 0 auto; 654 | } 655 | 656 | .badge { 657 | display: inline-block; 658 | padding: 2px 9px 3px; 659 | font-size: 12px; 660 | line-height: 1; 661 | color: #333; 662 | background-color: rgba(0, 0, 0, .15); 663 | border-radius: 100px; 664 | } 665 | .badge.badge-inverted { 666 | padding: 0 5px 0 0; 667 | background-color: transparent; 668 | } 669 | 670 | .badge-primary { 671 | color: #fff; 672 | background-color: #428bca; 673 | } 674 | .badge-primary.badge-inverted { 675 | color: #428bca; 676 | } 677 | 678 | .badge-positive { 679 | color: #fff; 680 | background-color: #5cb85c; 681 | } 682 | .badge-positive.badge-inverted { 683 | color: #5cb85c; 684 | } 685 | 686 | .badge-negative { 687 | color: #fff; 688 | background-color: #d9534f; 689 | } 690 | .badge-negative.badge-inverted { 691 | color: #d9534f; 692 | } 693 | 694 | .card { 695 | margin: 10px; 696 | overflow: hidden; 697 | background-color: white; 698 | border: 1px solid #ddd; 699 | border-radius: 6px; 700 | } 701 | 702 | .card .table-view { 703 | margin-bottom: 0; 704 | border-top: 0; 705 | border-bottom: 0; 706 | } 707 | .card .table-view .table-view-divider:first-child { 708 | top: 0; 709 | border-top-left-radius: 6px; 710 | border-top-right-radius: 6px; 711 | } 712 | .card .table-view .table-view-divider:last-child { 713 | border-bottom-right-radius: 6px; 714 | border-bottom-left-radius: 6px; 715 | } 716 | 717 | .card .table-view-cell:last-child { 718 | border-bottom: 0; 719 | } 720 | 721 | .table-view { 722 | padding-left: 0; 723 | margin-top: 0; 724 | margin-bottom: 15px; 725 | list-style: none; 726 | background-color: #fff; 727 | border-top: 1px solid #ddd; 728 | border-bottom: 1px solid #ddd; 729 | } 730 | 731 | .table-view-cell { 732 | position: relative; 733 | padding: 11px 65px 11px 15px; 734 | overflow: hidden; 735 | border-bottom: 1px solid #ddd; 736 | } 737 | .table-view-cell:last-child { 738 | border-bottom: 0; 739 | } 740 | .table-view-cell > a:not(.btn) { 741 | position: relative; 742 | display: block; 743 | padding: inherit; 744 | margin: -11px -65px -11px -15px; 745 | overflow: hidden; 746 | color: inherit; 747 | } 748 | .table-view-cell > a:not(.btn):active { 749 | background-color: #eee; 750 | } 751 | .table-view-cell p { 752 | margin-bottom: 0; 753 | } 754 | 755 | .table-view-divider { 756 | padding-top: 6px; 757 | padding-bottom: 6px; 758 | padding-left: 15px; 759 | margin-top: -1px; 760 | margin-left: 0; 761 | font-weight: 500; 762 | color: #999; 763 | background-color: #fafafa; 764 | border-top: 1px solid #ddd; 765 | border-bottom: 1px solid #ddd; 766 | } 767 | 768 | .table-view .media, 769 | .table-view .media-body { 770 | overflow: hidden; 771 | } 772 | 773 | .table-view .media-object.pull-left { 774 | margin-right: 10px; 775 | } 776 | .table-view .media-object.pull-right { 777 | margin-left: 10px; 778 | } 779 | 780 | .table-view-cell > .btn, 781 | .table-view-cell > .badge, 782 | .table-view-cell > .toggle, 783 | .table-view-cell > a > .btn, 784 | .table-view-cell > a > .badge, 785 | .table-view-cell > a > .toggle { 786 | position: absolute; 787 | top: 50%; 788 | right: 15px; 789 | -webkit-transform: translateY(-50%); 790 | -ms-transform: translateY(-50%); 791 | transform: translateY(-50%); 792 | } 793 | .table-view-cell .navigate-left > .btn, 794 | .table-view-cell .navigate-left > .badge, 795 | .table-view-cell .navigate-left > .toggle, 796 | .table-view-cell .navigate-right > .btn, 797 | .table-view-cell .navigate-right > .badge, 798 | .table-view-cell .navigate-right > .toggle, 799 | .table-view-cell .push-left > .btn, 800 | .table-view-cell .push-left > .badge, 801 | .table-view-cell .push-left > .toggle, 802 | .table-view-cell .push-right > .btn, 803 | .table-view-cell .push-right > .badge, 804 | .table-view-cell .push-right > .toggle, 805 | .table-view-cell > a .navigate-left > .btn, 806 | .table-view-cell > a .navigate-left > .badge, 807 | .table-view-cell > a .navigate-left > .toggle, 808 | .table-view-cell > a .navigate-right > .btn, 809 | .table-view-cell > a .navigate-right > .badge, 810 | .table-view-cell > a .navigate-right > .toggle, 811 | .table-view-cell > a .push-left > .btn, 812 | .table-view-cell > a .push-left > .badge, 813 | .table-view-cell > a .push-left > .toggle, 814 | .table-view-cell > a .push-right > .btn, 815 | .table-view-cell > a .push-right > .badge, 816 | .table-view-cell > a .push-right > .toggle { 817 | right: 35px; 818 | } 819 | 820 | .content > .table-view:first-child { 821 | margin-top: 15px; 822 | } 823 | 824 | input, 825 | textarea, 826 | button, 827 | select { 828 | font-family: "Helvetica Neue", Helvetica, sans-serif; 829 | font-size: 17px; 830 | } 831 | 832 | select, 833 | textarea, 834 | input[type="text"], 835 | input[type="search"], 836 | input[type="password"], 837 | input[type="datetime"], 838 | input[type="datetime-local"], 839 | input[type="date"], 840 | input[type="month"], 841 | input[type="time"], 842 | input[type="week"], 843 | input[type="number"], 844 | input[type="email"], 845 | input[type="url"], 846 | input[type="tel"], 847 | input[type="color"] { 848 | width: 100%; 849 | height: 35px; 850 | -webkit-appearance: none; 851 | padding: 0 15px; 852 | margin-bottom: 15px; 853 | line-height: 21px; 854 | background-color: #fff; 855 | border: 1px solid #ddd; 856 | border-radius: 3px; 857 | outline: none; 858 | } 859 | 860 | input[type="search"] { 861 | -webkit-box-sizing: border-box; 862 | -moz-box-sizing: border-box; 863 | box-sizing: border-box; 864 | padding: 0 10px; 865 | font-size: 16px; 866 | border-radius: 20px; 867 | } 868 | 869 | input[type="search"]:focus { 870 | text-align: left; 871 | } 872 | 873 | textarea { 874 | height: auto; 875 | } 876 | 877 | select { 878 | height: auto; 879 | font-size: 14px; 880 | background-color: #f8f8f8; 881 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .1); 882 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, .1); 883 | } 884 | 885 | .input-group { 886 | background-color: #fff; 887 | } 888 | 889 | .input-group input, 890 | .input-group textarea { 891 | margin-bottom: 0; 892 | background-color: transparent; 893 | border-top: 0; 894 | border-right: 0; 895 | border-left: 0; 896 | border-radius: 0; 897 | -webkit-box-shadow: none; 898 | box-shadow: none; 899 | } 900 | 901 | .input-row { 902 | height: 35px; 903 | overflow: hidden; 904 | border-bottom: 1px solid #ddd; 905 | } 906 | 907 | .input-row label { 908 | float: left; 909 | width: 35%; 910 | padding: 8px 15px; 911 | font-family: "Helvetica Neue", Helvetica, sans-serif; 912 | line-height: 1.1; 913 | } 914 | 915 | .input-row input { 916 | float: right; 917 | width: 65%; 918 | padding-left: 0; 919 | margin-bottom: 0; 920 | border: 0; 921 | } 922 | 923 | .segmented-control { 924 | position: relative; 925 | display: table; 926 | overflow: hidden; 927 | font-size: 12px; 928 | font-weight: 400; 929 | background-color: white; 930 | border: 1px solid #ccc; 931 | border-radius: 3px; 932 | } 933 | .segmented-control .control-item { 934 | display: table-cell; 935 | width: 1%; 936 | padding-top: 6px; 937 | padding-bottom: 7px; 938 | overflow: hidden; 939 | line-height: 1; 940 | color: #333; 941 | text-align: center; 942 | text-overflow: ellipsis; 943 | white-space: nowrap; 944 | border-left: 1px solid #ccc; 945 | } 946 | .segmented-control .control-item:first-child { 947 | border-left-width: 0; 948 | } 949 | .segmented-control .control-item:active { 950 | background-color: #eee; 951 | } 952 | .segmented-control .control-item.active { 953 | background-color: #ccc; 954 | } 955 | 956 | .segmented-control-primary { 957 | border-color: #428bca; 958 | } 959 | .segmented-control-primary .control-item { 960 | color: #428bca; 961 | border-color: inherit; 962 | } 963 | .segmented-control-primary .control-item:active { 964 | background-color: #cde1f1; 965 | } 966 | .segmented-control-primary .control-item.active { 967 | color: #fff; 968 | background-color: #428bca; 969 | } 970 | 971 | .segmented-control-positive { 972 | border-color: #5cb85c; 973 | } 974 | .segmented-control-positive .control-item { 975 | color: #5cb85c; 976 | border-color: inherit; 977 | } 978 | .segmented-control-positive .control-item:active { 979 | background-color: #d8eed8; 980 | } 981 | .segmented-control-positive .control-item.active { 982 | color: #fff; 983 | background-color: #5cb85c; 984 | } 985 | 986 | .segmented-control-negative { 987 | border-color: #d9534f; 988 | } 989 | .segmented-control-negative .control-item { 990 | color: #d9534f; 991 | border-color: inherit; 992 | } 993 | .segmented-control-negative .control-item:active { 994 | background-color: #f9e2e2; 995 | } 996 | .segmented-control-negative .control-item.active { 997 | color: #fff; 998 | background-color: #d9534f; 999 | } 1000 | 1001 | .control-content { 1002 | display: none; 1003 | } 1004 | .control-content.active { 1005 | display: block; 1006 | } 1007 | 1008 | .popover { 1009 | position: fixed; 1010 | top: 55px; 1011 | left: 50%; 1012 | z-index: 20; 1013 | display: none; 1014 | width: 280px; 1015 | margin-left: -140px; 1016 | background-color: white; 1017 | border-radius: 6px; 1018 | -webkit-box-shadow: 0 0 15px rgba(0, 0, 0, .1); 1019 | box-shadow: 0 0 15px rgba(0, 0, 0, .1); 1020 | opacity: 0; 1021 | -webkit-transition: all .25s linear; 1022 | -moz-transition: all .25s linear; 1023 | transition: all .25s linear; 1024 | -webkit-transform: translate3d(0, -15px, 0); 1025 | -ms-transform: translate3d(0, -15px, 0); 1026 | transform: translate3d(0, -15px, 0); 1027 | } 1028 | .popover:before { 1029 | position: absolute; 1030 | top: -15px; 1031 | left: 50%; 1032 | width: 0; 1033 | height: 0; 1034 | margin-left: -15px; 1035 | content: ''; 1036 | border-right: 15px solid transparent; 1037 | border-bottom: 15px solid white; 1038 | border-left: 15px solid transparent; 1039 | } 1040 | .popover.visible { 1041 | opacity: 1; 1042 | -webkit-transform: translate3d(0, 0, 0); 1043 | -ms-transform: translate3d(0, 0, 0); 1044 | transform: translate3d(0, 0, 0); 1045 | } 1046 | .popover .bar ~ .table-view { 1047 | padding-top: 44px; 1048 | } 1049 | 1050 | .backdrop { 1051 | position: fixed; 1052 | top: 0; 1053 | right: 0; 1054 | bottom: 0; 1055 | left: 0; 1056 | z-index: 15; 1057 | background-color: rgba(0, 0, 0, .3); 1058 | } 1059 | 1060 | .popover .btn-block { 1061 | margin-bottom: 5px; 1062 | } 1063 | .popover .btn-block:last-child { 1064 | margin-bottom: 0; 1065 | } 1066 | 1067 | .popover .bar-nav { 1068 | border-bottom: 1px solid #ddd; 1069 | border-top-left-radius: 12px; 1070 | border-top-right-radius: 12px; 1071 | -webkit-box-shadow: none; 1072 | box-shadow: none; 1073 | } 1074 | 1075 | .popover .table-view { 1076 | max-height: 300px; 1077 | margin-bottom: 0; 1078 | overflow: auto; 1079 | -webkit-overflow-scrolling: touch; 1080 | background-color: #fff; 1081 | border-top: 0; 1082 | border-bottom: 0; 1083 | border-radius: 6px; 1084 | } 1085 | 1086 | .modal { 1087 | position: fixed; 1088 | top: 0; 1089 | z-index: 11; 1090 | width: 100%; 1091 | min-height: 100%; 1092 | overflow: hidden; 1093 | background-color: #fff; 1094 | opacity: 0; 1095 | -webkit-transition: -webkit-transform .25s, opacity 1ms .25s; 1096 | -moz-transition: -moz-transform .25s, opacity 1ms .25s; 1097 | transition: transform .25s, opacity 1ms .25s; 1098 | -webkit-transform: translate3d(0, 100%, 0); 1099 | -ms-transform: translate3d(0, 100%, 0); 1100 | transform: translate3d(0, 100%, 0); 1101 | } 1102 | .modal.active { 1103 | height: 100%; 1104 | opacity: 1; 1105 | -webkit-transition: -webkit-transform .25s; 1106 | -moz-transition: -moz-transform .25s; 1107 | transition: transform .25s; 1108 | -webkit-transform: translate3d(0, 0, 0); 1109 | -ms-transform: translate3d(0, 0, 0); 1110 | transform: translate3d(0, 0, 0); 1111 | } 1112 | 1113 | .slider { 1114 | width: 100%; 1115 | } 1116 | 1117 | .slider { 1118 | overflow: hidden; 1119 | background-color: #000; 1120 | } 1121 | .slider .slide-group { 1122 | position: relative; 1123 | font-size: 0; 1124 | white-space: nowrap; 1125 | -webkit-transition: all 0s linear; 1126 | -moz-transition: all 0s linear; 1127 | transition: all 0s linear; 1128 | } 1129 | .slider .slide-group .slide { 1130 | display: inline-block; 1131 | width: 100%; 1132 | height: 100%; 1133 | font-size: 14px; 1134 | vertical-align: top; 1135 | } 1136 | 1137 | .toggle { 1138 | position: relative; 1139 | display: block; 1140 | width: 74px; 1141 | height: 30px; 1142 | background-color: #fff; 1143 | border: 2px solid #ddd; 1144 | border-radius: 20px; 1145 | -webkit-transition-duration: .2s; 1146 | -moz-transition-duration: .2s; 1147 | transition-duration: .2s; 1148 | -webkit-transition-property: background-color, border; 1149 | -moz-transition-property: background-color, border; 1150 | transition-property: background-color, border; 1151 | } 1152 | .toggle .toggle-handle { 1153 | position: absolute; 1154 | top: -1px; 1155 | left: -1px; 1156 | z-index: 2; 1157 | width: 28px; 1158 | height: 28px; 1159 | background-color: #fff; 1160 | border: 1px solid #ddd; 1161 | border-radius: 100px; 1162 | -webkit-transition-duration: .2s; 1163 | -moz-transition-duration: .2s; 1164 | transition-duration: .2s; 1165 | -webkit-transition-property: -webkit-transform, border, width; 1166 | -moz-transition-property: -moz-transform, border, width; 1167 | transition-property: transform, border, width; 1168 | } 1169 | .toggle:before { 1170 | position: absolute; 1171 | top: 3px; 1172 | right: 11px; 1173 | font-size: 13px; 1174 | color: #999; 1175 | text-transform: uppercase; 1176 | content: "Off"; 1177 | } 1178 | .toggle.active { 1179 | background-color: #5cb85c; 1180 | border: 2px solid #5cb85c; 1181 | } 1182 | .toggle.active .toggle-handle { 1183 | border-color: #5cb85c; 1184 | -webkit-transform: translate3d(44px, 0, 0); 1185 | -ms-transform: translate3d(44px, 0, 0); 1186 | transform: translate3d(44px, 0, 0); 1187 | } 1188 | .toggle.active:before { 1189 | right: auto; 1190 | left: 15px; 1191 | color: #fff; 1192 | content: "On"; 1193 | } 1194 | .toggle input[type="checkbox"] { 1195 | display: none; 1196 | } 1197 | 1198 | .content.fade { 1199 | left: 0; 1200 | opacity: 0; 1201 | } 1202 | .content.fade.in { 1203 | opacity: 1; 1204 | } 1205 | .content.sliding { 1206 | z-index: 2; 1207 | -webkit-transition: -webkit-transform .4s; 1208 | -moz-transition: -moz-transform .4s; 1209 | transition: transform .4s; 1210 | -webkit-transform: translate3d(0, 0, 0); 1211 | -ms-transform: translate3d(0, 0, 0); 1212 | transform: translate3d(0, 0, 0); 1213 | } 1214 | .content.sliding.left { 1215 | z-index: 1; 1216 | -webkit-transform: translate3d(-100%, 0, 0); 1217 | -ms-transform: translate3d(-100%, 0, 0); 1218 | transform: translate3d(-100%, 0, 0); 1219 | } 1220 | .content.sliding.right { 1221 | z-index: 3; 1222 | -webkit-transform: translate3d(100%, 0, 0); 1223 | -ms-transform: translate3d(100%, 0, 0); 1224 | transform: translate3d(100%, 0, 0); 1225 | } 1226 | 1227 | .navigate-left:after, 1228 | .navigate-right:after, 1229 | .push-left:after, 1230 | .push-right:after { 1231 | position: absolute; 1232 | top: 50%; 1233 | display: inline-block; 1234 | font-family: Ratchicons; 1235 | font-size: inherit; 1236 | line-height: 1; 1237 | color: #bbb; 1238 | text-decoration: none; 1239 | -webkit-transform: translateY(-50%); 1240 | -ms-transform: translateY(-50%); 1241 | transform: translateY(-50%); 1242 | 1243 | -webkit-font-smoothing: antialiased; 1244 | } 1245 | 1246 | .navigate-left:after, 1247 | .push-left:after { 1248 | left: 15px; 1249 | content: '\e822'; 1250 | } 1251 | 1252 | .navigate-right:after, 1253 | .push-right:after { 1254 | right: 15px; 1255 | content: '\e826'; 1256 | } 1257 | 1258 | @font-face { 1259 | font-family: Ratchicons; 1260 | font-style: normal; 1261 | font-weight: normal; 1262 | 1263 | src: url("../fonts/ratchicons.eot"); 1264 | src: url("../fonts/ratchicons.eot?#iefix") format("embedded-opentype"), url("../fonts/ratchicons.woff") format("woff"), url("../fonts/ratchicons.ttf") format("truetype"), url("../fonts/ratchicons.svg#svgFontName") format("svg"); 1265 | } 1266 | .icon { 1267 | display: inline-block; 1268 | font-family: Ratchicons; 1269 | font-size: 24px; 1270 | line-height: 1; 1271 | text-decoration: none; 1272 | 1273 | -webkit-font-smoothing: antialiased; 1274 | } 1275 | 1276 | .icon-back:before { 1277 | content: '\e80a'; 1278 | } 1279 | 1280 | .icon-bars:before { 1281 | content: '\e80e'; 1282 | } 1283 | 1284 | .icon-caret:before { 1285 | content: '\e80f'; 1286 | } 1287 | 1288 | .icon-check:before { 1289 | content: '\e810'; 1290 | } 1291 | 1292 | .icon-close:before { 1293 | content: '\e811'; 1294 | } 1295 | 1296 | .icon-code:before { 1297 | content: '\e812'; 1298 | } 1299 | 1300 | .icon-compose:before { 1301 | content: '\e813'; 1302 | } 1303 | 1304 | .icon-download:before { 1305 | content: '\e815'; 1306 | } 1307 | 1308 | .icon-edit:before { 1309 | content: '\e829'; 1310 | } 1311 | 1312 | .icon-forward:before { 1313 | content: '\e82a'; 1314 | } 1315 | 1316 | .icon-gear:before { 1317 | content: '\e821'; 1318 | } 1319 | 1320 | .icon-home:before { 1321 | content: '\e82b'; 1322 | } 1323 | 1324 | .icon-info:before { 1325 | content: '\e82c'; 1326 | } 1327 | 1328 | .icon-list:before { 1329 | content: '\e823'; 1330 | } 1331 | 1332 | .icon-more-vertical:before { 1333 | content: '\e82e'; 1334 | } 1335 | 1336 | .icon-more:before { 1337 | content: '\e82f'; 1338 | } 1339 | 1340 | .icon-pages:before { 1341 | content: '\e824'; 1342 | } 1343 | 1344 | .icon-pause:before { 1345 | content: '\e830'; 1346 | } 1347 | 1348 | .icon-person:before { 1349 | content: '\e832'; 1350 | } 1351 | 1352 | .icon-play:before { 1353 | content: '\e816'; 1354 | } 1355 | 1356 | .icon-plus:before { 1357 | content: '\e817'; 1358 | } 1359 | 1360 | .icon-refresh:before { 1361 | content: '\e825'; 1362 | } 1363 | 1364 | .icon-search:before { 1365 | content: '\e819'; 1366 | } 1367 | 1368 | .icon-share:before { 1369 | content: '\e81a'; 1370 | } 1371 | 1372 | .icon-sound:before { 1373 | content: '\e827'; 1374 | } 1375 | 1376 | .icon-sound2:before { 1377 | content: '\e828'; 1378 | } 1379 | 1380 | .icon-sound3:before { 1381 | content: '\e80b'; 1382 | } 1383 | 1384 | .icon-sound4:before { 1385 | content: '\e80c'; 1386 | } 1387 | 1388 | .icon-star-filled:before { 1389 | content: '\e81b'; 1390 | } 1391 | 1392 | .icon-star:before { 1393 | content: '\e81c'; 1394 | } 1395 | 1396 | .icon-stop:before { 1397 | content: '\e81d'; 1398 | } 1399 | 1400 | .icon-trash:before { 1401 | content: '\e81e'; 1402 | } 1403 | 1404 | .icon-up-nav:before { 1405 | content: '\e81f'; 1406 | } 1407 | 1408 | .icon-up:before { 1409 | content: '\e80d'; 1410 | } 1411 | 1412 | .icon-right-nav:before { 1413 | content: '\e818'; 1414 | } 1415 | 1416 | .icon-right:before { 1417 | content: '\e826'; 1418 | } 1419 | 1420 | .icon-down-nav:before { 1421 | content: '\e814'; 1422 | } 1423 | 1424 | .icon-down:before { 1425 | content: '\e820'; 1426 | } 1427 | 1428 | .icon-left-nav:before { 1429 | content: '\e82d'; 1430 | } 1431 | 1432 | .icon-left:before { 1433 | content: '\e822'; 1434 | } 1435 | -------------------------------------------------------------------------------- /src/css/ratchet.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * ===================================================== 3 | * Ratchet v2.0.2 (http://goratchet.com) 4 | * Copyright 2014 Connor Sears 5 | * Licensed under MIT (https://github.com/twbs/ratchet/blob/master/LICENSE) 6 | * 7 | * v2.0.2 designed by @connors. 8 | * ===================================================== 9 | *//*! normalize.css v3.0.1 | MIT License | git.io/normalize */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:0 0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}body{position:fixed;top:0;right:0;bottom:0;left:0;font-family:"Helvetica Neue",Helvetica,sans-serif;font-size:17px;line-height:21px;color:#000;background-color:#fff}a{color:#428bca;text-decoration:none;-webkit-tap-highlight-color:transparent}a:active{color:#3071a9}.content{position:absolute;top:0;right:0;bottom:0;left:0;overflow:auto;-webkit-overflow-scrolling:touch;background-color:#fff}.content>*{-webkit-transform:translateZ(0);-ms-transform:translateZ(0);transform:translateZ(0)}.bar-nav~.content{padding-top:44px}.bar-header-secondary~.content{padding-top:88px}.bar-footer~.content{padding-bottom:44px}.bar-footer-secondary~.content{padding-bottom:88px}.bar-tab~.content{padding-bottom:50px}.bar-footer-secondary-tab~.content{padding-bottom:94px}.content-padded{margin:10px}.pull-left{float:left}.pull-right{float:right}.clearfix:after,.clearfix:before{display:table;content:" "}.clearfix:after{clear:both}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:10px;line-height:1}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{margin-top:20px;font-size:14px}.h6,h6{margin-top:20px;font-size:12px}p{margin-top:0;margin-bottom:10px;font-size:14px;color:#777}.btn{position:relative;display:inline-block;padding:6px 8px 7px;margin-bottom:0;font-size:12px;font-weight:400;line-height:1;color:#333;text-align:center;white-space:nowrap;vertical-align:top;cursor:pointer;background-color:#fff;border:1px solid #ccc;border-radius:3px}.btn.active,.btn:active{color:inherit;background-color:#ccc}.btn.disabled,.btn:disabled{opacity:.6}.btn-primary{color:#fff;background-color:#428bca;border:1px solid #428bca}.btn-primary.active,.btn-primary:active{color:#fff;background-color:#3071a9;border:1px solid #3071a9}.btn-positive{color:#fff;background-color:#5cb85c;border:1px solid #5cb85c}.btn-positive.active,.btn-positive:active{color:#fff;background-color:#449d44;border:1px solid #449d44}.btn-negative{color:#fff;background-color:#d9534f;border:1px solid #d9534f}.btn-negative.active,.btn-negative:active{color:#fff;background-color:#c9302c;border:1px solid #c9302c}.btn-outlined{background-color:transparent}.btn-outlined.btn-primary{color:#428bca}.btn-outlined.btn-positive{color:#5cb85c}.btn-outlined.btn-negative{color:#d9534f}.btn-outlined.btn-negative:active,.btn-outlined.btn-positive:active,.btn-outlined.btn-primary:active{color:#fff}.btn-link{padding-top:6px;padding-bottom:6px;color:#428bca;background-color:transparent;border:0}.btn-link.active,.btn-link:active{color:#3071a9;background-color:transparent}.btn-block{display:block;width:100%;padding:15px 0;margin-bottom:10px;font-size:18px}input[type=button],input[type=reset],input[type=submit]{width:100%}.btn .badge{margin:-2px -4px -2px 4px;font-size:12px;background-color:rgba(0,0,0,.15)}.btn .badge-inverted,.btn:active .badge-inverted{background-color:transparent}.btn-negative:active .badge-inverted,.btn-positive:active .badge-inverted,.btn-primary:active .badge-inverted{color:#fff}.btn-block .badge{position:absolute;right:0;margin-right:10px}.btn .icon{font-size:inherit}.bar{position:fixed;right:0;left:0;z-index:10;height:44px;padding-right:10px;padding-left:10px;background-color:#fff;border-bottom:1px solid #ddd;-webkit-backface-visibility:hidden;backface-visibility:hidden}.bar-header-secondary{top:44px}.bar-footer{bottom:0}.bar-footer-secondary{bottom:44px}.bar-footer-secondary-tab{bottom:50px}.bar-footer,.bar-footer-secondary,.bar-footer-secondary-tab{border-top:1px solid #ddd;border-bottom:0}.bar-nav{top:0}.title{position:absolute;display:block;width:100%;padding:0;margin:0 -10px;font-size:17px;font-weight:500;line-height:44px;color:#000;text-align:center;white-space:nowrap}.title a{color:inherit}.bar-tab{bottom:0;display:table;width:100%;height:50px;padding:0;table-layout:fixed;border-top:1px solid #ddd;border-bottom:0}.bar-tab .tab-item{display:table-cell;width:1%;height:50px;color:#929292;text-align:center;vertical-align:middle}.bar-tab .tab-item.active,.bar-tab .tab-item:active{color:#428bca}.bar-tab .tab-item .icon{top:3px;width:24px;height:24px;padding-top:0;padding-bottom:0}.bar-tab .tab-item .icon~.tab-label{display:block;font-size:11px}.bar .btn{position:relative;top:7px;z-index:20;padding:6px 12px 7px;margin-top:0;font-weight:400}.bar .btn.pull-right{margin-left:10px}.bar .btn.pull-left{margin-right:10px}.bar .btn-link{top:0;padding:0;font-size:16px;line-height:44px;color:#428bca;border:0}.bar .btn-link.active,.bar .btn-link:active{color:#3071a9}.bar .btn-block{top:6px;padding:7px 0;margin-bottom:0;font-size:16px}.bar .btn-nav.pull-left{margin-left:-5px}.bar .btn-nav.pull-left .icon-left-nav{margin-right:-3px}.bar .btn-nav.pull-right{margin-right:-5px}.bar .btn-nav.pull-right .icon-right-nav{margin-left:-3px}.bar .icon{position:relative;z-index:20;padding-top:10px;padding-bottom:10px;font-size:24px}.bar .btn .icon{top:3px;padding:0}.bar .title .icon{padding:0}.bar .title .icon.icon-caret{top:4px;margin-left:-5px}.bar input[type=search]{height:29px;margin:6px 0}.bar .segmented-control{top:7px;margin:0 auto}.badge{display:inline-block;padding:2px 9px 3px;font-size:12px;line-height:1;color:#333;background-color:rgba(0,0,0,.15);border-radius:100px}.badge.badge-inverted{padding:0 5px 0 0;background-color:transparent}.badge-primary{color:#fff;background-color:#428bca}.badge-primary.badge-inverted{color:#428bca}.badge-positive{color:#fff;background-color:#5cb85c}.badge-positive.badge-inverted{color:#5cb85c}.badge-negative{color:#fff;background-color:#d9534f}.badge-negative.badge-inverted{color:#d9534f}.card{margin:10px;overflow:hidden;background-color:#fff;border:1px solid #ddd;border-radius:6px}.card .table-view{margin-bottom:0;border-top:0;border-bottom:0}.card .table-view .table-view-divider:first-child{top:0;border-top-left-radius:6px;border-top-right-radius:6px}.card .table-view .table-view-divider:last-child{border-bottom-right-radius:6px;border-bottom-left-radius:6px}.card .table-view-cell:last-child{border-bottom:0}.table-view{padding-left:0;margin-top:0;margin-bottom:15px;list-style:none;background-color:#fff;border-top:1px solid #ddd;border-bottom:1px solid #ddd}.table-view-cell{position:relative;padding:11px 65px 11px 15px;overflow:hidden;border-bottom:1px solid #ddd}.table-view-cell:last-child{border-bottom:0}.table-view-cell>a:not(.btn){position:relative;display:block;padding:inherit;margin:-11px -65px -11px -15px;overflow:hidden;color:inherit}.table-view-cell>a:not(.btn):active{background-color:#eee}.table-view-cell p{margin-bottom:0}.table-view-divider{padding-top:6px;padding-bottom:6px;padding-left:15px;margin-top:-1px;margin-left:0;font-weight:500;color:#999;background-color:#fafafa;border-top:1px solid #ddd;border-bottom:1px solid #ddd}.table-view .media,.table-view .media-body{overflow:hidden}.table-view .media-object.pull-left{margin-right:10px}.table-view .media-object.pull-right{margin-left:10px}.table-view-cell>.badge,.table-view-cell>.btn,.table-view-cell>.toggle,.table-view-cell>a>.badge,.table-view-cell>a>.btn,.table-view-cell>a>.toggle{position:absolute;top:50%;right:15px;-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%)}.table-view-cell .navigate-left>.badge,.table-view-cell .navigate-left>.btn,.table-view-cell .navigate-left>.toggle,.table-view-cell .navigate-right>.badge,.table-view-cell .navigate-right>.btn,.table-view-cell .navigate-right>.toggle,.table-view-cell .push-left>.badge,.table-view-cell .push-left>.btn,.table-view-cell .push-left>.toggle,.table-view-cell .push-right>.badge,.table-view-cell .push-right>.btn,.table-view-cell .push-right>.toggle,.table-view-cell>a .navigate-left>.badge,.table-view-cell>a .navigate-left>.btn,.table-view-cell>a .navigate-left>.toggle,.table-view-cell>a .navigate-right>.badge,.table-view-cell>a .navigate-right>.btn,.table-view-cell>a .navigate-right>.toggle,.table-view-cell>a .push-left>.badge,.table-view-cell>a .push-left>.btn,.table-view-cell>a .push-left>.toggle,.table-view-cell>a .push-right>.badge,.table-view-cell>a .push-right>.btn,.table-view-cell>a .push-right>.toggle{right:35px}.content>.table-view:first-child{margin-top:15px}button,input,select,textarea{font-family:"Helvetica Neue",Helvetica,sans-serif;font-size:17px}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{width:100%;height:35px;-webkit-appearance:none;padding:0 15px;margin-bottom:15px;line-height:21px;background-color:#fff;border:1px solid #ddd;border-radius:3px;outline:0}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0 10px;font-size:16px;border-radius:20px}input[type=search]:focus{text-align:left}textarea{height:auto}select{height:auto;font-size:14px;background-color:#f8f8f8;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.1);box-shadow:inset 0 1px 1px rgba(0,0,0,.1)}.input-group{background-color:#fff}.input-group input,.input-group textarea{margin-bottom:0;background-color:transparent;border-top:0;border-right:0;border-left:0;border-radius:0;-webkit-box-shadow:none;box-shadow:none}.input-row{height:35px;overflow:hidden;border-bottom:1px solid #ddd}.input-row label{float:left;width:35%;padding:8px 15px;font-family:"Helvetica Neue",Helvetica,sans-serif;line-height:1.1}.input-row input{float:right;width:65%;padding-left:0;margin-bottom:0;border:0}.segmented-control{position:relative;display:table;overflow:hidden;font-size:12px;font-weight:400;background-color:#fff;border:1px solid #ccc;border-radius:3px}.segmented-control .control-item{display:table-cell;width:1%;padding-top:6px;padding-bottom:7px;overflow:hidden;line-height:1;color:#333;text-align:center;text-overflow:ellipsis;white-space:nowrap;border-left:1px solid #ccc}.segmented-control .control-item:first-child{border-left-width:0}.segmented-control .control-item:active{background-color:#eee}.segmented-control .control-item.active{background-color:#ccc}.segmented-control-primary{border-color:#428bca}.segmented-control-primary .control-item{color:#428bca;border-color:inherit}.segmented-control-primary .control-item:active{background-color:#cde1f1}.segmented-control-primary .control-item.active{color:#fff;background-color:#428bca}.segmented-control-positive{border-color:#5cb85c}.segmented-control-positive .control-item{color:#5cb85c;border-color:inherit}.segmented-control-positive .control-item:active{background-color:#d8eed8}.segmented-control-positive .control-item.active{color:#fff;background-color:#5cb85c}.segmented-control-negative{border-color:#d9534f}.segmented-control-negative .control-item{color:#d9534f;border-color:inherit}.segmented-control-negative .control-item:active{background-color:#f9e2e2}.segmented-control-negative .control-item.active{color:#fff;background-color:#d9534f}.control-content{display:none}.control-content.active{display:block}.popover{position:fixed;top:55px;left:50%;z-index:20;display:none;width:280px;margin-left:-140px;background-color:#fff;border-radius:6px;-webkit-box-shadow:0 0 15px rgba(0,0,0,.1);box-shadow:0 0 15px rgba(0,0,0,.1);opacity:0;-webkit-transition:all .25s linear;-moz-transition:all .25s linear;transition:all .25s linear;-webkit-transform:translate3d(0,-15px,0);-ms-transform:translate3d(0,-15px,0);transform:translate3d(0,-15px,0)}.popover:before{position:absolute;top:-15px;left:50%;width:0;height:0;margin-left:-15px;content:'';border-right:15px solid transparent;border-bottom:15px solid #fff;border-left:15px solid transparent}.popover.visible{opacity:1;-webkit-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.popover .bar~.table-view{padding-top:44px}.backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:15;background-color:rgba(0,0,0,.3)}.popover .btn-block{margin-bottom:5px}.popover .btn-block:last-child{margin-bottom:0}.popover .bar-nav{border-bottom:1px solid #ddd;border-top-left-radius:12px;border-top-right-radius:12px;-webkit-box-shadow:none;box-shadow:none}.popover .table-view{max-height:300px;margin-bottom:0;overflow:auto;-webkit-overflow-scrolling:touch;background-color:#fff;border-top:0;border-bottom:0;border-radius:6px}.modal{position:fixed;top:0;z-index:11;width:100%;min-height:100%;overflow:hidden;background-color:#fff;opacity:0;-webkit-transition:-webkit-transform .25s,opacity 1ms .25s;-moz-transition:-moz-transform .25s,opacity 1ms .25s;transition:transform .25s,opacity 1ms .25s;-webkit-transform:translate3d(0,100%,0);-ms-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}.modal.active{height:100%;opacity:1;-webkit-transition:-webkit-transform .25s;-moz-transition:-moz-transform .25s;transition:transform .25s;-webkit-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.slider{width:100%;overflow:hidden;background-color:#000}.slider .slide-group{position:relative;font-size:0;white-space:nowrap;-webkit-transition:all 0s linear;-moz-transition:all 0s linear;transition:all 0s linear}.slider .slide-group .slide{display:inline-block;width:100%;height:100%;font-size:14px;vertical-align:top}.toggle{position:relative;display:block;width:74px;height:30px;background-color:#fff;border:2px solid #ddd;border-radius:20px;-webkit-transition-duration:.2s;-moz-transition-duration:.2s;transition-duration:.2s;-webkit-transition-property:background-color,border;-moz-transition-property:background-color,border;transition-property:background-color,border}.toggle .toggle-handle{position:absolute;top:-1px;left:-1px;z-index:2;width:28px;height:28px;background-color:#fff;border:1px solid #ddd;border-radius:100px;-webkit-transition-duration:.2s;-moz-transition-duration:.2s;transition-duration:.2s;-webkit-transition-property:-webkit-transform,border,width;-moz-transition-property:-moz-transform,border,width;transition-property:transform,border,width}.toggle:before{position:absolute;top:3px;right:11px;font-size:13px;color:#999;text-transform:uppercase;content:"Off"}.toggle.active{background-color:#5cb85c;border:2px solid #5cb85c}.toggle.active .toggle-handle{border-color:#5cb85c;-webkit-transform:translate3d(44px,0,0);-ms-transform:translate3d(44px,0,0);transform:translate3d(44px,0,0)}.toggle.active:before{right:auto;left:15px;color:#fff;content:"On"}.toggle input[type=checkbox]{display:none}.content.fade{left:0;opacity:0}.content.fade.in{opacity:1}.content.sliding{z-index:2;-webkit-transition:-webkit-transform .4s;-moz-transition:-moz-transform .4s;transition:transform .4s;-webkit-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}.content.sliding.left{z-index:1;-webkit-transform:translate3d(-100%,0,0);-ms-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.content.sliding.right{z-index:3;-webkit-transform:translate3d(100%,0,0);-ms-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.navigate-left:after,.navigate-right:after,.push-left:after,.push-right:after{position:absolute;top:50%;display:inline-block;font-family:Ratchicons;font-size:inherit;line-height:1;color:#bbb;text-decoration:none;-webkit-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%);-webkit-font-smoothing:antialiased}.navigate-left:after,.push-left:after{left:15px;content:'\e822'}.navigate-right:after,.push-right:after{right:15px;content:'\e826'}@font-face{font-family:Ratchicons;font-style:normal;font-weight:400;src:url(../fonts/ratchicons.eot);src:url(../fonts/ratchicons.eot?#iefix) format("embedded-opentype"),url(../fonts/ratchicons.woff) format("woff"),url(../fonts/ratchicons.ttf) format("truetype"),url(../fonts/ratchicons.svg#svgFontName) format("svg")}.icon{display:inline-block;font-family:Ratchicons;font-size:24px;line-height:1;text-decoration:none;-webkit-font-smoothing:antialiased}.icon-back:before{content:'\e80a'}.icon-bars:before{content:'\e80e'}.icon-caret:before{content:'\e80f'}.icon-check:before{content:'\e810'}.icon-close:before{content:'\e811'}.icon-code:before{content:'\e812'}.icon-compose:before{content:'\e813'}.icon-download:before{content:'\e815'}.icon-edit:before{content:'\e829'}.icon-forward:before{content:'\e82a'}.icon-gear:before{content:'\e821'}.icon-home:before{content:'\e82b'}.icon-info:before{content:'\e82c'}.icon-list:before{content:'\e823'}.icon-more-vertical:before{content:'\e82e'}.icon-more:before{content:'\e82f'}.icon-pages:before{content:'\e824'}.icon-pause:before{content:'\e830'}.icon-person:before{content:'\e832'}.icon-play:before{content:'\e816'}.icon-plus:before{content:'\e817'}.icon-refresh:before{content:'\e825'}.icon-search:before{content:'\e819'}.icon-share:before{content:'\e81a'}.icon-sound:before{content:'\e827'}.icon-sound2:before{content:'\e828'}.icon-sound3:before{content:'\e80b'}.icon-sound4:before{content:'\e80c'}.icon-star-filled:before{content:'\e81b'}.icon-star:before{content:'\e81c'}.icon-stop:before{content:'\e81d'}.icon-trash:before{content:'\e81e'}.icon-up-nav:before{content:'\e81f'}.icon-up:before{content:'\e80d'}.icon-right-nav:before{content:'\e818'}.icon-right:before{content:'\e826'}.icon-down-nav:before{content:'\e814'}.icon-down:before{content:'\e820'}.icon-left-nav:before{content:'\e82d'}.icon-left:before{content:'\e822'} -------------------------------------------------------------------------------- /src/fonts/ratchicons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesparny/flux-immutable-example/c25c73ec31533054c877990cbf2fbbe46c96c789/src/fonts/ratchicons.eot -------------------------------------------------------------------------------- /src/fonts/ratchicons.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Copyright (C) 2014 by original authors @ fontello.com 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /src/fonts/ratchicons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesparny/flux-immutable-example/c25c73ec31533054c877990cbf2fbbe46c96c789/src/fonts/ratchicons.ttf -------------------------------------------------------------------------------- /src/fonts/ratchicons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vesparny/flux-immutable-example/c25c73ec31533054c877990cbf2fbbe46c96c789/src/fonts/ratchicons.woff -------------------------------------------------------------------------------- /src/stores/FrameworkStore.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var Immutable = require('immutable'); 3 | var BaseContentStore = require('../utils/BaseContentStore'); 4 | 5 | class FrameworkStore extends BaseContentStore { 6 | constructor(flux) { 7 | var initialState = { 8 | frameworks: Immutable.Map(), 9 | isLoading: false 10 | }; 11 | super(flux, initialState); 12 | var frameworksActionIds = flux.getActionIds('framework'); 13 | var favoriteActionIds = flux.getActionIds('favorite'); 14 | this.registerAsync(frameworksActionIds.searchFrameworks, this.handleBeginAsyncRequest, 15 | this.handleSearchframeworkSuccess, this.handleErrorAsyncRequest); 16 | this.registerAsync(frameworksActionIds.getFrameworkById, this.handleBeginAsyncRequest, 17 | this.handleFrameworkDetailSuccess, this.handleErrorAsyncRequest); 18 | this.register(favoriteActionIds.addFavorite, this.handleAddFavorite); 19 | this.register(favoriteActionIds.removeFavorite, this.handleRemoveFavorite); 20 | } 21 | 22 | handleSearchframeworkSuccess(payload) { 23 | this.setState({ 24 | frameworks: this.mergeIntoBag(this.state.frameworks, payload.response) 25 | }); 26 | } 27 | 28 | handleFrameworkDetailSuccess(payload) { 29 | this.setState({ 30 | frameworks: this.addOneToBag(this.state.frameworks, payload.response), 31 | isLoading: false 32 | }); 33 | } 34 | 35 | handleAddFavorite(id) { 36 | var edited = this.state.frameworks.updateIn([id], function (el) { 37 | return el.set('isFavorited', true); 38 | }); 39 | this.setState({ 40 | frameworks: edited 41 | }); 42 | } 43 | 44 | handleRemoveFavorite(id) { 45 | var edited = this.state.frameworks.updateIn([id], function (el) { 46 | return el.set('isFavorited', false); 47 | }); 48 | this.setState({ 49 | frameworks: edited 50 | }); 51 | } 52 | 53 | getById(id) { 54 | return this.state.frameworks.get(id); 55 | } 56 | } 57 | 58 | module.exports = FrameworkStore; 59 | -------------------------------------------------------------------------------- /src/stores/SearchFrameworkStore.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var BaseLinkedListStore = require('../utils/BaseLinkedListStore'); 3 | 4 | class SearchFrameworkStore extends BaseLinkedListStore { 5 | constructor(flux) { 6 | super(flux); 7 | var frameworkActionIds = flux.getActionIds('framework'); 8 | this.registerAsync(frameworkActionIds.searchFrameworks, this.handleBeginAsyncRequest, 9 | this.handleSearchFrameworkSuccess, this.handleErrorAsyncRequest); 10 | } 11 | 12 | handleSearchFrameworkSuccess(payload) { 13 | var frameworkStore = this.flux.getStore('framework'); 14 | this.waitFor(frameworkStore); 15 | this.setData(payload.query, payload.response); 16 | } 17 | 18 | getframeworks(query) { 19 | return this.getIds(query).map(el => 20 | this.flux.getStore('framework').getById(el)); 21 | } 22 | 23 | } 24 | 25 | module.exports = SearchFrameworkStore; 26 | -------------------------------------------------------------------------------- /src/utils/ApiUtils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Promise = require('es6-promise').Promise; // jshint ignore:line 4 | var request = require('superagent'); 5 | var timeout = 10000; 6 | 7 | function makeCall (method, url, data = {}) { 8 | var req = request[method](url) 9 | .set('Accept', 'application/json') 10 | .timeout(timeout); 11 | 12 | if (method === 'get') { 13 | req.type('json') 14 | .query(data); 15 | } else { 16 | req.send(data); 17 | } 18 | 19 | return new Promise(function (resolve, reject) { 20 | req.end(function (err, res) { 21 | if (err) { 22 | reject(err); 23 | } else if (res.status === 400) { 24 | reject(err); 25 | } else if (!res.ok) { 26 | reject(err); 27 | } else { 28 | resolve(res.body); 29 | } 30 | }); 31 | }); 32 | } 33 | var ApiUtils = { 34 | get(url, data) { 35 | return makeCall('get', url, data); 36 | }, 37 | 38 | post(url, data) { 39 | return makeCall('post', url, data); 40 | }, 41 | 42 | put(url, data) { 43 | return makeCall('put', url, data); 44 | }, 45 | 46 | patch(url, data) { 47 | return makeCall('patch', url, data); 48 | }, 49 | 50 | delete(url, data) { 51 | return makeCall('del', url, data); 52 | } 53 | }; 54 | 55 | module.exports = ApiUtils; 56 | -------------------------------------------------------------------------------- /src/utils/ArrayUtils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var ArrayUtils = { 4 | remove(array, item) { 5 | for (var i = array.length - 1; i >= 0; i -= 1) { 6 | if (array[i] === item) { 7 | array.splice(i, 1); 8 | } 9 | } 10 | } 11 | }; 12 | 13 | module.exports = ArrayUtils; 14 | -------------------------------------------------------------------------------- /src/utils/BaseContentStore.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var BaseStore = require('./BaseStore'); 3 | var Immutable = require('immutable'); 4 | 5 | class BaseContentStore extends BaseStore { 6 | constructor(flux, initialState) { 7 | super(flux, initialState); 8 | } 9 | 10 | mergeIntoBag(immutableBag, data) { 11 | var newData = Immutable.fromJS(data); 12 | return immutableBag.merge(newData.reduce((result, element) => 13 | result.set(element.get('id'), element), Immutable.Map())); 14 | } 15 | 16 | addOneToBag(immutableBag, element) { 17 | var newData = Immutable.fromJS(element); 18 | return immutableBag.set(newData.get('id'), newData); 19 | } 20 | } 21 | 22 | module.exports = BaseContentStore; 23 | -------------------------------------------------------------------------------- /src/utils/BaseLinkedListStore.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var BaseStore = require('./BaseStore'); 3 | var Immutable = require('immutable'); 4 | 5 | class BaseLinkedListStore extends BaseStore { 6 | constructor(flux) { 7 | var initialState = { 8 | list: Immutable.Map(), 9 | isLoading: true 10 | }; 11 | this.flux = flux; 12 | super(flux, initialState); 13 | } 14 | 15 | getIds(key) { 16 | return this.state.list.get(key, Immutable.List()); 17 | } 18 | 19 | setData(key, data) { 20 | this.setState({ 21 | list: this.state.list.set(key, Immutable.fromJS(data).map(el => 22 | el.get('id'))), 23 | isLoading: false 24 | }); 25 | } 26 | } 27 | 28 | module.exports = BaseLinkedListStore; 29 | -------------------------------------------------------------------------------- /src/utils/BaseStore.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var { Store } = require('flummox'); 3 | 4 | class BaseStore extends Store { 5 | constructor(flux, initialState) { 6 | super(); 7 | this.state = initialState; 8 | } 9 | 10 | handleBeginAsyncRequest() { 11 | this.setState({ isLoading: true }); 12 | } 13 | 14 | handleErrorAsyncRequest(err) { 15 | this.setState({ isLoading: false }); 16 | console.log(err); 17 | } 18 | 19 | isLoading(){ 20 | return this.state.isLoading; 21 | } 22 | } 23 | 24 | module.exports = BaseStore; 25 | -------------------------------------------------------------------------------- /src/utils/LocalStorageUtils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var ArrayUtils = require('./ArrayUtils'); 4 | 5 | function getAll(key) { 6 | return JSON.parse(window.localStorage.getItem(key)) || []; 7 | } 8 | 9 | var LocalStorageUtils = { 10 | push(key, value) { 11 | var data = getAll(key); 12 | if (data.indexOf(value) === -1) { 13 | data.push(value); 14 | window.localStorage.setItem(key, JSON.stringify(data)); 15 | } 16 | return data; 17 | }, 18 | pop(key, value) { 19 | var data = getAll(key); 20 | ArrayUtils.remove(data, value); 21 | window.localStorage.setItem(key, JSON.stringify(data)); 22 | return data; 23 | }, 24 | getAll(key){ 25 | return JSON.parse(window.localStorage.getItem(key)) || []; 26 | } 27 | }; 28 | 29 | module.exports = LocalStorageUtils; 30 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | devtool: 'eval', 5 | entry: [ 6 | './src/client.js' 7 | ], 8 | output: { 9 | path: __dirname + '/dist', 10 | filename: 'bundle.js' 11 | }, 12 | externals:[{ 13 | xmlhttprequest: '{XMLHttpRequest:XMLHttpRequest}' 14 | }], 15 | watch: true, 16 | module: { 17 | loaders: [{ 18 | test: /\.js$/, 19 | exclude: /node_modules/, 20 | loader: 'babel-loader' 21 | }] 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /webpack.config.production.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var webpack = require('webpack'); 4 | 5 | module.exports = { 6 | devtool: 'source-map', 7 | entry: './src/client.js', 8 | output: { 9 | path: __dirname + '/dist', 10 | filename: 'bundle.js' 11 | }, 12 | externals:[{ 13 | xmlhttprequest: '{XMLHttpRequest:XMLHttpRequest}' 14 | }], 15 | plugins: [ 16 | new webpack.optimize.OccurenceOrderPlugin(), 17 | new webpack.optimize.UglifyJsPlugin({ 18 | compressor: { 19 | warnings: false 20 | } 21 | }), 22 | new webpack.NoErrorsPlugin(), 23 | ], 24 | module: { 25 | loaders: [{ 26 | test: /\.js$/, 27 | exclude: /node_modules/, 28 | loader: 'babel-loader' 29 | }] 30 | } 31 | }; 32 | --------------------------------------------------------------------------------