├── .gitignore ├── src ├── js │ ├── constants │ │ ├── misc.js │ │ ├── runtimeEventsTypes.js │ │ ├── taskStatuses.js │ │ └── runnableTypes.js │ ├── utils │ │ ├── migrator.js │ │ ├── taskTitle.js │ │ ├── presentDuration.test.js │ │ ├── runtimeEvents.js │ │ ├── presentDuration.js │ │ ├── runningItemInfos.js │ │ └── runningItemInfos.test.js │ ├── options.js │ ├── popup.js │ ├── popup │ │ ├── containers │ │ │ ├── Content.jsx │ │ │ ├── Counter.jsx │ │ │ ├── Sidebar.jsx │ │ │ └── Tasks.jsx │ │ ├── reducers │ │ │ ├── currentListID.js │ │ │ ├── app.js │ │ │ ├── runningItem.js │ │ │ ├── list.js │ │ │ ├── tasks.js │ │ │ └── tasks.test.js │ │ ├── components │ │ │ ├── Content.jsx │ │ │ ├── TaskPomodoros.jsx │ │ │ ├── App.jsx │ │ │ ├── Task.jsx │ │ │ ├── NewTaskForm.jsx │ │ │ ├── TaskForm.jsx │ │ │ ├── Sidebar.jsx │ │ │ ├── TaskRow.jsx │ │ │ ├── TodaysPomodoros.jsx │ │ │ ├── TaskListLink.jsx │ │ │ ├── Tasks.jsx │ │ │ └── Counter.jsx │ │ ├── constants │ │ │ └── actionTypes.js │ │ └── actions │ │ │ ├── list.js │ │ │ ├── counter.js │ │ │ └── task.js │ ├── repositories │ │ ├── runningItem.js │ │ ├── app.js │ │ ├── configs.js │ │ ├── lists.js │ │ └── tasks.js │ ├── options │ │ └── components │ │ │ └── App.jsx │ └── background.js ├── fonts │ ├── lato.ttf │ ├── icons │ │ └── base │ │ │ ├── play.svg │ │ │ ├── check.svg │ │ │ ├── plus.svg │ │ │ ├── square-o.svg │ │ │ ├── pencil.svg │ │ │ ├── warning.svg │ │ │ ├── check-square.svg │ │ │ ├── trash.svg │ │ │ ├── pointup.svg │ │ │ └── pointdown.svg │ └── BaseIcons.font.js ├── sounds │ └── ring.ogg ├── img │ ├── pomotodo-128.png │ ├── browser-action-icon.png │ └── pomotodo-notification.png ├── background.html ├── popup.html ├── manifest.json ├── options.html └── css │ └── popup.css ├── misc ├── banner.png └── screenshot.png ├── utils ├── env.js ├── build.js ├── prepare.js ├── generate_manifest.js └── webserver.js ├── .babelrc ├── .eslintrc.json ├── README.md ├── package.json ├── webpack.config.js └── LICENSE.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | secrets.*.js 4 | -------------------------------------------------------------------------------- /src/js/constants/misc.js: -------------------------------------------------------------------------------- 1 | export const TODAYS_POMODORO_LIST = 'TODAYS_POMODORO_LIST' 2 | -------------------------------------------------------------------------------- /misc/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samuelsimoes/pomotasking/HEAD/misc/banner.png -------------------------------------------------------------------------------- /misc/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samuelsimoes/pomotasking/HEAD/misc/screenshot.png -------------------------------------------------------------------------------- /src/fonts/lato.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samuelsimoes/pomotasking/HEAD/src/fonts/lato.ttf -------------------------------------------------------------------------------- /src/sounds/ring.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samuelsimoes/pomotasking/HEAD/src/sounds/ring.ogg -------------------------------------------------------------------------------- /src/img/pomotodo-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samuelsimoes/pomotasking/HEAD/src/img/pomotodo-128.png -------------------------------------------------------------------------------- /src/img/browser-action-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samuelsimoes/pomotasking/HEAD/src/img/browser-action-icon.png -------------------------------------------------------------------------------- /src/img/pomotodo-notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samuelsimoes/pomotasking/HEAD/src/img/pomotodo-notification.png -------------------------------------------------------------------------------- /src/js/constants/runtimeEventsTypes.js: -------------------------------------------------------------------------------- 1 | export const START_COUNTER = 'START_COUNTER' 2 | export const STOP_COUNTER = 'STOP_COUNTER' 3 | -------------------------------------------------------------------------------- /src/js/constants/taskStatuses.js: -------------------------------------------------------------------------------- 1 | export const FINISHED = 'FINISHED' 2 | export const RUNNING = 'RUNNING' 3 | export const OPEN = 'OPEN' 4 | -------------------------------------------------------------------------------- /src/js/constants/runnableTypes.js: -------------------------------------------------------------------------------- 1 | export const POMODORO = 'POMODORO' 2 | export const SHORT_PAUSE = 'SHORT_PAUSE' 3 | export const LONG_PAUSE = 'LONG_PAUSE' 4 | -------------------------------------------------------------------------------- /src/js/utils/migrator.js: -------------------------------------------------------------------------------- 1 | export default function () { 2 | window.localStorage.setItem('current-version', window.chrome.runtime.getManifest().version) 3 | } 4 | -------------------------------------------------------------------------------- /src/background.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /utils/env.js: -------------------------------------------------------------------------------- 1 | // tiny wrapper with default env vars 2 | module.exports = { 3 | NODE_ENV: (process.env.NODE_ENV || "development"), 4 | PORT: (process.env.PORT || 3000) 5 | }; 6 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "react", 5 | "stage-0" 6 | ], 7 | "env": { 8 | "development": { 9 | "presets":["react-hmre"] 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/js/utils/taskTitle.js: -------------------------------------------------------------------------------- 1 | export default function (description) { 2 | description = description.split('\n')[0] 3 | 4 | return description.length > 65 ? `${description.substring(0, 65)}…` : description 5 | } 6 | -------------------------------------------------------------------------------- /src/js/options.js: -------------------------------------------------------------------------------- 1 | import App from './options/components/App' 2 | import React from 'react' 3 | import { render } from 'react-dom' 4 | 5 | render( 6 | , 7 | window.document.getElementById('app-container') 8 | ) 9 | -------------------------------------------------------------------------------- /utils/build.js: -------------------------------------------------------------------------------- 1 | var webpack = require("webpack"), 2 | config = require("../webpack.config"); 3 | 4 | require("./prepare"); 5 | 6 | webpack( 7 | config, 8 | function (err) { if (err) throw err; } 9 | ); 10 | -------------------------------------------------------------------------------- /utils/prepare.js: -------------------------------------------------------------------------------- 1 | var fileSystem = require("fs-extra"), 2 | path = require("path"); 3 | 4 | // clean de dist folder 5 | fileSystem.emptyDirSync(path.join(__dirname, "../build")); 6 | 7 | require("./generate_manifest"); 8 | -------------------------------------------------------------------------------- /src/fonts/icons/base/play.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true, 5 | "jest": true 6 | }, 7 | "extends": ["standard", "standard-jsx"], 8 | "parser": "babel-eslint", 9 | "rules": { 10 | "react/jsx-closing-bracket-location": [1, "after-props"] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/js/popup.js: -------------------------------------------------------------------------------- 1 | import '../fonts/BaseIcons.font' 2 | import '../css/popup.css' 3 | import App from './popup/components/App' 4 | import React from 'react' 5 | import { render } from 'react-dom' 6 | 7 | render( 8 | , 9 | window.document.getElementById('app-container') 10 | ) 11 | -------------------------------------------------------------------------------- /src/fonts/icons/base/check.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/js/popup/containers/Content.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import Content from '../components/Content' 3 | 4 | function mapStateToProps (state) { 5 | return { 6 | currentListID: state.currentListID 7 | } 8 | } 9 | 10 | export default connect(mapStateToProps)(Content) 11 | -------------------------------------------------------------------------------- /src/fonts/icons/base/plus.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/js/utils/presentDuration.test.js: -------------------------------------------------------------------------------- 1 | import { presentFullDuration, presentMinutesDuration } from './presentDuration' 2 | 3 | test('present minutes duration', () => { 4 | expect(presentFullDuration(130)).toBe('02:10') 5 | }) 6 | 7 | test('present short minutes duration', () => { 8 | expect(presentMinutesDuration(130)).toBe('2') 9 | }) 10 | -------------------------------------------------------------------------------- /src/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 | 15 | 16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /src/fonts/icons/base/square-o.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/js/utils/runtimeEvents.js: -------------------------------------------------------------------------------- 1 | import * as runtimeEventsTypes from '../constants/runtimeEventsTypes.js' 2 | 3 | export function stopBadgeCounter () { 4 | window.chrome.runtime.sendMessage({ type: runtimeEventsTypes.STOP_COUNTER }) 5 | } 6 | 7 | export function startBadgeCounter () { 8 | window.chrome.runtime.sendMessage({ type: runtimeEventsTypes.START_COUNTER }) 9 | } 10 | -------------------------------------------------------------------------------- /src/fonts/icons/base/pencil.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/js/popup/reducers/currentListID.js: -------------------------------------------------------------------------------- 1 | import * as actions from '../constants/actionTypes' 2 | 3 | export default function currentListIDReducer (state, action) { 4 | if (action.type === actions.CHOOSE_TASK_LIST) { 5 | return action.id 6 | } else if (action.type === actions.DESTROY_TASK_LIST && state === action.id) { 7 | return null 8 | } else { 9 | return state 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/js/repositories/runningItem.js: -------------------------------------------------------------------------------- 1 | export function get () { 2 | let item = JSON.parse(window.localStorage.getItem('running-item')) 3 | 4 | if (item) { 5 | item = { 6 | ...item, 7 | startedAt: new Date(item.startedAt) 8 | } 9 | } 10 | 11 | return item 12 | } 13 | 14 | export function persist (data) { 15 | window.localStorage.setItem('running-item', JSON.stringify(data)) 16 | } 17 | -------------------------------------------------------------------------------- /src/js/repositories/app.js: -------------------------------------------------------------------------------- 1 | import * as listsRepository from './lists' 2 | import * as tasksRepository from './tasks' 3 | import * as runningItemTask from './runningItem' 4 | 5 | export function getState () { 6 | let lists = listsRepository.getLists() 7 | let currentListID = listsRepository.getCurrentListID() 8 | 9 | return { 10 | currentListID, 11 | lists: lists, 12 | runningItem: runningItemTask.get(), 13 | tasks: tasksRepository.getTasks(currentListID) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /utils/generate_manifest.js: -------------------------------------------------------------------------------- 1 | var manifest = require("../src/manifest.json"), 2 | fileSystem = require("fs"), 3 | path = require("path"), 4 | env = require("./env"); 5 | 6 | // generates the manifest file using the package.json informations 7 | manifest.description = process.env.npm_package_description; 8 | manifest.version = process.env.npm_package_version; 9 | 10 | fileSystem.writeFileSync( 11 | path.join(__dirname, "../build/manifest.json"), 12 | JSON.stringify(manifest) 13 | ); 14 | -------------------------------------------------------------------------------- /src/fonts/BaseIcons.font.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'files': [ 3 | './icons/base/*.svg' 4 | ], 5 | 'fontName': 'BaseIcons', 6 | 'classPrefix': 'bi-', 7 | 'baseClass': 'bi', 8 | 'codepoints': { 9 | 'square-o': '0xF0C8', 10 | 'check-square': '0xF14A', 11 | 'play': '0xF04B', 12 | 'pencil': '0xF040', 13 | 'trash': '0xF014', 14 | 'plus': '0xF104', 15 | 'pointdown': '0xF0A7', 16 | 'pointup': '0xF0A6', 17 | 'warning': '0xF071' 18 | }, 19 | 'types': ['woff'] 20 | } 21 | -------------------------------------------------------------------------------- /src/fonts/icons/base/warning.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/js/repositories/configs.js: -------------------------------------------------------------------------------- 1 | export function get () { 2 | let infos = JSON.parse(window.localStorage.getItem('configs')) 3 | 4 | return { 5 | pomodoroLength: 20, 6 | shortPauseLength: 15, 7 | longPauseLength: 5, 8 | ...infos 9 | } 10 | } 11 | 12 | export function persist (configs) { 13 | window.localStorage.setItem('configs', JSON.stringify({ 14 | pomodoroLength: configs.pomodoroLength, 15 | shortPauseLength: configs.shortPauseLength, 16 | longPauseLength: configs.longPauseLength 17 | })) 18 | } 19 | -------------------------------------------------------------------------------- /src/js/popup/containers/Counter.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import { bindActionCreators } from 'redux' 3 | import Counter from '../components/Counter' 4 | import * as actions from '../actions/counter' 5 | 6 | function mapStateToProps (state) { 7 | return { 8 | runningItem: state.runningItem 9 | } 10 | } 11 | 12 | function mapDispatchToProps (dispatch) { 13 | return { 14 | actions: bindActionCreators(actions, dispatch) 15 | } 16 | } 17 | 18 | export default connect( 19 | mapStateToProps, 20 | mapDispatchToProps 21 | )(Counter) 22 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Pomotasking: Tasks lists with Pomodoro Timer", 3 | "background": { 4 | "page": "background.html" 5 | }, 6 | "options_ui": { 7 | "page": "options.html", 8 | "chrome_style": true 9 | }, 10 | "browser_action": { 11 | "default_popup": "popup.html", 12 | "default_icon": "browser-action-icon.png" 13 | }, 14 | "icons": { 15 | "128": "pomotodo-128.png" 16 | }, 17 | "permissions": ["notifications"], 18 | "manifest_version": 2, 19 | "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'" 20 | } 21 | -------------------------------------------------------------------------------- /src/fonts/icons/base/check-square.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/js/popup/containers/Sidebar.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import { bindActionCreators } from 'redux' 3 | import Sidebar from '../components/Sidebar' 4 | import * as actions from '../actions/list' 5 | 6 | function mapStateToProps (state) { 7 | return { 8 | lists: state.lists, 9 | currentListID: state.currentListID 10 | } 11 | } 12 | 13 | function mapDispatchToProps (dispatch) { 14 | return { 15 | actions: bindActionCreators(actions, dispatch) 16 | } 17 | } 18 | 19 | export default connect( 20 | mapStateToProps, 21 | mapDispatchToProps 22 | )(Sidebar) 23 | -------------------------------------------------------------------------------- /src/js/utils/presentDuration.js: -------------------------------------------------------------------------------- 1 | export function presentFullDuration (duration) { 2 | if (!duration) { return '00:00' } 3 | 4 | duration = Math.abs(parseInt(duration, 10)) 5 | 6 | let hours = Math.floor(duration / 3600) 7 | let minutes = parseInt((duration / 60), 10) 8 | let seconds = ((duration - (hours * 3600)) % 60) 9 | 10 | if (minutes < 10) { minutes = '0' + minutes } 11 | if (seconds < 10) { seconds = '0' + seconds } 12 | 13 | return `${minutes}:${seconds}` 14 | } 15 | 16 | export function presentMinutesDuration (duration) { 17 | return Math.abs(parseInt((duration / 60), 10)).toString() 18 | } 19 | -------------------------------------------------------------------------------- /src/js/popup/components/Content.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import Counter from '../containers/Counter' 3 | import Tasks from '../containers/Tasks' 4 | import TodaysPomodoros from './TodaysPomodoros' 5 | import { TODAYS_POMODORO_LIST } from '../../constants/misc' 6 | 7 | export default class Content extends Component { 8 | render () { 9 | if (this.props.currentListID === TODAYS_POMODORO_LIST) { 10 | return 11 | } else { 12 | return ( 13 |
14 | 15 | 16 |
17 | ) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/fonts/icons/base/trash.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Options 6 | 34 | 35 | 36 |
37 | 38 | 39 | -------------------------------------------------------------------------------- /src/js/popup/reducers/app.js: -------------------------------------------------------------------------------- 1 | import listReducer from './list' 2 | import tasksReducer from './tasks' 3 | import runningItemReducer from './runningItem' 4 | import currentListIDReducer from './currentListID' 5 | import * as appRepository from '../../repositories/app' 6 | 7 | export default function (state = appRepository.getState(), action) { 8 | return { 9 | currentListID: currentListIDReducer(state.currentListID, action), 10 | lists: listReducer(state.lists, action), 11 | runningItem: runningItemReducer( 12 | state.runningItem, 13 | state.tasks, 14 | action 15 | ), 16 | tasks: tasksReducer( 17 | state.tasks, 18 | state.currentListID, 19 | state.runningItem ? state.runningItem.id : null, 20 | action 21 | ) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/js/utils/runningItemInfos.js: -------------------------------------------------------------------------------- 1 | import * as runnableTypes from '../constants/runnableTypes' 2 | import * as configsRepository from '../repositories/configs' 3 | 4 | export default function (startedAt, itemType, now = new Date(), configs = configsRepository.get()) { 5 | let timeToFinish = { 6 | [runnableTypes.SHORT_PAUSE]: configs.shortPauseLength * 60, 7 | [runnableTypes.LONG_PAUSE]: configs.longPauseLength * 60, 8 | [runnableTypes.POMODORO]: configs.pomodoroLength * 60 9 | }[itemType] 10 | 11 | let elapsedTimeInSeconds = (((now.getTime()) - (startedAt.getTime())) / 1000) 12 | 13 | return { 14 | late: (elapsedTimeInSeconds > timeToFinish), 15 | leftTimeInSeconds: (timeToFinish - elapsedTimeInSeconds), 16 | elapsedTime: elapsedTimeInSeconds 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/js/popup/containers/Tasks.jsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | import { bindActionCreators } from 'redux' 3 | import Tasks from '../components/Tasks' 4 | import * as actions from '../actions/task' 5 | import * as taskStatuses from '../../constants/taskStatuses' 6 | 7 | function mapStateToProps (state) { 8 | return { 9 | tasks: state.tasks, 10 | notFinishedTasks: state.tasks.filter(task => task.status !== taskStatuses.FINISHED), 11 | finishedTasks: state.tasks.filter(task => task.status === taskStatuses.FINISHED).reverse(), 12 | currentListID: state.currentListID 13 | } 14 | } 15 | 16 | function mapDispatchToProps (dispatch) { 17 | return { 18 | actions: bindActionCreators(actions, dispatch) 19 | } 20 | } 21 | 22 | export default connect( 23 | mapStateToProps, 24 | mapDispatchToProps 25 | )(Tasks) 26 | -------------------------------------------------------------------------------- /src/js/popup/components/TaskPomodoros.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | 3 | let times = function (n, iterator) { 4 | var accum = Array(Math.max(0, n)) 5 | for (var i = 0; i < n; i++) accum[i] = iterator(i) 6 | return accum 7 | } 8 | 9 | export default class TaskPomodoros extends Component { 10 | render () { 11 | return ( 12 |
13 | {this.renderPomodoros()} 14 |
15 | ) 16 | } 17 | 18 | renderPomodoros () { 19 | let pomodorosCount = 20 | this 21 | .props 22 | .pomodoros 23 | .filter(pomodoro => !pomodoro.running) 24 | .length 25 | 26 | return times(pomodorosCount, (index) => 27 |
30 | ) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Pomotasking 3 |

4 | 5 | A Chrome extension pomodoro timer integrated with ToDo list. 6 | 7 | **[Install through the Chrome Web Store](https://chrome.google.com/webstore/detail/pomotasking/diofopnenjmjcpcimiimefbcmjahgldg)** 8 | 9 |

10 | Pomotasking Screenshot 11 |

12 | 13 | ## Setup 14 | 15 | 1. Install [yarn](https://yarnpkg.com): `npm install -g yarn`. 16 | 2. Run `yarn`. 17 | 3. Run `yarn start`. 18 | 4. Load the extension in Chrome: 19 | 1. Access `chrome://extensions/` 20 | 2. Check `Developer mode` 21 | 3. Click on `Load unpacked extension` 22 | 4. Select the `build` folder. 23 | 24 | ------------- 25 | Samuel Simões ~ [@samuelsimoes](https://twitter.com/samuelsimoes) ~ [Blog](http://blog.samuelsimoes.com/) 26 | -------------------------------------------------------------------------------- /src/fonts/icons/base/pointup.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/js/popup/components/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import Sidebar from '../containers/Sidebar' 3 | import Content from '../containers/Content' 4 | import appReducer from '../reducers/app' 5 | import { createStore, applyMiddleware } from 'redux' 6 | import { Provider } from 'react-redux' 7 | import ReduxThunk from 'redux-thunk' 8 | 9 | export default class App extends Component { 10 | constructor () { 11 | super(...arguments) 12 | 13 | this.store = 14 | createStore( 15 | appReducer, 16 | applyMiddleware(ReduxThunk) 17 | ) 18 | } 19 | 20 | render () { 21 | return ( 22 | 23 |
24 | 25 | 26 |
27 | 28 |
29 |
30 |
31 | ) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/fonts/icons/base/pointdown.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/js/popup/constants/actionTypes.js: -------------------------------------------------------------------------------- 1 | export const START_EDIT_TASK_LIST = 'START_EDIT_TASK_LIST' 2 | export const BUILD_NEW_TASK_LIST = 'BUILD_NEW_TASK_LIST' 3 | export const CANCEL_EDIT_TASK_LIST = 'CANCEL_EDIT_TASK_LIST' 4 | export const SUBMIT_EDIT_TASK_LIST = 'SUBMIT_EDIT_TASK_LIST' 5 | export const UPDATE_TASK_LIST = 'UPDATE_TASK_LIST' 6 | export const DESTROY_TASK_LIST = 'DESTROY_TASK_LIST' 7 | export const CHOOSE_TASK_LIST = 'CHOOSE_TASK_LIST' 8 | export const NEW_TASK = 'NEW_TASK' 9 | export const DESTROY_TASK = 'DESTROY_TASK' 10 | export const UPDATE_TASK = 'UPDATE_TASK' 11 | export const START_POMODORO = 'START_POMODORO' 12 | export const START_PAUSE = 'START_PAUSE' 13 | export const FINISH_CURRENT_ITEM = 'FINISH_CURRENT_ITEM' 14 | export const CANCEL_CURRENT_ITEM = 'CANCEL_CURRENT_ITEM' 15 | export const LOADED_TASKS = 'LOADED_TASKS' 16 | export const UPDATE_OPEN_TASKS_ORDER = 'UPDATE_OPEN_TASKS_ORDER' 17 | export const TOGGLE_FINISH_TASK = 'TOGGLE_FINISH_TASK' 18 | -------------------------------------------------------------------------------- /utils/webserver.js: -------------------------------------------------------------------------------- 1 | var WebpackDevServer = require("webpack-dev-server"), 2 | webpack = require("webpack"), 3 | config = require("../webpack.config"), 4 | env = require("./env"), 5 | path = require("path"); 6 | 7 | require("./prepare"); 8 | 9 | var excludeEntriesToHotReload = (config.excludeEntriesToHotReload || []); 10 | 11 | for (var entryName in config.entry) { 12 | if (excludeEntriesToHotReload.indexOf(entryName) === -1) { 13 | config.entry[entryName] = 14 | [ 15 | ("webpack-dev-server/client?http://localhost:" + env.PORT), 16 | "webpack/hot/dev-server" 17 | ].concat(config.entry[entryName]); 18 | } 19 | } 20 | 21 | config.plugins = 22 | [new webpack.HotModuleReplacementPlugin()].concat(config.plugins || []); 23 | 24 | var compiler = webpack(config); 25 | 26 | var server = 27 | new WebpackDevServer(compiler, { 28 | hot: true, 29 | contentBase: path.join(__dirname, "../build"), 30 | headers: { "Access-Control-Allow-Origin": "*" } 31 | }); 32 | 33 | server.listen(env.PORT); 34 | -------------------------------------------------------------------------------- /src/js/popup/components/Task.jsx: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react' 2 | import TaskRow from './TaskRow' 3 | import TaskForm from './TaskForm' 4 | 5 | export default class Task extends PureComponent { 6 | constructor () { 7 | super(...arguments) 8 | 9 | this.state = { editing: false } 10 | } 11 | 12 | renderTaskName () { 13 | if (this.state.editing) { return } 14 | 15 | return ( 16 | this.setState({ editing: true })} 18 | actions={this.props.actions} 19 | task={this.props.task} /> 20 | ) 21 | } 22 | 23 | renderForm () { 24 | if (!this.state.editing) { return } 25 | 26 | return ( 27 | this.setState({ editing: false })} 29 | actions={this.props.actions} 30 | task={this.props.task} /> 31 | ) 32 | } 33 | 34 | render () { 35 | return ( 36 |
38 | {this.renderTaskName()} 39 | {this.renderForm()} 40 |
41 | ) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/js/utils/runningItemInfos.test.js: -------------------------------------------------------------------------------- 1 | import runningItemInfos from './runningItemInfos' 2 | import * as runnableTypes from '../constants/runnableTypes' 3 | 4 | test('returns the correct infos to pomodoro', () => { 5 | expect(runningItemInfos( 6 | { getTime: () => 1000 }, 7 | runnableTypes.POMODORO, 8 | { getTime: () => 3000 }, 9 | { pomodoroLength: 10 } 10 | )).toEqual({ 11 | late: false, 12 | leftTimeInSeconds: 598, 13 | elapsedTime: 2 14 | }) 15 | }) 16 | 17 | test('returns the correct infos to short pause', () => { 18 | expect(runningItemInfos( 19 | { getTime: () => 1000 }, 20 | runnableTypes.SHORT_PAUSE, 21 | { getTime: () => 3000 }, 22 | { shortPauseLength: 5 } 23 | )).toEqual({ 24 | late: false, 25 | leftTimeInSeconds: 298, 26 | elapsedTime: 2 27 | }) 28 | }) 29 | 30 | test('returns the correct infos to long pause', () => { 31 | expect(runningItemInfos( 32 | { getTime: () => 1000 }, 33 | runnableTypes.LONG_PAUSE, 34 | { getTime: () => 3000 }, 35 | { longPauseLength: 6 } 36 | )).toEqual({ 37 | late: false, 38 | leftTimeInSeconds: 358, 39 | elapsedTime: 2 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /src/js/repositories/lists.js: -------------------------------------------------------------------------------- 1 | export function getLists () { 2 | let lists = JSON.parse(window.localStorage.getItem(`lists`)) || [] 3 | return lists 4 | } 5 | 6 | export function deleteList (listID) { 7 | let lists = getLists() 8 | 9 | lists = lists.filter(list => { 10 | return listID !== list.id 11 | }) 12 | 13 | window.localStorage.setItem( 14 | 'lists', 15 | JSON.stringify(lists) 16 | ) 17 | 18 | window.localStorage.removeItem(`task-list-${listID}`) 19 | } 20 | 21 | export function persistCurrentListID (listID) { 22 | window.localStorage.setItem('current-list-id', listID) 23 | } 24 | 25 | export function getCurrentListID () { 26 | return window.localStorage.getItem('current-list-id') 27 | } 28 | 29 | export function persistList (listData) { 30 | let lists = getLists() 31 | let updated 32 | 33 | lists = lists.map(list => { 34 | if (list.id === listData.id) { 35 | updated = true 36 | return { ...list, ...listData } 37 | } else { 38 | return list 39 | } 40 | }) 41 | 42 | if (!updated) { 43 | lists = lists.concat(listData) 44 | } 45 | 46 | window.localStorage.setItem( 47 | 'lists', 48 | JSON.stringify(lists) 49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /src/js/popup/reducers/runningItem.js: -------------------------------------------------------------------------------- 1 | import * as actions from '../constants/actionTypes' 2 | import * as runnableTypes from '../../constants/runnableTypes' 3 | 4 | export default function (state = null, tasks, action) { 5 | switch (action.type) { 6 | case actions.START_POMODORO: 7 | let runningTask = tasks.find(task => task.id === action.id) 8 | 9 | return { 10 | id: action.id, 11 | type: runnableTypes.POMODORO, 12 | description: runningTask.description, 13 | startedAt: action.now, 14 | taskID: action.taskID, 15 | listID: runningTask.listID 16 | } 17 | case actions.DESTROY_TASK_LIST: 18 | if (state && state.listID === action.id) { 19 | return null 20 | } else { 21 | return state 22 | } 23 | case actions.START_PAUSE: 24 | return { 25 | id: action.id, 26 | type: action.pauseType, 27 | description: { 28 | [runnableTypes.SHORT_PAUSE]: 'Short Pause', 29 | [runnableTypes.LONG_PAUSE]: 'Long Pause' 30 | }[action.pauseType], 31 | startedAt: action.now 32 | } 33 | case actions.FINISH_CURRENT_ITEM: 34 | case actions.CANCEL_CURRENT_ITEM: 35 | return null 36 | default: 37 | return state 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/js/popup/reducers/list.js: -------------------------------------------------------------------------------- 1 | import * as actions from '../constants/actionTypes' 2 | 3 | export default function (state = [], action) { 4 | switch (action.type) { 5 | case actions.BUILD_NEW_TASK_LIST: 6 | return state.concat([{ id: action.id, editing: true }]) 7 | case actions.UPDATE_TASK_LIST: 8 | return state.map(list => { 9 | return (list.id === action.id) ? { ...list, ...action.data } : list 10 | }) 11 | case actions.SUBMIT_EDIT_TASK_LIST: 12 | return state.map(list => { 13 | if (list.id === action.id) { 14 | return { ...list, editing: false, name: list.intended_name } 15 | } else { 16 | return list 17 | } 18 | }) 19 | case actions.CANCEL_EDIT_TASK_LIST: 20 | return state.map(list => { 21 | if (list.id === action.id) { 22 | return { ...list, editing: false } 23 | } else { 24 | return list 25 | } 26 | }) 27 | case actions.START_EDIT_TASK_LIST: 28 | return state.map(list => { 29 | if (list.id === action.id) { 30 | return { ...list, editing: true, intended_name: list.name } 31 | } else { 32 | return list 33 | } 34 | }) 35 | case actions.DESTROY_TASK_LIST: 36 | return state.filter(list => list.id !== action.id) 37 | default: 38 | return state 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chrome-pomotasking", 3 | "version": "0.0.4", 4 | "description": "A Chrome Pomodoro timer with ToDo list", 5 | "scripts": { 6 | "build": "node utils/build.js", 7 | "start": "node utils/webserver.js" 8 | }, 9 | "dependencies": { 10 | "babel-core": "^6.17.0", 11 | "babel-eslint": "^7.1.1", 12 | "babel-loader": "^6.2.4", 13 | "babel-preset-es2015": "^6.6.0", 14 | "babel-preset-react": "^6.16.0", 15 | "babel-preset-react-hmre": "^1.1.1", 16 | "babel-preset-stage-0": "^6.16.0", 17 | "css-loader": "^0.25.0", 18 | "eslint": "^3.13.0", 19 | "eslint-config-standard": "^6.2.1", 20 | "eslint-config-standard-jsx": "^3.2.0", 21 | "eslint-loader": "^1.6.1", 22 | "eslint-plugin-promise": "^3.4.0", 23 | "eslint-plugin-react": "^6.9.0", 24 | "eslint-plugin-standard": "^2.0.1", 25 | "file-loader": "^0.10.0", 26 | "fontgen-loader": "^0.2.1", 27 | "fs-extra": "^0.30.0", 28 | "html-webpack-plugin": "2.24.1", 29 | "jest": "^19.0.2", 30 | "react": "^15.3.2", 31 | "react-dom": "^15.3.2", 32 | "react-redux": "^5.0.1", 33 | "react-sortable-hoc": "^0.4.12", 34 | "react-textarea-autosize": "^4.0.5", 35 | "redux": "^3.6.0", 36 | "redux-thunk": "^2.2.0", 37 | "style-loader": "^0.13.0", 38 | "uuid": "^3.0.1", 39 | "webpack": "^1.12.15", 40 | "webpack-dev-server": "1.14.1", 41 | "write-file-webpack-plugin": "3.4.2" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/js/popup/components/NewTaskForm.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import Textarea from 'react-textarea-autosize' 3 | 4 | let ENTER_KEY_CODE = 13 5 | 6 | export default class NewTaskForm extends Component { 7 | constructor () { 8 | super(...arguments) 9 | 10 | this.state = { description: '' } 11 | 12 | this.onChange = this.onChange.bind(this) 13 | this.onSubmit = this.onSubmit.bind(this) 14 | this.onKeyDown = this.onKeyDown.bind(this) 15 | } 16 | 17 | onChange (evt) { 18 | this.setState({ description: evt.target.value }) 19 | } 20 | 21 | onSubmit (evt) { 22 | evt.preventDefault() 23 | this.submit() 24 | } 25 | 26 | submit () { 27 | let description = this.state.description.trim() 28 | 29 | if (!description.length) return 30 | 31 | this.setState({ description: '' }) 32 | 33 | this.props.onSubmit(this.state.description) 34 | } 35 | 36 | onKeyDown (evt) { 37 | if (evt.keyCode === ENTER_KEY_CODE && !evt.shiftKey) { 38 | evt.preventDefault() 39 | this.submit() 40 | } 41 | } 42 | 43 | render () { 44 | return ( 45 |
48 |