/internals/flow/WebpackAsset.js.flow'
25 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe
26 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue
27 |
--------------------------------------------------------------------------------
/app/mirror/core/components.js:
--------------------------------------------------------------------------------
1 | import config from '../config/config';
2 | import React from 'react';
3 |
4 | const { modules } = config;
5 | const loaderHelp = require.context('common', true, /index.js$/);
6 |
7 | function requireAll(requireContext) {
8 | return requireContext.keys().map(requireContext);
9 | }
10 |
11 | export default function getModules() {
12 | const comps = {};
13 | const allMods = requireAll(loaderHelp);
14 | modules.filter((mod) => {
15 | return !mod.hide;
16 | }).map((item) => {
17 | for (let j = 0; j < allMods.length; j += 1) {
18 | if (item.name === allMods[j].moduleName) {
19 | const MOD = allMods[j];
20 | if (item.position in comps) {
21 | comps[item.position] = [
22 | ...comps[item.position],
23 |
24 | ];
25 | } else {
26 | comps[item.position] = [];
27 | }
28 | break;
29 | }
30 | }
31 | });
32 | return comps;
33 | }
34 |
--------------------------------------------------------------------------------
/app/mirror/modules/default/Snow/Snow.css:
--------------------------------------------------------------------------------
1 | .Snow .snowFlake {
2 | position: absolute;
3 | top: -20px;
4 | animation-name: snowDrop;
5 | animation-iteration-count: infinite;
6 | }
7 |
8 | .Snow .snowFlake .flake {
9 | width: 20px;
10 | height: 20px;
11 |
12 | background-repeat: no-repeat;
13 | background-size: cover;
14 |
15 | animation-name: snowJiggle;
16 | animation-iteration-count: infinite;
17 | }
18 |
19 | .Snow .flake1 {background-image: url('./images/flake1.png');}
20 | .Snow .flake2 {background-image: url('./images/flake2.png');}
21 | .Snow .flake3 {background-image: url('./images/flake3.png');}
22 |
23 | @keyframes snowDrop {
24 | from {transform: translateY(0vh)}
25 | to {transform: translateY(calc(100vh + 20px))}
26 | }
27 |
28 | @keyframes snowJiggle {
29 | 0% {transform: translateX(0vw)}
30 | 20% {transform: translateX(20vw)}
31 | 30% {transform: translateX(5vw)}
32 | 50% {transform: translateX(25vw)}
33 | 70% {transform: translateX(0vw)}
34 | 85% {transform: translateX(15vw)}
35 | 100% {transform: translateX(0vw)}
36 | }
37 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Compiled binary addons (http://nodejs.org/api/addons.html)
23 | build/Release
24 | .eslintcache
25 |
26 | # Dependency directory
27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
28 | node_modules
29 | app/node_modules
30 |
31 | # OSX
32 | .DS_Store
33 |
34 | # flow-typed
35 | flow-typed/npm/*
36 | !flow-typed/npm/module_vx.x.x.js
37 |
38 | # App packaged
39 | release
40 | app/main.prod.js
41 | app/main.prod.js.map
42 | app/renderer.prod.js
43 | app/renderer.prod.js.map
44 | app/style.css
45 | app/style.css.map
46 | dist
47 | dll
48 | main.js
49 | main.js.map
50 |
51 | .idea
52 | npm-debug.log.*
53 | __snapshots__
54 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Compiled binary addons (http://nodejs.org/api/addons.html)
23 | build/Release
24 | .eslintcache
25 |
26 | # Dependency directory
27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
28 | node_modules
29 | app/node_modules
30 |
31 | # OSX
32 | .DS_Store
33 |
34 | # flow-typed
35 | flow-typed/npm/*
36 | !flow-typed/npm/module_vx.x.x.js
37 |
38 | # App packaged
39 | release
40 | app/main.prod.js
41 | app/main.prod.js.map
42 | app/renderer.prod.js
43 | app/renderer.prod.js.map
44 | app/style.css
45 | app/style.css.map
46 | dist
47 | dll
48 | main.js
49 | main.js.map
50 |
51 | .idea
52 | npm-debug.log.*
53 |
54 | # Mirror
55 | app/mirror/config/config.js
56 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015-present C. T. Lin
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 |
--------------------------------------------------------------------------------
/app/mirror/modules/default/Snow/Flake.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import classNames from 'classnames';
3 | import styles from './Snow.css';
4 |
5 | export default class Flake extends Component {
6 | render() {
7 | return (
8 |
31 | );
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/mirror/modules/default/CurrentWeather/WeatherDetail.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | export default class WeatherDetail extends Component {
4 | render() {
5 | let windDir = '';
6 | if (this.props.showWindDirection) {
7 | if (this.props.showWindDirectionAsArrow && this.props.windDegrees) {
8 | console.log("degrees");
9 | windDir = ();
10 | } else if (this.props.windDegrees) {
11 | windDir = `${this.props.windDegrees}`;
12 | } else {
13 | windDir = this.props.units === 'imperial' ? 'MPH' : 'KPH';
14 | }
15 | }
16 |
17 | return (
18 |
19 |
20 | {this.props.windSpeed}
21 | {
22 | (this.props.showWindDirection && windDir) &&
23 | {windDir}
24 | }
25 |
26 | {
27 | this.props.showHumidity &&
28 | {this.props.humidity}
29 | }
30 |
31 | {this.props.sunriseSunsetTime}
32 |
33 | );
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/mirror/modules/default/Snow/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React, { Component } from 'react';
3 | import PropTypes from 'prop-types';
4 | import styles from './Snow.css';
5 | import Flake from './Flake';
6 |
7 | class Snow extends Component {
8 |
9 | constructor(props: any) {
10 | super(props);
11 | this.generateDom = this.generateDom.bind(this);
12 | }
13 |
14 | generateDom() {
15 | return Array.from(new Array(this.props.flakeCount), (item, index) => {
16 | return (
17 |
26 | );
27 | });
28 | }
29 |
30 | render() {
31 | return (
32 |
33 | {this.generateDom()}
34 |
35 | );
36 | }
37 | }
38 |
39 | Snow.moduleName = 'Snow';
40 |
41 | Snow.defaultProps = {
42 | position: 'full_screen_above',
43 | flakeCount: 100,
44 | };
45 |
46 | Snow.propTypes = {
47 | position: PropTypes.string,
48 | flakeCount: PropTypes.number
49 | };
50 |
51 | export default Snow;
52 |
--------------------------------------------------------------------------------
/app/mirror/config/defaults.js:
--------------------------------------------------------------------------------
1 | export default {
2 | kioskmode: false,
3 | electronOptions: {},
4 | language: 'en',
5 | timeFormat: 24,
6 | units: 'metric',
7 | zoom: 1,
8 | modules: [
9 | {
10 | module: 'updatenotification',
11 | position: 'top_center'
12 | },
13 | {
14 | module: 'helloworld',
15 | position: 'upper_third',
16 | classes: 'large thin',
17 | config: {
18 | text: 'Reactive Mirror'
19 | }
20 | },
21 | {
22 | module: 'helloworld',
23 | position: 'middle_center',
24 | config: {
25 | text: 'Please create a config file.'
26 | }
27 | },
28 | {
29 | module: 'helloworld',
30 | position: 'middle_center',
31 | classes: 'small dimmed',
32 | config: {
33 | text: 'See README for more information.'
34 | }
35 | },
36 | {
37 | module: 'helloworld',
38 | position: 'middle_center',
39 | classes: 'xsmall',
40 | config: {
41 | text: 'If you get this message while your config file is already
created, your config file probably contains an error.
Use a JavaScript linter to validate your file.'
42 | }
43 | },
44 | {
45 | module: 'helloworld',
46 | position: 'bottom_bar',
47 | classes: 'xsmall dimmed',
48 | config: {
49 | text: 'www.michaelteeuw.nl'
50 | }
51 | },
52 | ]
53 | };
54 |
--------------------------------------------------------------------------------
/app/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Reactive Mirror
6 |
17 |
18 |
19 |
20 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "parserOptions": {
4 | "sourceType": "module",
5 | "allowImportExportEverywhere": true
6 | },
7 | "extends": "airbnb",
8 | "env": {
9 | "browser": true,
10 | "node": true
11 | },
12 | "rules": {
13 | "arrow-parens": ["off"],
14 | "compat/compat": "error",
15 | "consistent-return": "off",
16 | "comma-dangle": "off",
17 | "flowtype-errors/show-errors": "error",
18 | "generator-star-spacing": "off",
19 | "import/no-unresolved": "error",
20 | "import/no-extraneous-dependencies": "off",
21 | "no-console": "off",
22 | "no-use-before-define": "off",
23 | "no-multi-assign": "off",
24 | "promise/param-names": "error",
25 | "promise/always-return": "error",
26 | "promise/catch-or-return": "error",
27 | "promise/no-native": "off",
28 | "react/sort-comp": ["error", {
29 | "order": ["type-annotations", "static-methods", "lifecycle", "everything-else", "render"]
30 | }],
31 | "react/jsx-no-bind": "off",
32 | "react/jsx-filename-extension": ["error", { "extensions": [".js", ".jsx"] }],
33 | "react/prefer-stateless-function": "off"
34 | },
35 | "plugins": [
36 | "flowtype",
37 | "flowtype-errors",
38 | "import",
39 | "promise",
40 | "compat",
41 | "react"
42 | ],
43 | "settings": {
44 | "import/resolver": {
45 | "webpack": {
46 | "config": "webpack.config.eslint.js"
47 | }
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/webpack.config.base.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Base webpack config used across other specific configs
3 | */
4 |
5 | import path from 'path';
6 | import webpack from 'webpack';
7 | import { dependencies as externals } from './app/package.json';
8 | import defaults from './app/mirror/config/defaults';
9 | import config from './app/mirror/config/config';
10 |
11 | export default {
12 | externals: Object.keys(externals || {}),
13 |
14 | module: {
15 | rules: [{
16 | test: /\.jsx?$/,
17 | exclude: /node_modules/,
18 | use: {
19 | loader: 'babel-loader',
20 | options: {
21 | cacheDirectory: true
22 | }
23 | }
24 | }]
25 | },
26 |
27 | output: {
28 | path: path.join(__dirname, 'app'),
29 | filename: 'renderer.dev.js',
30 | // https://github.com/webpack/webpack/issues/1114
31 | libraryTarget: 'commonjs2'
32 | },
33 |
34 | /**
35 | * Determine the array of extensions that should be used to resolve modules.
36 | */
37 | resolve: {
38 | extensions: ['.js', '.jsx', '.json'],
39 | modules: [
40 | path.join(__dirname, 'app'),
41 | 'node_modules',
42 | ],
43 | alias: {
44 | common: path.resolve(__dirname, './app/mirror/modules')
45 | }
46 | },
47 |
48 | plugins: [
49 | new webpack.DefinePlugin({
50 | 'process.env.MIRROR_CONFIG': JSON.stringify(process.env.MIRROR_CONFIG || { ...defaults, ...config }),
51 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production')
52 | }),
53 | new webpack.NamedModulesPlugin(),
54 | ],
55 | };
56 |
--------------------------------------------------------------------------------
/app/mirror/splashscreen/ReactiveMirror.script:
--------------------------------------------------------------------------------
1 | screen_width = Window.GetWidth();
2 | screen_height = Window.GetHeight();
3 |
4 | if (Plymouth.GetMode() != "shutdown")
5 | {
6 | theme_image = Image("splash.png");
7 | }
8 | else
9 | {
10 | theme_image = Image("splash_halt.png");
11 | }
12 |
13 | image_width = theme_image.GetWidth();
14 | image_height = theme_image.GetHeight();
15 |
16 | scale_x = image_width / screen_width;
17 | scale_y = image_height / screen_height;
18 |
19 | if (scale_x > 1 || scale_y > 1)
20 | {
21 | if (scale_x > scale_y)
22 | {
23 | resized_image = theme_image.Scale (screen_width, image_height / scale_x);
24 | image_x = 0;
25 | image_y = (screen_height - ((image_height * screen_width) / image_width)) / 2;
26 | }
27 | else
28 | {
29 | resized_image = theme_image.Scale (image_width / scale_y, screen_height);
30 | image_x = (screen_width - ((image_width * screen_height) / image_height)) / 2;
31 | image_y = 0;
32 | }
33 | }
34 | else
35 | {
36 | resized_image = theme_image.Scale (image_width, image_height);
37 | image_x = (screen_width - image_width) / 2;
38 | image_y = (screen_height - image_height) / 2;
39 | }
40 |
41 | sprite = Sprite (resized_image);
42 | sprite.SetPosition (image_x, image_y, -100);
43 |
44 | message_sprite = Sprite();
45 | message_sprite.SetPosition(screen_width * 0.1, screen_height * 0.9, 10000);
46 |
47 | fun message_callback (text) {
48 | my_image = Image.Text(text, 1, 1, 1);
49 | message_sprite.SetImage(my_image);
50 | sprite.SetImage (resized_image);
51 | }
52 |
53 | Plymouth.SetUpdateStatusFunction(message_callback);
54 |
--------------------------------------------------------------------------------
/app/mirror/modules/default/Compliments/core/utils.js:
--------------------------------------------------------------------------------
1 | export const defaultCompliments = {
2 | anytime: [
3 | 'Hey there sexy!'
4 | ],
5 | morning: [
6 | 'Good morning, beautiful!',
7 | 'Enjoy your day!',
8 | 'How was your sleep?'
9 | ],
10 | afternoon: [
11 | 'Hello, beauty!',
12 | 'You look gorgeous!',
13 | 'You look sexy!',
14 | 'Looking good today!'
15 | ],
16 | evening: [
17 | 'Wow, you look great!',
18 | 'Wow, you look hot!',
19 | 'You look nice!',
20 | 'Hi, hot stuff!',
21 | 'Hi, sexy!'
22 | ]
23 | };
24 |
25 | export function getRandom(length: number) {
26 | return Math.floor(Math.random() * length);
27 | }
28 |
29 | export function complimentFile(remoteFile, callback) {
30 | const xobj = new XMLHttpRequest();
31 | xobj.overrideMimeType('application/json');
32 | xobj.open('GET', remoteFile, true);
33 | xobj.onreadystatechange = () => {
34 | if (xobj.readyState === 4 && xobj.status === '200') {
35 | callback(xobj.responseText);
36 | }
37 | };
38 | xobj.send(null);
39 | }
40 |
41 | export const weatherIconTable = {
42 | '01d': 'day_sunny',
43 | '02d': 'day_cloudy',
44 | '03d': 'cloudy',
45 | '04d': 'cloudy_windy',
46 | '09d': 'showers',
47 | '10d': 'rain',
48 | '11d': 'thunderstorm',
49 | '13d': 'snow',
50 | '50d': 'fog',
51 | '01n': 'night_clear',
52 | '02n': 'night_cloudy',
53 | '03n': 'night_cloudy',
54 | '04n': 'night_cloudy',
55 | '09n': 'night_showers',
56 | '10n': 'night_rain',
57 | '11n': 'night_thunderstorm',
58 | '13n': 'night_snow',
59 | '50n': 'night_alt_cloudy_windy'
60 | };
61 |
--------------------------------------------------------------------------------
/app/mirror/config/config.js.sample:
--------------------------------------------------------------------------------
1 | /*
2 | * Reactive Mirror Config Sample
3 | */
4 |
5 | export default {
6 | language: 'en',
7 | timeFormat: 24,
8 | units: 'metric',
9 | modules: [
10 | {
11 | name: 'Clock',
12 | position: 'top_left',
13 | },
14 | {
15 | name: 'Calendar',
16 | position: 'top_left',
17 | config: {
18 | calendars: [
19 | {
20 | symbol: 'calendar-check-o ',
21 | url: 'webcal://www.calendarlabs.com/templates/ical/US-Holidays.ics'
22 | }
23 | ]
24 | }
25 | },
26 | {
27 | name: 'CurrentWeather',
28 | position: 'top_right',
29 | config: {
30 | location: 'New York',
31 | locationID: '5128581', // ID from http://www.openweathermap.org/help/city_list.txt
32 | appid: ''
33 | }
34 | },
35 | {
36 | name: 'WeatherForecast',
37 | position: 'top_right',
38 | config: {
39 | location: 'New York',
40 | locationID: '5128581', // ID from http://www.openweathermap.org/help/city_list.txt
41 | appid: ''
42 | }
43 | },
44 | {
45 | name: 'Compliments',
46 | position: 'lower_third',
47 | },
48 | {
49 | name: 'Snow',
50 | position: 'fullscreen_above',
51 | hide: true,
52 | config: {
53 | flakCount: 100,
54 | }
55 | },
56 | {
57 | name: 'NewsFeed',
58 | position: 'bottom_bar',
59 | config: {
60 | apiKey: '',
61 | showSourceTitle: true,
62 | showPublishDate: true
63 | }
64 | },
65 | {
66 | name: 'Jokes',
67 | hide: true,
68 | position: 'bottom_bar',
69 | },
70 | {
71 | name: 'alert',
72 | hide: true,
73 | }
74 | ]
75 | };
76 |
--------------------------------------------------------------------------------
/app/mirror/modules/default/Clock/Clock.css:
--------------------------------------------------------------------------------
1 | .clockCircle {
2 | margin: 0 auto;
3 | position: relative;
4 | border-radius: 50%;
5 | background-size: 100%;
6 | }
7 |
8 | .clockFace {
9 | width: 100%;
10 | height: 100%;
11 | }
12 |
13 | .clockFace::after {
14 | position: absolute;
15 | top: 50%;
16 | left: 50%;
17 | width: 6px;
18 | height: 6px;
19 | margin: -3px 0 0 -3px;
20 | background: white;
21 | border-radius: 3px;
22 | content: "";
23 | display: block;
24 | }
25 |
26 | .clockHour {
27 | width: 0;
28 | height: 0;
29 | position: absolute;
30 | top: 50%;
31 | left: 50%;
32 | margin: -2px 0 -2px -25%; /* numbers much match negative length & thickness */
33 | padding: 2px 0 2px 25%; /* indicator length & thickness */
34 | background: white;
35 | -webkit-transform-origin: 100% 50%;
36 | -ms-transform-origin: 100% 50%;
37 | transform-origin: 100% 50%;
38 | border-radius: 3px 0 0 3px;
39 | }
40 |
41 | .clockMinute {
42 | width: 0;
43 | height: 0;
44 | position: absolute;
45 | top: 50%;
46 | left: 50%;
47 | margin: -35% -2px 0; /* numbers must match negative length & thickness */
48 | padding: 35% 2px 0; /* indicator length & thickness */
49 | background: white;
50 | -webkit-transform-origin: 50% 100%;
51 | -ms-transform-origin: 50% 100%;
52 | transform-origin: 50% 100%;
53 | border-radius: 3px 0 0 3px;
54 | }
55 |
56 | .clockSecond {
57 | width: 0;
58 | height: 0;
59 | position: absolute;
60 | top: 50%;
61 | left: 50%;
62 | margin: -38% -1px 0 0; /* numbers must match negative length & thickness */
63 | padding: 38% 1px 0 0; /* indicator length & thickness */
64 | background: #888;
65 | -webkit-transform-origin: 50% 100%;
66 | -ms-transform-origin: 50% 100%;
67 | transform-origin: 50% 100%;
68 | }
69 |
--------------------------------------------------------------------------------
/app/store/configureStore.dev.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware, compose } from 'redux';
2 | import thunk from 'redux-thunk';
3 | import { createHashHistory } from 'history';
4 | import { routerMiddleware, routerActions } from 'react-router-redux';
5 | import { createLogger } from 'redux-logger';
6 | import rootReducer from '../reducers';
7 |
8 | const history = createHashHistory();
9 |
10 | const configureStore = (initialState?) => {
11 | // Redux Configuration
12 | const middleware = [];
13 | const enhancers = [];
14 |
15 | // Thunk Middleware
16 | middleware.push(thunk);
17 |
18 | // Logging Middleware
19 | const logger = createLogger({
20 | level: 'info',
21 | collapsed: true
22 | });
23 | middleware.push(logger);
24 |
25 | // Router Middleware
26 | const router = routerMiddleware(history);
27 | middleware.push(router);
28 |
29 | // Redux DevTools Configuration
30 | const actionCreators = {
31 | ...routerActions,
32 | };
33 | // If Redux DevTools Extension is installed use it, otherwise use Redux compose
34 | /* eslint-disable no-underscore-dangle */
35 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
36 | ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
37 | // Options: http://zalmoxisus.github.io/redux-devtools-extension/API/Arguments.html
38 | actionCreators,
39 | })
40 | : compose;
41 | /* eslint-enable no-underscore-dangle */
42 |
43 | // Apply Middleware & Compose Enhancers
44 | enhancers.push(applyMiddleware(...middleware));
45 | const enhancer = composeEnhancers(...enhancers);
46 |
47 | // Create Store
48 | const store = createStore(rootReducer, initialState, enhancer);
49 |
50 | if (module.hot) {
51 | module.hot.accept('../reducers', () =>
52 | store.replaceReducer(require('../reducers')) // eslint-disable-line global-require
53 | );
54 | }
55 |
56 | return store;
57 | };
58 |
59 | export default { configureStore, history };
60 |
--------------------------------------------------------------------------------
/app/mirror/modules/default/Calendar/core/utils.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export function shorten(stringToShorten: string, maxLength: number = 25, wrapEvents: boolean) {
4 | if (wrapEvents) {
5 | let temp = '';
6 | let currentLine = '';
7 | const words = stringToShorten.split(' ');
8 |
9 | words.forEach((word) => {
10 | if (currentLine.length + word.length < maxLength - 1) { // max - 1 to account for a space
11 | currentLine += `${word} `;
12 | } else {
13 | if (currentLine.length > 0) {
14 | temp += ({currentLine}
{word} );
15 | } else {
16 | temp += ({word}
);
17 | }
18 | currentLine = '';
19 | }
20 | });
21 | return (temp + currentLine).trim();
22 | }
23 |
24 | if (stringToShorten.length > maxLength) {
25 | return `${stringToShorten.trim().slice(0, maxLength)}${String.fromCharCode(8230)}`;
26 | }
27 |
28 | return stringToShorten.trim();
29 | }
30 |
31 | export function capFirst(string) {
32 | return string.charAt(0).toUpperCase() + string.slice(1);
33 | }
34 |
35 | export function titleTransform(title, configReplacements, maxTitleLength, wrapEvents) {
36 | for (let needle in configReplacements) {
37 | const replacement = configReplacements[needle];
38 |
39 | const regParts = needle.match(/^\/(.+)\/([gim]*)$/);
40 | if (regParts) {
41 | // the parsed pattern is a regexp.
42 | needle = new RegExp(regParts[1], regParts[2]);
43 | }
44 |
45 | title = title.replace(needle, replacement);
46 | }
47 | title = shorten(title, maxTitleLength, wrapEvents);
48 | return title;
49 | }
50 |
51 | export function isFullDayEvent(event) {
52 | if (event.start.length === 8) {
53 | return true;
54 | }
55 |
56 | const start = event.start || 0;
57 | const startDate = new Date(start);
58 | const end = event.end || 0;
59 | if (end - start === 24 * 60 * 60 * 1000 && startDate.getHours() === 0 && startDate.getMinutes() === 0) {
60 | // Is 24 hours, and starts on the middle of the night.
61 | return true;
62 | }
63 |
64 | return false;
65 | }
66 |
--------------------------------------------------------------------------------
/webpack.config.main.prod.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Webpack config for production electron main process
3 | */
4 |
5 | import webpack from 'webpack';
6 | import merge from 'webpack-merge';
7 | // import BabiliPlugin from 'babili-webpack-plugin';
8 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
9 | import baseConfig from './webpack.config.base';
10 | import CheckNodeEnv from './internals/scripts/CheckNodeEnv';
11 | import defaults from './app/mirror/config/defaults';
12 | import config from './app/mirror/config/config';
13 |
14 | CheckNodeEnv('production');
15 |
16 | export default merge.smart(baseConfig, {
17 | devtool: 'source-map',
18 |
19 | target: 'electron-main',
20 |
21 | entry: './app/main.dev',
22 |
23 | // 'main.js' in root
24 | output: {
25 | path: __dirname,
26 | filename: './app/main.prod.js'
27 | },
28 |
29 | plugins: [
30 | /**
31 | * Babli is an ES6+ aware minifier based on the Babel toolchain (beta)
32 | */
33 | // new BabiliPlugin(),
34 |
35 | new BundleAnalyzerPlugin({
36 | analyzerMode: process.env.OPEN_ANALYZER === 'true' ? 'server' : 'disabled',
37 | openAnalyzer: process.env.OPEN_ANALYZER === 'true'
38 | }),
39 |
40 | /**
41 | * Create global constants which can be configured at compile time.
42 | *
43 | * Useful for allowing different behaviour between development builds and
44 | * release builds
45 | *
46 | * NODE_ENV should be production so that modules do not perform certain
47 | * development checks
48 | */
49 | new webpack.DefinePlugin({
50 | 'process.env.MIRROR_CONFIG': JSON.stringify(process.env.MIRROR_CONFIG || { ...defaults, ...config }),
51 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production'),
52 | 'process.env.DEBUG_PROD': JSON.stringify(process.env.DEBUG_PROD || 'false')
53 | })
54 | ],
55 |
56 | /**
57 | * Disables webpack processing of __dirname and __filename.
58 | * If you run the bundle in node.js it falls back to these values of node.js.
59 | * https://github.com/webpack/webpack/issues/2010
60 | */
61 | node: {
62 | __dirname: false,
63 | __filename: false
64 | },
65 | });
66 |
--------------------------------------------------------------------------------
/app/containers/App.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React, { Component } from 'react';
3 | import type { Children } from 'react';
4 | //TODO: REMOVE MOUSE HIDING SINCE IT'S HANDLED BY RASPBERRY PI
5 | export default class App extends Component {
6 | props: {
7 | children: Children
8 | };
9 |
10 | constructor(props) {
11 | super(props);
12 | this.mouseMove = this.mouseMove.bind(this);
13 | this.state = {
14 | mouseTimer: setTimeout(()=> {
15 | this.setState({
16 | cursorVisible: false,
17 | forceHide: true,
18 | });
19 | setTimeout(() => {
20 | this.setState({
21 | forceHide: false,
22 | });
23 | }, 200);
24 | }, 4000),
25 | cursorVisible: false,
26 | forceHide: false
27 | };
28 | }
29 |
30 | mouseMove() {
31 | if (!this.state.forceHide && this.state.mouseTimer){
32 | this.setState({
33 | cursorVisible: true,
34 | });
35 |
36 | clearTimeout(this.state.mouseTimer);
37 |
38 | this.setState({
39 | mouseTimer: setTimeout(()=> {
40 | this.setState({
41 | cursorVisible: false,
42 | forceHide: true,
43 | });
44 | setTimeout(() => {
45 | this.setState({
46 | forceHide: false,
47 | });
48 | }, 200);
49 | }, 4000)
50 | });
51 | }
52 | }
53 |
54 | render() {
55 | const configStyle = {
56 | color: '#fff',
57 | padding: '16px',
58 | borderRadius: '5px',
59 | borderWidth: '2px',
60 | borderColor: '#fff',
61 | backgroundColor: '#eeee',
62 | fontSize: '48px',
63 | width: 'fit-content',
64 | zIndex: '1000',
65 | position: 'fixed',
66 | top: 0,
67 | right: 0,
68 | justifyContent: 'center',
69 | display: this.state.cursorVisible ? 'flex' : 'none'
70 | };
71 |
72 | return (
73 |
74 | {this.props.children}
75 |
76 |
77 |
78 |
79 | );
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/app/mirror/modules/default/Jokes/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React, { Component } from 'react';
3 | import PropTypes from 'prop-types';
4 | import { getJoke } from './core/utils';
5 |
6 | class Jokes extends Component {
7 |
8 | constructor(props: any) {
9 | super(props);
10 | this.updateModule = this.updateModule.bind(this);
11 | this.hideShowModule = this.hideShowModule.bind(this);
12 | }
13 |
14 | state = {
15 | opacity: 0,
16 | hidden: true,
17 | intervalId: null,
18 | showHideTimer: null,
19 | joke: ''
20 | };
21 |
22 | componentDidMount() {
23 | this.updateModule();
24 | this.setState({
25 | intervalId: setInterval(() => {
26 | this.updateModule();
27 | }, this.props.updateInterval)
28 | });
29 | }
30 |
31 | componentWillUnmount() {
32 | clearInterval(this.state.intervalId);
33 | clearTimeout(this.state.showHideTimer);
34 | }
35 |
36 | hideShowModule(hide: boolean, callback: any) {
37 | this.setState({
38 | opacity: hide ? 0 : 1,
39 | hidden: hide
40 | });
41 | if (hide && callback) {
42 | this.setState({
43 | showHideTimer: setTimeout(() => { callback(); }, this.props.fadeSpeed / 2),
44 | });
45 | } else {
46 | clearTimeout(this.state.showHideTimer);
47 | }
48 | }
49 |
50 | updateModule() {
51 | this.hideShowModule(true, () => {
52 | getJoke((j)=>{
53 | this.setState({
54 | joke: j,
55 | }, () => {
56 | this.hideShowModule(false);
57 | });
58 | });
59 | });
60 | }
61 |
62 | render() {
63 | return (
64 |
71 | {this.state.joke}
72 |
73 | );
74 | }
75 | }
76 |
77 | Jokes.moduleName = 'Jokes';
78 |
79 | Jokes.defaultProps = {
80 | fadeSpeed: 4000,
81 | updateInterval: 30000, // update the module to change the current item every 30 seconds
82 | animation: 'ease-in',
83 | wrapJoke: true
84 | };
85 |
86 | Jokes.propTypes = {
87 | fadeSpeed: PropTypes.number,
88 | updateInterval: PropTypes.number,
89 | animation: PropTypes.string,
90 | wrapJoke: PropTypes.bool
91 | };
92 |
93 | export default Jokes;
94 |
--------------------------------------------------------------------------------
/app/mirror/modules/default/Clock/README.md:
--------------------------------------------------------------------------------
1 | # Clock
2 | The `clock` module is one of the default modules of the ReactiveMirror.
3 | This module displays the current date and time. The information will be updated realtime.
4 |
5 | ## Using the module
6 |
7 | Add it to the modules array in the `config/config.js` file:
8 |
9 | ````javascript
10 | modules: [
11 | {
12 | name: "Clock",
13 | position: "top_left", // This can be any of the regions.
14 | config: {
15 | // The config property is optional.
16 | // See 'Configuration options' for more information.
17 | }
18 | }
19 | ]
20 | ````
21 |
22 | ## Configuration options
23 |
24 | The following properties can be configured:
25 |
26 | | Option | Description
27 | | ----------------- | -----------
28 | | `timeFormat` | Use 12 or 24 hour format.
**Possible values:** `12` or `24`
**Default value:** uses value of _config.timeFormat_
29 | | `displaySeconds` | Display seconds.
**Possible values:** `true` or `false`
**Default value:** `true`
30 | | `showPeriod` | Show the period (am/pm) with 12 hour format.
**Possible values:** `true` or `false`
**Default value:** `true`
31 | | `showPeriodUpper` | Show the period (AM/PM) with 12 hour format as uppercase.
**Possible values:** `true` or `false`
**Default value:** `false`
32 | | `clockBold` | Remove the colon and bold the minutes to make a more modern look.
**Possible values:** `true` or `false`
**Default value:** `false`
33 | | `showDate` | Turn off or on the Date section.
**Possible values:** `true` or `false`
**Default value:** `true`
34 | | `showWeek` | Turn off or on the Week section.
**Possible values:** `true` or `false`
**Default value:** `false`
35 | | `dateFormat` | Configure the date format as you like.
**Possible values:** [Docs](http://momentjs.com/docs/#/displaying/format/)
**Default value:** `"dddd, LL"`
36 | | `secondsColor` | **Specific to the analog clock.** Specifies what color to make the 'seconds' hand.
**Possible values:** `any HTML RGB Color`
**Default value:** `#888888`
37 | | `timezone` | Specific a timezone to show clock.
**Possible examples values:** `America/New_York`, `America/Santiago`, `Etc/GMT+10`
**Default value:** `none`. See more informations about configuration value [here](https://momentjs.com/timezone/docs/#/data-formats/packed-format/)
38 |
--------------------------------------------------------------------------------
/app/components/Mirror.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React from 'react';
3 | import getModules from '../mirror/core/components';
4 | import styles from './Mirror.css';
5 |
6 | export default class Mirror extends React.Component {
7 |
8 | constructor(props) {
9 | super(props);
10 | this.state = {
11 | components: getModules(),
12 | };
13 | }
14 |
15 | render() {
16 | if (this.state.components) {
17 | return (
18 |
19 |
23 |
24 |
25 | {this.state.components.top_bar}
26 |
27 |
28 |
29 | {this.state.components.top_left}
30 |
31 |
32 |
33 |
34 | {this.state.components.top_center}
35 |
36 |
37 |
38 |
39 | {this.state.components.top_right}
40 |
41 |
42 |
43 |
{this.state.components.upper_third}
44 |
{this.state.components.middle_center}
45 |
{this.state.components.lower_third}
46 |
47 |
{this.state.components.bottom_bar}
48 |
{this.state.components.bottom_left}
49 |
{this.state.components.bottom_center}
50 |
{this.state.components.bottom_right}
51 |
52 |
{this.state.components.fullscreen_above}
53 |
54 | );
55 | }
56 | return (
57 | Reactive Mirror
58 | );
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/app/mirror/modules/default/CurrentWeather/core/utils.js:
--------------------------------------------------------------------------------
1 | export const iconTable = {
2 | '01d': 'wi-day-sunny',
3 | '02d': 'wi-day-cloudy',
4 | '03d': 'wi-cloudy',
5 | '04d': 'wi-cloudy-windy',
6 | '09d': 'wi-showers',
7 | '10d': 'wi-rain',
8 | '11d': 'wi-thunderstorm',
9 | '13d': 'wi-snow',
10 | '50d': 'wi-fog',
11 | '01n': 'wi-night-clear',
12 | '02n': 'wi-night-cloudy',
13 | '03n': 'wi-night-cloudy',
14 | '04n': 'wi-night-cloudy',
15 | '09n': 'wi-night-showers',
16 | '10n': 'wi-night-rain',
17 | '11n': 'wi-night-thunderstorm',
18 | '13n': 'wi-night-snow',
19 | '50n': 'wi-night-alt-cloudy-windy'
20 | };
21 |
22 | /* function(temperature)
23 | * Rounds a temperature to 1 decimal or integer (depending on config.roundTemp).
24 | *
25 | * argument temperature number - Temperature.
26 | *
27 | * return string - Rounded Temperature.
28 | */
29 | export function roundValue(temperature, roundTemp) {
30 | const decimals = roundTemp ? 0 : 1;
31 | return parseFloat(temperature).toFixed(decimals);
32 | }
33 |
34 | /* ms2Beaufort(ms)
35 | * Converts m2 to beaufort (windspeed).
36 | *
37 | * see:
38 | * http://www.spc.noaa.gov/faq/tornado/beaufort.html
39 | * https://en.wikipedia.org/wiki/Beaufort_scale#Modern_scale
40 | *
41 | * argument ms number - Windspeed in m/s.
42 | *
43 | * return number - Windspeed in beaufort.
44 | */
45 | export function ms2Beaufort(ms) {
46 | const kmh = ((ms * 60 * 60) / 1000);
47 | const speeds = [1, 5, 11, 19, 28, 38, 49, 61, 74, 88, 102, 117, 1000];
48 |
49 | for (let i = 0; i < speeds.length; i += 1) {
50 | const speed = speeds[speeds[i]];
51 | if (speed > kmh) {
52 | return speeds[i];
53 | }
54 | }
55 | return 12;
56 | }
57 |
58 | export function deg2Cardinal(deg) {
59 | if (deg > 11.25 && deg <= 33.75) {
60 | return 'NNE';
61 | } else if (deg > 33.75 && deg <= 56.25) {
62 | return 'NE';
63 | } else if (deg > 56.25 && deg <= 78.75) {
64 | return 'ENE';
65 | } else if (deg > 78.75 && deg <= 101.25) {
66 | return 'E';
67 | } else if (deg > 101.25 && deg <= 123.75) {
68 | return 'ESE';
69 | } else if (deg > 123.75 && deg <= 146.25) {
70 | return 'SE';
71 | } else if (deg > 146.25 && deg <= 168.75) {
72 | return 'SSE';
73 | } else if (deg > 168.75 && deg <= 191.25) {
74 | return 'S';
75 | } else if (deg > 191.25 && deg <= 213.75) {
76 | return 'SSW';
77 | } else if (deg > 213.75 && deg <= 236.25) {
78 | return 'SW';
79 | } else if (deg > 236.25 && deg <= 258.75) {
80 | return 'WSW';
81 | } else if (deg > 258.75 && deg <= 281.25) {
82 | return 'W';
83 | } else if (deg > 281.25 && deg <= 303.75) {
84 | return 'WNW';
85 | } else if (deg > 303.75 && deg <= 326.25) {
86 | return 'NW';
87 | } else if (deg > 326.25 && deg <= 348.75) {
88 | return 'NNW';
89 | } else {
90 | return 'N';
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/app/mirror/modules/default/Clock/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React, { Component } from 'react';
3 | import PropTypes from 'prop-types';
4 | import moment from 'moment';
5 | import classNames from 'classnames';
6 | import styles from './Clock.css';
7 |
8 | class Clock extends Component {
9 |
10 | constructor(props: any) {
11 | super(props);
12 | this.updateClock = this.updateClock.bind(this);
13 | moment.locale('en');
14 | }
15 |
16 | state = {
17 | date: '',
18 | week: '',
19 | time: '',
20 | period: '',
21 | seconds: '',
22 | }
23 |
24 | componentDidMount() {
25 | this.updateClock();
26 | this.setState({
27 | timeIntervalID: setInterval(() => {
28 | this.updateClock();
29 | }, 1000)
30 | });
31 | }
32 |
33 | componentWillUnmount() {
34 | clearInterval(this.state.timeIntervalID);
35 | }
36 |
37 | updateClock() {
38 | let now = moment();
39 | if (this.props.timezone) {
40 | now.tz(this.props.timezone);
41 | }
42 |
43 | let hourSymbol = this.props.timeFormat === 24 ? 'HH' : 'h';
44 | this.setState({
45 | hour: now.format(hourSymbol),
46 | minute: now.format('mm'),
47 | date: now.format(this.props.dateFormat),
48 | week: `${'WEEK'} ${now.week()}`,
49 | seconds: now.format('ss'),
50 | period: this.props.showPeriodUpper ? now.format('A') : now.format('a'),
51 | });
52 | }
53 |
54 | render() {
55 | return (
56 |
57 |
{this.state.date}
61 |
65 | {this.state.hour}
66 | {this.props.clockBold ? '' : ':'}{this.state.minute}
67 | {this.state.seconds}
68 | {
69 | (this.props.showPeriod && this.props.timeFormat !== 24) &&
70 | {this.state.period}
71 | }
72 |
73 | {
74 | this.props.showWeek &&
75 |
{this.state.week}
79 | }
80 |
81 | );
82 | }
83 | }
84 |
85 | Clock.moduleName = 'Clock';
86 |
87 | Clock.defaultProps = {
88 | position: 'top_right',
89 | displayType: 'digital',
90 | timeFormat: 12,
91 | timezone: null,
92 | displaySeconds: true,
93 | showPeriod: true,
94 | showPeriodUpper: true,
95 | clockBold: false,
96 | showDate: true,
97 | showWeek: false,
98 | dateFormat: "dddd, LL",
99 | };
100 |
101 | Clock.propTypes = {
102 | position: PropTypes.string,
103 | displayType: PropTypes.string,
104 | dateFormat: PropTypes.string,
105 | timeFormat: PropTypes.number,
106 | displaySeconds: PropTypes.bool,
107 | showPeriod: PropTypes.bool,
108 | showPeriodUpper: PropTypes.bool,
109 | clockBold: PropTypes.bool,
110 | showDate: PropTypes.bool,
111 | showWeek: PropTypes.bool,
112 | timezone: PropTypes.string,
113 | };
114 |
115 | export default Clock;
116 |
--------------------------------------------------------------------------------
/app/main.dev.js:
--------------------------------------------------------------------------------
1 | /* eslint global-require: 1, flowtype-errors/show-errors: 0 */
2 |
3 | /**
4 | * This module executes inside of electron's main process. You can start
5 | * electron renderer process from here and communicate with the other processes
6 | * through IPC.
7 | *
8 | * When running `npm run build` or `npm run build-main`, this file is compiled to
9 | * `./app/main.prod.js` using webpack. This gives us some performance wins.
10 | *
11 | * @flow
12 | */
13 | import { app, BrowserWindow } from 'electron';
14 | import MenuBuilder from './menu';
15 |
16 | const config = process.env.MIRROR_CONFIG ? { ...process.env.MIRROR_CONFIG } : {};
17 | let mainWindow = null;
18 |
19 | if (process.env.NODE_ENV === 'production') {
20 | const sourceMapSupport = require('source-map-support');
21 | sourceMapSupport.install();
22 | }
23 |
24 | if (process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true') {
25 | require('electron-debug')();
26 | const path = require('path');
27 | const p = path.join(__dirname, '..', 'app', 'node_modules');
28 | require('module').globalPaths.push(p);
29 | }
30 |
31 | const installExtensions = async () => {
32 | const installer = require('electron-devtools-installer');
33 | const forceDownload = !!process.env.UPGRADE_EXTENSIONS;
34 | const extensions = [
35 | 'REACT_DEVELOPER_TOOLS',
36 | 'REDUX_DEVTOOLS'
37 | ];
38 |
39 | return Promise
40 | .all(extensions.map(name => installer.default(installer[name], forceDownload)))
41 | .catch(console.log);
42 | };
43 |
44 |
45 | /**
46 | * Add event listeners...
47 | */
48 |
49 | app.on('window-all-closed', () => {
50 | // Respect the OSX convention of having the application in memory even
51 | // after all windows have been closed
52 | if (process.platform !== 'darwin') {
53 | app.quit();
54 | }
55 | });
56 |
57 |
58 | app.on('ready', async () => {
59 | if (process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true') {
60 | await installExtensions();
61 | }
62 |
63 | const productionWindow = {
64 | frameless: true,
65 | fullscreen: true,
66 | autoHideMenuBar: true
67 | };
68 |
69 | mainWindow = new BrowserWindow({
70 | x: 0,
71 | y: 0,
72 | show: false,
73 | width: 800,
74 | height: 600,
75 | darkTheme: true,
76 | backgroundColor: '#000000',
77 | webPreferences: {
78 | zoomFactor: config.zoom
79 | },
80 | ...(process.env.NODE_ENV === 'production' ? productionWindow : {}),
81 | ...config.electronOptions
82 | });
83 |
84 | mainWindow.loadURL(`file://${__dirname}/app.html`);
85 |
86 | // @TODO: Use 'ready-to-show' event
87 | // https://github.com/electron/electron/blob/master/docs/api/browser-window.md#using-ready-to-show-event
88 | mainWindow.webContents.on('did-finish-load', () => {
89 | if (!mainWindow) {
90 | throw new Error('\'mainWindow\' is not defined');
91 | }
92 | mainWindow.show();
93 | mainWindow.focus();
94 | });
95 |
96 | mainWindow.on('closed', () => {
97 | mainWindow = null;
98 | });
99 |
100 | if (config.kiosk) {
101 | mainWindow.on('blur', () => {
102 | mainWindow.focus();
103 | });
104 |
105 | mainWindow.on('leave-full-screen', () => {
106 | mainWindow.setFullScreen(true);
107 | });
108 |
109 | mainWindow.on('resize', () => {
110 | setTimeout(() => {
111 | mainWindow.reload();
112 | }, 1000);
113 | });
114 | }
115 |
116 | const menuBuilder = new MenuBuilder(mainWindow);
117 | menuBuilder.buildMenu();
118 | });
119 |
--------------------------------------------------------------------------------
/test/e2e/e2e.spec.js:
--------------------------------------------------------------------------------
1 | import { Application } from 'spectron';
2 | import electronPath from 'electron';
3 | import path from 'path';
4 |
5 | jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000;
6 |
7 | const delay = time => new Promise(resolve => setTimeout(resolve, time));
8 |
9 | describe('main window', function spec() {
10 | beforeAll(async () => {
11 | this.app = new Application({
12 | path: electronPath,
13 | args: [path.join(__dirname, '..', '..', 'app')],
14 | });
15 |
16 | return this.app.start();
17 | });
18 |
19 | afterAll(() => {
20 | if (this.app && this.app.isRunning()) {
21 | return this.app.stop();
22 | }
23 | });
24 |
25 | const findCounter = () => this.app.client.element('[data-tid="counter"]');
26 |
27 | const findButtons = async () => {
28 | const { value } = await this.app.client.elements('[data-tclass="btn"]');
29 | return value.map(btn => btn.ELEMENT);
30 | };
31 |
32 | it('should open window', async () => {
33 | const { client, browserWindow } = this.app;
34 |
35 | await client.waitUntilWindowLoaded();
36 | await delay(500);
37 | const title = await browserWindow.getTitle();
38 | expect(title).toBe('Hello Electron React!');
39 | });
40 |
41 | it('should haven\'t any logs in console of main window', async () => {
42 | const { client } = this.app;
43 | const logs = await client.getRenderProcessLogs();
44 | // Print renderer process logs
45 | logs.forEach(log => {
46 | console.log(log.message);
47 | console.log(log.source);
48 | console.log(log.level);
49 | });
50 | expect(logs).toHaveLength(0);
51 | });
52 |
53 | it('should to Counter with click "to Counter" link', async () => {
54 | const { client } = this.app;
55 |
56 | await client.click('[data-tid=container] > a');
57 | expect(await findCounter().getText()).toBe('0');
58 | });
59 |
60 | it('should display updated count after increment button click', async () => {
61 | const { client } = this.app;
62 |
63 | const buttons = await findButtons();
64 | await client.elementIdClick(buttons[0]); // +
65 | expect(await findCounter().getText()).toBe('1');
66 | });
67 |
68 | it('should display updated count after descrement button click', async () => {
69 | const { client } = this.app;
70 |
71 | const buttons = await findButtons();
72 | await client.elementIdClick(buttons[1]); // -
73 | expect(await findCounter().getText()).toBe('0');
74 | });
75 |
76 | it('shouldnt change if even and if odd button clicked', async () => {
77 | const { client } = this.app;
78 |
79 | const buttons = await findButtons();
80 | await client.elementIdClick(buttons[2]); // odd
81 | expect(await findCounter().getText()).toBe('0');
82 | });
83 |
84 | it('should change if odd and if odd button clicked', async () => {
85 | const { client } = this.app;
86 |
87 | const buttons = await findButtons();
88 | await client.elementIdClick(buttons[0]); // +
89 | await client.elementIdClick(buttons[2]); // odd
90 | expect(await findCounter().getText()).toBe('2');
91 | });
92 |
93 | it('should change if async button clicked and a second later', async () => {
94 | const { client } = this.app;
95 |
96 | const buttons = await findButtons();
97 | await client.elementIdClick(buttons[3]); // async
98 | expect(await findCounter().getText()).toBe('2');
99 | await delay(1500);
100 | expect(await findCounter().getText()).toBe('3');
101 | });
102 |
103 | it('should back to home if back button clicked', async () => {
104 | const { client } = this.app;
105 | await client.element(
106 | '[data-tid="backButton"] > a'
107 | ).click();
108 |
109 | expect(
110 | await client.isExisting('[data-tid="container"]')
111 | ).toBe(true);
112 | });
113 | });
114 |
--------------------------------------------------------------------------------
/app/mirror/styles/roboto.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: Roboto;
3 | font-style: normal;
4 | font-weight: 100;
5 | src:
6 | local("Roboto Thin"),
7 | local("Roboto-Thin"),
8 | url("../../node_modules/roboto-fontface/fonts/roboto/Roboto-Thin.woff2") format("woff2"),
9 | url("../../node_modules/roboto-fontface/fonts/roboto/Roboto-Thin.woff") format("woff"),
10 | url("../../node_modules/roboto-fontface/fonts/roboto/Roboto-Thin.ttf") format("truetype");
11 | }
12 |
13 | @font-face {
14 | font-family: "Roboto Condensed";
15 | font-style: normal;
16 | font-weight: 300;
17 | src:
18 | local("Roboto Condensed Light"),
19 | local("RobotoCondensed-Light"),
20 | url("../../node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Light.woff2") format("woff2"),
21 | url("../../node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Light.woff") format("woff"),
22 | url("../../node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Light.ttf") format("truetype");
23 | }
24 |
25 | @font-face {
26 | font-family: "Roboto Condensed";
27 | font-style: normal;
28 | font-weight: 400;
29 | src:
30 | local("Roboto Condensed"),
31 | local("RobotoCondensed-Regular"),
32 | url("../../node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Regular.woff2") format("woff2"),
33 | url("../../node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Regular.woff") format("woff"),
34 | url("../../node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Regular.ttf") format("truetype");
35 | }
36 |
37 | @font-face {
38 | font-family: "Roboto Condensed";
39 | font-style: normal;
40 | font-weight: 700;
41 | src:
42 | local("Roboto Condensed Bold"),
43 | local("RobotoCondensed-Bold"),
44 | url("../../node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Bold.woff2") format("woff2"),
45 | url("../../node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Bold.woff") format("woff"),
46 | url("../../node_modules/roboto-fontface/fonts/roboto-condensed/Roboto-Condensed-Bold.ttf") format("truetype");
47 | }
48 |
49 | @font-face {
50 | font-family: Roboto;
51 | font-style: normal;
52 | font-weight: 400;
53 | src:
54 | local("Roboto"),
55 | local("Roboto-Regular"),
56 | url("../../node_modules/roboto-fontface/fonts/roboto/Roboto-Regular.woff2") format("woff2"),
57 | url("../../node_modules/roboto-fontface/fonts/roboto/Roboto-Regular.woff") format("woff"),
58 | url("../../node_modules/roboto-fontface/fonts/roboto/Roboto-Regular.ttf") format("truetype");
59 | }
60 |
61 | @font-face {
62 | font-family: Roboto;
63 | font-style: normal;
64 | font-weight: 500;
65 | src:
66 | local("Roboto Medium"),
67 | local("Roboto-Medium"),
68 | url("../../node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.woff2") format("woff2"),
69 | url("../../node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.woff") format("woff"),
70 | url("../../node_modules/roboto-fontface/fonts/roboto/Roboto-Medium.ttf") format("truetype");
71 | }
72 |
73 | @font-face {
74 | font-family: Roboto;
75 | font-style: normal;
76 | font-weight: 700;
77 | src:
78 | local("Roboto Bold"),
79 | local("Roboto-Bold"),
80 | url("../../node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.woff2") format("woff2"),
81 | url("../../node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.woff") format("woff"),
82 | url("../../node_modules/roboto-fontface/fonts/roboto/Roboto-Bold.ttf") format("truetype");
83 | }
84 |
85 | @font-face {
86 | font-family: Roboto;
87 | font-style: normal;
88 | font-weight: 300;
89 | src:
90 | local("Roboto Light"),
91 | local("Roboto-Light"),
92 | url("../../node_modules/roboto-fontface/fonts/roboto/Roboto-Light.woff2") format("woff2"),
93 | url("../../node_modules/roboto-fontface/fonts/roboto/Roboto-Light.woff") format("woff"),
94 | url("../../node_modules/roboto-fontface/fonts/roboto/Roboto-Light.ttf") format("truetype");
95 | }
96 |
--------------------------------------------------------------------------------
/app/mirror/styles/main.css:
--------------------------------------------------------------------------------
1 | @import "~weathericons/css/weather-icons.css";
2 | html {
3 | overflow: hidden;
4 | background: #000;
5 | }
6 |
7 | body{
8 | margin: 0;
9 | width: 100vw;
10 | height: 100vh;
11 | }
12 |
13 | :not(input):not(textarea) {
14 | -webkit-user-select: none;
15 | user-select: none;
16 | }
17 |
18 | ::-webkit-scrollbar {
19 | display: none;
20 | }
21 |
22 | /**
23 | * Default styles.
24 | */
25 |
26 | .dimmed {
27 | color: #666;
28 | }
29 |
30 | .normal {
31 | color: #999;
32 | }
33 |
34 | .bright {
35 | color: #fff;
36 | }
37 |
38 | .xsmall {
39 | font-size: 15px;
40 | line-height: 20px;
41 | }
42 |
43 | .small {
44 | font-size: 20px;
45 | line-height: 25px;
46 | }
47 |
48 | .medium {
49 | font-size: 30px;
50 | line-height: 35px;
51 | }
52 |
53 | .large {
54 | font-size: 65px;
55 | line-height: 65px;
56 | }
57 |
58 | .xlarge {
59 | font-size: 75px;
60 | line-height: 75px;
61 | letter-spacing: -3px;
62 | }
63 |
64 | .thin {
65 | font-family: Roboto, sans-serif;
66 | font-weight: 100;
67 | }
68 |
69 | .light {
70 | font-family: 'Roboto Condensed', sans-serif;
71 | font-weight: 300;
72 | }
73 |
74 | .regular {
75 | font-family: 'Roboto Condensed', sans-serif;
76 | font-weight: 400;
77 | }
78 |
79 | .bold {
80 | font-family: 'Roboto Condensed', sans-serif;
81 | font-weight: 700;
82 | }
83 |
84 | .align-right {
85 | text-align: right;
86 | }
87 |
88 | .align-left {
89 | text-align: left;
90 | }
91 |
92 | header {
93 | text-transform: uppercase;
94 | font-size: 15px;
95 | font-family: 'Roboto Condensed';
96 | font-weight: 400;
97 | border-bottom: 1px solid #666;
98 | line-height: 15px;
99 | padding-bottom: 5px;
100 | margin-bottom: 10px;
101 | color: #999;
102 | }
103 |
104 | sup {
105 | font-size: 50%;
106 | line-height: 50%;
107 | }
108 |
109 | /**
110 | * Module styles.
111 | */
112 |
113 | .module {
114 | margin-bottom: 30px;
115 | }
116 |
117 | .region.bottom .module {
118 | margin-top: 30px;
119 | margin-bottom: 0;
120 | }
121 |
122 | .no-wrap {
123 | white-space: nowrap;
124 | overflow: hidden;
125 | text-overflow: ellipsis;
126 | }
127 |
128 | /**
129 | * Region Definitions.
130 | */
131 |
132 | .region {
133 | position: absolute;
134 | }
135 |
136 | .region.fullscreen {
137 | position: absolute;
138 | top: -60px;
139 | left: -60px;
140 | right: -60px;
141 | bottom: -60px;
142 | pointer-events: none;
143 | }
144 |
145 | .region.fullscreen * {
146 | pointer-events: auto;
147 | }
148 |
149 | .region.right {
150 | right: 0;
151 | }
152 |
153 | .region.top {
154 | top: 0;
155 | }
156 |
157 | .region.top .container {
158 | margin-bottom: 25px;
159 | }
160 |
161 | .region.top .container:empty {
162 | margin-bottom: 0;
163 | }
164 |
165 | .region.top.center,
166 | .region.bottom.center {
167 | left: 50%;
168 | -moz-transform: translateX(-50%);
169 | -o-transform: translateX(-50%);
170 | -webkit-transform: translateX(-50%);
171 | -ms-transform: translateX(-50%);
172 | transform: translateX(-50%);
173 | }
174 |
175 | .region.top.right,
176 | .region.top.left,
177 | .region.top.center {
178 | top: 100%;
179 | }
180 |
181 | .region.bottom {
182 | bottom: 0;
183 | }
184 |
185 | .region.bottom .container {
186 | margin-top: 25px;
187 | }
188 |
189 | .region.bottom .container:empty {
190 | margin-top: 0;
191 | }
192 |
193 | .region.bottom.right,
194 | .region.bottom.center,
195 | .region.bottom.left {
196 | bottom: 100%;
197 | }
198 |
199 | .region.bar {
200 | width: 100%;
201 | text-align: center;
202 | }
203 |
204 | .region.third,
205 | .region.middle.center {
206 | width: 100%;
207 | text-align: center;
208 | -moz-transform: translateY(-50%);
209 | -o-transform: translateY(-50%);
210 | -webkit-transform: translateY(-50%);
211 | -ms-transform: translateY(-50%);
212 | transform: translateY(-50%);
213 | }
214 |
215 | .region.upper.third {
216 | top: 33%;
217 | }
218 |
219 | .region.middle.center {
220 | top: 50%;
221 | }
222 |
223 | .region.lower.third {
224 | top: 66%;
225 | }
226 |
227 | .region.left {
228 | text-align: left;
229 | }
230 |
231 | .region.right {
232 | text-align: right;
233 | }
234 |
235 | .region table {
236 | width: 100%;
237 | border-spacing: 0;
238 | border-collapse: separate;
239 | }
240 |
--------------------------------------------------------------------------------
/app/mirror/modules/default/Compliments/README.md:
--------------------------------------------------------------------------------
1 | # Compliments
2 | The `compliments` module is one of the default modules of the MagicMirror.
3 | This module displays a random compliment.
4 |
5 | ## Using the module
6 |
7 | Add it to the modules array in the `config/config.js` file:
8 |
9 | ````javascript
10 | modules: [
11 | {
12 | module: "Compliments",
13 | position: "lower_third", // This can be any of the regions.
14 | // Best results in one of the middle regions like: lower_third
15 | config: {
16 | // The config property is optional.
17 | // If no config is set, the default compliments are shown.
18 | // See 'Configuration options' for more information.
19 | }
20 | }
21 | ]
22 | ````
23 |
24 | ## Configuration options
25 |
26 | The following properties can be configured:
27 |
28 |
29 | | Option | Description
30 | | ---------------- | -----------
31 | | `updateInterval` | How often does the compliment have to change? (Milliseconds)
**Possible values:** `1000` - `86400000`
**Default value:** `30000` (30 seconds)
32 | | `fadeSpeed` | Speed of the update animation. (Milliseconds)
**Possible values:**`0` - `5000`
**Default value:** `4000` (4 seconds)
33 | | `compliments` | The list of compliments.
**Possible values:** An object with four arrays: `morning`, `afternoon`, `evening` and `anytime`. See _compliment configuration_ below.
**Default value:** See _compliment configuration_ below.
34 | | `remoteFile` | External file from which to load the compliments
**Possible values:** Path to a JSON file containing compliments, configured as per the value of the _compliments configuration_ (see below). An object with four arrays: `morning`, `afternoon`, `evening` and `anytime`. - `compliments.json`
**Default value:** `null` (Do not load from file)
35 | | `classes` | Override the CSS classes of the div showing the compliments
**Default value:** `thin xlarge bright`
36 |
37 | ### Compliment configuration
38 |
39 | The `compliments` property contains an object with four arrays: morning, afternoon, evening and anytime. Based on the time of the day, the compliments will be picked out of one of these arrays. The arrays contain one or multiple compliments.
40 |
41 |
42 | If use the currentweather is possible use a actual weather for set compliments. The availables properties are:
43 | * `day_sunny`
44 | * `day_cloudy`
45 | * `cloudy`
46 | * `cloudy_windy`
47 | * `showers`
48 | * `rain`
49 | * `thunderstorm`
50 | * `snow`
51 | * `fog`
52 | * `night_clear`
53 | * `night_cloudy`
54 | * `night_showers`
55 | * `night_rain`
56 | * `night_thunderstorm`
57 | * `night_snow`
58 | * `night_alt_cloudy_windy`
59 |
60 | #### Example use with CurrentWeather module
61 | ````javascript
62 | config: {
63 | compliments: {
64 | day_sunny: [
65 | "Today is a sunny day",
66 | "It's a beautiful day"
67 | ],
68 | snow: [
69 | "Snowball battle!"
70 | ],
71 | rain: [
72 | "Don't forget your umbrella"
73 | ]
74 | }
75 | }
76 | ````
77 |
78 |
79 | #### Default value:
80 | ````javascript
81 | config: {
82 | compliments: {
83 | anytime: [
84 | "Hey there sexy!"
85 | ],
86 | morning: [
87 | "Good morning, handsome!",
88 | "Enjoy your day!",
89 | "How was your sleep?"
90 | ],
91 | afternoon: [
92 | "Hello, beauty!",
93 | "You look sexy!",
94 | "Looking good today!"
95 | ],
96 | evening: [
97 | "Wow, you look hot!",
98 | "You look nice!",
99 | "Hi, sexy!"
100 | ]
101 | }
102 | }
103 | ````
104 |
105 | ### External Compliment File
106 | You may specify an external file that contains the three compliment arrays. This is particularly useful if you have a
107 | large number of compliments and do not wish to crowd your `config.js` file with a large array of compliments.
108 | Adding the `remoteFile` variable will override an array you specify in the configuration file.
109 |
110 | This file must be straight JSON. Note that the array names need quotes
111 | around them ("morning", "afternoon", "evening", "snow", "rain", etc.).
112 | #### Example compliments.json file:
113 | ````json
114 | {
115 | "anytime" : [
116 | "Hey there sexy!"
117 | ],
118 | "morning" : [
119 | "Good morning, sunshine!",
120 | "Who needs coffee when you have your smile?",
121 | "Go get 'em, Tiger!"
122 | ],
123 | "afternoon" : [
124 | "Hitting your stride!",
125 | "You are making a difference!",
126 | "You're more fun than bubble wrap!"
127 | ],
128 | "evening" : [
129 | "You made someone smile today, I know it.",
130 | "You are making a difference.",
131 | "The day was better for your efforts."
132 | ]
133 | }
134 | ````
135 |
--------------------------------------------------------------------------------
/app/mirror/modules/default/WeatherForecast/core/utils.js:
--------------------------------------------------------------------------------
1 | import moment from 'moment';
2 |
3 | export function parseWeather(forecast) {
4 | let t;
5 | if (forecast.main) {
6 | t = { min: forecast.main.temp_min, max: forecast.main.temp_max };
7 | }
8 | return { ...forecast, temp: t, timeOfDay: moment(forecast.dt, 'X').format('h A') };
9 | }
10 |
11 | export function processWeather(data) {
12 | if (!data) {
13 | // Did not receive usable new data.
14 | // Maybe this needs a better check?
15 | console.log('didnt work');
16 | return;
17 | }
18 |
19 | const getForecast = () => {
20 | const forecastData = {};
21 | let lastDay = null;
22 | return data.list.map((f) => {
23 | const forecast = { ...parseWeather(f) };
24 | const fDay = moment(forecast.dt, 'X').format('ddd');
25 | const hour = moment(forecast.dt, 'X').format('H');
26 | if (fDay !== lastDay) {
27 | const fData = {
28 | day: fDay,
29 | icon: iconTable[forecast.weather[0].icon],
30 | maxTemp: roundValue(forecast.temp.max),
31 | minTemp: roundValue(forecast.temp.min),
32 | rain: roundValue(forecast.rain),
33 | timeOfDay: forecast.timeOfDay
34 | };
35 |
36 | lastDay = fData;
37 | return fData;
38 | }
39 | forecastData.maxTemp = (forecast.temp.max > parseFloat(forecastData.maxTemp)) ? roundValue(forecast.temp.max) : forecastData.maxTemp;
40 | forecastData.minTemp = (forecast.temp.min < parseFloat(forecastData.minTemp)) ? roundValue(forecast.temp.min) : forecastData.minTemp;
41 | if (hour >= 8 && hour <= 17) {
42 | forecastData.icon = iconTable[forecast.weather[0].icon];
43 | }
44 | });
45 | };
46 |
47 | const weatherObj = {
48 | fetchedLocationName: `${data.city.name}, ${data.city.country}`,
49 | forecast: getForecast(),
50 | loading: false,
51 | };
52 |
53 | return weatherObj;
54 | }
55 |
56 | export const iconTable = {
57 | '01d': 'wi-day-sunny',
58 | '02d': 'wi-day-cloudy',
59 | '03d': 'wi-cloudy',
60 | '04d': 'wi-cloudy-windy',
61 | '09d': 'wi-showers',
62 | '10d': 'wi-rain',
63 | '11d': 'wi-thunderstorm',
64 | '13d': 'wi-snow',
65 | '50d': 'wi-fog',
66 | '01n': 'wi-night-clear',
67 | '02n': 'wi-night-cloudy',
68 | '03n': 'wi-night-cloudy',
69 | '04n': 'wi-night-cloudy',
70 | '09n': 'wi-night-showers',
71 | '10n': 'wi-night-rain',
72 | '11n': 'wi-night-thunderstorm',
73 | '13n': 'wi-night-snow',
74 | '50n': 'wi-night-alt-cloudy-windy'
75 | };
76 |
77 | /* function(temperature)
78 | * Rounds a temperature to 1 decimal or integer (depending on config.roundTemp).
79 | *
80 | * argument temperature number - Temperature.
81 | *
82 | * return string - Rounded Temperature.
83 | */
84 | export function roundValue(temperature, roundTemp) {
85 | const decimals = roundTemp ? 0 : 1;
86 | return parseFloat(temperature).toFixed(decimals);
87 | }
88 |
89 | /* ms2Beaufort(ms)
90 | * Converts m2 to beaufort (windspeed).
91 | *
92 | * see:
93 | * http://www.spc.noaa.gov/faq/tornado/beaufort.html
94 | * https://en.wikipedia.org/wiki/Beaufort_scale#Modern_scale
95 | *
96 | * argument ms number - Windspeed in m/s.
97 | *
98 | * return number - Windspeed in beaufort.
99 | */
100 | export function ms2Beaufort(ms) {
101 | const kmh = ((ms * 60 * 60) / 1000);
102 | const speeds = [1, 5, 11, 19, 28, 38, 49, 61, 74, 88, 102, 117, 1000];
103 |
104 | for (let i = 0; i < speeds.length; i += 1) {
105 | const speed = speeds[speeds[i]];
106 | if (speed > kmh) {
107 | return speeds[i];
108 | }
109 | }
110 | return 12;
111 | }
112 |
113 | export function deg2Cardinal(deg) {
114 | if (deg > 11.25 && deg <= 33.75) {
115 | return 'NNE';
116 | } else if (deg > 33.75 && deg <= 56.25) {
117 | return 'NE';
118 | } else if (deg > 56.25 && deg <= 78.75) {
119 | return 'ENE';
120 | } else if (deg > 78.75 && deg <= 101.25) {
121 | return 'E';
122 | } else if (deg > 101.25 && deg <= 123.75) {
123 | return 'ESE';
124 | } else if (deg > 123.75 && deg <= 146.25) {
125 | return 'SE';
126 | } else if (deg > 146.25 && deg <= 168.75) {
127 | return 'SSE';
128 | } else if (deg > 168.75 && deg <= 191.25) {
129 | return 'S';
130 | } else if (deg > 191.25 && deg <= 213.75) {
131 | return 'SSW';
132 | } else if (deg > 213.75 && deg <= 236.25) {
133 | return 'SW';
134 | } else if (deg > 236.25 && deg <= 258.75) {
135 | return 'WSW';
136 | } else if (deg > 258.75 && deg <= 281.25) {
137 | return 'W';
138 | } else if (deg > 281.25 && deg <= 303.75) {
139 | return 'WNW';
140 | } else if (deg > 303.75 && deg <= 326.25) {
141 | return 'NW';
142 | } else if (deg > 326.25 && deg <= 348.75) {
143 | return 'NNW';
144 | } else {
145 | return 'N';
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/app/mirror/modules/default/Compliments/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React, { Component } from 'react';
3 | import PropTypes from 'prop-types';
4 | import moment from 'moment';
5 | import classNames from 'classnames';
6 | import styles from './Compliments.css';
7 | import notifications from '../../../core/notifications';
8 | import { defaultCompliments, getRandom, complimentFile, weatherIconTable } from './core/utils';
9 |
10 | class Compliments extends Component {
11 |
12 | constructor(props: any) {
13 | super(props);
14 | if (this.props.config && this.props.config.remoteFile) {
15 | complimentFile(this.props.config.remoteFile, (response) => {
16 | this.setState({
17 | compliments: JSON.parse(response)
18 | });
19 | });
20 | }
21 |
22 | this.updateModule = this.updateModule.bind(this);
23 | this.hideShowModule = this.hideShowModule.bind(this);
24 | this.randomIndex = this.randomIndex.bind(this);
25 | this.complimentArray = this.complimentArray.bind(this);
26 | }
27 |
28 | state = {
29 | opacity: 0,
30 | intervalId: null,
31 | showHideTimer: null,
32 | complimentIndex: 0,
33 | currentWeatherType: '',
34 | lastComplimentIndex: -1,
35 | compliments: defaultCompliments,
36 | currentCompliments: [],
37 | hidden: true,
38 | };
39 |
40 | componentDidMount() {
41 | this.updateModule();
42 | this.setState({
43 | intervalId: setInterval(() => {
44 | this.updateModule();
45 | }, this.props.updateInterval),
46 | });
47 |
48 | notifications.on('NOTIFICATION', (arg) => {
49 | switch (arg.type) {
50 | case 'CURRENTWEATHER_DATA':
51 | this.setState({
52 | currentWeatherType: weatherIconTable[arg.payload.weather[0].icon]
53 | });
54 | break;
55 | }
56 | });
57 | }
58 |
59 | componentWillUnmount() {
60 | clearInterval(this.state.intervalId);
61 | clearTimeout(this.state.showHideTimer);
62 | }
63 |
64 | hideShowModule(hide: boolean, callback: any) {
65 | this.setState({
66 | opacity: hide ? 0 : 1,
67 | hidden: hide
68 | });
69 | if (hide && callback) {
70 | this.setState({
71 | showHideTimer: setTimeout(() => { callback(); }, this.props.fadeSpeed / 2),
72 | });
73 | } else {
74 | clearTimeout(this.state.showHideTimer);
75 | }
76 | }
77 |
78 | randomIndex() {
79 | const len = this.state.currentCompliments.length;
80 |
81 | if (len === 1) {
82 | return 0;
83 | }
84 |
85 | let compIndex = getRandom(len);
86 |
87 | while (compIndex === this.state.lastComplimentIndex) {
88 | compIndex = getRandom(len);
89 | }
90 |
91 | this.setState({
92 | lastComplimentIndex: compIndex,
93 | });
94 |
95 | return compIndex;
96 | }
97 |
98 | complimentArray() {
99 | const hour = moment().hour();
100 | let compliments = [];
101 |
102 | if (hour >= 3 && hour < 12 && this.state.compliments.hasOwnProperty('morning')) {
103 | compliments = this.state.compliments.morning;
104 | } else if (hour >= 12 && hour < 17 && this.state.compliments.hasOwnProperty('afternoon')) {
105 | compliments = this.state.compliments.afternoon;
106 | } else if (this.state.compliments.hasOwnProperty('evening')) {
107 | compliments = this.state.compliments.evening;
108 | }
109 |
110 | if (this.state.currentWeatherType in this.state.compliments) {
111 | compliments = [...compliments, this.state.compliments[this.state.currentWeatherType]];
112 | }
113 |
114 | compliments = [...compliments, ...this.state.compliments.anytime];
115 |
116 | return compliments;
117 | }
118 |
119 | updateModule() {
120 | this.hideShowModule(true, () => {
121 | this.setState({
122 | complimentIndex: this.randomIndex(),
123 | currentCompliments: this.complimentArray(),
124 | }, () => {
125 | this.hideShowModule(false);
126 | });
127 | });
128 | }
129 |
130 | render() {
131 | //console.log(this.state.opacity);
132 | return (
133 |
140 | {this.state.currentCompliments[this.state.complimentIndex]}
141 |
142 | );
143 | }
144 | }
145 |
146 | Compliments.moduleName = 'Compliments';
147 |
148 | Compliments.defaultProps = {
149 | fadeSpeed: 4000,
150 | updateInterval: 30000,
151 | animation: 'ease-in',
152 | position: 'lower_third',
153 | };
154 |
155 | Compliments.propTypes = {
156 | fadeSpeed: PropTypes.number,
157 | updateInterval: PropTypes.number,
158 | animation: PropTypes.string,
159 | position: PropTypes.string
160 | };
161 |
162 | export default Compliments;
163 |
--------------------------------------------------------------------------------
/webpack.config.renderer.prod.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Build config for electron renderer process
3 | */
4 |
5 | import path from 'path';
6 | import webpack from 'webpack';
7 | import ExtractTextPlugin from 'extract-text-webpack-plugin';
8 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer';
9 | import merge from 'webpack-merge';
10 | // import BabiliPlugin from 'babili-webpack-plugin';
11 | import baseConfig from './webpack.config.base';
12 | import CheckNodeEnv from './internals/scripts/CheckNodeEnv';
13 | import defaults from './app/mirror/config/defaults';
14 | import config from './app/mirror/config/config';
15 |
16 | CheckNodeEnv('production');
17 |
18 | export default merge.smart(baseConfig, {
19 | devtool: 'source-map',
20 |
21 | target: 'electron-renderer',
22 |
23 | entry: './app/index',
24 |
25 | output: {
26 | path: path.join(__dirname, 'app/dist'),
27 | publicPath: '../dist/',
28 | filename: 'renderer.prod.js'
29 | },
30 |
31 | module: {
32 | rules: [
33 | // Extract all .global.css to style.css as is
34 | {
35 | test: /\.global\.css$/,
36 | use: ExtractTextPlugin.extract({
37 | use: 'css-loader',
38 | fallback: 'style-loader',
39 | })
40 | },
41 | // Pipe other styles through css modules and append to style.css
42 | {
43 | test: /^((?!\.global).)*\.css$/,
44 | use: ExtractTextPlugin.extract({
45 | use: {
46 | loader: 'css-loader',
47 | options: {
48 | modules: true,
49 | importLoaders: 1,
50 | localIdentName: '[name]__[local]__[hash:base64:5]',
51 | }
52 | }
53 | }),
54 | },
55 | // Add SASS support - compile all .global.scss files and pipe it to style.css
56 | {
57 | test: /\.global\.scss$/,
58 | use: ExtractTextPlugin.extract({
59 | use: [
60 | {
61 | loader: 'css-loader'
62 | },
63 | {
64 | loader: 'sass-loader'
65 | }
66 | ],
67 | fallback: 'style-loader',
68 | })
69 | },
70 | // Add SASS support - compile all other .scss files and pipe it to style.css
71 | {
72 | test: /^((?!\.global).)*\.scss$/,
73 | use: ExtractTextPlugin.extract({
74 | use: [{
75 | loader: 'css-loader',
76 | options: {
77 | modules: true,
78 | importLoaders: 1,
79 | localIdentName: '[name]__[local]__[hash:base64:5]',
80 | }
81 | },
82 | {
83 | loader: 'sass-loader'
84 | }]
85 | }),
86 | },
87 | // WOFF Font
88 | {
89 | test: /\.woff(\?v=\d+\.\d+\.\d+)?$/,
90 | use: {
91 | loader: 'url-loader',
92 | options: {
93 | limit: 10000,
94 | mimetype: 'application/font-woff',
95 | }
96 | },
97 | },
98 | // WOFF2 Font
99 | {
100 | test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/,
101 | use: {
102 | loader: 'url-loader',
103 | options: {
104 | limit: 10000,
105 | mimetype: 'application/font-woff',
106 | }
107 | }
108 | },
109 | // TTF Font
110 | {
111 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
112 | use: {
113 | loader: 'url-loader',
114 | options: {
115 | limit: 10000,
116 | mimetype: 'application/octet-stream'
117 | }
118 | }
119 | },
120 | // EOT Font
121 | {
122 | test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
123 | use: 'file-loader',
124 | },
125 | // SVG Font
126 | {
127 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
128 | use: {
129 | loader: 'url-loader',
130 | options: {
131 | limit: 10000,
132 | mimetype: 'image/svg+xml',
133 | }
134 | }
135 | },
136 | // Common Image Formats
137 | {
138 | test: /\.(?:ico|gif|png|jpg|jpeg|webp)$/,
139 | use: 'url-loader',
140 | }
141 | ]
142 | },
143 |
144 | plugins: [
145 | /**
146 | * Create global constants which can be configured at compile time.
147 | *
148 | * Useful for allowing different behaviour between development builds and
149 | * release builds
150 | *
151 | * NODE_ENV should be production so that modules do not perform certain
152 | * development checks
153 | */
154 | new webpack.DefinePlugin({
155 | 'process.env.MIRROR_CONFIG': JSON.stringify(process.env.MIRROR_CONFIG || { ...defaults, ...config }),
156 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production')
157 | }),
158 |
159 | /**
160 | * Babli is an ES6+ aware minifier based on the Babel toolchain (beta)
161 | */
162 | // new BabiliPlugin(),
163 |
164 | new ExtractTextPlugin('style.css'),
165 |
166 | new BundleAnalyzerPlugin({
167 | analyzerMode: process.env.OPEN_ANALYZER === 'true' ? 'server' : 'disabled',
168 | openAnalyzer: process.env.OPEN_ANALYZER === 'true'
169 | }),
170 | ],
171 | });
172 |
--------------------------------------------------------------------------------
/webpack.config.renderer.dev.dll.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Builds the DLL for development electron renderer process
3 | */
4 |
5 | import webpack from 'webpack';
6 | import path from 'path';
7 | import merge from 'webpack-merge';
8 | import baseConfig from './webpack.config.base';
9 | import { dependencies } from './package.json';
10 | import CheckNodeEnv from './internals/scripts/CheckNodeEnv';
11 |
12 | CheckNodeEnv('development');
13 |
14 | const dist = path.resolve(process.cwd(), 'dll');
15 |
16 | export default merge.smart(baseConfig, {
17 | context: process.cwd(),
18 |
19 | devtool: 'eval',
20 |
21 | target: 'electron-renderer',
22 |
23 | externals: ['fsevents', 'crypto-browserify'],
24 |
25 | /**
26 | * @HACK: Copy and pasted from renderer dev config. Consider merging these
27 | * rules into the base config. May cause breaking changes.
28 | */
29 | module: {
30 | rules: [
31 | {
32 | test: /\.global\.css$/,
33 | use: [
34 | {
35 | loader: 'style-loader'
36 | },
37 | {
38 | loader: 'css-loader',
39 | options: {
40 | sourceMap: true,
41 | },
42 | }
43 | ]
44 | },
45 | {
46 | test: /^((?!\.global).)*\.css$/,
47 | use: [
48 | {
49 | loader: 'style-loader'
50 | },
51 | {
52 | loader: 'css-loader',
53 | options: {
54 | modules: true,
55 | sourceMap: true,
56 | importLoaders: 1,
57 | localIdentName: '[name]__[local]__[hash:base64:5]',
58 | }
59 | },
60 | ]
61 | },
62 | // Add SASS support - compile all .global.scss files and pipe it to style.css
63 | {
64 | test: /\.global\.scss$/,
65 | use: [
66 | {
67 | loader: 'style-loader'
68 | },
69 | {
70 | loader: 'css-loader',
71 | options: {
72 | sourceMap: true,
73 | },
74 | },
75 | {
76 | loader: 'sass-loader'
77 | }
78 | ]
79 | },
80 | // Add SASS support - compile all other .scss files and pipe it to style.css
81 | {
82 | test: /^((?!\.global).)*\.scss$/,
83 | use: [
84 | {
85 | loader: 'style-loader'
86 | },
87 | {
88 | loader: 'css-loader',
89 | options: {
90 | modules: true,
91 | sourceMap: true,
92 | importLoaders: 1,
93 | localIdentName: '[name]__[local]__[hash:base64:5]',
94 | }
95 | },
96 | {
97 | loader: 'sass-loader'
98 | }
99 | ]
100 | },
101 | // WOFF Font
102 | {
103 | test: /\.woff(\?v=\d+\.\d+\.\d+)?$/,
104 | use: {
105 | loader: 'url-loader',
106 | options: {
107 | limit: 10000,
108 | mimetype: 'application/font-woff',
109 | }
110 | },
111 | },
112 | // WOFF2 Font
113 | {
114 | test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/,
115 | use: {
116 | loader: 'url-loader',
117 | options: {
118 | limit: 10000,
119 | mimetype: 'application/font-woff',
120 | }
121 | }
122 | },
123 | // TTF Font
124 | {
125 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
126 | use: {
127 | loader: 'url-loader',
128 | options: {
129 | limit: 10000,
130 | mimetype: 'application/octet-stream'
131 | }
132 | }
133 | },
134 | // EOT Font
135 | {
136 | test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
137 | use: 'file-loader',
138 | },
139 | // SVG Font
140 | {
141 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
142 | use: {
143 | loader: 'url-loader',
144 | options: {
145 | limit: 10000,
146 | mimetype: 'image/svg+xml',
147 | }
148 | }
149 | },
150 | // Common Image Formats
151 | {
152 | test: /\.(?:ico|gif|png|jpg|jpeg|webp)$/,
153 | use: 'url-loader',
154 | }
155 | ]
156 | },
157 |
158 | resolve: {
159 | modules: [
160 | 'app',
161 | ],
162 | },
163 |
164 | entry: {
165 | renderer: (
166 | Object
167 | .keys(dependencies || {})
168 | .filter(dependency => dependency !== 'font-awesome')
169 | )
170 | },
171 |
172 | output: {
173 | library: 'renderer',
174 | path: dist,
175 | filename: '[name].dev.dll.js',
176 | libraryTarget: 'var'
177 | },
178 |
179 | plugins: [
180 | new webpack.DllPlugin({
181 | path: path.join(dist, '[name].json'),
182 | name: '[name]',
183 | }),
184 |
185 | /**
186 | * Create global constants which can be configured at compile time.
187 | *
188 | * Useful for allowing different behaviour between development builds and
189 | * release builds
190 | *
191 | * NODE_ENV should be production so that modules do not perform certain
192 | * development checks
193 | */
194 | new webpack.DefinePlugin({
195 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
196 | }),
197 |
198 | new webpack.LoaderOptionsPlugin({
199 | debug: true,
200 | options: {
201 | context: path.resolve(process.cwd(), 'app'),
202 | output: {
203 | path: path.resolve(process.cwd(), 'dll'),
204 | },
205 | },
206 | })
207 | ],
208 | });
209 |
--------------------------------------------------------------------------------
/app/menu.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import { app, Menu, shell, BrowserWindow } from 'electron';
3 |
4 | export default class MenuBuilder {
5 | mainWindow: BrowserWindow;
6 |
7 | constructor(mainWindow: BrowserWindow) {
8 | this.mainWindow = mainWindow;
9 | }
10 |
11 | buildMenu() {
12 | if (process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true') {
13 | this.setupDevelopmentEnvironment();
14 | }
15 |
16 | let template;
17 |
18 | if (process.platform === 'darwin') {
19 | template = this.buildDarwinTemplate();
20 | } else {
21 | template = this.buildDefaultTemplate();
22 | }
23 |
24 | const menu = Menu.buildFromTemplate(template);
25 | Menu.setApplicationMenu(menu);
26 |
27 | return menu;
28 | }
29 |
30 | setupDevelopmentEnvironment() {
31 | this.mainWindow.openDevTools();
32 | this.mainWindow.webContents.on('context-menu', (e, props) => {
33 | const { x, y } = props;
34 |
35 | Menu
36 | .buildFromTemplate([{
37 | label: 'Inspect element',
38 | click: () => {
39 | this.mainWindow.inspectElement(x, y);
40 | }
41 | }])
42 | .popup(this.mainWindow);
43 | });
44 | }
45 |
46 | buildDarwinTemplate() {
47 | const subMenuAbout = {
48 | label: 'Electron',
49 | submenu: [
50 | { label: 'About ElectronReact', selector: 'orderFrontStandardAboutPanel:' },
51 | { type: 'separator' },
52 | { label: 'Services', submenu: [] },
53 | { type: 'separator' },
54 | { label: 'Hide ElectronReact', accelerator: 'Command+H', selector: 'hide:' },
55 | { label: 'Hide Others', accelerator: 'Command+Shift+H', selector: 'hideOtherApplications:' },
56 | { label: 'Show All', selector: 'unhideAllApplications:' },
57 | { type: 'separator' },
58 | { label: 'Quit', accelerator: 'Command+Q', click: () => { app.quit(); } }
59 | ]
60 | };
61 | const subMenuEdit = {
62 | label: 'Edit',
63 | submenu: [
64 | { label: 'Undo', accelerator: 'Command+Z', selector: 'undo:' },
65 | { label: 'Redo', accelerator: 'Shift+Command+Z', selector: 'redo:' },
66 | { type: 'separator' },
67 | { label: 'Cut', accelerator: 'Command+X', selector: 'cut:' },
68 | { label: 'Copy', accelerator: 'Command+C', selector: 'copy:' },
69 | { label: 'Paste', accelerator: 'Command+V', selector: 'paste:' },
70 | { label: 'Select All', accelerator: 'Command+A', selector: 'selectAll:' }
71 | ]
72 | };
73 | const subMenuViewDev = {
74 | label: 'View',
75 | submenu: [
76 | { label: 'Reload', accelerator: 'Command+R', click: () => { this.mainWindow.webContents.reload(); } },
77 | { label: 'Toggle Full Screen', accelerator: 'Ctrl+Command+F', click: () => { this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen()); } },
78 | { label: 'Toggle Developer Tools', accelerator: 'Alt+Command+I', click: () => { this.mainWindow.toggleDevTools(); } }
79 | ]
80 | };
81 | const subMenuViewProd = {
82 | label: 'View',
83 | submenu: [
84 | { label: 'Toggle Full Screen', accelerator: 'Ctrl+Command+F', click: () => { this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen()); } }
85 | ]
86 | };
87 | const subMenuWindow = {
88 | label: 'Window',
89 | submenu: [
90 | { label: 'Minimize', accelerator: 'Command+M', selector: 'performMiniaturize:' },
91 | { label: 'Close', accelerator: 'Command+W', selector: 'performClose:' },
92 | { type: 'separator' },
93 | { label: 'Bring All to Front', selector: 'arrangeInFront:' }
94 | ]
95 | };
96 | const subMenuHelp = {
97 | label: 'Help',
98 | submenu: [
99 | { label: 'Learn More', click() { shell.openExternal('http://electron.atom.io'); } },
100 | { label: 'Documentation', click() { shell.openExternal('https://github.com/atom/electron/tree/master/docs#readme'); } },
101 | { label: 'Community Discussions', click() { shell.openExternal('https://discuss.atom.io/c/electron'); } },
102 | { label: 'Search Issues', click() { shell.openExternal('https://github.com/atom/electron/issues'); } }
103 | ]
104 | };
105 |
106 | const subMenuView = process.env.NODE_ENV === 'development'
107 | ? subMenuViewDev
108 | : subMenuViewProd;
109 |
110 | return [
111 | subMenuAbout,
112 | subMenuEdit,
113 | subMenuView,
114 | subMenuWindow,
115 | subMenuHelp
116 | ];
117 | }
118 |
119 | buildDefaultTemplate() {
120 | const templateDefault = [{
121 | label: '&File',
122 | submenu: [{
123 | label: '&Open',
124 | accelerator: 'Ctrl+O'
125 | }, {
126 | label: '&Close',
127 | accelerator: 'Ctrl+W',
128 | click: () => {
129 | this.mainWindow.close();
130 | }
131 | }]
132 | }, {
133 | label: '&View',
134 | submenu: (process.env.NODE_ENV === 'development') ? [{
135 | label: '&Reload',
136 | accelerator: 'Ctrl+R',
137 | click: () => {
138 | this.mainWindow.webContents.reload();
139 | }
140 | }, {
141 | label: 'Toggle &Full Screen',
142 | accelerator: 'F11',
143 | click: () => {
144 | this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen());
145 | }
146 | }, {
147 | label: 'Toggle &Developer Tools',
148 | accelerator: 'Alt+Ctrl+I',
149 | click: () => {
150 | this.mainWindow.toggleDevTools();
151 | }
152 | }] : [{
153 | label: 'Toggle &Full Screen',
154 | accelerator: 'F11',
155 | click: () => {
156 | this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen());
157 | }
158 | }]
159 | }, {
160 | label: 'Help',
161 | submenu: [{
162 | label: 'Learn More',
163 | click() {
164 | shell.openExternal('http://electron.atom.io');
165 | }
166 | }, {
167 | label: 'Documentation',
168 | click() {
169 | shell.openExternal('https://github.com/atom/electron/tree/master/docs#readme');
170 | }
171 | }, {
172 | label: 'Community Discussions',
173 | click() {
174 | shell.openExternal('https://discuss.atom.io/c/electron');
175 | }
176 | }, {
177 | label: 'Search Issues',
178 | click() {
179 | shell.openExternal('https://github.com/atom/electron/issues');
180 | }
181 | }]
182 | }];
183 |
184 | return templateDefault;
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/app/mirror/modules/default/NewsFeed/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React, { Component } from 'react';
3 | import PropTypes from 'prop-types';
4 | import moment from 'moment';
5 | import classNames from 'classnames';
6 | import request from 'request';
7 |
8 | class NewsFeed extends Component {
9 |
10 | constructor(props: any) {
11 | super(props);
12 | this.updateModule = this.updateModule.bind(this);
13 | this.hideShowModule = this.hideShowModule.bind(this);
14 | this.foramtUrlParams = this.foramtUrlParams.bind(this);
15 | this.fetchNewsItems = this.fetchNewsItems.bind(this);
16 | }
17 |
18 | state = {
19 | opacity: 0,
20 | hidden: true,
21 | intervalId: null,
22 | refreshId: null,
23 | showHideTimer: null,
24 | newsItems: [],
25 | loaded: false,
26 | activeItem: -1,
27 | };
28 |
29 | componentDidMount() {
30 | this.fetchNewsItems((items) => {
31 | this.setState({
32 | loaded: true,
33 | newsItems: items,
34 | intervalId: setInterval(() => {
35 | this.updateModule();
36 | }, this.props.updateInterval),
37 | refreshId: setInterval(() => {
38 | this.fetchNewsItems((items)=>{
39 | this.setState({
40 | newsItems: items
41 | })
42 | });
43 | }, this.props.refetchInterval),
44 | });
45 | this.updateModule();
46 | });
47 | }
48 |
49 | componentWillUnmount() {
50 | clearInterval(this.state.intervalId);
51 | clearInterval(this.state.refreshId);
52 | clearTimeout(this.state.showHideTimer);
53 | }
54 |
55 | hideShowModule(hide: boolean, callback: any) {
56 | this.setState({
57 | opacity: hide ? 0 : 1,
58 | hidden: hide
59 | });
60 | if (hide && callback) {
61 | this.setState({
62 | showHideTimer: setTimeout(() => { callback(); }, this.props.fadeSpeed / 2),
63 | });
64 | } else {
65 | clearTimeout(this.state.showHideTimer);
66 | }
67 | }
68 |
69 | updateModule() {
70 | if (this.state.newsItems.length > 0) {
71 | this.hideShowModule(true, () => {
72 | let index = this.state.activeItem;
73 | if (this.state.activeItem < this.state.newsItems.length - 1) {
74 | index += 1;
75 | } else {
76 | index = 0;
77 | }
78 | this.setState({
79 | activeItem: index,
80 | }, () => {
81 | this.hideShowModule(false);
82 | });
83 | });
84 | }
85 | }
86 |
87 | foramtUrlParams() {
88 | let url = `${this.props.apiBase}${this.props.apiVersion}${this.props.apiEndpoint}sources=`;
89 | url += this.props.sources.join();
90 | url += `&apiKey=${this.props.apiKey}`;
91 | return url;
92 | }
93 |
94 | fetchNewsItems(callback) {
95 | if (!this.props.apiKey) {
96 | console.log('There is no api Key');
97 | return;
98 | }
99 |
100 | const URL = this.foramtUrlParams();
101 | request({
102 | method: 'GET',
103 | url: URL,
104 | }, (error, response, body) => {
105 | if (!error && response.statusCode === 200) {
106 | callback(JSON.parse(body).articles);
107 | } else {
108 | callback({ error: `${NewsFeed.moduleName}: Could not load news items.` });
109 | }
110 | });
111 | }
112 |
113 | render() {
114 | if (!this.props.apiKey) {
115 | return (
116 |
117 | Their is no api key in the configuration options for the newsfeed module.
118 |
119 | Please check the documentation
120 |
121 | );
122 | }
123 |
124 | if (!this.state.loaded || this.state.activeItem < 0) {
125 | return (
126 |
127 | {this.props.hideLoading ? '' : 'Loading'}
128 |
129 | );
130 | }
131 |
132 | let sourceTitle;
133 | let newsTitle;
134 | let description;
135 | let fullArticle;
136 | if (this.state.newsItems.length > 0 && this.state.loaded) {
137 | if (this.props.showSourceTitle) {
138 | sourceTitle = (
139 |
140 | {this.state.newsItems[this.state.activeItem].source.name}
141 | {
142 | this.props.showPublishDate &&
143 | `, ${moment(new Date(this.state.newsItems[this.state.activeItem].publishedAt)).fromNow()}`
144 | }:
145 |
146 | );
147 | }
148 |
149 | if (this.props.showFullArticle) {
150 | fullArticle = (
151 |
163 | );
164 | } else {
165 | newsTitle = (
166 |
167 | {this.state.newsItems[this.state.activeItem].title}
168 |
169 | );
170 | }
171 |
172 | if (this.props.showDescription) {
173 | description = (
174 |
175 | {this.state.newsItems[this.state.activeItem].description}
176 |
177 | );
178 | }
179 |
180 | return (
181 |
187 | {sourceTitle}
188 | {newsTitle}
189 | {fullArticle}
190 | {description}
191 |
192 | );
193 | }
194 |
195 | return (
196 |
197 | No news articles...
198 |
199 | );
200 | }
201 | }
202 |
203 | NewsFeed.moduleName = 'NewsFeed';
204 |
205 | NewsFeed.defaultProps = {
206 | fadeSpeed: 4000,
207 | refetchInterval: 600000, // call the api every 10 minutes
208 | updateInterval: 30000, // update the module to change the current item every 30 seconds
209 | animation: 'ease-in',
210 | apiEndpoint: 'top-headlines?',
211 | apiVersion: 'v2/',
212 | apiBase: 'https://newsapi.org/',
213 | apiKey: null,
214 | language: 'en',
215 | sources: [
216 | 'the-new-york-times',
217 | 'google-news',
218 | ],
219 | showSourceTitle: true,
220 | showPublishDate: true,
221 | showDescription: true,
222 | wrapTitle: true,
223 | wrapDescription: true,
224 | hideLoading: false,
225 | showFullArticle: false,
226 | };
227 |
228 | NewsFeed.propTypes = {
229 | fadeSpeed: PropTypes.number,
230 | updateInterval: PropTypes.number,
231 | animation: PropTypes.string,
232 | sources: PropTypes.array,
233 | showSourceTitle: PropTypes.bool,
234 | showPublishDate: PropTypes.bool,
235 | showDescription: PropTypes.bool,
236 | wrapTitle: PropTypes.bool,
237 | wrapDescription: PropTypes.bool,
238 | hideLoading: PropTypes.bool,
239 | };
240 |
241 | export default NewsFeed;
242 |
--------------------------------------------------------------------------------
/app/mirror/modules/default/Calendar/README.md:
--------------------------------------------------------------------------------
1 | # Calendar
2 | The `calendar` module is one of the default modules of the ReactiveMirror.
3 | This module displays events from a public .ical calendar. It can combine multiple calendars.
4 |
5 | ## Using the module
6 |
7 | Add it to the modules array in the `config/config.js` file:
8 |
9 | ````javascript
10 | modules: [
11 | {
12 | module: "Calendar",
13 | position: "top_left", // This can be any of the regions. Best results in left or right regions.
14 | config: {
15 | // The config property is optional.
16 | // If no config is set, an example calendar is shown.
17 | // See 'Configuration options' for more information.
18 | }
19 | }
20 | ]
21 | ````
22 |
23 | ## Configuration options
24 |
25 | The following properties can be configured:
26 |
27 |
28 | | Option | Description
29 | | ---------------------------- | -----------
30 | | `maximumEntries` | The maximum number of events shown. / **Possible values:** `0` - `100`
**Default value:** `10`
31 | | `maximumNumberOfDays` | The maximum number of days in the future.
**Default value:** `365`
32 | | `displaySymbol` | Display a symbol in front of an entry.
**Possible values:** `true` or `false`
**Default value:** `true`
33 | | `defaultSymbol` | The default symbol.
**Possible values:** See [Font Awsome](http://fontawesome.io/icons/) website.
**Default value:** `calendar`
34 | | `maxTitleLength` | The maximum title length.
**Possible values:** `10` - `50`
**Default value:** `25`
35 | | `wrapEvents` | Wrap event titles to multiple lines. Breaks lines at the length defined by `maxTitleLength`.
**Possible values:** `true` or `false`
**Default value:** `false`
36 | | `fetchInterval` | How often does the content needs to be fetched? (Milliseconds)
**Possible values:** `1000` - `86400000`
**Default value:** `300000` (5 minutes)
37 | | `animationSpeed` | Speed of the update animation. (Milliseconds)
**Possible values:**`0` - `5000`
**Default value:** `2000` (2 seconds)
38 | | `fade` | Fade the future events to black. (Gradient)
**Possible values:** `true` or `false`
**Default value:** `true`
39 | | `fadePoint` | Where to start fade?
**Possible values:** `0` (top of the list) - `1` (bottom of list)
**Default value:** `0.25`
40 | | `calendars` | The list of calendars.
**Possible values:** An array, see _calendar configuration_ below.
**Default value:** _An example calendar._
41 | | `titleReplace` | An object of textual replacements applied to the tile of the event. This allow to remove or replace certains words in the title.
**Example:** `{'Birthday of ' : '', 'foo':'bar'}`
**Default value:** `{ "De verjaardag van ": "", "'s birthday": "" }`
42 | | `displayRepeatingCountTitle` | Show count title for yearly repeating events (e.g. "X. Birthday", "X. Anniversary")
**Possible values:** `true` or `false`
**Default value:** `false`
43 | | `dateFormat` | Format to use for the date of events (when using absolute dates)
**Possible values:** See [Moment.js formats](http://momentjs.com/docs/#/parsing/string-format/)
**Default value:** `MMM Do` (e.g. Jan 18th)
44 | | `fullDayEventDateFormat` | Format to use for the date of full day events (when using absolute dates)
**Possible values:** See [Moment.js formats](http://momentjs.com/docs/#/parsing/string-format/)
**Default value:** `MMM Do` (e.g. Jan 18th)
45 | | `timeFormat` | Display event times as absolute dates, or relative time
**Possible values:** `absolute` or `relative`
**Default value:** `relative`
46 | | `getRelative` | How much time (in hours) should be left until calendar events start getting relative?
**Possible values:** `0` (events stay absolute) - `48` (48 hours before the event starts)
**Default value:** `6`
47 | | `urgency` | When using a timeFormat of `absolute`, the `urgency` setting allows you to display events within a specific time frame as `relative`. This allows events within a certain time frame to be displayed as relative (in xx days) while others are displayed as absolute dates
**Possible values:** a positive integer representing the number of days for which you want a relative date, for example `7` (for 7 days)
**Default value:** `7`
48 | | `broadcastEvents` | If this property is set to true, the calendar will broadcast all the events to all other modules with the notification message: `CALENDAR_EVENTS`. The event objects are stored in an array and contain the following fields: `title`, `startDate`, `endDate`, `fullDayEvent`, `location` and `geo`.
**Possible values:** `true`, `false`
**Default value:** `true`
49 | | `hidePrivate` | Hides private calendar events.
**Possible values:** `true` or `false`
**Default value:** `false`
50 | | `excludedEvents` | An array of words / phrases from event titles that will be excluded from being shown.
**Example:** `['Birthday', 'Hide This Event']`
**Default value:** `[]`
51 |
52 | ### Calendar configuration
53 |
54 | The `calendars` property contains an array of the configured calendars.
55 | The `colored` property gives the option for an individual color for each calendar.
56 |
57 | #### Default value:
58 | ````javascript
59 | config: {
60 | colored: false,
61 | calendars: [
62 | {
63 | url: 'http://www.calendarlabs.com/templates/ical/US-Holidays.ics',
64 | symbol: 'calendar',
65 | auth: {
66 | user: 'username',
67 | pass: 'superstrongpassword',
68 | method: 'basic'
69 | }
70 | },
71 | ],
72 | }
73 | ````
74 |
75 | #### Calendar configuration options:
76 | | Option | Description
77 | | --------------------- | -----------
78 | | `url` | The url of the calendar .ical. This property is required.
**Possible values:** Any public accessble .ical calendar.
79 | | `symbol` | The symbol to show in front of an event. This property is optional.
**Possible values:** See [Font Awesome](http://fontawesome.io/icons/) website. To have multiple symbols you can define them in an array e.g. `["calendar", "plane"]`
80 | | `color` | The font color of an event from this calendar. This property should be set if the config is set to colored: true.
**Possible values:** HEX, RGB or RGBA values (#efefef, rgb(242,242,242), rgba(242,242,242,0.5)).
81 | | `repeatingCountTitle` | The count title for yearly repating events in this calendar.
**Example:** `'Birthday'`
82 | | `maximumEntries` | The maximum number of events shown. Overrides global setting. **Possible values:** `0` - `100`
83 | | `maximumNumberOfDays` | The maximum number of days in the future. Overrides global setting
84 | | `auth` | The object containing options for authentication against the calendar.
85 |
86 |
87 | #### Calendar authentication options:
88 | | Option | Description
89 | | --------------------- | -----------
90 | | `user` | The username for HTTP authentication.
91 | | `pass` | The password for HTTP authentication. (If you use Bearer authentication, this should be your BearerToken.)
92 | | `method` | Which authentication method should be used. HTTP Basic, Digest and Bearer authentication methods are supported. Basic authentication is used by default if this option is omitted. **Possible values:** `digest`, `basic`, `bearer` **Default value:** `basic`
93 |
--------------------------------------------------------------------------------
/app/mirror/installers/raspberry.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # This is an installer script for ReactiveMirror. It works well enough
4 | # that it can detect if you have Node installed, run a binary script
5 | # and then download and run ReactiveMirror
6 |
7 | echo -e "\e[0m"
8 | echo ' /$$$$$$$ /$$ /$$ '
9 | echo '| $$__ $$ | $$ |__/ '
10 | echo '| $$ \ $$ /$$$$$$ /$$$$$$ /$$$$$$$ /$$$$$$ /$$ /$$ /$$ /$$$$$$ '
11 | echo '| $$$$$$$/ /$$__ $$ |____ $$ /$$_____/|_ $$_/ | $$| $$ /$$/ /$$__ $$'
12 | echo '| $$__ $$| $$$$$$$$ /$$$$$$$| $$ | $$ | $$ \ $$/$$/ | $$$$$$$$'
13 | echo '| $$ \ $$| $$_____/ /$$__ $$| $$ | $$ /$$| $$ \ $$$/ | $$_____/'
14 | echo '| $$ | $$| $$$$$$$| $$$$$$$| $$$$$$$ | $$$$/| $$ \ $/ | $$$$$$$'
15 | echo '|__/ |__/ \_______/ \_______/ \_______/ \___/ |__/ \_/ \_______/'
16 | echo ' /$$ /$$ /$$ '
17 | echo ' | $$$ /$$$|__/ '
18 | echo ' | $$$$ /$$$$ /$$ /$$$$$$ /$$$$$$ /$$$$$$ /$$$$$$ '
19 | echo ' | $$ $$/$$ $$| $$ /$$__ $$ /$$__ $$ /$$__ $$ /$$__ $$'
20 | echo ' | $$ $$$| $$| $$| $$ \__/| $$ \__/| $$ \ $$| $$ \__/'
21 | echo ' | $$\ $ | $$| $$| $$ | $$ | $$ | $$| $$ '
22 | echo ' | $$ \/ | $$| $$| $$ | $$ | $$$$$$/| $$ '
23 | echo ' |__/ |__/|__/|__/ |__/ \______/ |__/ '
24 | echo -e "\e[0m"
25 |
26 | # Define the tested version of Node.js.
27 | NODE_TESTED="v8.0.0"
28 |
29 | # Determine which Pi is running.
30 | ARM=$(uname -m)
31 |
32 | # Check the Raspberry Pi version.
33 | if [ "$ARM" != "armv7l" ]; then
34 | echo -e "\e[91mSorry, your Raspberry Pi is not supported."
35 | echo -e "\e[91mPlease run ReactiveMirror on a Raspberry Pi 2 or 3."
36 | echo -e "\e[91mIf this is a Pi Zero, you are in the same boat as the original Raspberry Pi. You must run in server only mode."
37 | exit;
38 | fi
39 |
40 | # Define helper methods.
41 | function version_gt() { test "$(echo "$@" | tr " " "\n" | sort -V | head -n 1)" != "$1"; }
42 | function command_exists () { type "$1" &> /dev/null ;}
43 |
44 | # Update before first apt-get
45 | echo -e "\e[96mUpdating packages ...\e[90m"
46 | sudo apt-get update || echo -e "\e[91mUpdate failed, carrying on installation ...\e[90m"
47 |
48 | # Installing helper tools
49 | echo -e "\e[96mInstalling helper tools ...\e[90m"
50 | sudo apt-get install curl wget git build-essential unzip || exit
51 |
52 | # Check if we need to install or upgrade Node.js.
53 | echo -e "\e[96mCheck current Node installation ...\e[0m"
54 | NODE_INSTALL=false
55 | if command_exists node; then
56 | echo -e "\e[0mNode currently installed. Checking version number.";
57 | NODE_CURRENT=$(node -v)
58 | echo -e "\e[0mMinimum Node version: \e[1m$NODE_TESTED\e[0m"
59 | echo -e "\e[0mInstalled Node version: \e[1m$NODE_CURRENT\e[0m"
60 | if version_gt $NODE_TESTED $NODE_CURRENT; then
61 | echo -e "\e[96mNode should be upgraded.\e[0m"
62 | NODE_INSTALL=true
63 |
64 | # Check if a node process is currenlty running.
65 | # If so abort installation.
66 | if pgrep "node" > /dev/null; then
67 | echo -e "\e[91mA Node process is currently running. Can't upgrade."
68 | echo "Please quit all Node processes and restart the installer."
69 | exit;
70 | fi
71 |
72 | else
73 | echo -e "\e[92mNo Node.js upgrade necessary.\e[0m"
74 | fi
75 |
76 | else
77 | echo -e "\e[93mNode.js is not installed.\e[0m";
78 | NODE_INSTALL=true
79 | fi
80 |
81 | # Install or upgrade node if necessary.
82 | if $NODE_INSTALL; then
83 |
84 | echo -e "\e[96mInstalling Node.js ...\e[90m"
85 |
86 | # Fetch the latest version of Node.js from the selected branch
87 | # The NODE_STABLE_BRANCH variable will need to be manually adjusted when a new branch is released. (e.g. 7.x)
88 | # Only tested (stable) versions are recommended as newer versions could break ReactiveMirror.
89 |
90 | NODE_STABLE_BRANCH="8.x"
91 | curl -sL https://deb.nodesource.com/setup_$NODE_STABLE_BRANCH | sudo -E bash -
92 | sudo apt-get install -y nodejs
93 | echo -e "\e[92mNode.js installation Done!\e[0m"
94 | fi
95 |
96 | # Install ReactiveMirror
97 | cd ~
98 | if [ -d "$HOME/ReactiveMirror" ] ; then
99 | echo -e "\e[93mIt seems like ReactiveMirror is already installed."
100 | echo -e "To prevent overwriting, the installer will be aborted."
101 | echo -e "Please rename the \e[1m~/ReactiveMirror\e[0m\e[93m folder and try again.\e[0m"
102 | echo ""
103 | echo -e "If you want to upgrade your installation run \e[1m\e[97mgit pull\e[0m from the ~/ReactiveMirror directory."
104 | echo ""
105 | exit;
106 | fi
107 |
108 | echo -e "\e[96mCloning ReactiveMirror ...\e[90m"
109 | if git clone https://github.com/Cristian006/ReactiveMirror.git; then
110 | echo -e "\e[92mCloning ReactiveMirror Done!\e[0m"
111 | else
112 | echo -e "\e[91mUnable to clone ReactiveMirror."
113 | exit;
114 | fi
115 |
116 | cd ~/ReactiveMirror || exit
117 |
118 | # Use sample config for start ReactiveMirror
119 | cp app/mirror/config/config.js.sample app/mirror/config/config.js
120 | echo -e "\e[92mCreated mirror config from sample!\e[0m"
121 |
122 | cd ~/ReactiveMirror/app || exit
123 | echo -e "\e[96mInstalling Mirror dependencies ...\e[90m"
124 | if npm install; then
125 | echo -e "\e[92mMirror dependencies installation done!\e[0m"
126 | else
127 | echo -e "\e[91mUnable to install Mirror dependencies!"
128 | exit;
129 | fi
130 |
131 | cd ~/ReactiveMirror || exit
132 | echo -e "\e[96mInstalling app dependencies ...\e[90m"
133 | if npm install; then
134 | echo -e "\e[92mApp dependencies installation Done!\e[0m"
135 | else
136 | echo -e "\e[91mUnable to install all app dependencies!"
137 | echo -e "\e[91mCarrying on installation ...\e[90m"
138 | fi
139 |
140 | # Check if plymouth is installed (default with PIXEL desktop environment), then install custom splashscreen.
141 | echo -e "\e[96mCheck plymouth installation ...\e[0m"
142 | if command_exists plymouth; then
143 | THEME_DIR="/usr/share/plymouth/themes"
144 | echo -e "\e[90mSplashscreen: Checking themes directory.\e[0m"
145 | if [ -d $THEME_DIR ]; then
146 | echo -e "\e[90mSplashscreen: Create theme directory if not exists.\e[0m"
147 | if [ ! -d $THEME_DIR/ReactiveMirror ]; then
148 | sudo mkdir $THEME_DIR/ReactiveMirror
149 | fi
150 |
151 | if sudo cp ~/ReactiveMirror/app/mirror/splashscreen/splash.png $THEME_DIR/ReactiveMirror/splash.png && sudo cp ~/ReactiveMirror/app/mirror/splashscreen/ReactiveMirror.plymouth $THEME_DIR/ReactiveMirror/ReactiveMirror.plymouth && sudo cp ~/ReactiveMirror/app/mirror/splashscreen/ReactiveMirror.script $THEME_DIR/ReactiveMirror/ReactiveMirror.script; then
152 | echo -e "\e[90mSplashscreen: Theme copied successfully.\e[0m"
153 | if sudo plymouth-set-default-theme -R ReactiveMirror; then
154 | echo -e "\e[92mSplashscreen: Changed theme to ReactiveMirror successfully.\e[0m"
155 | else
156 | echo -e "\e[91mSplashscreen: Couldn't change theme to ReactiveMirror!\e[0m"
157 | fi
158 | else
159 | echo -e "\e[91mSplashscreen: Copying theme failed!\e[0m"
160 | fi
161 | else
162 | echo -e "\e[91mSplashscreen: Themes folder doesn't exist!\e[0m"
163 | fi
164 | else
165 | echo -e "\e[93mplymouth is not installed.\e[0m";
166 | fi
167 |
168 | # Use pm2 control ReactiveMirror like a service
169 | read -p "Do you want use pm2 to auto start your ReactiveMirror (y/n)?" choice
170 | if [[ $choice =~ ^[Yy]$ ]]; then
171 | sudo npm install -g pm2
172 | sudo su -c "env PATH=$PATH:/usr/bin pm2 startup systemd -u pi --hp /home/pi"
173 | pm2 start ~/ReactiveMirror/app/mirror/installers/pm2_ReactiveMirror.json
174 | pm2 save
175 | fi
176 |
177 | echo " "
178 | echo -e "\e[92mWe're ready! Run \e[1m\e[97mDISPLAY=:0 npm start\e[0m\e[92m from the ~/ReactiveMirror directory to start your ReactiveMirror.\e[0m"
179 | echo " "
180 | echo " "
181 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "reactive-mirror",
3 | "productName": "ReactiveMirror",
4 | "version": "0.12.0",
5 | "description": "Electron application boilerplate based on React, React Router, Webpack, React Hot Loader for rapid application development",
6 | "scripts": {
7 | "build": "concurrently \"npm run build-main\" \"npm run build-renderer\"",
8 | "build-dll": "cross-env NODE_ENV=development node --trace-warnings -r babel-register ./node_modules/webpack/bin/webpack --config webpack.config.renderer.dev.dll.js --colors",
9 | "build-main": "cross-env NODE_ENV=production node --trace-warnings -r babel-register ./node_modules/webpack/bin/webpack --config webpack.config.main.prod.js --colors",
10 | "build-renderer": "cross-env NODE_ENV=production node --trace-warnings -r babel-register ./node_modules/webpack/bin/webpack --config webpack.config.renderer.prod.js --colors",
11 | "dev": "cross-env START_HOT=1 npm run start-renderer-dev",
12 | "flow": "flow",
13 | "flow-typed": "rimraf flow-typed/npm && flow-typed install --overwrite || true",
14 | "lint": "eslint --cache --format=node_modules/eslint-formatter-pretty .",
15 | "lint-fix": "npm run lint -- --fix",
16 | "lint-styles": "stylelint app/*.css app/components/*.css --syntax scss",
17 | "lint-styles-fix": "stylefmt -r app/*.css app/components/*.css",
18 | "package": "npm run build && build --publish never",
19 | "package-all": "npm run build && build -mwl",
20 | "package-linux": "npm run build && build --linux",
21 | "package-win": "npm run build && build --win --x64",
22 | "postinstall": "concurrently \"npm run flow-typed\" \"npm run build-dll\" \"electron-builder install-app-deps\" \"node node_modules/fbjs-scripts/node/check-dev-engines.js package.json\"",
23 | "prestart": "npm run build",
24 | "start": "cross-env NODE_ENV=production electron ./app/",
25 | "start-main-dev": "cross-env HOT=1 NODE_ENV=development electron -r babel-register ./app/main.dev",
26 | "start-renderer-dev": "cross-env NODE_ENV=development node --trace-warnings -r babel-register ./node_modules/webpack-dev-server/bin/webpack-dev-server --config webpack.config.renderer.dev.js",
27 | "test": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 node --trace-warnings ./test/runTests.js",
28 | "test-all": "npm run lint && npm run flow && npm run build && npm run test && npm run test-e2e",
29 | "test-e2e": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 node --trace-warnings ./test/runTests.js e2e",
30 | "test-watch": "npm test -- --watch"
31 | },
32 | "browserslist": "electron 1.6",
33 | "build": {
34 | "productName": "ReactiveMirror",
35 | "appId": "com.crispcrafts.ReactiveMirror",
36 | "files": [
37 | "dist/",
38 | "node_modules/",
39 | "app.html",
40 | "main.prod.js",
41 | "main.prod.js.map",
42 | "package.json"
43 | ],
44 | "dmg": {
45 | "contents": [
46 | {
47 | "x": 130,
48 | "y": 220
49 | },
50 | {
51 | "x": 410,
52 | "y": 220,
53 | "type": "link",
54 | "path": "/Applications"
55 | }
56 | ]
57 | },
58 | "win": {
59 | "target": [
60 | "nsis"
61 | ]
62 | },
63 | "linux": {
64 | "target": [
65 | "deb",
66 | "AppImage"
67 | ]
68 | },
69 | "directories": {
70 | "buildResources": "resources",
71 | "output": "release"
72 | }
73 | },
74 | "repository": {
75 | "type": "git",
76 | "url": "git+https://github.com/chentsulin/electron-react-boilerplate.git"
77 | },
78 | "author": {
79 | "name": "C. T. Lin",
80 | "email": "chentsulin@gmail.com",
81 | "url": "https://github.com/chentsulin"
82 | },
83 | "license": "MIT",
84 | "bugs": {
85 | "url": "https://github.com/chentsulin/electron-react-boilerplate/issues"
86 | },
87 | "keywords": [
88 | "electron",
89 | "boilerplate",
90 | "react",
91 | "redux",
92 | "flow",
93 | "sass",
94 | "webpack",
95 | "hot",
96 | "reload"
97 | ],
98 | "homepage": "https://github.com/chentsulin/electron-react-boilerplate#readme",
99 | "jest": {
100 | "moduleNameMapper": {
101 | "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/internals/mocks/fileMock.js",
102 | "\\.(css|less|sass|scss)$": "identity-obj-proxy"
103 | },
104 | "moduleFileExtensions": [
105 | "js"
106 | ],
107 | "moduleDirectories": [
108 | "node_modules",
109 | "app/node_modules"
110 | ],
111 | "transform": {
112 | "^.+\\.js$": "babel-jest"
113 | },
114 | "setupFiles": [
115 | "./internals/scripts/CheckBuiltsExist.js"
116 | ]
117 | },
118 | "devDependencies": {
119 | "babel-core": "^6.24.1",
120 | "babel-eslint": "^7.2.3",
121 | "babel-jest": "^20.0.3",
122 | "babel-loader": "^7.1.0",
123 | "babel-plugin-add-module-exports": "^0.2.1",
124 | "babel-plugin-dev-expression": "^0.2.1",
125 | "babel-plugin-dynamic-import-webpack": "^1.0.1",
126 | "babel-plugin-flow-runtime": "^0.11.1",
127 | "babel-plugin-transform-class-properties": "^6.24.1",
128 | "babel-plugin-transform-es2015-classes": "^6.24.1",
129 | "babel-preset-env": "^1.5.1",
130 | "babel-preset-react": "^6.24.1",
131 | "babel-preset-react-hmre": "^1.1.1",
132 | "babel-preset-react-optimize": "^1.0.1",
133 | "babel-preset-stage-0": "^6.24.1",
134 | "babel-register": "^6.24.1",
135 | "babili-webpack-plugin": "^0.1.2",
136 | "chalk": "^2.0.1",
137 | "concurrently": "^3.5.0",
138 | "cross-env": "^5.0.0",
139 | "cross-spawn": "^5.1.0",
140 | "css-loader": "^0.28.3",
141 | "electron": "^1.6.10",
142 | "electron-builder": "^19.8.0",
143 | "electron-devtools-installer": "^2.2.0",
144 | "enzyme": "^2.9.1",
145 | "enzyme-to-json": "^1.5.1",
146 | "eslint": "3.19.0",
147 | "eslint-config-airbnb": "^15.0.1",
148 | "eslint-formatter-pretty": "^1.1.0",
149 | "eslint-import-resolver-webpack": "^0.8.3",
150 | "eslint-plugin-compat": "^1.0.4",
151 | "eslint-plugin-flowtype": "^2.33.0",
152 | "eslint-plugin-flowtype-errors": "^3.3.0",
153 | "eslint-plugin-import": "^2.6.0",
154 | "eslint-plugin-jest": "^20.0.3",
155 | "eslint-plugin-jsx-a11y": "5.0.3",
156 | "eslint-plugin-promise": "^3.5.0",
157 | "eslint-plugin-react": "^7.1.0",
158 | "express": "^4.15.3",
159 | "extract-text-webpack-plugin": "^3.0.2",
160 | "fbjs-scripts": "^0.8.0",
161 | "file-loader": "^0.11.1",
162 | "flow-bin": "^0.48.0",
163 | "flow-runtime": "^0.13.0",
164 | "flow-typed": "^2.1.2",
165 | "html-webpack-plugin": "^2.29.0",
166 | "identity-obj-proxy": "^3.0.0",
167 | "jest": "^20.0.4",
168 | "jsdom": "^11.0.0",
169 | "minimist": "^1.2.0",
170 | "node-sass": "^4.7.2",
171 | "react-addons-test-utils": "^15.6.0",
172 | "react-test-renderer": "^15.6.1",
173 | "redux-logger": "^3.0.6",
174 | "rimraf": "^2.6.1",
175 | "sass-loader": "^6.0.6",
176 | "sinon": "^2.3.5",
177 | "spectron": "^3.7.0",
178 | "style-loader": "^0.18.1",
179 | "stylefmt": "^6.0.0",
180 | "stylelint": "^7.12.0",
181 | "stylelint-config-standard": "^16.0.0",
182 | "url-loader": "^0.5.8",
183 | "webpack": "^3.0.0",
184 | "webpack-bundle-analyzer": "^2.8.2",
185 | "webpack-dev-server": "^2.5.0",
186 | "webpack-merge": "^4.1.0",
187 | "webpack-sources": "1.0.1"
188 | },
189 | "dependencies": {
190 | "devtron": "^1.4.0",
191 | "electron-debug": "^1.2.0",
192 | "font-awesome": "^4.7.0",
193 | "history": "^4.6.3",
194 | "react": "^15.6.1",
195 | "react-dom": "^15.6.1",
196 | "react-hot-loader": "3.0.0-beta.6",
197 | "react-redux": "^5.0.5",
198 | "react-router": "^4.1.1",
199 | "react-router-dom": "^4.1.1",
200 | "react-router-redux": "^5.0.0-alpha.6",
201 | "redux": "^3.7.1",
202 | "redux-thunk": "^2.2.0",
203 | "request": "^2.83.0",
204 | "source-map-support": "^0.4.15"
205 | },
206 | "devEngines": {
207 | "node": ">=7.x",
208 | "npm": ">=4.x",
209 | "yarn": ">=0.21.3"
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/webpack.config.renderer.dev.js:
--------------------------------------------------------------------------------
1 | /* eslint global-require: 0, import/no-dynamic-require: 0 */
2 |
3 | /**
4 | * Build config for development electron renderer process that uses
5 | * Hot-Module-Replacement
6 | *
7 | * https://webpack.js.org/concepts/hot-module-replacement/
8 | */
9 |
10 | import path from 'path';
11 | import fs from 'fs';
12 | import webpack from 'webpack';
13 | import chalk from 'chalk';
14 | import merge from 'webpack-merge';
15 | import { spawn, execSync } from 'child_process';
16 | import ExtractTextPlugin from 'extract-text-webpack-plugin';
17 | import baseConfig from './webpack.config.base';
18 | import CheckNodeEnv from './internals/scripts/CheckNodeEnv';
19 |
20 | CheckNodeEnv('development');
21 |
22 | const port = process.env.PORT || 1212;
23 | const publicPath = `http://localhost:${port}/dist`;
24 | const dll = path.resolve(process.cwd(), 'dll');
25 | const manifest = path.resolve(dll, 'renderer.json');
26 |
27 | /**
28 | * Warn if the DLL is not built
29 | */
30 | if (!(fs.existsSync(dll) && fs.existsSync(manifest))) {
31 | console.log(chalk.black.bgYellow.bold(
32 | 'The DLL files are missing. Sit back while we build them for you with "npm run build-dll"'
33 | ));
34 | execSync('npm run build-dll');
35 | }
36 |
37 | export default merge.smart(baseConfig, {
38 | devtool: 'inline-source-map',
39 |
40 | target: 'electron-renderer',
41 |
42 | entry: [
43 | 'react-hot-loader/patch',
44 | `webpack-dev-server/client?http://localhost:${port}/`,
45 | 'webpack/hot/only-dev-server',
46 | path.join(__dirname, 'app/index.js'),
47 | ],
48 |
49 | output: {
50 | publicPath: `http://localhost:${port}/dist/`
51 | },
52 |
53 | module: {
54 | rules: [
55 | {
56 | test: /\.jsx?$/,
57 | exclude: /node_modules/,
58 | use: {
59 | loader: 'babel-loader',
60 | options: {
61 | cacheDirectory: true,
62 | plugins: [
63 | // Here, we include babel plugins that are only required for the
64 | // renderer process. The 'transform-*' plugins must be included
65 | // before react-hot-loader/babel
66 | 'transform-class-properties',
67 | 'transform-es2015-classes',
68 | 'react-hot-loader/babel'
69 | ],
70 | }
71 | }
72 | },
73 | {
74 | test: /\.global\.css$/,
75 | use: [
76 | {
77 | loader: 'style-loader'
78 | },
79 | {
80 | loader: 'css-loader',
81 | options: {
82 | sourceMap: true,
83 | },
84 | }
85 | ]
86 | },
87 | {
88 | test: /^((?!\.global).)*\.css$/,
89 | use: [
90 | {
91 | loader: 'style-loader'
92 | },
93 | {
94 | loader: 'css-loader',
95 | options: {
96 | modules: true,
97 | sourceMap: true,
98 | importLoaders: 1,
99 | localIdentName: '[name]__[local]__[hash:base64:5]',
100 | }
101 | },
102 | ]
103 | },
104 | // Add SASS support - compile all .global.scss files and pipe it to style.css
105 | {
106 | test: /\.global\.scss$/,
107 | use: [
108 | {
109 | loader: 'style-loader'
110 | },
111 | {
112 | loader: 'css-loader',
113 | options: {
114 | sourceMap: true,
115 | },
116 | },
117 | {
118 | loader: 'sass-loader'
119 | }
120 | ]
121 | },
122 | // Add SASS support - compile all other .scss files and pipe it to style.css
123 | {
124 | test: /^((?!\.global).)*\.scss$/,
125 | use: [
126 | {
127 | loader: 'style-loader'
128 | },
129 | {
130 | loader: 'css-loader',
131 | options: {
132 | modules: true,
133 | sourceMap: true,
134 | importLoaders: 1,
135 | localIdentName: '[name]__[local]__[hash:base64:5]',
136 | }
137 | },
138 | {
139 | loader: 'sass-loader'
140 | }
141 | ]
142 | },
143 | // WOFF Font
144 | {
145 | test: /\.woff(\?v=\d+\.\d+\.\d+)?$/,
146 | use: {
147 | loader: 'url-loader',
148 | options: {
149 | limit: 10000,
150 | mimetype: 'application/font-woff',
151 | }
152 | },
153 | },
154 | // WOFF2 Font
155 | {
156 | test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/,
157 | use: {
158 | loader: 'url-loader',
159 | options: {
160 | limit: 10000,
161 | mimetype: 'application/font-woff',
162 | }
163 | }
164 | },
165 | // TTF Font
166 | {
167 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/,
168 | use: {
169 | loader: 'url-loader',
170 | options: {
171 | limit: 10000,
172 | mimetype: 'application/octet-stream'
173 | }
174 | }
175 | },
176 | // EOT Font
177 | {
178 | test: /\.eot(\?v=\d+\.\d+\.\d+)?$/,
179 | use: 'file-loader',
180 | },
181 | // SVG Font
182 | {
183 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/,
184 | use: {
185 | loader: 'url-loader',
186 | options: {
187 | limit: 10000,
188 | mimetype: 'image/svg+xml',
189 | }
190 | }
191 | },
192 | // Common Image Formats
193 | {
194 | test: /\.(?:ico|gif|png|jpg|jpeg|webp)$/,
195 | use: 'url-loader',
196 | }
197 | ]
198 | },
199 |
200 | plugins: [
201 | new webpack.DllReferencePlugin({
202 | context: process.cwd(),
203 | manifest: require(manifest),
204 | sourceType: 'var',
205 | }),
206 |
207 | /**
208 | * https://webpack.js.org/concepts/hot-module-replacement/
209 | */
210 | new webpack.HotModuleReplacementPlugin({
211 | // @TODO: Waiting on https://github.com/jantimon/html-webpack-plugin/issues/533
212 | // multiStep: true
213 | }),
214 |
215 | new webpack.NoEmitOnErrorsPlugin(),
216 |
217 | /**
218 | * Create global constants which can be configured at compile time.
219 | *
220 | * Useful for allowing different behaviour between development builds and
221 | * release builds
222 | *
223 | * NODE_ENV should be production so that modules do not perform certain
224 | * development checks
225 | *
226 | * By default, use 'development' as NODE_ENV. This can be overriden with
227 | * 'staging', for example, by changing the ENV variables in the npm scripts
228 | */
229 | new webpack.DefinePlugin({
230 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development')
231 | }),
232 |
233 | new webpack.LoaderOptionsPlugin({
234 | debug: true
235 | }),
236 |
237 | new ExtractTextPlugin({
238 | filename: '[name].css'
239 | }),
240 | ],
241 |
242 | node: {
243 | __dirname: false,
244 | __filename: false
245 | },
246 |
247 | devServer: {
248 | port,
249 | publicPath,
250 | compress: true,
251 | noInfo: true,
252 | stats: 'errors-only',
253 | inline: true,
254 | lazy: false,
255 | hot: true,
256 | headers: { 'Access-Control-Allow-Origin': '*' },
257 | contentBase: path.join(__dirname, 'dist'),
258 | watchOptions: {
259 | aggregateTimeout: 300,
260 | ignored: /node_modules/,
261 | poll: 100
262 | },
263 | historyApiFallback: {
264 | verbose: true,
265 | disableDotRule: false,
266 | },
267 | before() {
268 | if (process.env.START_HOT) {
269 | console.log('Staring Main Process...');
270 | spawn(
271 | 'npm',
272 | ['run', 'start-main-dev'],
273 | { shell: true, env: process.env, stdio: 'inherit' }
274 | )
275 | .on('close', code => process.exit(code))
276 | .on('error', spawnError => console.error(spawnError));
277 | }
278 | }
279 | },
280 | });
281 |
--------------------------------------------------------------------------------
/app/mirror/modules/default/WeatherForecast/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React, { Component } from 'react';
3 | import PropTypes from 'prop-types';
4 | import classNames from 'classnames';
5 | import moment from 'moment-timezone';
6 | import request from 'request';
7 | import styles from './WeatherForecast.css';
8 | import { processWeather } from './core/utils';
9 | import notifications from '../../../core/notifications';
10 |
11 | class WeatherForecast extends Component {
12 |
13 | constructor(props: any) {
14 | super(props);
15 | this.updateModule = this.updateModule.bind(this);
16 | this.hideShowModule = this.hideShowModule.bind(this);
17 | this.updateWeather = this.updateWeather.bind(this);
18 | this.getUrlParams = this.getUrlParams.bind(this);
19 | this.generateTable = this.generateTable.bind(this);
20 | }
21 |
22 | state = {
23 | forecastEndpoint: 'forecast/daily',
24 | maxNumberOfDays: 1,
25 | fetchedLocationName: '',
26 | intervalId: null,
27 | windSpeed: null,
28 | windDirection: null,
29 | weatherType: null,
30 | forecast: [],
31 | loading: true
32 | };
33 |
34 | componentDidMount() {
35 | if (this.props.language) {
36 | moment.loacale(this.props.language);
37 | }
38 |
39 | this.updateModule();
40 | this.setState({
41 | intervalId: setInterval(() => {
42 | this.updateModule();
43 | }, this.props.updateInterval)
44 | });
45 |
46 | notifications.on('NOTIFICATION', (arg) => {
47 | switch (arg.type) {
48 | case 'CALENDAR_EVENTS':
49 | for (var e in arg.payload) {
50 | if (e.location || e.geo) {
51 | this.setState({
52 | firstEvent: event,
53 | });
54 | }
55 | }
56 | break;
57 | }
58 | });
59 | }
60 |
61 | componentWillUnmount() {
62 | clearInterval(this.state.intervalId);
63 | clearTimeout(this.state.showHideTimer);
64 | }
65 |
66 | updateModule() {
67 | this.hideShowModule(true, () => {
68 | this.updateWeather((data) => {
69 | if (data.error) {
70 | console.log(data.error);
71 | return;
72 | }
73 | this.setState({
74 | ...processWeather(data),
75 | }, () => {
76 | this.hideShowModule(false);
77 | });
78 | });
79 | });
80 | }
81 |
82 | hideShowModule(hide: boolean, callback: any) {
83 | this.setState({
84 | opacity: hide ? 0 : 1,
85 | hidden: hide
86 | });
87 | if (hide && callback) {
88 | this.setState({
89 | showHideTimer: setTimeout(() => { callback(); }, this.props.fadeSpeed / 2),
90 | });
91 | } else {
92 | clearTimeout(this.state.showHideTimer);
93 | }
94 | }
95 |
96 | getUrlParams() {
97 | let params = '?';
98 | if (this.props.locationID) {
99 | params += `id=${this.props.locationID}`;
100 | } else if (this.props.location) {
101 | params += `q=${this.props.location}`;
102 | } else if (this.firstEvent && this.firstEvent.geo) {
103 | params += `lat=${this.state.firstEvent.geo.lat}&lon=${this.state.firstEvent.geo.lon}`;
104 | } else if (this.state.firstEvent && this.state.firstEvent.location) {
105 | params += `q=${this.state.firstEvent.location}`;
106 | } else {
107 | console.log('hide???');
108 | // this.hide(this.props.animationSpeed, {lockString:this.identifier});
109 | return;
110 | }
111 |
112 | params += `&units=${this.props.units}`;
113 | params += `&lang=${this.props.lang}`;
114 | params += `&cnt=${(((this.state.maxNumberOfDays < 1) || (this.state.maxNumberOfDays > 16)) ? 7 * 8 : this.state.maxNumberOfDays)}`;
115 | params += `&APPID=${this.props.appid}`;
116 | return params;
117 | }
118 |
119 | updateWeather(callback) {
120 | if (this.props.appid === '') {
121 | console.log('[ERROR] WeatherForecast: APPID not set!');
122 | return;
123 | }
124 |
125 | const URL = `${this.props.apiBase}${this.props.apiVersion}/${this.state.forecastEndpoint}${this.getUrlParams()}`;
126 | let retry = true;
127 | request({
128 | method: 'GET',
129 | url: URL,
130 | }, (error, response, body) => {
131 | if (!error && response.statusCode === 200) {
132 | callback(JSON.parse(body));
133 | return;
134 | } else if (response.statusCode === 401) {
135 | this.setState({
136 | forecastEndpoint: 'forecast',
137 | maxNumberOfDays: this.state.maxNumberOfDays * 8,
138 | });
139 | console.log(WeatherForecast.moduleName + ": Your AppID does not support long term forecasts. Switching to fallback endpoint.");
140 | retry = true;
141 | } else {
142 | callback({ error: `${WeatherForecast.moduleName}: Could not load weather.` });
143 | }
144 | if (retry) {
145 | setTimeout(() => {
146 | this.updateModule();
147 | }, this.state.loading ? 0 : this.props.retryDelay);
148 | }
149 | });
150 | }
151 |
152 | generateTable() {
153 | if (this.state.forecast.length > 0) {
154 | return this.state.forecast.map((f, indx) => {
155 | let degreeLabel = '';
156 | if (this.props.scale) {
157 | switch (this.props.units) {
158 | case 'metric':
159 | degreeLabel = 'C';
160 | break;
161 | case 'imperial':
162 | degreeLabel = 'F';
163 | break;
164 | default:
165 | degreeLabel = 'K';
166 | break;
167 | }
168 | }
169 |
170 | let rainCell;
171 | let rainText = this.props.units !== 'imperial' ? `${f.rain} mm` : (parseFloat(f.rain) / 25.4).toFixed(2) + ' in';
172 | if (this.props.showRainAmount) {
173 | rainCell = (
174 |
178 | {rainText}
179 | |
180 | );
181 | }
182 | let op = 1;
183 | if (this.props.fade && this.props.fadePoint < 1) {
184 | let fPoint = this.props.fadePoint > 0 ? this.props.fadePoint : 0;
185 | let startPoint = this.state.forecast.length * fPoint;
186 | let steps = this.state.forecast.length - startPoint;
187 | if (indx >= startPoint) {
188 | let currentStep = indx - startPoint;
189 | op = 1 - ((1 / steps) * currentStep);
190 | }
191 | }
192 |
193 | return (
194 |
201 | | {this.props.showTime ? f.timeOfDay : ''} |
202 | {f.day} |
203 |
207 |
208 | |
209 |
213 | {f.maxTemp}{degreeLabel}
214 | |
215 |
219 | {f.minTemp}{degreeLabel}
220 | |
221 |
222 | );
223 | });
224 | }
225 | }
226 |
227 | render() {
228 | if (!this.props.appid) {
229 | return (
230 |
231 | Please set the correct openweather appid in the config for module:
232 | {this.moduleName}.
233 |
234 | );
235 | }
236 |
237 | if (this.state.loading) {
238 | return (
239 |
240 | Loading
241 |
242 | );
243 | }
244 |
245 | return (
246 |
253 |
254 |
255 | {this.generateTable()}
256 |
257 |
258 |
259 | );
260 | }
261 | }
262 |
263 | WeatherForecast.moduleName = 'WeatherForecast';
264 |
265 | WeatherForecast.defaultProps = {
266 | location: false,
267 | locationID: false,
268 | appid: "",
269 | units: "imperial",
270 | showRainAmount: false,
271 | updateInterval: 600000, // every 10 minutes
272 | animationSpeed: 1000,
273 | timeFormat: 12,
274 | lang: "en",
275 | fade: true,
276 | fadePoint: 0.25,
277 | colored: true,
278 | scale: false,
279 | initialLoadDelay: 0, // 0 seconds delay
280 | retryDelay: 2500,
281 | apiVersion: "2.5",
282 | apiBase: "http://api.openweathermap.org/data/",
283 | appendLocationNameToHeader: true,
284 | calendarClass: "calendar",
285 | roundTemp: false,
286 | showTime: true
287 | };
288 |
289 | WeatherForecast.propTypes = {
290 | fadeSpeed: PropTypes.number,
291 | updateInterval: PropTypes.number,
292 | animation: PropTypes.string,
293 | position: PropTypes.string
294 | };
295 |
296 | export default WeatherForecast;
297 |
--------------------------------------------------------------------------------
/app/mirror/modules/default/CurrentWeather/index.js:
--------------------------------------------------------------------------------
1 | // @flow
2 | import React, { Component } from 'react';
3 | import PropTypes from 'prop-types';
4 | import classNames from 'classnames';
5 | import moment from 'moment-timezone';
6 | import request from 'request';
7 | import styles from './CurrentWeather.css';
8 | import WeatherDetail from './WeatherDetail';
9 | import { roundValue, ms2Beaufort, deg2Cardinal, iconTable } from './core/utils';
10 | import notifications from '../../../core/notifications';
11 |
12 | class CurrentWeather extends Component {
13 |
14 | constructor(props: any) {
15 | super(props);
16 | this.updateModule = this.updateModule.bind(this);
17 | this.hideShowModule = this.hideShowModule.bind(this);
18 | this.processWeather = this.processWeather.bind(this);
19 | this.updateWeather = this.updateWeather.bind(this);
20 | this.getHeader = this.getHeader.bind(this);
21 | this.getUrlParams = this.getUrlParams.bind(this);
22 | }
23 |
24 | state = {
25 | intervalId: null,
26 | windSpeed: null,
27 | windDirection: null,
28 | windDegrees: 0,
29 | sunriseSunsetTime: null,
30 | sunriseSunsetIcon: null,
31 | temperature: null,
32 | humidity: null,
33 | indoorTemperature: null,
34 | indoorHumidity: null,
35 | weatherType: null,
36 | loading: true
37 | };
38 |
39 | componentDidMount() {
40 | if (this.props.language) {
41 | moment.loacale(this.props.language);
42 | }
43 |
44 | this.updateModule();
45 | this.setState({
46 | intervalId: setInterval(() => {
47 | this.updateModule();
48 | }, this.props.updateInterval),
49 | });
50 |
51 | notifications.on('NOTIFICATION', (arg) => {
52 | switch (arg.type) {
53 | case 'CALENDAR_EVENTS':
54 | for (var e in arg.payload) {
55 | if (e.location || e.geo) {
56 | this.setState({
57 | firstEvent: event,
58 | });
59 | }
60 | }
61 | break;
62 | case 'INDOOR_TEMPURATURE':
63 | this.setState({
64 | indoorTemperature: roundValue(arg.payload)
65 | });
66 | break;
67 | case 'INDOOR_HUMIDITY':
68 | this.setState({
69 | indoorHumidity: roundValue(arg.payload)
70 | });
71 | break;
72 | }
73 | });
74 | }
75 |
76 | componentWillUnmount() {
77 | clearInterval(this.state.intervalId);
78 | clearTimeout(this.state.showHideTimer);
79 | }
80 |
81 | updateModule() {
82 | this.hideShowModule(true, () => {
83 | this.updateWeather((data) => {
84 | if(data.error) {
85 | console.log(data.error);
86 | return;
87 | }
88 | this.setState({
89 | ...this.processWeather(data),
90 | }, ()=>{
91 | this.hideShowModule(false);
92 | });
93 | });
94 | });
95 | }
96 |
97 | hideShowModule(hide: boolean, callback: any) {
98 | this.setState({
99 | opacity: hide ? 0 : 1,
100 | hidden: hide
101 | });
102 | if (hide && callback) {
103 | this.setState({
104 | showHideTimer: setTimeout(() => { callback(); }, this.props.fadeSpeed / 2),
105 | });
106 | } else {
107 | clearTimeout(this.state.showHideTimer);
108 | }
109 | }
110 |
111 | processWeather(data) {
112 | if (!data || !data.main || typeof data.main.temp === 'undefined') {
113 | // Did not receive usable new data.
114 | // Maybe this needs a better check?
115 | return;
116 | }
117 | const weatherObj = {
118 | humidity: parseFloat(data.main.humidity),
119 | temperature: roundValue(data.main.temp),
120 | };
121 |
122 | weatherObj.windSpeed = this.props.useBeaufort ? ms2Beaufort(roundValue(data.wind.speed))
123 | : parseFloat(data.wind.speed).toFixed(0);
124 |
125 | weatherObj.windDirection = deg2Cardinal(data.wind.deg);
126 | weatherObj.windDegrees = data.wind.deg;
127 | weatherObj.weatherType = iconTable[data.weather[0].icon];
128 |
129 | const now = new Date();
130 | const sunrise = new Date(data.sys.sunrise * 1000);
131 | const sunset = new Date(data.sys.sunset * 1000);
132 |
133 | // The moment().format('h') method has a bug on the Raspberry Pi.
134 | // So we need to generate the timestring manually.
135 | // See issue: https://github.com/MichMich/MagicMirror/issues/181
136 | const sunriseSunsetDateObject = (sunrise < now && sunset > now) ? sunset : sunrise;
137 | let timeString = moment(sunriseSunsetDateObject).format('HH:mm');
138 | if (this.props.timeFormat !== 24) {
139 | // var hours = sunriseSunsetDateObject.getHours() % 12 || 12;
140 | if (this.props.showPeriod) {
141 | if (this.props.showPeriodUpper) {
142 | // timeString = hours + moment(sunriseSunsetDateObject).format(':mm A');
143 | timeString = moment(sunriseSunsetDateObject).format('h:mm A');
144 | } else {
145 | // timeString = hours + moment(sunriseSunsetDateObject).format(':mm a');
146 | timeString = moment(sunriseSunsetDateObject).format('h:mm a');
147 | }
148 | } else {
149 | // timeString = hours + moment(sunriseSunsetDateObject).format(':mm');
150 | timeString = moment(sunriseSunsetDateObject).format('h:mm');
151 | }
152 | }
153 |
154 | weatherObj.sunriseSunsetTime = timeString;
155 | weatherObj.sunriseSunsetIcon = (sunrise < now && sunset > now) ? 'wi-sunset' : 'wi-sunrise';
156 | // this.show(this.config.animationSpeed, {lockString:this.identifier});
157 | weatherObj.loading = false;
158 | notifications.emit('NOTIFICATION', { type: 'CURRENTWEATHER_DATA', payload: data });
159 | return weatherObj;
160 | }
161 |
162 | getUrlParams() {
163 | let params = '?';
164 | if (this.props.locationID) {
165 | params += `id=${this.props.locationID}`;
166 | } else if (this.props.location) {
167 | params += `q=${this.props.location}`;
168 | } else if (this.firstEvent && this.firstEvent.geo) {
169 | params += `lat=${this.firstEvent.geo.lat}&lon=${this.firstEvent.geo.lon}`;
170 | } else if (this.firstEvent && this.firstEvent.location) {
171 | params += `q=${this.firstEvent.location}`;
172 | } else {
173 | console.log('hide???');
174 | // this.hide(this.props.animationSpeed, {lockString:this.identifier});
175 | return;
176 | }
177 |
178 | params += `&units=${this.props.units}`;
179 | params += `&lang=${this.props.lang}`;
180 | params += `&APPID=${this.props.appid}`;
181 |
182 | return params;
183 | }
184 |
185 | updateWeather(callback) {
186 | if (this.props.appid === '') {
187 | console.log("[ERROR] CurrentWeather: APPID not set!");
188 | return;
189 | }
190 |
191 | const URL = `${this.props.apiBase}${this.props.apiVersion}/${this.props.weatherEndpoint}${this.getUrlParams()}`;
192 | let retry = true;
193 |
194 | request({
195 | method: 'GET',
196 | url: URL,
197 | }, (error, response, body) => {
198 | if (!error && response.statusCode === 200) {
199 | //console.log(body);
200 | callback(JSON.parse(body));
201 | return;
202 | } else if (response.statusCode === 401) {
203 | retry = true;
204 | } else {
205 | callback({ error: `${CurrentWeather.moduleName}: Could not load weather.` });
206 | }
207 | if (retry) {
208 | setTimeout(() => {
209 | this.updateModule();
210 | }, this.state.loading ? 0 : this.props.retryDelay);
211 | }
212 | });
213 | }
214 |
215 | getHeader() {
216 | if (this.props.appendLocationNameToHeader) {
217 | return `${this.props.header} ${this.props.fetchedLocatioName}`;
218 | }
219 | return this.props.header;
220 | }
221 |
222 | render() {
223 | if (!this.props.appid) {
224 | return (
225 |
226 | Please set the correct openweather appid in the config for module: {this.moduleName}.
227 |
228 | );
229 | }
230 |
231 | if (this.state.loading) {
232 | return (
233 |
234 | Loading
235 |
236 | );
237 | }
238 |
239 | let degreeLabel = '';
240 | if (this.props.showDegreeLabel) {
241 | switch (this.props.units) {
242 | case 'metric':
243 | degreeLabel = 'C';
244 | break;
245 | case 'imperial':
246 | degreeLabel = 'F';
247 | break;
248 | default:
249 | degreeLabel = 'K';
250 | break;
251 | }
252 | }
253 |
254 | return (
255 |
262 | {
263 | (!this.props.onlyTemp) &&
264 |
274 | }
275 |
276 |
288 | {this.state.temperature}{this.props.showDegreeLabel ? °{degreeLabel} : ''}
289 | {
290 | (this.props.showIndoorTemperature && this.state.indoorTemperature) &&
291 |
292 |
297 | {this.state.indoorTemperature}°{degreeLabel}
298 |
299 | }
300 | {
301 | (this.props.showIndoorHumidity && this.state.indoorHumidity) &&
302 |
303 |
304 | {this.state.indoorHumidity}%
305 |
306 | }
307 |
308 |
309 | );
310 | }
311 | }
312 |
313 | CurrentWeather.moduleName = 'CurrentWeather';
314 |
315 | CurrentWeather.defaultProps = {
316 | location: false,
317 | locationID: false,
318 | appid: "",
319 | units: "imperial",
320 | updateInterval: 600000, // every 10 minutes
321 | animationSpeed: 1000,
322 | timeFormat: 12,
323 | showPeriod: true,
324 | showPeriodUpper: false,
325 | showWindDirection: true,
326 | showWindDirectionAsArrow: true,
327 | useBeaufort: true,
328 | lang: "en",
329 | showHumidity: false,
330 | showDegreeLabel: true,
331 | showIndoorTemperature: false,
332 | showIndoorHumidity: false,
333 | initialLoadDelay: 0, // 0 seconds delay
334 | retryDelay: 2500,
335 | apiVersion: "2.5",
336 | apiBase: "http://api.openweathermap.org/data/",
337 | weatherEndpoint: "weather",
338 | appendLocationNameToHeader: true,
339 | calendarClass: "calendar",
340 | onlyTemp: false,
341 | roundTemp: false
342 | };
343 |
344 | CurrentWeather.propTypes = {
345 | fadeSpeed: PropTypes.number,
346 | updateInterval: PropTypes.number,
347 | animation: PropTypes.string,
348 | position: PropTypes.string
349 | };
350 |
351 | export default CurrentWeather;
352 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | ### An application built with React + Electron meant to run on the Raspberry Pi for a DIY Smart Mirror modeled after [Magic Mirror2](https://github.com/MichMich/MagicMirror)
4 |
5 | Every module is just a react component that's dynamically imported into the app
6 |
7 | ## Usage
8 |
9 | ### Tested Hardware
10 |
11 | * Raspberry Pi 3
12 |
13 | ### Automatic Installation (Raspberry Pi 3)
14 |
15 | Run the following command on your Raspberry Pi to install Reactive Mirror
16 |
17 | ```
18 | curl -sL https://raw.githubusercontent.com/Cristian006/ReactiveMirror/master/app/mirror/installers/raspberry.sh | bash
19 | ```
20 |
21 | ### Manual Installation
22 |
23 | ```
24 | # Download and install the latest Node.js version
25 |
26 | git clone https://github.com/Cristian006/ReactiveMirror
27 |
28 | # install app module dependencies
29 | cd ~/ReactiveMirror/app
30 | npm install
31 |
32 | # install app dependencies
33 | cd ~/ReactiveMirror
34 | npm install
35 |
36 | # start the application
37 | npm start
38 | ```
39 |
40 | # Development
41 |
42 | [](https://facebook.github.io/react/)
43 | [](https://webpack.github.io/)
44 | [](http://redux.js.org/)
45 | [](https://github.com/ReactTraining/react-router)
46 | [](https://flowtype.org/)
47 | [](http://eslint.org/)
48 | [](https://facebook.github.io/jest/)
49 | [](https://yarnpkg.com/)
50 |
51 | [Electron](http://electron.atom.io/) mirror application based on [React](https://facebook.github.io/react/), [Redux](https://github.com/reactjs/redux), [React Router](https://github.com/reactjs/react-router), [Webpack](http://webpack.github.io/docs/), [React Transform HMR](https://github.com/gaearon/react-transform-hmr) for rapid application development.
52 |
53 | ## Install
54 |
55 | * **Note: requires a node version >= 7 and an npm version >= 4.**
56 |
57 | First, clone the repo via git:
58 |
59 | ```bash
60 | git clone --depth=1 https://github.com/cristian006/ReactiveMirror.git your-project-name
61 | ```
62 |
63 | And then install dependencies with yarn or npm
64 |
65 | ```bash
66 | $ cd your-project-name
67 | $ yarn
68 | $ cd app
69 | $ yarn //to install mirror module dependencies
70 | ```
71 |
72 | **Note**: If you can't use [yarn](https://github.com/yarnpkg/yarn) for some reason, try `npm install`.
73 |
74 | ## Run
75 |
76 | Start the app in the `dev` environment. This starts the renderer process in [**hot-module-replacement**](https://webpack.js.org/guides/hmr-react/) mode and starts a server that sends hot updates to the renderer process:
77 |
78 | ```bash
79 | $ npm run dev
80 | ```
81 |
82 | Run these two commands __simultaneously__ in different console tabs:
83 |
84 | ```bash
85 | $ npm run start-renderer-dev
86 | $ npm run start-main-dev
87 | ```
88 |
89 | Module configuration:
90 |
91 | | **Option** | **Description** |
92 | | --- | --- |
93 | | `name` | The name of the module. This can also contain the subfolder. Valid examples include `Clock`, `Calendar` and `CustomModule`. |
94 | | `position` | The location of the module in which the module will be loaded. Possible values are `top_ bar`, `top_left`, `top_center`, `top_right`, `upper_third`, `middle_center`, `lower_third`, `bottom_left`, `bottom_center`, `bottom_right`, `bottom_bar`, `fullscreen_above`, and `fullscreen_below`. This field is optional but most modules require this field to set. Check the documentation of the module for more information. Multiple modules with the same position will be ordered based on the order in the configuration file. |
95 | | `hide` | Set hide to `true` to skip creating the module. This field is optional. |
96 | | `config` | An object with the module configuration properties. Check the documentation of the module for more information. This field is optional, unless the module requires extra configuration. |
97 |
98 | ## Modules
99 |
100 | The following modules are installed by default.
101 |
102 | - [**Clock**](app/mirror/modules/default/Clock)
103 | - [**Calendar**](app/mirror/modules/default/Calendar)
104 | - [**Current Weather**](app/mirror/modules/default/Currentweather)
105 | - [**Weather Forecast**](app/mirror/modules/default/WeatherForecast)
106 | - [**News Feed**](app/mirror/modules/default/NewsFeed)
107 | - [**Compliments**](app/mirror/modules/default/Compliments)
108 | - [**Snow**](app/mirror/modules/default/Snow)
109 | - [**Jokes**](app/mirror/modules/default/Jokes)
110 |
111 | ## Editor Configuration
112 | **Atom**
113 | ```bash
114 | apm install editorconfig es6-javascript atom-ternjs javascript-snippets linter linter-eslint language-babel autocomplete-modules file-icons
115 | ```
116 |
117 | **VSCode**
118 | * [Editorconfig](https://github.com/editorconfig/editorconfig-vscode)
119 | * [ESLint](https://github.com/Microsoft/vscode-eslint)
120 | * [Flow](https://github.com/flowtype/flow-for-vscode)
121 | * [Babel](https://github.com/dzannotti/vscode-babel)
122 | * [Jest](https://github.com/orta/vscode-jest)
123 | * [ES6 Snippets](https://marketplace.visualstudio.com/items?itemName=xabikos.JavaScriptSnippets)
124 | * [React Snippets](https://marketplace.visualstudio.com/items?itemName=xabikos.ReactSnippets)
125 | :bulb: *If you are using the `flow-for-vscode` plugin, make sure to disable the `flowtype-errors/show-errors` eslint rule in the `.eslintrc` by setting it to `0`*
126 |
127 | **Sublime**
128 | * [Editorconfig Integration](https://github.com/sindresorhus/editorconfig-sublime#readme)
129 | * [Linting](https://github.com/SublimeLinter/SublimeLinter3)
130 | * [ESLint Integration](https://github.com/roadhump/SublimeLinter-eslint)
131 | * [Syntax Highlighting](https://github.com/babel/babel-sublime)
132 | * [Autocompletion](https://github.com/ternjs/tern_for_sublime)
133 | * [Node Snippets](https://packagecontrol.io/packages/JavaScript%20%26%20NodeJS%20Snippets)
134 | * [ES6 Snippets](https://packagecontrol.io/packages/ES6-Toolkit)
135 |
136 | **Others**
137 | * [Editorconfig](http://editorconfig.org/#download)
138 | * [ESLint](http://eslint.org/docs/user-guide/integrations#editors)
139 | * Babel Syntax Plugin
140 |
141 | ## DevTools
142 |
143 | #### Toggle Chrome DevTools
144 |
145 | - OS X: Cmd Alt I or F12
146 | - Linux: Ctrl Shift I or F12
147 | - Windows: Ctrl Shift I or F12
148 |
149 | *See [electron-debug](https://github.com/sindresorhus/electron-debug) for more information.*
150 |
151 | #### DevTools extension
152 |
153 | * [Devtron](https://github.com/electron/devtron) - Install via [electron-debug](https://github.com/sindresorhus/electron-debug).
154 | * [React Developer Tools](https://github.com/facebook/react-devtools) - Install via [electron-devtools-installer](https://github.com/GPMDP/electron-devtools-installer).
155 | * [Redux DevTools](https://github.com/zalmoxisus/redux-devtools-extension) - Install via [electron-devtools-installer](https://github.com/GPMDP/electron-devtools-installer).
156 |
157 | You can find the tabs on Chrome DevTools.
158 |
159 | If you want to update extensions version, please set `UPGRADE_EXTENSIONS` env, just run:
160 |
161 | ```bash
162 | $ UPGRADE_EXTENSIONS=1 npm run dev
163 |
164 | # For Windows
165 | $ set UPGRADE_EXTENSIONS=1 && npm run dev
166 | ```
167 |
168 | :bulb: You can debug your production build with devtools by simply setting the `DEBUG_PROD` env variable:
169 | ```
170 | DEBUG_PROD=true npm run package
171 | ```
172 |
173 |
174 | ## CSS Modules
175 |
176 | This boilerplate is configured to use [css-modules](https://github.com/css-modules/css-modules) out of the box.
177 |
178 | All `.css` file extensions will use css-modules unless it has `.global.css`.
179 |
180 | If you need global styles, stylesheets with `.global.css` will not go through the
181 | css-modules loader. e.g. `app.global.css`
182 |
183 | If you want to import global css libraries (like `bootstrap`), you can just write the following code in `.global.css`:
184 |
185 | ```css
186 | @import "~bootstrap/dist/css/bootstrap.css";
187 | ```
188 |
189 | ## Sass support
190 |
191 | If you want to use Sass in your app, you only need to import `.sass` files instead of `.css` once:
192 | ```js
193 | import './app.global.scss';
194 | ```
195 |
196 | ## Packaging
197 |
198 | To package apps for the local platform:
199 |
200 | ```bash
201 | $ npm run package
202 | ```
203 |
204 | To package apps for all platforms:
205 |
206 | First, refer to [Multi Platform Build](https://www.electron.build/multi-platform-build) for dependencies.
207 |
208 | Then,
209 | ```bash
210 | $ npm run package-all
211 | ```
212 |
213 | To package apps with options:
214 |
215 | ```bash
216 | $ npm run package -- --[option]
217 | ```
218 |
219 | ## Further commands
220 |
221 | To run the application without packaging run
222 |
223 | ```bash
224 | $ npm run build
225 | $ npm start
226 | ```
227 |
228 | To run End-to-End Test
229 |
230 | ```bash
231 | $ npm run build
232 | $ npm run test-e2e
233 | ```
234 |
235 | #### Options
236 |
237 | See [electron-builder CLI Usage](https://github.com/electron-userland/electron-builder#cli-usage)
238 |
239 | ## How to add modules to the project
240 |
241 | You will need to add other modules to this boilerplate, depending on the requirements of your project. For example, you may want to add [node-postgres](https://github.com/brianc/node-postgres) to communicate with PostgreSQL database, or
242 | [material-ui](http://www.material-ui.com/) to reuse react UI components.
243 |
244 | ⚠️ Please read the following section before installing any dependencies ⚠️
245 |
246 | ### Module Structure
247 |
248 | This app uses a [two package.json structure](https://github.com/electron-userland/electron-builder/wiki/Two-package.json-Structure). This means, you will have two `package.json` files.
249 |
250 | 1. `./package.json` in the root of your project
251 | 1. `./app/package.json` inside `app` folder
252 |
253 | ### Which `package.json` file to use
254 |
255 | **Rule of thumb** is: all modules go into `./package.json` except native modules. Native modules go into `./app/package.json`.
256 |
257 | 1. If the module is native to a platform (like node-postgres) or otherwise should be included with the published package (i.e. bcrypt, openbci), it should be listed under `dependencies` in `./app/package.json`.
258 | 2. If a module is `import`ed by another module, include it in `dependencies` in `./package.json`. See [this ESLint rule](https://github.com/benmosher/eslint-plugin-import/blob/master/docs/rules/no-extraneous-dependencies.md). Examples of such modules are `material-ui`, `redux-form`, and `moment`.
259 | 3. Otherwise, modules used for building, testing and debugging should be included in `devDependencies` in `./package.json`.
260 |
261 | ### Further Readings
262 |
263 | See the wiki page, [Module Structure — Two package.json Structure](https://github.com/chentsulin/electron-react-boilerplate/wiki/Module-Structure----Two-package.json-Structure) to understand what is native module, the rationale behind two package.json structure and more.
264 |
265 | For an example app that uses this boilerplate and packages native dependencies, see [erb-sqlite-example](https://github.com/amilajack/erb-sqlite-example).
266 |
267 | ## Static Type Checking
268 | This project comes with Flow support out of the box! You can annotate your code with types, [get Flow errors as ESLint errors](https://github.com/amilajack/eslint-plugin-flowtype-errors), and get [type errors during runtime](https://github.com/codemix/flow-runtime) during development. Types are completely optional.
269 |
270 | ## Dispatching redux actions from main process
271 |
272 | see discusses in [#118](https://github.com/chentsulin/electron-react-boilerplate/issues/118) and [#108](https://github.com/chentsulin/electron-react-boilerplate/issues/108)
273 |
274 | ## License
275 | MIT © [Cristian Ponce](https://github.com/cristian006)
276 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # 0.12.0 (2017.7.8)
2 |
3 | #### Misc
4 | - Removed `babel-polyfill`
5 | - Renamed and alphabetized npm scripts
6 |
7 | #### Breaking
8 | - Changed node dev `__dirname` and `__filename` to node built in fn's (https://github.com/chentsulin/electron-react-boilerplate/pull/1035)
9 | - Renamed `app/bundle.js` to `app/renderer.prod.js` for consistency
10 | - Renamed `dll/vendor.js` to `dll/renderer.dev.dll.js` for consistency
11 |
12 | #### Additions
13 | - Enable node_modules cache on CI
14 |
15 | # 0.11.2 (2017.5.1)
16 |
17 | Yay! Another patch release. This release mostly includes refactorings and router bug fixes. Huge thanks to @anthonyraymond!
18 |
19 | ⚠️ Windows electron builds are failing because of [this issue](https://github.com/electron/electron/issues/9321). This is not an issue with the boilerplate ⚠️
20 |
21 | #### Breaking
22 | - **Renamed `./app/main.development.js` => `./app/main.{dev,prod}.js`:** [#963](https://github.com/chentsulin/electron-react-boilerplate/pull/963)
23 |
24 | #### Fixes
25 | - **Fixed reloading when not on `/` path:** [#958](https://github.com/chentsulin/electron-react-boilerplate/pull/958) [#949](https://github.com/chentsulin/electron-react-boilerplate/pull/949)
26 |
27 | #### Additions
28 | - **Added support for stylefmt:** [#960](https://github.com/chentsulin/electron-react-boilerplate/pull/960)
29 |
30 | # 0.11.1 (2017.4.23)
31 |
32 | You can now debug the production build with devtools like so:
33 | ```
34 | DEBUG_PROD=true npm run package
35 | ```
36 |
37 | 🎉🎉🎉
38 |
39 | #### Additions
40 | - **Added support for debugging production build:** [#fab245a](https://github.com/chentsulin/electron-react-boilerplate/pull/941/commits/fab245a077d02a09630f74270806c0c534a4ff95)
41 |
42 | #### Bug Fixes
43 | - **Fixed bug related to importing native dependencies:** [#933](https://github.com/chentsulin/electron-react-boilerplate/pull/933)
44 |
45 | #### Improvements
46 | - **Updated all deps to latest semver**
47 |
48 | # 0.11.0 (2017.4.19)
49 |
50 | Here's the most notable changes since `v0.10.0`. Its been about a year since a release has been pushed. Expect a new release to be published every 3-4 weeks.
51 |
52 | #### Breaking Changes
53 |
54 | - **Dropped support for node < 6**
55 | - **Refactored webpack config files**
56 | - **Migrate to two-package.json project structure**
57 | - **Updated all devDeps to latest semver**
58 | - **Migrated to Jest:** [#768](https://github.com/chentsulin/electron-react-boilerplate/pull/768)
59 | - **Migrated to `react-router@4`**
60 | - **Migrated to `electron-builder@4`**
61 | - **Migrated to `webpack@2`**
62 | - **Migrated to `react-hot-loader@3`**
63 | - **Changed default live reload server PORT to `1212` from `3000`**
64 |
65 | #### Additions
66 |
67 | - **Added support for Yarn:** [#451](https://github.com/chentsulin/electron-react-boilerplate/pull/451)
68 | - **Added support for Flow:** [#425](https://github.com/chentsulin/electron-react-boilerplate/pull/425)
69 | - **Added support for stylelint:** [#911](https://github.com/chentsulin/electron-react-boilerplate/pull/911)
70 | - **Added support for electron-builder:** [#876](https://github.com/chentsulin/electron-react-boilerplate/pull/876)
71 | - **Added optional support for SASS:** [#880](https://github.com/chentsulin/electron-react-boilerplate/pull/880)
72 | - **Added support for eslint-plugin-flowtype:** [#911](https://github.com/chentsulin/electron-react-boilerplate/pull/911)
73 | - **Added support for appveyor:** [#280](https://github.com/chentsulin/electron-react-boilerplate/pull/280)
74 | - **Added support for webpack dlls:** [#860](https://github.com/chentsulin/electron-react-boilerplate/pull/860)
75 | - **Route based code splitting:** [#884](https://github.com/chentsulin/electron-react-boilerplate/pull/884)
76 | - **Added support for Webpack Bundle Analyzer:** [#922](https://github.com/chentsulin/electron-react-boilerplate/pull/922)
77 |
78 | #### Improvements
79 |
80 | - **Parallelize renderer and main build processes when running `npm run build`**
81 | - **Dynamically generate electron app menu**
82 | - **Improved vscode integration:** [#856](https://github.com/chentsulin/electron-react-boilerplate/pull/856)
83 |
84 | #### Bug Fixes
85 |
86 | - **Fixed hot module replacement race condition bug:** [#917](https://github.com/chentsulin/electron-react-boilerplate/pull/917) [#920](https://github.com/chentsulin/electron-react-boilerplate/pull/920)
87 |
88 | # 0.10.0 (2016.4.18)
89 |
90 | #### Improvements
91 |
92 | - **Use Babel in main process with Webpack build:** [#201](https://github.com/chentsulin/electron-react-boilerplate/pull/201)
93 | - **Change targets to built-in support by webpack:** [#197](https://github.com/chentsulin/electron-react-boilerplate/pull/197)
94 | - **use es2015 syntax for webpack configs:** [#195](https://github.com/chentsulin/electron-react-boilerplate/pull/195)
95 | - **Open application when webcontent is loaded:** [#192](https://github.com/chentsulin/electron-react-boilerplate/pull/192)
96 | - **Upgraded dependencies**
97 |
98 | #### Bug fixed
99 |
100 | - **Fix `npm list electron-prebuilt` in package.js:** [#188](https://github.com/chentsulin/electron-react-boilerplate/pull/188)
101 |
102 |
103 | # 0.9.0 (2016.3.23)
104 |
105 | #### Improvements
106 |
107 | - **Added [redux-logger](https://github.com/fcomb/redux-logger)**
108 | - **Upgraded [react-router-redux](https://github.com/reactjs/react-router-redux) to v4**
109 | - **Upgraded dependencies**
110 | - **Added `npm run dev` command:** [#162](https://github.com/chentsulin/electron-react-boilerplate/pull/162)
111 | - **electron to v0.37.2**
112 |
113 | #### Breaking Changes
114 |
115 | - **css module as default:** [#154](https://github.com/chentsulin/electron-react-boilerplate/pull/154).
116 | - **set default NODE_ENV to production:** [#140](https://github.com/chentsulin/electron-react-boilerplate/issues/140)
117 |
118 |
119 | # 0.8.0 (2016.2.17)
120 |
121 | #### Bug fixed
122 |
123 | - **Fix lint errors**
124 | - **Fix Webpack publicPath for production builds**: [#119](https://github.com/chentsulin/electron-react-boilerplate/issues/119).
125 | - **package script now chooses correct OS icon extension**
126 |
127 | #### Improvements
128 |
129 | - **babel 6**
130 | - **Upgrade Dependencies**
131 | - **Enable CSS source maps**
132 | - **Add json-loader**: [#128](https://github.com/chentsulin/electron-react-boilerplate/issues/128).
133 | - **react-router 2.0 and react-router-redux 3.0**
134 |
135 |
136 | # 0.7.1 (2015.12.27)
137 |
138 | #### Bug fixed
139 |
140 | - **Fixed npm script on windows 10:** [#103](https://github.com/chentsulin/electron-react-boilerplate/issues/103).
141 | - **history and react-router version bump**: [#109](https://github.com/chentsulin/electron-react-boilerplate/issues/109), [#110](https://github.com/chentsulin/electron-react-boilerplate/pull/110).
142 |
143 | #### Improvements
144 |
145 | - **electron 0.36**
146 |
147 |
148 |
149 | # 0.7.0 (2015.12.16)
150 |
151 | #### Bug fixed
152 |
153 | - **Fixed process.env.NODE_ENV variable in webpack:** [#74](https://github.com/chentsulin/electron-react-boilerplate/pull/74).
154 | - **add missing object-assign**: [#76](https://github.com/chentsulin/electron-react-boilerplate/pull/76).
155 | - **packaging in npm@3:** [#77](https://github.com/chentsulin/electron-react-boilerplate/pull/77).
156 | - **compatibility in windows:** [#100](https://github.com/chentsulin/electron-react-boilerplate/pull/100).
157 | - **disable chrome debugger in production env:** [#102](https://github.com/chentsulin/electron-react-boilerplate/pull/102).
158 |
159 | #### Improvements
160 |
161 | - **redux**
162 | - **css-modules**
163 | - **upgrade to react-router 1.x**
164 | - **unit tests**
165 | - **e2e tests**
166 | - **travis-ci**
167 | - **upgrade to electron 0.35.x**
168 | - **use es2015**
169 | - **check dev engine for node and npm**
170 |
171 |
172 | # 0.6.5 (2015.11.7)
173 |
174 | #### Improvements
175 |
176 | - **Bump style-loader to 0.13**
177 | - **Bump css-loader to 0.22**
178 |
179 |
180 | # 0.6.4 (2015.10.27)
181 |
182 | #### Improvements
183 |
184 | - **Bump electron-debug to 0.3**
185 |
186 |
187 | # 0.6.3 (2015.10.26)
188 |
189 | #### Improvements
190 |
191 | - **Initialize ExtractTextPlugin once:** [#64](https://github.com/chentsulin/electron-react-boilerplate/issues/64).
192 |
193 |
194 | # 0.6.2 (2015.10.18)
195 |
196 | #### Bug fixed
197 |
198 | - **Babel plugins production env not be set properly:** [#57](https://github.com/chentsulin/electron-react-boilerplate/issues/57).
199 |
200 |
201 | # 0.6.1 (2015.10.17)
202 |
203 | #### Improvements
204 |
205 | - **Bump electron to v0.34.0**
206 |
207 |
208 | # 0.6.0 (2015.10.16)
209 |
210 | #### Breaking Changes
211 |
212 | - **From react-hot-loader to react-transform**
213 |
214 |
215 | # 0.5.2 (2015.10.15)
216 |
217 | #### Improvements
218 |
219 | - **Run tests with babel-register:** [#29](https://github.com/chentsulin/electron-react-boilerplate/issues/29).
220 |
221 |
222 | # 0.5.1 (2015.10.12)
223 |
224 | #### Bug fixed
225 |
226 | - **Fix #51:** use `path.join(__dirname` instead of `./`.
227 |
228 |
229 | # 0.5.0 (2015.10.11)
230 |
231 | #### Improvements
232 |
233 | - **Simplify webpack config** see [#50](https://github.com/chentsulin/electron-react-boilerplate/pull/50).
234 |
235 | #### Breaking Changes
236 |
237 | - **webpack configs**
238 | - **port changed:** changed default port from 2992 to 3000.
239 | - **npm scripts:** remove `start-dev` and `dev-server`. rename `hot-dev-server` to `hot-server`.
240 |
241 |
242 | # 0.4.3 (2015.9.22)
243 |
244 | #### Bug fixed
245 |
246 | - **Fix #45 zeromq crash:** bump version of `electron-prebuilt`.
247 |
248 |
249 | # 0.4.2 (2015.9.15)
250 |
251 | #### Bug fixed
252 |
253 | - **run start-hot breaks chrome refresh(CTRL+R) (#42)**: bump `electron-debug` to `0.2.1`
254 |
255 |
256 | # 0.4.1 (2015.9.11)
257 |
258 | #### Improvements
259 |
260 | - **use electron-prebuilt version for packaging (#33)**
261 |
262 |
263 | # 0.4.0 (2015.9.5)
264 |
265 | #### Improvements
266 |
267 | - **update dependencies**
268 |
269 |
270 | # 0.3.0 (2015.8.31)
271 |
272 | #### Improvements
273 |
274 | - **eslint-config-airbnb**
275 |
276 |
277 | # 0.2.10 (2015.8.27)
278 |
279 | #### Features
280 |
281 | - **custom placeholder icon**
282 |
283 | #### Improvements
284 |
285 | - **electron-renderer as target:** via [webpack-target-electron-renderer](https://github.com/chentsulin/webpack-target-electron-renderer)
286 |
287 |
288 | # 0.2.9 (2015.8.18)
289 |
290 | #### Bug fixed
291 |
292 | - **Fix hot-reload**
293 |
294 |
295 | # 0.2.8 (2015.8.13)
296 |
297 | #### Improvements
298 |
299 | - **bump electron-debug**
300 | - **babelrc**
301 | - **organize webpack scripts**
302 |
303 |
304 | # 0.2.7 (2015.7.9)
305 |
306 | #### Bug fixed
307 |
308 | - **defaultProps:** fix typos.
309 |
310 |
311 | # 0.2.6 (2015.7.3)
312 |
313 | #### Features
314 |
315 | - **menu**
316 |
317 | #### Bug fixed
318 |
319 | - **package.js:** include webpack build.
320 |
321 |
322 | # 0.2.5 (2015.7.1)
323 |
324 | #### Features
325 |
326 | - **NPM Script:** support multi-platform
327 | - **package:** `--all` option
328 |
329 |
330 | # 0.2.4 (2015.6.9)
331 |
332 | #### Bug fixed
333 |
334 | - **Eslint:** typo, [#17](https://github.com/chentsulin/electron-react-boilerplate/issues/17) and improve `.eslintrc`
335 |
336 |
337 | # 0.2.3 (2015.6.3)
338 |
339 | #### Features
340 |
341 | - **Package Version:** use latest release electron version as default
342 | - **Ignore Large peerDependencies**
343 |
344 | #### Bug fixed
345 |
346 | - **Npm Script:** typo, [#6](https://github.com/chentsulin/electron-react-boilerplate/pull/6)
347 | - **Missing css:** [#7](https://github.com/chentsulin/electron-react-boilerplate/pull/7)
348 |
349 |
350 | # 0.2.2 (2015.6.2)
351 |
352 | #### Features
353 |
354 | - **electron-debug**
355 |
356 | #### Bug fixed
357 |
358 | - **Webpack:** add `.json` and `.node` to extensions for imitating node require.
359 | - **Webpack:** set `node_modules` to externals for native module support.
360 |
361 |
362 | # 0.2.1 (2015.5.30)
363 |
364 | #### Bug fixed
365 |
366 | - **Webpack:** #1, change build target to `atom`.
367 |
368 |
369 | # 0.2.0 (2015.5.30)
370 |
371 | #### Features
372 |
373 | - **Ignore:** `test`, `tools`, `release` folder and devDependencies in `package.json`.
374 | - **Support asar**
375 | - **Support icon**
376 |
377 |
378 | # 0.1.0 (2015.5.27)
379 |
380 | #### Features
381 |
382 | - **Webpack:** babel, react-hot, ...
383 | - **Flux:** actions, api, components, containers, stores..
384 | - **Package:** darwin (osx), linux and win32 (windows) platform.
385 |
--------------------------------------------------------------------------------