├── .gitattributes
├── .gitignore
├── app
├── audio
│ └── ding.mp3
├── fonts
│ ├── fontello.eot
│ ├── fontello.ttf
│ ├── fontello.woff
│ └── fontello.svg
├── img
│ ├── background.jpg
│ ├── notification-96x96-work.png
│ ├── notification-96x96-break.png
│ └── notification-96x96-longbreak.png
├── icons
│ ├── favicon-32x32.png
│ ├── favicon-96x96.png
│ ├── favicon-break.ico
│ ├── favicon-work.ico
│ ├── mstile-144x144.png
│ ├── mstile-150x150.png
│ ├── mstile-310x150.png
│ ├── mstile-310x310.png
│ ├── mstile-70x70.png
│ ├── apple-touch-icon.png
│ ├── favicon-160x160.png
│ ├── favicon-192x192.png
│ ├── favicon-16x16-break.png
│ ├── favicon-16x16-work.png
│ ├── favicon-longbreak.ico
│ ├── apple-touch-icon-114x114.png
│ ├── apple-touch-icon-120x120.png
│ ├── apple-touch-icon-144x144.png
│ ├── apple-touch-icon-152x152.png
│ ├── apple-touch-icon-180x180.png
│ ├── apple-touch-icon-57x57.png
│ ├── apple-touch-icon-60x60.png
│ ├── apple-touch-icon-72x72.png
│ ├── apple-touch-icon-76x76.png
│ ├── favicon-16x16-longbreak.png
│ ├── apple-touch-icon-precomposed.png
│ └── browserconfig.xml
├── js
│ ├── namespace.js
│ ├── app.js
│ ├── sidebar.js
│ ├── views
│ │ ├── timer.js
│ │ ├── sidebar.js
│ │ ├── controls.js
│ │ ├── progress.js
│ │ └── settings.js
│ ├── services
│ │ ├── storage.js
│ │ ├── title.js
│ │ ├── audio.js
│ │ ├── taskbarFlash.js
│ │ ├── browserDetection.js
│ │ ├── notification.js
│ │ └── favicon.js
│ ├── config.js
│ ├── hotkeys.js
│ ├── settings.js
│ └── timer.js
├── css
│ ├── reset.css
│ ├── shared.css
│ ├── media.css
│ ├── animations.css
│ ├── main.css
│ ├── fontello.css
│ └── sidebar.css
└── index.html
├── resources
├── tomato
│ ├── tomato.png
│ ├── tomato-work.png
│ └── tomato.svg
└── background
│ ├── imgbg7.jpg
│ ├── slide_04.jpg
│ ├── ambientpink.jpg
│ ├── ambientturquoise.jpg
│ └── gaussian_blur_desktop_1440x900_hd-wallpaper-910291.png
├── .editorconfig
├── LICENSE
├── package.json
├── README.md
├── gulpfile.js
└── .eslintrc
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
2 | node_modules/
3 | app/components
4 |
--------------------------------------------------------------------------------
/app/audio/ding.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/app/audio/ding.mp3
--------------------------------------------------------------------------------
/app/fonts/fontello.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/app/fonts/fontello.eot
--------------------------------------------------------------------------------
/app/fonts/fontello.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/app/fonts/fontello.ttf
--------------------------------------------------------------------------------
/app/fonts/fontello.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/app/fonts/fontello.woff
--------------------------------------------------------------------------------
/app/img/background.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/app/img/background.jpg
--------------------------------------------------------------------------------
/app/icons/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/app/icons/favicon-32x32.png
--------------------------------------------------------------------------------
/app/icons/favicon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/app/icons/favicon-96x96.png
--------------------------------------------------------------------------------
/app/icons/favicon-break.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/app/icons/favicon-break.ico
--------------------------------------------------------------------------------
/app/icons/favicon-work.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/app/icons/favicon-work.ico
--------------------------------------------------------------------------------
/app/icons/mstile-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/app/icons/mstile-144x144.png
--------------------------------------------------------------------------------
/app/icons/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/app/icons/mstile-150x150.png
--------------------------------------------------------------------------------
/app/icons/mstile-310x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/app/icons/mstile-310x150.png
--------------------------------------------------------------------------------
/app/icons/mstile-310x310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/app/icons/mstile-310x310.png
--------------------------------------------------------------------------------
/app/icons/mstile-70x70.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/app/icons/mstile-70x70.png
--------------------------------------------------------------------------------
/resources/tomato/tomato.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/resources/tomato/tomato.png
--------------------------------------------------------------------------------
/app/icons/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/app/icons/apple-touch-icon.png
--------------------------------------------------------------------------------
/app/icons/favicon-160x160.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/app/icons/favicon-160x160.png
--------------------------------------------------------------------------------
/app/icons/favicon-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/app/icons/favicon-192x192.png
--------------------------------------------------------------------------------
/app/icons/favicon-16x16-break.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/app/icons/favicon-16x16-break.png
--------------------------------------------------------------------------------
/app/icons/favicon-16x16-work.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/app/icons/favicon-16x16-work.png
--------------------------------------------------------------------------------
/app/icons/favicon-longbreak.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/app/icons/favicon-longbreak.ico
--------------------------------------------------------------------------------
/resources/background/imgbg7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/resources/background/imgbg7.jpg
--------------------------------------------------------------------------------
/resources/background/slide_04.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/resources/background/slide_04.jpg
--------------------------------------------------------------------------------
/resources/tomato/tomato-work.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/resources/tomato/tomato-work.png
--------------------------------------------------------------------------------
/app/img/notification-96x96-work.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/app/img/notification-96x96-work.png
--------------------------------------------------------------------------------
/app/icons/apple-touch-icon-114x114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/app/icons/apple-touch-icon-114x114.png
--------------------------------------------------------------------------------
/app/icons/apple-touch-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/app/icons/apple-touch-icon-120x120.png
--------------------------------------------------------------------------------
/app/icons/apple-touch-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/app/icons/apple-touch-icon-144x144.png
--------------------------------------------------------------------------------
/app/icons/apple-touch-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/app/icons/apple-touch-icon-152x152.png
--------------------------------------------------------------------------------
/app/icons/apple-touch-icon-180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/app/icons/apple-touch-icon-180x180.png
--------------------------------------------------------------------------------
/app/icons/apple-touch-icon-57x57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/app/icons/apple-touch-icon-57x57.png
--------------------------------------------------------------------------------
/app/icons/apple-touch-icon-60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/app/icons/apple-touch-icon-60x60.png
--------------------------------------------------------------------------------
/app/icons/apple-touch-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/app/icons/apple-touch-icon-72x72.png
--------------------------------------------------------------------------------
/app/icons/apple-touch-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/app/icons/apple-touch-icon-76x76.png
--------------------------------------------------------------------------------
/app/icons/favicon-16x16-longbreak.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/app/icons/favicon-16x16-longbreak.png
--------------------------------------------------------------------------------
/app/img/notification-96x96-break.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/app/img/notification-96x96-break.png
--------------------------------------------------------------------------------
/resources/background/ambientpink.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/resources/background/ambientpink.jpg
--------------------------------------------------------------------------------
/app/img/notification-96x96-longbreak.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/app/img/notification-96x96-longbreak.png
--------------------------------------------------------------------------------
/app/icons/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/app/icons/apple-touch-icon-precomposed.png
--------------------------------------------------------------------------------
/resources/background/ambientturquoise.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/resources/background/ambientturquoise.jpg
--------------------------------------------------------------------------------
/app/js/namespace.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | 'use strict';
3 |
4 | // TT stands for TomatoTim
5 | window.TT = {
6 | Services: {},
7 | Views: {}
8 | };
9 | })();
10 |
--------------------------------------------------------------------------------
/resources/background/gaussian_blur_desktop_1440x900_hd-wallpaper-910291.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Hurtak/Tomatotim/HEAD/resources/background/gaussian_blur_desktop_1440x900_hd-wallpaper-910291.png
--------------------------------------------------------------------------------
/app/js/app.js:
--------------------------------------------------------------------------------
1 | (function () {
2 | 'use strict';
3 |
4 | TT.Services.Favicon.init();
5 | TT.Services.Audio.init();
6 |
7 | TT.Settings.init();
8 | TT.Sidebar.init();
9 | TT.Timer.init();
10 | TT.Hotkeys.init();
11 | })();
12 |
--------------------------------------------------------------------------------
/app/css/reset.css:
--------------------------------------------------------------------------------
1 | html {
2 | box-sizing: border-box;
3 | }
4 |
5 | *,
6 | *:before,
7 | *:after {
8 | box-sizing: inherit;
9 | }
10 |
11 | html,
12 | body {
13 | width: 100%;
14 | height: 100%;
15 | margin: 0;
16 | }
17 |
18 | button,
19 | input {
20 | outline: 0;
21 | }
22 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = tab
5 | end_of_line = lf
6 | charset = utf-8
7 | trim_trailing_whitespace = true
8 | insert_final_newline = true
9 |
10 | [{package.json,*.yml}]
11 | indent_style = space
12 | indent_size = 2
13 |
14 | [*.md]
15 | trim_trailing_whitespace = false
16 |
--------------------------------------------------------------------------------
/app/js/sidebar.js:
--------------------------------------------------------------------------------
1 | TT.Sidebar = (function () {
2 | 'use strict';
3 |
4 | function init() {
5 | TT.Views.Sidebar.getSidebarButton().addEventListener('click', TT.Views.Sidebar.toogleSidebar);
6 | TT.Views.Sidebar.getSidebarOverlay().addEventListener('click', TT.Views.Sidebar.closeSidebar);
7 | }
8 |
9 | return {
10 | init: init
11 | };
12 | })();
13 |
--------------------------------------------------------------------------------
/app/js/views/timer.js:
--------------------------------------------------------------------------------
1 | TT.Views.Timer = (function () {
2 | 'use strict';
3 |
4 | var timerDiv = document.getElementById('clock');
5 |
6 | function getTime() {
7 | return timerDiv.innerHTML.trim();
8 | }
9 |
10 | function setTime(time) {
11 | timerDiv.innerHTML = time;
12 | }
13 |
14 | return {
15 | getTime: getTime,
16 | setTime: setTime
17 | };
18 | })();
19 |
--------------------------------------------------------------------------------
/app/icons/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
32 |
33 | * Dynamic favicon and title
34 |
35 |
36 |
37 | * Audio and web browser notifications
38 |
39 |
40 |
41 | * Customization options
42 |
43 |
44 |
45 | * Keyboard hotkeys
46 |
47 |
48 |
49 | ### Build
50 |
51 | ##### Prerequisites
52 |
53 | [Node.js](http://nodejs.org) is required.
54 | ```
55 | npm install -g gulp
56 | ```
57 |
58 | ##### Create App
59 |
60 | ```
61 | git clone https://github.com/Hurtak/Tomatotim.git
62 | cd Tomatotim
63 | npm install
64 | ```
65 |
66 | ##### Usage
67 |
68 | build app into dist folder and run it from there
69 |
70 | ```
71 | gulp
72 | ```
73 |
74 | run app from app folder
75 |
76 | ```
77 | gulp dev
78 | ```
79 |
--------------------------------------------------------------------------------
/app/css/fontello.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'fontello';
3 | src: url('../fonts/fontello.eot?96155523');
4 | src: url('../fonts/fontello.eot?96155523#iefix') format('embedded-opentype'),
5 | url('../fonts/fontello.woff?96155523') format('woff'),
6 | url('../fonts/fontello.ttf?96155523') format('truetype'),
7 | url('../fonts/fontello.svg?96155523#fontello') format('svg');
8 | font-weight: normal;
9 | font-style: normal;
10 | }
11 | /* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
12 | /* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
13 | /*
14 | @media screen and (-webkit-min-device-pixel-ratio:0) {
15 | @font-face {
16 | font-family: 'fontello';
17 | src: url('../fonts/fontello.svg?96155523#fontello') format('svg');
18 | }
19 | }
20 | */
21 |
22 | [class^="icon-"]:before, [class*=" icon-"]:before {
23 | font-family: "fontello";
24 | font-style: normal;
25 | font-weight: normal;
26 | speak: none;
27 |
28 | display: inline-block;
29 | text-decoration: inherit;
30 | width: 1em;
31 | margin-right: .2em;
32 | text-align: center;
33 | /* opacity: .8; */
34 |
35 | /* For safety - reset parent styles, that can break glyph codes*/
36 | font-variant: normal;
37 | text-transform: none;
38 |
39 | /* fix buttons height, for twitter bootstrap */
40 | line-height: 1em;
41 |
42 | /* Animation center compensation - margins should be symmetric */
43 | /* remove if not needed */
44 | margin-left: .2em;
45 |
46 | /* you can be more comfortable with increased icons size */
47 | /* font-size: 120%; */
48 |
49 | /* Uncomment for 3D effect */
50 | /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
51 | }
52 |
53 | .icon-play-1:before { content: '\e800'; } /* '' */
54 | .icon-fast-forward:before { content: '\e801'; } /* '' */
55 | .icon-cw-1:before { content: '\e802'; } /* '' */
56 | .icon-cog-circled:before { content: '\e803'; } /* '' */
57 | .icon-tomato:before { content: '\e804'; } /* '' */
58 | .icon-cancel-circle-2:before { content: '\e805'; } /* '' */
59 | .icon-info-circled-1:before { content: '\e806'; } /* '' */
60 | .icon-wrench-4:before { content: '\e807'; } /* '' */
61 | .icon-keyboard:before { content: '\e808'; } /* '' */
62 | .icon-github-circled:before { content: '\e809'; } /* '' */
63 | .icon-bitcoin:before { content: '\e80a'; } /* '' */
64 | .icon-wallet:before { content: '\e80b'; } /* '' */
65 | .icon-qrcode-1:before { content: '\e80c'; } /* '' */
66 | .icon-search-5:before { content: '\e80d'; } /* '' */
67 | .icon-file-code:before { content: '\e80e'; } /* '' */
68 | .icon-link-ext:before { content: '\e80f'; } /* '' */
69 | .icon-twitter-1:before { content: '\e810'; } /* '' */
70 | .icon-flashlight:before { content: '\e811'; } /* '' */
71 | .icon-pause-1:before { content: '\e812'; } /* '' */
72 | .icon-bell-1:before { content: '\e815'; } /* '' */
73 | .icon-volume-down:before { content: '\e816'; } /* '' */
74 | .icon-plus-3:before { content: '\e817'; } /* '' */
75 | .icon-minus-3:before { content: '\e818'; } /* '' */
76 | .icon-mail-4:before { content: '\e81b'; } /* '' */
77 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var gulp = require('gulp');
4 |
5 | var $ = require('gulp-load-plugins')();
6 | var browserSync = require('browser-sync');
7 | var del = require('del');
8 |
9 | // Paths
10 |
11 | var appPath = 'app';
12 | var distPath = 'dist';
13 |
14 | var paths = {
15 | app: {
16 | html: appPath + '/**/*.{html,htm}',
17 | js: appPath + '/js/**/*.js',
18 | css: appPath + '/css/**/*.css',
19 | img: appPath + '/img/**/*',
20 | fonts: appPath + '/fonts/*.{eot,svg,ttf,woff}',
21 | icons: appPath + '/icons/*',
22 | audio: appPath + '/audio/*'
23 | },
24 | dist: {
25 | fonts: distPath + '/fonts',
26 | icons: distPath + '/icons',
27 | audio: distPath + '/audio',
28 | img: distPath + '/img'
29 | }
30 | };
31 |
32 | // Options
33 |
34 | var options = {
35 | autoprefixer: {
36 | browsers: [
37 | '> 2%',
38 | 'last 2 versions',
39 | 'Firefox ESR',
40 | 'ie >= 9'
41 | ],
42 | cascade: false
43 | },
44 | htmlmin: {
45 | removeComments: true,
46 | collapseWhitespace: true
47 | },
48 | imagemin: {
49 | progressive: true,
50 | svgoPlugins: [
51 | {removeViewBox: false}
52 | ],
53 | use: []
54 | }
55 | };
56 |
57 | // linters
58 |
59 | gulp.task('lint', function () {
60 | return gulp.src([paths.app.js, 'gulpfile.js'])
61 | .pipe($.xo());
62 | });
63 |
64 | // clean
65 |
66 | gulp.task('clean', function () {
67 | del([
68 | distPath + '/*'
69 | ]);
70 | });
71 |
72 | // compile
73 |
74 | gulp.task('compile', function () {
75 | var assets = $.useref.assets();
76 |
77 | return gulp.src(paths.app.html)
78 | .pipe(assets)
79 | .pipe($.if('*.js', $.uglify()))
80 | .pipe($.if('*.css', $.autoprefixer(options.autoprefixer)))
81 | .pipe($.if('*.css', $.csso()))
82 | .pipe($.rev())
83 | .pipe(assets.restore())
84 | .pipe($.useref())
85 | .pipe($.revReplace())
86 | .pipe($.if('*.html', $.htmlmin(options.htmlmin)))
87 | .pipe(gulp.dest(distPath));
88 | });
89 |
90 | gulp.task('img', function () {
91 | return gulp.src(paths.app.img)
92 | .pipe($.imagemin(options.imagemin))
93 | .pipe(gulp.dest(paths.dist.img));
94 | });
95 |
96 | gulp.task('fonts', function () {
97 | return gulp.src(paths.app.fonts)
98 | .pipe($.flatten())
99 | .pipe(gulp.dest(paths.dist.fonts));
100 | });
101 |
102 | gulp.task('icons', function () {
103 | return gulp.src(paths.app.icons)
104 | .pipe(gulp.dest(paths.dist.icons));
105 | });
106 |
107 | gulp.task('audio', function () {
108 | return gulp.src(paths.app.audio)
109 | .pipe(gulp.dest(paths.dist.audio));
110 | });
111 |
112 | // Browser sync
113 |
114 | gulp.task('browser-sync', function () {
115 | return browserSync({
116 | server: {
117 | baseDir: distPath,
118 | index: 'index.html',
119 | routes: {
120 | '/node_modules': 'node_modules'
121 | }
122 | }
123 | });
124 | });
125 |
126 | gulp.task('browser-sync-dev', function () {
127 | return browserSync({
128 | server: {
129 | baseDir: appPath,
130 | index: 'index.html',
131 | routes: {
132 | '/node_modules': 'node_modules'
133 | }
134 | }
135 | });
136 | });
137 |
138 | // Main gulp tasks
139 |
140 | // builds all files and runs from dist directory
141 | gulp.task('default', ['lint', 'compile', 'img', 'fonts', 'icons', 'audio', 'browser-sync']);
142 |
143 | // skips building phase and runs from dist directory
144 | gulp.task('run', ['browser-sync']);
145 |
146 | // runs from app directory
147 | gulp.task('dev', ['browser-sync-dev'], function () {
148 | // watch for JS changes
149 | gulp.watch(paths.app.js, ['lint', browserSync.reload]);
150 |
151 | // watch for CSS changes
152 | gulp.watch(paths.app.css, browserSync.reload);
153 |
154 | // watch for HTML changes
155 | gulp.watch(paths.app.html, browserSync.reload);
156 | });
157 |
--------------------------------------------------------------------------------
/app/css/sidebar.css:
--------------------------------------------------------------------------------
1 | .sidebar {
2 | position: absolute;
3 | top: 0;
4 | right: -450px;
5 | width: 450px;
6 | height: 100%;
7 | padding: 20px;
8 | overflow-y: auto;
9 | color: #fff;
10 | background-color: #213a57;
11 | }
12 |
13 | .sidebar h2 {
14 | margin-top: 40px;
15 | font-family: Verdana;
16 | font-weight: 400;
17 | }
18 |
19 | .sidebar h2:first-child {
20 | margin-top: 10px;
21 | }
22 |
23 | .sidebar p,
24 | .sidebar li {
25 | line-height: 140%;
26 | }
27 |
28 | .sidebar a {
29 | color: #fff;
30 | }
31 |
32 | .sidebar .button {
33 | display: inline-block;
34 | line-height: 30px;
35 | padding: 0 10px 0 7px;
36 | color: #fff;
37 | background-color: transparent;
38 | border: 1px solid #fff;
39 | border-radius: 5px;
40 | text-decoration: none;
41 | }
42 |
43 | .sidebar .button:active {
44 | transform: translateY(2px);
45 | }
46 |
47 | .sidebar .donation .button {
48 | margin-right: 14px;
49 | }
50 |
51 | .sidebar .donation .button:last-child {
52 | margin-right: 0;
53 | }
54 |
55 | .sidebar .highlight {
56 | padding: 3px 8px 4px;
57 | color: #fff;
58 | background-color: #41576f;
59 | border-radius: 5px;
60 | font-family: 'Consolas', 'Courier New';
61 | }
62 |
63 | .sidebar .hotkeys .highlight {
64 | margin-right: 6px;
65 | }
66 |
67 | .sidebar .bitcoin-address {
68 | word-wrap: break-word;
69 | }
70 |
71 | .sidebar .links i {
72 | font-size: 120%;
73 | }
74 |
75 | .sidebar .links .email {
76 | position: relative;
77 | top: 1px;
78 | }
79 |
80 | .sidebar .links .code {
81 | font-size: 100%;
82 | }
83 |
84 | /* Settings */
85 |
86 | .settings td {
87 | padding: 5px 0;
88 | padding-right: 10px;
89 | }
90 |
91 | .settings td:last-child {
92 | padding-right: 0;
93 | }
94 |
95 | .checkbox {
96 | display: none;
97 | }
98 |
99 | .checkbox + .checkbox-button {
100 | position: relative;
101 | display: block;
102 | width: 60px;
103 | height: 25px;
104 | overflow: hidden;
105 | border: 1px solid #fff;
106 | border-radius: 5px;
107 | cursor: pointer;
108 | }
109 |
110 | .checkbox + .checkbox-button:after,
111 | .checkbox + .checkbox-button:before {
112 | position: absolute;
113 | width: 100%;
114 | line-height: 25px;
115 | color: #fff;
116 | text-shadow: 0 1px 0 rgba(0, 0, 0, .4);
117 | text-align: center;
118 | font-family: sans-serif;
119 | font-weight: bold;
120 | }
121 |
122 | .checkbox:checked + .checkbox-button {
123 | background: rgba(101, 230, 69, .6);
124 | }
125 |
126 | .checkbox + .checkbox-button:after {
127 | left: 100%;
128 | content: attr(data-caption-on);
129 | }
130 |
131 | .checkbox + .checkbox-button:before {
132 | left: 0;
133 | content: attr(data-caption-off);
134 | }
135 |
136 | .checkbox + .checkbox-button:active:before {
137 | transform: translateX(-10%);
138 | }
139 |
140 | .checkbox:checked + .checkbox-button:active:after {
141 | transform: translateX(-90%);
142 | }
143 |
144 | .checkbox:checked + .checkbox-button:before {
145 | transform: translateX(-100%);
146 | }
147 |
148 | .checkbox:checked + .checkbox-button:after {
149 | transform: translateX(-100%);
150 | }
151 |
152 | .settings input[type=number] {
153 | width: 60px;
154 | height: 25px;
155 | padding-right: 1em;
156 | background-color: transparent;
157 | border: 1px solid #fff;
158 | border-radius: 5px;
159 | text-align: right;
160 | font-family: 'Consolas', 'Courier New';
161 | }
162 |
163 | .settings input[type=number] {
164 | /* disable browser's +- buttons inside input */
165 | -moz-appearance: textfield;
166 | }
167 |
168 | .settings input::-webkit-outer-spin-button,
169 | .settings input::-webkit-inner-spin-button {
170 | /* disable browser's +- buttons inside input */
171 | -webkit-appearance: none;
172 | }
173 |
174 | .settings .test,
175 | .settings .plus,
176 | .settings .minus {
177 | height: 25px;
178 | line-height: 23px;
179 | padding: 0;
180 | background-color: transparent;
181 | border: 1px solid #fff;
182 | border-radius: 5px;
183 | }
184 |
185 | .settings .test:active,
186 | .settings .plus:active,
187 | .settings .minus:active {
188 | transform: translateY(1px);
189 | }
190 |
191 | .settings .plus,
192 | .settings .minus {
193 | float: left;
194 | width: 30px;
195 | text-align: center;
196 | }
197 |
198 | .settings .plus i,
199 | .settings .minus i {
200 | position: relative;
201 | top: -2px;
202 | font-size: 70%;
203 | }
204 |
205 | .settings .plus {
206 | border-left: 0;
207 | border-radius: 0 5px 5px 0;
208 | }
209 |
210 | .settings .minus {
211 | border-radius: 5px 0 0 5px;
212 | }
213 |
214 | .settings .test {
215 | width: 60px;
216 | }
217 |
218 | .settings .test i {
219 | position: relative;
220 | left: -1px;
221 | }
222 |
223 | .settings .test span {
224 | position: relative;
225 | left: -4px;
226 | }
227 |
228 | /* Share buttons */
229 |
230 | .share-buttons > div,
231 | .share-buttons > iframe {
232 | float: left;
233 | margin-right: 10px;
234 | }
235 |
236 | .share-buttons > div:last-child {
237 | margin-right: 0;
238 | }
239 |
240 | /* Sidebar button */
241 |
242 | .sidebar-button {
243 | position: absolute;
244 | top: 10px;
245 | right: 10px;
246 | width: 55px;
247 | height: 55px;
248 | z-index: 10;
249 | color: #fff;
250 | background-color: transparent;
251 | border: 0;
252 | outline: 0;
253 | cursor: pointer;
254 | font-size: 40px;
255 | }
256 |
257 | .sidebar-button i {
258 | position: absolute;
259 | top: 5px;
260 | left: 0;
261 | z-index: 15;
262 | }
263 |
264 | .sidebar-button .cancel,
265 | .sidebar-button .background {
266 | opacity: 0;
267 | }
268 |
269 | .sidebar-button .background {
270 | position: relative;
271 | top: 5px;
272 | left: 5px;
273 | width: 46px;
274 | height: 46px;
275 |
276 | background-color: #213a57;
277 | border-radius: 50%;
278 | }
279 |
280 | /* Sidebar overlay */
281 |
282 | .sidebar-overlay {
283 | position: fixed;
284 | top: 0;
285 | left: 100%;
286 | width: 100%;
287 | height: 100%;
288 | opacity: 0;
289 | background-color: rgba(0, 0, 0, .5);
290 | }
291 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | // http://eslint.org/docs/rules
2 | {
3 | "rules": {
4 | // Possible Errors
5 | "no-extra-parens": 1, // disallow unnecessary parentheses (off by default)
6 | "valid-jsdoc": [1, {"requireReturn": false}], // Ensure JSDoc comments are valid (off by default)
7 |
8 | // Best Practices
9 | "block-scoped-var": 0, // treat var statements as if they were block scoped (off by default)
10 | "guard-for-in": 1, // make sure for-in loops have an if statement (off by default)
11 | "no-else-return": 1, // disallow else after a return in an if (off by default)
12 | "no-eq-null": 2, // disallow comparisons to null without a type-checking operator (off by default)
13 | "no-floating-decimal": 1, // disallow the use of leading or trailing decimal points in numeric literals (off by default)
14 | "no-self-compare": 2, // disallow comparisons where both sides are exactly the same (off by default)
15 | "no-throw-literal": 1, // restrict what can be thrown as an exception (off by default)
16 | "no-void": 1, // disallow use of void operator (off by default)
17 | "wrap-iife": [2, "inside"], // require immediate function invocation to be wrapped in parentheses (off by default)
18 |
19 | // Strict Mode
20 | "strict": [2, "function"], // controls location of Use Strict Directives
21 |
22 | // Variables
23 | "no-undef": 0, // disallow use of undeclared variables unless mentioned in a /*global */ block
24 | "no-undefined": 1, // disallow use of undefined variable (off by default)
25 | "no-unused-vars": 0, // disallow usage of expressions in statement position
26 | "no-use-before-define": 0, // disallow use of variables before they are defined
27 |
28 | // Node.js
29 |
30 | // Stylistic Issues
31 | "indent": [1, 2], // this option sets a specific tab width for your code (off by default)
32 | "brace-style": [1, "1tbs", {"allowSingleLine": false}], // enforce one true brace style (off by default)
33 | "comma-style": [1, "last"], // enforce one true comma style (off by default)
34 | "consistent-this": [1, "_this"], // enforces consistent naming when capturing the current execution context (off by default)
35 | "comma-spacing": [2, {"before": false, "after": true}], // enforce spacing before and after comma
36 | "func-style": [1, "epression"], // enforces use of function declarations or expressions (off by default)
37 | "max-nested-callbacks": [1, 2], // specify the maximum depth callbacks can be nested (off by default)
38 | "no-lonely-if": [1, "tab"], // disallow if as the only statement in an else block (off by default)
39 | "no-multiple-empty-lines": [1, {"max": 2}], // disallow multiple empty lines (off by default)
40 | "no-nested-ternary": 1, // disallow nested ternary expressions (off by default)
41 | "one-var": [1, "never"], // allow just one var statement per function (off by default)
42 | "quote-props": [1, "as-needed"], // require quotes around object literal property names (off by default)
43 | "quotes": [1, "single"], // specify whether double or single quotes should be used
44 | "space-after-keywords": [1, "always"], // require a space after certain keywords (off by default)
45 | "space-before-blocks": [1, "always"], // require or disallow space before blocks (off by default)
46 | "space-before-function-paren": [1, "never"], // require or disallow space before function opening parenthesis (off by default)
47 | "space-in-brackets": [1, "never"], // require or disallow spaces inside brackets (off by default)
48 | "space-in-parens": [1, "never"], // require or disallow spaces inside parentheses (off by default)
49 | "spaced-line-comment": [1, "always"], // require or disallow a space immediately following the // in a line comment (off by default)
50 |
51 | // ECMAScript 6
52 | "generator-star-spacing": [2, "after"], // enforce the spacing around the * in generator functions (off by default)
53 | "no-var": 0, // require let or const instead of var (off by default)
54 |
55 | // Legacy
56 | "no-bitwise": 1 // disallow use of bitwise operators (off by default)
57 | },
58 | "ecmaFeatures": {
59 | // Enable support for ECMAScript 6 features
60 | "arrowFunctions": false, // enable arrow functions
61 | "binaryLiterals": false, // enable binary literals
62 | "blockBindings": false, // enable let and const (aka block bindings)
63 | "classes": false, // enable classes
64 | "defaultParams": false, // enable default function parameters
65 | "destructuring": false, // enable destructuring
66 | "forOf": false, // enable for-of loops
67 | "generators": false, // enable generators
68 | "modules": false, // enable modules and global strict mode
69 | "objectLiteralComputedProperties": false, // enable computed object literal property names
70 | "objectLiteralDuplicateProperties": false, // enable duplicate object literal properties in strict mode
71 | "objectLiteralShorthandMethods": false, // enable object literal shorthand methods
72 | "objectLiteralShorthandProperties": false, // enable object literal shorthand properties
73 | "octalLiterals": false, // enable octal literals
74 | "regexUFlag": false, // enable the regular expression u flag
75 | "regexYFlag": false, // enable the regular expression y flag
76 | "spread": false, // enable the spread operator
77 | "superInFunctions": false, // enable super references inside of functions
78 | "templateStrings": false, // enable template strings
79 | "unicodeCodePointEscapes": false, // enable code point escapes
80 | "globalReturn": false, // allow return statements in the global scope
81 | "jsx": false, // enable JSX
82 | },
83 | "env": {
84 | "browser": false, // browser global variables
85 | "es6": false, // enable all ECMAScript 6 features except for modules
86 | "node": false, // Node.js global variables and Node.js-specific rules
87 | "amd": false, // defines require() and define() as global variables as per the amd spec
88 | "mocha": false, // adds all of the Mocha testing global variables
89 | "jasmine": false, // adds all of the Jasmine testing global variables for version 1.3 and 2.0
90 | "phantomjs": false, // phantomjs global variables
91 | "jquery": false // jquery global variables
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/app/js/settings.js:
--------------------------------------------------------------------------------
1 | TT.Settings = (function () {
2 | 'use strict';
3 |
4 | function init() {
5 | // update config defaults with saved settings (if available)
6 | TT.Config.set('audio', Boolean(TT.Services.Storage.get('audio')));
7 | TT.Config.set('notifications', Boolean(TT.Services.Storage.get('notifications')));
8 | TT.Config.set('taskbarFlash', Boolean(TT.Services.Storage.get('taskbarFlash')));
9 | TT.Config.set('timerAutoPause', Boolean(TT.Services.Storage.get('timerAutoPause')));
10 |
11 | TT.Config.set('workInterval', TT.Services.Storage.get('workInterval') || TT.Config.get('workInterval'));
12 | TT.Config.set('breakInterval', TT.Services.Storage.get('breakInterval') || TT.Config.get('breakInterval'));
13 | TT.Config.set('longbreakInterval', TT.Services.Storage.get('longbreakInterval') || TT.Config.get('longbreakInterval'));
14 |
15 | TT.Config.set('repeat', TT.Services.Storage.get('repeat') || TT.Config.get('repeat'));
16 |
17 | // update settings view
18 | TT.Views.Settings.audio.checked = TT.Config.get('audio');
19 | TT.Views.Settings.notifications.checked = TT.Config.get('notifications');
20 | TT.Views.Settings.taskbarFlash.checked = TT.Config.get('taskbarFlash');
21 | TT.Views.Settings.timerAutoPause.checked = TT.Config.get('timerAutoPause');
22 |
23 | TT.Views.Settings.workInterval.value = TT.Config.get('workInterval') / 60;
24 | TT.Views.Settings.breakInterval.value = TT.Config.get('breakInterval') / 60;
25 | TT.Views.Settings.longbreakInterval.value = TT.Config.get('longbreakInterval') / 60;
26 |
27 | TT.Views.Settings.repeat.value = TT.Config.get('repeat');
28 |
29 | // checkboxes
30 | TT.Views.Settings.audio.addEventListener('click', function () {
31 | TT.Config.set('audio', this.checked);
32 | TT.Services.Storage.set('audio', TT.Config.get('audio'));
33 | });
34 |
35 | if (TT.Services.Notification.isAvaliable()) {
36 | TT.Views.Settings.notifications.addEventListener('click', function () {
37 | TT.Config.set('notifications', this.checked);
38 |
39 | if (TT.Config.get('notifications') === true) {
40 | TT.Services.Notification.requestPermission();
41 | }
42 |
43 | TT.Services.Storage.set('notifications', TT.Config.get('notifications'));
44 | });
45 | } else {
46 | TT.Views.Settings.hide(TT.Views.Settings.notifications);
47 | }
48 |
49 | if (TT.Services.TaskbarFlash.isAvaliable()) {
50 | TT.Views.Settings.taskbarFlash.addEventListener('click', function () {
51 | TT.Config.set('taskbarFlash', this.checked);
52 | TT.Services.Storage.set('taskbarFlash', this.checked);
53 | });
54 | } else {
55 | TT.Views.Settings.hide(TT.Views.Settings.taskbarFlash);
56 | }
57 |
58 | TT.Views.Settings.timerAutoPause.addEventListener('click', function () {
59 | TT.Config.set('timerAutoPause', this.checked);
60 | TT.Services.Storage.set('timerAutoPause', this.checked);
61 | });
62 |
63 | // test buttons
64 | TT.Views.Settings.audioTest.addEventListener('click', function () {
65 | TT.Services.Audio.play();
66 | });
67 |
68 | TT.Views.Settings.notificationsTest.addEventListener('click', function () {
69 | TT.Services.Notification.newNotification('Web notification test', 'work');
70 | });
71 |
72 | TT.Views.Settings.taskbarFlashTest.addEventListener('click', function () {
73 | // flashing only works when browser doesn't have focus
74 | for (var count = 0; count < 20; count++) {
75 | setTimeout(TT.Services.TaskbarFlash.flash, 500 * count);
76 | }
77 | });
78 |
79 | // inputs type number and +- buttons
80 | var intervalNames = ['workInterval', 'breakInterval', 'longbreakInterval', 'repeat'];
81 |
82 | var numberInputs = TT.Views.Settings.getNumberInputs();
83 | var plusMinusButtons = TT.Views.Settings.getPlusMinusButtons();
84 |
85 | for (var i = 0; i < intervalNames.length; i++) {
86 | // interval settings inputs
87 | numberInputs[i].addEventListener('blur', makeClickHandlerForInput(numberInputs[i], intervalNames[i]));
88 |
89 | // plus minus buttons
90 | for (var j = 0; j < 2; j++) {
91 | plusMinusButtons[i * 2 + j].addEventListener('click', makeClickHandlerForControls(plusMinusButtons[i * 2 + j], intervalNames[i]));
92 | }
93 | }
94 |
95 | // reset settings
96 | TT.Views.Settings.resetSettings.addEventListener('click', function () {
97 | var confim = confirm('Are you sure?'); // eslint-disable-line no-alert
98 | if (confim) {
99 | TT.Services.Storage.clear();
100 | location.reload(false);
101 | }
102 | });
103 |
104 | // request permission in case we have notifications enabled in saved settings
105 | if (TT.Config.get('notifications') === true) {
106 | TT.Services.Notification.requestPermission();
107 | }
108 | }
109 |
110 | function validateInput(value, min, max, defaultValue) {
111 | // non-number values converted to NaN
112 | value = Math.floor(value);
113 |
114 | if (!value) {
115 | value = defaultValue;
116 | } else if (value < Number(min)) {
117 | value = min;
118 | } else if (value > Number(max)) {
119 | value = max;
120 | }
121 |
122 | return Number(value);
123 | }
124 |
125 | function intervalInput(that, intervalType) {
126 | // conversion between seconds and minutes
127 | var multiplier = 60;
128 | if (intervalType === 'repeat') {
129 | multiplier = 1;
130 | }
131 |
132 | that.value = validateInput(that.value, that.min, that.max, TT.Config.get(intervalType) / multiplier);
133 | TT.Config.set(intervalType, that.value * multiplier);
134 |
135 | if (intervalType === 'repeat') {
136 | TT.Views.Progress.removeImages();
137 | TT.Timer.init();
138 | } else {
139 | TT.Timer.updateIntervals();
140 | }
141 |
142 | TT.Services.Storage.set(intervalType, TT.Config.get(intervalType));
143 | }
144 |
145 | // click handlers for number inputs in settings
146 | function makeClickHandlerForInput(that, intervalName) {
147 | return function () {
148 | intervalInput(that, intervalName);
149 | };
150 | }
151 |
152 | // click handler for +- buttons next to settings inputs
153 | function makeClickHandlerForControls(that, intervalName) {
154 | return function () {
155 | // TODO: move to views
156 | var target = that.getAttribute('data-target');
157 | target = document.getElementById(target);
158 |
159 | target.value = Number(target.value) + Number(that.getAttribute('data-increment'));
160 |
161 | intervalInput(target, intervalName);
162 | };
163 | }
164 |
165 | return {
166 | init: init
167 | };
168 | })();
169 |
--------------------------------------------------------------------------------
/app/js/timer.js:
--------------------------------------------------------------------------------
1 | TT.Timer = (function () {
2 | 'use strict';
3 |
4 | var intervalIndex;
5 | var timerInterval;
6 |
7 | var intervals = [];
8 |
9 | var timer;
10 | // how often are we running precise timer to check if second of real time elapsed in ms
11 | var timerPrecision = 100;
12 |
13 | function init() {
14 | // initialize intervals array
15 | updateIntervals();
16 |
17 | // load saved progress
18 | intervalIndex = TT.Services.Storage.get('intervalIndex') || 0;
19 | timerInterval = TT.Services.Storage.get('timerInterval') || TT.Config.get('workInterval');
20 |
21 | // when user changes number of intervals in settings
22 | if (intervalIndex > intervals.length - 1) {
23 | if (intervalIndex % 2 === 0) {
24 | intervalIndex = intervals.length - 2;
25 | } else {
26 | intervalIndex = intervals.length - 1;
27 | }
28 | }
29 |
30 | // initialize progress images
31 | for (var i = 0; i < TT.Config.get('repeat'); i++) {
32 | TT.Views.Progress.createImage('unfinished');
33 | }
34 |
35 | for (var index = 0; index <= intervalIndex; index++) {
36 | updateTimerViews(index, true);
37 | }
38 |
39 | if (intervalIndex === 0 && timerInterval < TT.Config.get('workInterval')) {
40 | TT.Views.Progress.setImageType('work', 0);
41 | TT.Views.Progress.setDescription('work');
42 | }
43 |
44 | if (intervalIndex === 0 && timerInterval === TT.Config.get('workInterval')) {
45 | TT.Services.Title.resetTitle();
46 | } else {
47 | TT.Services.Title.setTitle(secondsToTime(timerInterval));
48 | }
49 |
50 | var firstVisit = !TT.Services.Storage.get('recurringVisit');
51 | if (firstVisit) {
52 | TT.Views.Sidebar.openSidebar();
53 | TT.Services.Storage.set('recurringVisit', true);
54 | }
55 |
56 | // binding
57 | TT.Views.Controls.getStartButton().addEventListener('click', startTimer);
58 | TT.Views.Controls.getSkipButton().addEventListener('click', skipInterval);
59 | TT.Views.Controls.getResetButton().addEventListener('click', resetTimer);
60 | }
61 |
62 | function updateIntervals() {
63 | intervals = [];
64 |
65 | for (var i = 0; i < TT.Config.get('repeat'); i++) {
66 | intervals.push(TT.Config.get('workInterval'));
67 | intervals.push(TT.Config.get('breakInterval'));
68 | }
69 |
70 | // replace last break with long break
71 | intervals.pop();
72 | intervals.push(TT.Config.get('longbreakInterval'));
73 | }
74 |
75 | function secondsToTime(seconds) {
76 | function addLeadingZero(number) {
77 | if (number < 10) {
78 | number = '0' + number;
79 | }
80 | return number;
81 | }
82 |
83 | var minutes = Math.floor(seconds / 60);
84 | seconds %= 60;
85 |
86 | return addLeadingZero(minutes) + ':' + addLeadingZero(seconds);
87 | }
88 |
89 | function timerTick() {
90 | timerInterval--;
91 |
92 | if (timerInterval <= 0) {
93 | nextInterval();
94 | }
95 |
96 | var time = secondsToTime(timerInterval);
97 |
98 | TT.Views.Timer.setTime(time);
99 |
100 | TT.Services.Title.setTitle(time, timer);
101 | TT.Services.Storage.set('timerInterval', timerInterval);
102 | }
103 |
104 | function skipInterval() {
105 | nextInterval(true);
106 | }
107 |
108 | function nextInterval(skipped) {
109 | skipped = skipped || false;
110 |
111 | intervalIndex++;
112 | if (intervalIndex > intervals.length - 1) {
113 | intervalIndex = 0;
114 | resetTimer();
115 | }
116 |
117 | timerInterval = intervals[intervalIndex];
118 |
119 | updateTimerViews(intervalIndex, skipped);
120 |
121 | TT.Services.Storage.set('intervalIndex', intervalIndex);
122 |
123 | if (skipped) {
124 | if (timer) {
125 | // resets timeout countdown
126 | pauseTimer();
127 | runTimer();
128 | }
129 |
130 | TT.Services.Title.setTitle(secondsToTime(timerInterval), timer);
131 | TT.Services.Storage.set('timerInterval', timerInterval);
132 | }
133 |
134 | if (TT.Config.get('timerAutoPause')) {
135 | pauseTimer();
136 | // TODO: refactor this into pauseTimer()
137 | TT.Views.Controls.resetStartButton();
138 | TT.Services.Title.setTitle(secondsToTime(timerInterval), timer);
139 | }
140 | }
141 |
142 | function updateTimerViews(index, skipped) {
143 | var imageIndex = Math.floor(index / 2);
144 |
145 | TT.Views.Timer.setTime(secondsToTime(timerInterval));
146 |
147 | // intervals[ work, break, work, break, ... , long break ]
148 | if (index === intervals.length - 1) {
149 | // last interval
150 |
151 | TT.Services.Favicon.setFavicon('longbreak');
152 | if (!skipped && TT.Config.get('notifications')) {
153 | TT.Services.Notification.newNotification(TT.Config.get('longbreakInterval') / 60 + ' minute long break', 'longbreak');
154 | }
155 |
156 | TT.Views.Progress.setDescription('long break');
157 | TT.Views.Progress.setImageType('longbreak', imageIndex);
158 | } else if (index === 0) {
159 | // first interval: 0
160 | TT.Services.Favicon.setFavicon('work');
161 | if (!skipped && TT.Config.get('notifications')) {
162 | // TODO: think of better notification text
163 | TT.Services.Notification.newNotification('Done', 'work');
164 | }
165 | } else if (index % 2 === 1) {
166 | // odd interval: 1, 3, 5..
167 | TT.Services.Favicon.setFavicon('break');
168 | if (!skipped && TT.Config.get('notifications')) {
169 | TT.Services.Notification.newNotification(TT.Config.get('breakInterval') / 60 + ' minute break', 'break');
170 | }
171 |
172 | TT.Views.Progress.setDescription('break');
173 | TT.Views.Progress.setImageType('break', imageIndex);
174 | } else if (index % 2 === 0) {
175 | // even interval: 2, 4, 6..
176 | TT.Services.Favicon.setFavicon('work');
177 | if (!skipped && TT.Config.get('notifications')) {
178 | TT.Services.Notification.newNotification(TT.Config.get('workInterval') / 60 + ' minute work', 'work');
179 | }
180 |
181 | TT.Views.Progress.setDescription('work');
182 | TT.Views.Progress.setImageType('work', imageIndex);
183 | TT.Views.Progress.setImageType('finished', imageIndex - 1);
184 | }
185 |
186 | if (!skipped) {
187 | if (TT.Config.get('audio')) {
188 | TT.Services.Audio.play();
189 | }
190 | if (TT.Config.get('taskbarFlash')) {
191 | TT.Services.TaskbarFlash.flash();
192 | }
193 | }
194 | }
195 |
196 | function startTimer() {
197 | if (!timer) {
198 | runTimer();
199 | } else {
200 | pauseTimer();
201 | }
202 |
203 | TT.Services.Title.setTitle(secondsToTime(timerInterval), timer);
204 | TT.Views.Controls.toogleStartButtonCaption();
205 |
206 | if (intervalIndex === 0) {
207 | TT.Views.Progress.setImageType('work', 0);
208 | TT.Views.Progress.setDescription('work');
209 | }
210 | }
211 |
212 | function runTimer() {
213 | var elapsedTime = 0;
214 | var before = new Date();
215 |
216 | timer = setInterval(function () {
217 | elapsedTime += new Date().getTime() - before.getTime();
218 |
219 | if (elapsedTime >= 1000) {
220 | timerTick();
221 | elapsedTime -= 1000;
222 | }
223 |
224 | before = new Date();
225 | }, timerPrecision);
226 | }
227 |
228 | function pauseTimer() {
229 | timer = clearInterval(timer);
230 | }
231 |
232 | function resetTimer() {
233 | pauseTimer();
234 |
235 | intervalIndex = 0;
236 | timerInterval = TT.Config.get('workInterval');
237 |
238 | var time = secondsToTime(timerInterval);
239 |
240 | TT.Views.Timer.setTime(time);
241 | TT.Views.Controls.resetStartButton();
242 |
243 | TT.Services.Title.resetTitle();
244 | TT.Services.Favicon.setFavicon('work');
245 | TT.Services.Storage.set('intervalIndex', intervalIndex);
246 | TT.Services.Storage.set('timerInterval', timerInterval);
247 |
248 | TT.Views.Progress.resetProgress();
249 | }
250 |
251 | return {
252 | init: init,
253 | updateIntervals: updateIntervals,
254 | startTimer: startTimer,
255 | pauseTimer: pauseTimer,
256 | skipInterval: skipInterval,
257 | resetTimer: resetTimer
258 | };
259 | })();
260 |
--------------------------------------------------------------------------------
/app/fonts/fontello.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |