diff --git a/src/main/AutoUpdater.ts b/src/main/AutoUpdater.ts index 80dfcd1..9ab9723 100644 --- a/src/main/AutoUpdater.ts +++ b/src/main/AutoUpdater.ts @@ -22,13 +22,18 @@ export class AutoUpdater { autoUpdater.on('error', err => { sendStatusToWindow('error', 'Error in auto-updater. ' + err); + sendStatusToWindow('error', err); }); autoUpdater.on('download-progress', progressObj => { let log_message = 'Download speed: ' + progressObj.bytesPerSecond; log_message = log_message + ' - Downloaded ' + progressObj.percent + '%'; log_message = log_message + ' (' + progressObj.transferred + '/' + progressObj.total + ')'; - sendStatusToWindow('download-progress', log_message); + console.log(log_message); + sendStatusToWindow('download-progress', { + percent: progressObj.percent, + bytesPerSecond: progressObj.bytesPerSecond + }); }); autoUpdater.on('update-downloaded', info => { console.log(info); diff --git a/src/main/main.ts b/src/main/main.ts index c994314..a5f41bc 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -114,11 +114,24 @@ app.on('ready', async () => { }); await createWindow(); - const autoUpdater = new AutoUpdater((type: string, info: string) => { + const autoUpdater = new AutoUpdater((type: string, info: any) => { console.log(info); if (win) { win.webContents.send(type, info); } + + if (type === 'download-progress') { + const { percent } = info; + if (win) { + win.setProgressBar(percent / 100); + } + } + + if (type === 'update-downloaded' || type === 'error') { + if (win) { + win.setProgressBar(-1); + } + } }); ipcMain.on('download-update', () => { diff --git a/src/renderer/components/Setting/Setting.tsx b/src/renderer/components/Setting/Setting.tsx index 97e104f..9ada489 100644 --- a/src/renderer/components/Setting/Setting.tsx +++ b/src/renderer/components/Setting/Setting.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { TimerActionTypes, TimerState } from '../Timer/action'; import styled from 'styled-components'; -import { Button, Icon, message, notification, Popconfirm, Slider, Switch } from 'antd'; +import { Button, Col, Icon, message, notification, Popconfirm, Row, Slider, Switch } from 'antd'; import { deleteAllUserData, exportDBData } from '../../monitor/sessionManager'; import { writeFile } from 'fs'; import { shell, remote, app } from 'electron'; @@ -38,6 +38,10 @@ const marks = { const restMarks = { 5: '5min', + 10: '10min' +}; + +const longBreakMarks = { 10: '10min', 15: '15min', 20: '20min' @@ -61,6 +65,14 @@ export const Setting: React.FunctionComponent = (props: Props) => { props.setRestDuration(v * 60); }, []); + const onChangeLongBreak = React.useCallback((v: number | [number, number]) => { + if (v instanceof Array) { + return; + } + + props.setLongBreakDuration(v * 60); + }, []); + const switchScreenshot = React.useCallback((v: boolean) => { if (v) { props.setScreenShotInterval(5000); @@ -131,24 +143,41 @@ export const Setting: React.FunctionComponent = (props: Props) => { -

Rest Duration

- - - + + +

Short Break

+ + + + + +

Long Break

+ + + + +

Start On Boot

diff --git a/src/renderer/components/Timer/SessionEndingMask.tsx b/src/renderer/components/Timer/SessionEndingMask.tsx index 55371d9..5f302a7 100644 --- a/src/renderer/components/Timer/SessionEndingMask.tsx +++ b/src/renderer/components/Timer/SessionEndingMask.tsx @@ -1,5 +1,5 @@ import { Button, List, Popover, Row } from 'antd'; -import { actions } from '../Timer/action'; +import { actions, LONG_BREAK_INTERVAL } from '../Timer/action'; import { PomodoroNumView } from './PomodoroNumView'; import React from 'react'; import styled from 'styled-components'; @@ -8,6 +8,12 @@ import { RootState } from '../../reducers'; import { KanbanBoardState } from '../Kanban/Board/action'; import { Dispatch } from 'redux'; +const ButtonContainer = styled.div` + position: absolute; + top: 16px; + right: 16px; +`; + const Mask = styled.div` left: 0; top: 0; @@ -54,6 +60,7 @@ export interface InputProps { onCancel: () => void; onStart: () => void; pomodoroNum: number; + extendCurrentSession: (timeInMinutes: number) => void; } export interface MaskProps extends InputProps { @@ -61,6 +68,7 @@ export interface MaskProps extends InputProps { isFocusing: boolean; boardId?: string; boards: KanbanBoardState; + isLongBreak: boolean; } const _TimerMask = (props: MaskProps) => { @@ -97,6 +105,21 @@ const _TimerMask = (props: MaskProps) => { props.onStart(); }; + const extend10 = React.useCallback( + (event: any) => { + event.stopPropagation(); + props.extendCurrentSession(10); + }, + [props.extendCurrentSession] + ); + const extend5 = React.useCallback( + (event: any) => { + event.stopPropagation(); + props.extendCurrentSession(5); + }, + [props.extendCurrentSession] + ); + return ( @@ -110,20 +133,37 @@ const _TimerMask = (props: MaskProps) => { ) : ( - Resting + Break )}

Session Finished

Today Pomodoros

+ {props.isFocusing ? ( + + + + + ) : ( + undefined + )}
); }; @@ -131,6 +171,7 @@ const _TimerMask = (props: MaskProps) => { export const TimerMask = connect( (state: RootState, props: InputProps) => ({ isFocusing: state.timer.isFocusing, + isLongBreak: !((state.timer.iBreak + 1) % LONG_BREAK_INTERVAL), boardId: state.timer.boardId, boards: state.kanban.boards }), diff --git a/src/renderer/components/Timer/Timer.tsx b/src/renderer/components/Timer/Timer.tsx index 9ea74eb..992b2f5 100644 --- a/src/renderer/components/Timer/Timer.tsx +++ b/src/renderer/components/Timer/Timer.tsx @@ -1,9 +1,9 @@ import React, { Component } from 'react'; -import { Button, Divider, message, Tooltip } from 'antd'; +import { Button, Divider, message, Tooltip, Popconfirm } from 'antd'; import Progress from './Progress'; import { KanbanActionTypes } from '../Kanban/action'; import { BoardActionTypes } from '../Kanban/Board/action'; -import { TimerActionTypes as ThisActionTypes } from './action'; +import { LONG_BREAK_INTERVAL, TimerActionTypes as ThisActionTypes } from './action'; import { RootState } from '../../reducers'; import { FocusSelector } from './FocusSelector'; import { Monitor } from '../../monitor'; @@ -141,6 +141,7 @@ class Timer extends Component { win?: BrowserWindow; mainDiv: React.RefObject; sound: React.RefObject; + extendedTimeInMinute: number; private stagedSession?: PomodoroRecord; constructor(props: Props) { @@ -155,6 +156,7 @@ class Timer extends Component { }; this.mainDiv = React.createRef(); this.sound = React.createRef(); + this.extendedTimeInMinute = 0; } componentDidMount(): void { @@ -190,7 +192,7 @@ class Timer extends Component { } }, { - label: 'Start Resting', + label: 'Start Break', type: 'normal', click: () => { if (this.props.timer.isFocusing) { @@ -246,13 +248,7 @@ class Timer extends Component { } const leftTime = `${to2digits(Math.floor(sec / 60))}:${to2digits(sec % 60)}`; - const percent = - 100 - - timeSpan / - 10 / - (this.props.timer.isFocusing - ? this.props.timer.focusDuration - : this.props.timer.restDuration); + const percent = 100 - timeSpan / 10 / (this.getDuration() + this.extendedTimeInMinute * 60); if (leftTime.slice(0, 2) !== this.state.leftTime.slice(0, 2)) { setTrayImageWithMadeIcon( leftTime.slice(0, 2), @@ -291,6 +287,12 @@ class Timer extends Component { private onStop() { this.props.stopTimer(); + setTrayImageWithMadeIcon( + this.state.leftTime.slice(0, 2), + this.state.percent / 100, + this.props.timer.isFocusing, + true + ).catch(console.error); if (this.monitor) { this.monitor.stop(); } @@ -306,16 +308,22 @@ class Timer extends Component { this.updateLeftTime(); }; - private defaultLeftTime = (isFocusing?: boolean) => { + private getDuration = (isFocusing?: boolean) => { if (isFocusing === undefined) { // tslint:disable-next-line:no-parameter-reassignment isFocusing = this.props.timer.isFocusing; } - const duration = isFocusing + const isLongBreak = this.props.timer.iBreak % 4 === 0; + return isFocusing ? this.props.timer.focusDuration + : isLongBreak + ? this.props.timer.longBreakDuration : this.props.timer.restDuration; - return `${to2digits(duration / 60)}:00`; + }; + + private defaultLeftTime = (isFocusing?: boolean) => { + return `${to2digits(this.getDuration(isFocusing) / 60)}:00`; }; private clearStat = () => { @@ -334,12 +342,13 @@ class Timer extends Component { } this.clearStat(); + this.extendedTimeInMinute = 0; }; - onDone = async () => { + onDone = async (shouldRemind: boolean = true, isRotten?: boolean) => { if (this.props.timer.isFocusing) { - await this.onFocusingSessionDone(); - } else { + await this.onFocusingSessionDone(shouldRemind, isRotten); + } else if (shouldRemind) { const notification = new remote.Notification({ title: 'Resting session ended', body: `Completed ${this.state.pomodoroNum} sessions today. \n\n`, @@ -348,43 +357,65 @@ class Timer extends Component { notification.show(); } - this.focusOnCurrentWindow(); this.setState({ showMask: true }); this.props.stopTimer(); this.props.changeAppTab('timer'); this.clearStat(); - this.remindUserTimeout(0); - this.remindUserTimeout(60 * 1000, 1.0); + if (shouldRemind) { + this.focusOnCurrentWindow(); + this.remindUserTimeout(0); + this.remindUserTimeout(60 * 1000, 1.0); + } }; - private onFocusingSessionDone = async () => { + private onFocusingSessionDone = async (shouldRemind = true, isRotten = false) => { if (!this.monitor) { throw new Error('No monitor'); } - const notification = new remote.Notification({ - title: 'Focusing finished. Start resting.', - body: `Completed ${this.state.pomodoroNum + 1} sessions today. \n\n`, - icon: nativeImage.createFromPath(`${__dirname}/${AppIcon}`) - }); - notification.show(); + if (shouldRemind) { + const notification = new remote.Notification({ + title: 'Focusing finished. Start resting.', + body: `Completed ${this.state.pomodoroNum + 1} sessions today. \n\n`, + icon: nativeImage.createFromPath(`${__dirname}/${AppIcon}`) + }); + notification.show(); + } const thisSession = this.monitor.sessionData; - thisSession.spentTimeInHour = this.props.timer.focusDuration / 3600; + if (!isRotten) { + thisSession.spentTimeInHour = this.props.timer.focusDuration / 3600; + } else { + const elapsedTimeInSec = this.getElapsedTimeInSecond(); + thisSession.spentTimeInHour = elapsedTimeInSec / 3600; + thisSession.isRotten = true; + } + this.stagedSession = thisSession; this.monitor.stop(); - this.monitor.clear(); - this.monitor = undefined; if (this.props.timer.boardId === undefined) { this.props.inferProject(thisSession); } - - this.setState({ pomodoroNum: this.state.pomodoroNum + 1 }); }; + private getElapsedTimeInSecond() { + const { targetTime, isFocusing } = this.props.timer; + const now = new Date().getTime(); + const timeSpan = targetTime! - now; + const leftTimeInSec = Math.floor(timeSpan / 1000 + 0.5); + const duration = this.getDuration(isFocusing); + return duration - leftTimeInSec; + } + private onSessionConfirmed = async () => { + if (this.monitor) { + this.monitor.clear(); + this.monitor = undefined; + } + + this.setState({ pomodoroNum: this.state.pomodoroNum + 1 }); if (this.stagedSession === undefined) { // Resting session this.props.timerFinished(); @@ -397,6 +428,8 @@ class Timer extends Component { } } + this.stagedSession.spentTimeInHour += this.extendedTimeInMinute / 60; + this.extendedTimeInMinute = 0; if (this.props.timer.boardId !== undefined) { this.stagedSession.boardId = this.props.timer.boardId; const kanban = this.props.kanban; @@ -473,6 +506,33 @@ class Timer extends Component { }, timeout); }; + private extendCurrentSession = (timeInMinutes: number) => { + if (this.monitor) { + this.monitor.resume(); + } else if (process.env.NODE_ENV === 'development') { + throw new Error(); + } + + this.extendedTimeInMinute += timeInMinutes; + this.props.extendCurrentSession(timeInMinutes * 60); + this.setState({ showMask: false }); + }; + + private onFinishButtonClick = async () => { + const { isFocusing } = this.props.timer; + if (!isFocusing) { + return this.onDone(false); + } + + const eTime = this.getElapsedTimeInSecond(); + if (eTime < 600) { + message.warn('Focus at least for 10 minutes to finish'); + return; + } + + await this.onDone(false, true); + }; + render() { const { leftTime, percent, more, pomodorosToday, showMask } = this.state; const { isRunning, targetTime } = this.props.timer; @@ -498,6 +558,7 @@ class Timer extends Component { return ( { @@ -577,6 +641,22 @@ class Timer extends Component { /> )} + {this.props.timer.isRunning || this.props.timer.leftTime ? ( +