qw/setting-time-tracking-hh-mm (#24)
* Format duration depending on settingTimeTrackingHHMM * Fix formatDuation without second argument * Fix time format after updating bubble * Add tests for formatDuration
This commit is contained in:
@@ -11,7 +11,8 @@ function timerStoppedForCurrentService(service, timedActivity) {
|
|||||||
return timedActivity.service_id && timedActivity.service_id === service?.id
|
return timedActivity.service_id && timedActivity.service_id === service?.id
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetBubble({ tab, apiClient, service, timedActivity }) {
|
function resetBubble({ tab, settings, service, timedActivity }) {
|
||||||
|
const apiClient = new ApiClient(settings)
|
||||||
apiClient
|
apiClient
|
||||||
.activitiesStatus(service)
|
.activitiesStatus(service)
|
||||||
.then(({ data }) => {
|
.then(({ data }) => {
|
||||||
@@ -20,6 +21,7 @@ function resetBubble({ tab, apiClient, service, timedActivity }) {
|
|||||||
payload: {
|
payload: {
|
||||||
bookedSeconds: data.seconds,
|
bookedSeconds: data.seconds,
|
||||||
timedActivity: data.timed_activity,
|
timedActivity: data.timed_activity,
|
||||||
|
settingTimeTrackingHHMM: settings.settingTimeTrackingHHMM,
|
||||||
service,
|
service,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -58,7 +60,7 @@ chrome.runtime.onMessage.addListener(action => {
|
|||||||
const apiClient = new ApiClient(settings)
|
const apiClient = new ApiClient(settings)
|
||||||
apiClient
|
apiClient
|
||||||
.createActivity(activity)
|
.createActivity(activity)
|
||||||
.then(() => resetBubble({ tab, apiClient, service }))
|
.then(() => resetBubble({ tab, settings, service }))
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
if (error.response?.status === 422) {
|
if (error.response?.status === 422) {
|
||||||
chrome.runtime.sendMessage({
|
chrome.runtime.sendMessage({
|
||||||
@@ -78,7 +80,7 @@ chrome.runtime.onMessage.addListener(action => {
|
|||||||
const apiClient = new ApiClient(settings)
|
const apiClient = new ApiClient(settings)
|
||||||
apiClient
|
apiClient
|
||||||
.stopTimer(timedActivity)
|
.stopTimer(timedActivity)
|
||||||
.then(() => resetBubble({ tab, apiClient, service, timedActivity }))
|
.then(() => resetBubble({ tab, settings, service, timedActivity }))
|
||||||
.catch(() => null)
|
.catch(() => null)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,16 +3,19 @@ import PropTypes from "prop-types"
|
|||||||
import mocoLogo from "images/moco-32x32.png"
|
import mocoLogo from "images/moco-32x32.png"
|
||||||
import mocoTimerLogo from "images/moco-timer-32x32.png"
|
import mocoTimerLogo from "images/moco-timer-32x32.png"
|
||||||
import { parseISO } from "date-fns"
|
import { parseISO } from "date-fns"
|
||||||
|
import { formatDuration } from "utils"
|
||||||
import Timer from "./shared/Timer"
|
import Timer from "./shared/Timer"
|
||||||
|
|
||||||
const Bubble = ({ bookedSeconds, timedActivity }) => {
|
const Bubble = ({ bookedSeconds, timedActivity, settingTimeTrackingHHMM }) => {
|
||||||
const logo = timedActivity ? mocoTimerLogo : mocoLogo
|
const logo = timedActivity ? mocoTimerLogo : mocoLogo
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="moco-bx-bubble-inner">
|
<div className="moco-bx-bubble-inner">
|
||||||
<img className="moco-bx-logo" src={chrome.extension.getURL(logo)} />
|
<img className="moco-bx-logo" src={chrome.extension.getURL(logo)} />
|
||||||
{!timedActivity && bookedSeconds > 0 && (
|
{!timedActivity && bookedSeconds > 0 && (
|
||||||
<span className="moco-bx-booked-hours">{(bookedSeconds / 3600).toFixed(2)}</span>
|
<span className="moco-bx-booked-hours">
|
||||||
|
{formatDuration(bookedSeconds, { settingTimeTrackingHHMM, showSeconds: false })}
|
||||||
|
</span>
|
||||||
)}
|
)}
|
||||||
{timedActivity && (
|
{timedActivity && (
|
||||||
<Timer
|
<Timer
|
||||||
@@ -32,10 +35,12 @@ Bubble.propTypes = {
|
|||||||
timer_started_at: PropTypes.string.isRequired,
|
timer_started_at: PropTypes.string.isRequired,
|
||||||
seconds: PropTypes.number.isRequired,
|
seconds: PropTypes.number.isRequired,
|
||||||
}),
|
}),
|
||||||
|
settingTimeTrackingHHMM: PropTypes.bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
Bubble.defaultProps = {
|
Bubble.defaultProps = {
|
||||||
bookedSeconds: 0,
|
bookedSeconds: 0,
|
||||||
|
settingTimeTrackingHHMM: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Bubble
|
export default Bubble
|
||||||
|
|||||||
@@ -25,7 +25,11 @@ class Options extends Component {
|
|||||||
handleSubmit = _event => {
|
handleSubmit = _event => {
|
||||||
this.isSuccess = false
|
this.isSuccess = false
|
||||||
this.errorMessage = null
|
this.errorMessage = null
|
||||||
setStorage({ subdomain: this.subdomain, apiKey: this.apiKey }).then(() => {
|
setStorage({
|
||||||
|
subdomain: this.subdomain,
|
||||||
|
apiKey: this.apiKey,
|
||||||
|
settingTimeTrackingHHMM: false,
|
||||||
|
}).then(() => {
|
||||||
const { version } = chrome.runtime.getManifest()
|
const { version } = chrome.runtime.getManifest()
|
||||||
const apiClient = new ApiClient({
|
const apiClient = new ApiClient({
|
||||||
subdomain: this.subdomain,
|
subdomain: this.subdomain,
|
||||||
@@ -34,6 +38,9 @@ class Options extends Component {
|
|||||||
})
|
})
|
||||||
apiClient
|
apiClient
|
||||||
.login()
|
.login()
|
||||||
|
.then(({ data }) =>
|
||||||
|
setStorage({ settingTimeTrackingHHMM: data.setting_time_tracking_hh_mm }),
|
||||||
|
)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.isSuccess = true
|
this.isSuccess = true
|
||||||
this.closeWindow()
|
this.closeWindow()
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import React, { useState } from "react"
|
import React, { useState } from "react"
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types"
|
||||||
import { useInterval } from "./hooks"
|
import { useInterval } from "./hooks"
|
||||||
import { differenceInSeconds, addSeconds, startOfDay, format } from "date-fns"
|
import { differenceInSeconds } from "date-fns"
|
||||||
|
import { formatDuration } from "utils"
|
||||||
|
|
||||||
Timer.propTypes = {
|
Timer.propTypes = {
|
||||||
startedAt: PropTypes.instanceOf(Date).isRequired,
|
startedAt: PropTypes.instanceOf(Date).isRequired,
|
||||||
@@ -22,7 +23,7 @@ function Timer({ startedAt, offset = 0, onTick, ...domProps }) {
|
|||||||
|
|
||||||
function formattedTimerLabel(startedAt, offset) {
|
function formattedTimerLabel(startedAt, offset) {
|
||||||
const seconds = differenceInSeconds(new Date(), startedAt) + offset
|
const seconds = differenceInSeconds(new Date(), startedAt) + offset
|
||||||
return format(addSeconds(startOfDay(new Date()), seconds), "HH:mm:ss")
|
return formatDuration(seconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Timer
|
export default Timer
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ chrome.runtime.onConnect.addListener(function(port) {
|
|||||||
document.removeEventListener("click", clickHandler, true)
|
document.removeEventListener("click", clickHandler, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
function updateBubble({ service, bookedSeconds, timedActivity } = {}) {
|
function updateBubble({ service, bookedSeconds, settingTimeTrackingHHMM, timedActivity } = {}) {
|
||||||
if (!document.getElementById("moco-bx-root")) {
|
if (!document.getElementById("moco-bx-root")) {
|
||||||
const domRoot = document.createElement("div")
|
const domRoot = document.createElement("div")
|
||||||
domRoot.setAttribute("id", "moco-bx-root")
|
domRoot.setAttribute("id", "moco-bx-root")
|
||||||
@@ -50,6 +50,7 @@ chrome.runtime.onConnect.addListener(function(port) {
|
|||||||
<Bubble
|
<Bubble
|
||||||
key={service.url}
|
key={service.url}
|
||||||
bookedSeconds={bookedSeconds}
|
bookedSeconds={bookedSeconds}
|
||||||
|
settingTimeTrackingHHMM={settingTimeTrackingHHMM}
|
||||||
timedActivity={timedActivity}
|
timedActivity={timedActivity}
|
||||||
/>
|
/>
|
||||||
</animated.div>
|
</animated.div>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ export const isFirefox = () => typeof browser !== "undefined" && chrome
|
|||||||
const DEFAULT_SUBDOMAIN = "unset"
|
const DEFAULT_SUBDOMAIN = "unset"
|
||||||
|
|
||||||
export const getSettings = (withDefaultSubdomain = true) => {
|
export const getSettings = (withDefaultSubdomain = true) => {
|
||||||
const keys = ["subdomain", "apiKey"]
|
const keys = ["subdomain", "apiKey", "settingTimeTrackingHHMM"]
|
||||||
const { version } = chrome.runtime.getManifest()
|
const { version } = chrome.runtime.getManifest()
|
||||||
if (isChrome()) {
|
if (isChrome()) {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
pick,
|
pick,
|
||||||
head,
|
head,
|
||||||
defaultTo,
|
defaultTo,
|
||||||
|
padCharsStart,
|
||||||
} from "lodash/fp"
|
} from "lodash/fp"
|
||||||
import { startOfWeek, endOfWeek } from "date-fns"
|
import { startOfWeek, endOfWeek } from "date-fns"
|
||||||
import { format } from "date-fns"
|
import { format } from "date-fns"
|
||||||
@@ -123,3 +124,22 @@ export const extractAndSetTag = changeset => {
|
|||||||
tag: match[1],
|
tag: match[1],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const formatDuration = (
|
||||||
|
durationInSeconds,
|
||||||
|
{ settingTimeTrackingHHMM = true, showSeconds = true } = {},
|
||||||
|
) => {
|
||||||
|
if (settingTimeTrackingHHMM) {
|
||||||
|
const hours = Math.floor(durationInSeconds / 3600)
|
||||||
|
const minutes = Math.floor((durationInSeconds % 3600) / 60)
|
||||||
|
const result = `${hours}:${padCharsStart("0", 2, minutes)}`
|
||||||
|
if (!showSeconds) {
|
||||||
|
return result
|
||||||
|
} else {
|
||||||
|
const seconds = durationInSeconds % 60
|
||||||
|
return result + `:${padCharsStart("0", 2, seconds)}`
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return (durationInSeconds / 3600).toFixed(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
import { get, forEach, reject, isNil } from "lodash/fp"
|
import { get, forEach, reject, isNil } from "lodash/fp"
|
||||||
import { createMatcher } from "utils/urlMatcher"
|
import { createMatcher } from "utils/urlMatcher"
|
||||||
import remoteServices from "remoteServices"
|
import remoteServices from "remoteServices"
|
||||||
import { queryTabs, isBrowserTab, getSettings } from "utils/browser"
|
import { queryTabs, isBrowserTab, getSettings, setStorage } from "utils/browser"
|
||||||
|
|
||||||
const matcher = createMatcher(remoteServices)
|
const matcher = createMatcher(remoteServices)
|
||||||
|
|
||||||
@@ -31,6 +31,7 @@ export function tabUpdated(tab, { messenger, settings }) {
|
|||||||
type: "showBubble",
|
type: "showBubble",
|
||||||
payload: {
|
payload: {
|
||||||
bookedSeconds: data.seconds,
|
bookedSeconds: data.seconds,
|
||||||
|
settingTimeTrackingHHMM: settings.settingTimeTrackingHHMM,
|
||||||
timedActivity: data.timed_activity,
|
timedActivity: data.timed_activity,
|
||||||
service,
|
service,
|
||||||
},
|
},
|
||||||
@@ -41,6 +42,7 @@ export function tabUpdated(tab, { messenger, settings }) {
|
|||||||
type: "showBubble",
|
type: "showBubble",
|
||||||
payload: {
|
payload: {
|
||||||
bookedSeconds: 0,
|
bookedSeconds: 0,
|
||||||
|
settingTimeTrackingHHMM: settings.settingTimeTrackingHHMM,
|
||||||
service,
|
service,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -97,6 +99,9 @@ export async function openPopup(tab, { service, messenger }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const settingTimeTrackingHHMM = get("[0].data.setting_time_tracking_hh_mm", responses)
|
||||||
|
!isNil(settingTimeTrackingHHMM) && setStorage({ settingTimeTrackingHHMM })
|
||||||
|
|
||||||
const action = {
|
const action = {
|
||||||
type: "openPopup",
|
type: "openPopup",
|
||||||
payload: {
|
payload: {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
defaultTask,
|
defaultTask,
|
||||||
groupedProjectOptions,
|
groupedProjectOptions,
|
||||||
extractAndSetTag,
|
extractAndSetTag,
|
||||||
|
formatDuration,
|
||||||
} from "../../src/js/utils"
|
} from "../../src/js/utils"
|
||||||
import { map, compose } from "lodash/fp"
|
import { map, compose } from "lodash/fp"
|
||||||
|
|
||||||
@@ -142,4 +143,21 @@ describe("utils", () => {
|
|||||||
expect(extractAndSetTag(changeset)).toEqual(changeset)
|
expect(extractAndSetTag(changeset)).toEqual(changeset)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe("formatDuration", () => {
|
||||||
|
it("format with defaults", () => {
|
||||||
|
expect(formatDuration(3600)).toBe("1:00:00")
|
||||||
|
expect(formatDuration(3661)).toBe("1:01:01")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("format without seconds", () => {
|
||||||
|
expect(formatDuration(3600, { showSeconds: false })).toBe("1:00")
|
||||||
|
expect(formatDuration(3661, { showSeconds: false })).toBe("1:01")
|
||||||
|
})
|
||||||
|
|
||||||
|
it("format in decimals", () => {
|
||||||
|
expect(formatDuration(3600, { settingTimeTrackingHHMM: false })).toBe("1.00")
|
||||||
|
expect(formatDuration(3661, { settingTimeTrackingHHMM: false })).toBe("1.02")
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user