qw/timer (#23)
* Rename logo and add 32x32 version * Set timer icon if a timer is running * Do not query activities on initialization * Show timer in bubble if timed activity exists * Pass timed activity to App * Code cleanup * Show timer view and stop timer * Make hours optional * Use booked seconds instead of hours * Add type submit to form button * Define colors as sass variables⎄ * Style timer view * Show start timer submit label * Update view layouts and content * Update version and changelog * Dyanically set iframe height * Reduce h1 font size * Add svg webpack loader * Parse empty string (TimeInputParser) * Forward ref in Popup component * Start time on current day only, format buttons * Improve styling * Set standard height as iframe default height, validate form * Upgrade packages to supress react warning * Show activity form in popup after timer was stoped * Use stop-watch icon in timer view * Fix empty description * Close TimerView if timer stopped for current service * Style timerview * Improve timer view styling * 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:
@@ -2,11 +2,13 @@ export default class TimeInputParser {
|
||||
#input
|
||||
|
||||
constructor(input) {
|
||||
this.#input = input.toLowerCase().replace(/[\s()]/g, "")
|
||||
this.#input = (input ?? "").toLowerCase().replace(/[\s()]/g, "")
|
||||
}
|
||||
|
||||
parseSeconds() {
|
||||
if (this.#isDecimal()) {
|
||||
if (this.#input === "") {
|
||||
return 0
|
||||
} else if (this.#isDecimal()) {
|
||||
return Math.round(parseFloat(this.#parseDecimal()) * 3600)
|
||||
} else if (this.#isTime()) {
|
||||
return this.#parseTimeAsSeconds()
|
||||
|
||||
@@ -5,7 +5,7 @@ export const isFirefox = () => typeof browser !== "undefined" && chrome
|
||||
const DEFAULT_SUBDOMAIN = "unset"
|
||||
|
||||
export const getSettings = (withDefaultSubdomain = true) => {
|
||||
const keys = ["subdomain", "apiKey"]
|
||||
const keys = ["subdomain", "apiKey", "settingTimeTrackingHHMM"]
|
||||
const { version } = chrome.runtime.getManifest()
|
||||
if (isChrome()) {
|
||||
return new Promise(resolve => {
|
||||
|
||||
@@ -12,7 +12,9 @@ import {
|
||||
pick,
|
||||
head,
|
||||
defaultTo,
|
||||
padCharsStart,
|
||||
} from "lodash/fp"
|
||||
import { startOfWeek, endOfWeek } from "date-fns"
|
||||
import { format } from "date-fns"
|
||||
|
||||
const nilToArray = input => input || []
|
||||
@@ -105,6 +107,8 @@ export const trace = curry((tag, value) => {
|
||||
|
||||
export const weekStartsOn = 1
|
||||
export const formatDate = date => format(date, "yyyy-MM-dd")
|
||||
export const getStartOfWeek = () => startOfWeek(new Date(), { weekStartsOn })
|
||||
export const getEndOfWeek = () => endOfWeek(new Date(), { weekStartsOn })
|
||||
|
||||
export const extensionSettingsUrl = () => `chrome://extensions/?id=${chrome.runtime.id}`
|
||||
|
||||
@@ -120,3 +124,22 @@ export const extractAndSetTag = changeset => {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,34 +4,35 @@ import {
|
||||
ERROR_UPGRADE_REQUIRED,
|
||||
ERROR_UNKNOWN,
|
||||
groupedProjectOptions,
|
||||
weekStartsOn,
|
||||
getStartOfWeek,
|
||||
getEndOfWeek,
|
||||
} from "utils"
|
||||
import { get, forEach, reject, isNil } from "lodash/fp"
|
||||
import { startOfWeek, endOfWeek } from "date-fns"
|
||||
import { createMatcher } from "utils/urlMatcher"
|
||||
import remoteServices from "remoteServices"
|
||||
import { queryTabs, isBrowserTab, getSettings } from "utils/browser"
|
||||
import { queryTabs, isBrowserTab, getSettings, setStorage } from "utils/browser"
|
||||
|
||||
const getStartOfWeek = () => startOfWeek(new Date(), { weekStartsOn })
|
||||
const getEndOfWeek = () => endOfWeek(new Date(), { weekStartsOn })
|
||||
const matcher = createMatcher(remoteServices)
|
||||
|
||||
export function tabUpdated(tab, { messenger, settings }) {
|
||||
messenger.connectTab(tab)
|
||||
|
||||
const service = matcher(tab.url)
|
||||
const apiClient = new ApiClient(settings)
|
||||
|
||||
if (service?.match?.id) {
|
||||
messenger.postMessage(tab, { type: "requestService" })
|
||||
|
||||
messenger.once("newService", ({ payload: { service } }) => {
|
||||
const apiClient = new ApiClient(settings)
|
||||
apiClient
|
||||
.bookedHours(service)
|
||||
.activitiesStatus(service)
|
||||
.then(({ data }) => {
|
||||
messenger.postMessage(tab, {
|
||||
type: "showBubble",
|
||||
payload: {
|
||||
bookedHours: parseFloat(data[0]?.hours) || 0,
|
||||
bookedSeconds: data.seconds,
|
||||
settingTimeTrackingHHMM: settings.settingTimeTrackingHHMM,
|
||||
timedActivity: data.timed_activity,
|
||||
service,
|
||||
},
|
||||
})
|
||||
@@ -40,7 +41,8 @@ export function tabUpdated(tab, { messenger, settings }) {
|
||||
messenger.postMessage(tab, {
|
||||
type: "showBubble",
|
||||
payload: {
|
||||
bookedHours: 0,
|
||||
bookedSeconds: 0,
|
||||
settingTimeTrackingHHMM: settings.settingTimeTrackingHHMM,
|
||||
service,
|
||||
},
|
||||
})
|
||||
@@ -76,25 +78,36 @@ export function togglePopup(tab, { messenger }) {
|
||||
}
|
||||
}
|
||||
|
||||
async function openPopup(tab, { service, messenger }) {
|
||||
export async function openPopup(tab, { service, messenger }) {
|
||||
messenger.postMessage(tab, { type: "openPopup", payload: { loading: true } })
|
||||
|
||||
const fromDate = getStartOfWeek()
|
||||
const toDate = getEndOfWeek()
|
||||
const settings = await getSettings()
|
||||
const apiClient = new ApiClient(settings)
|
||||
const responses = []
|
||||
try {
|
||||
const responses = await Promise.all([
|
||||
apiClient.login(service),
|
||||
apiClient.projects(),
|
||||
apiClient.activities(fromDate, toDate),
|
||||
apiClient.schedules(fromDate, toDate),
|
||||
])
|
||||
responses.push(await apiClient.login(service))
|
||||
// we can forgo the following calls if a timed activity exists
|
||||
if (!responses[0].data.timed_activity) {
|
||||
responses.push(
|
||||
...(await Promise.all([
|
||||
apiClient.projects(),
|
||||
apiClient.activities(fromDate, toDate),
|
||||
apiClient.schedules(fromDate, toDate),
|
||||
])),
|
||||
)
|
||||
}
|
||||
|
||||
const settingTimeTrackingHHMM = get("[0].data.setting_time_tracking_hh_mm", responses)
|
||||
!isNil(settingTimeTrackingHHMM) && setStorage({ settingTimeTrackingHHMM })
|
||||
|
||||
const action = {
|
||||
type: "openPopup",
|
||||
payload: {
|
||||
service,
|
||||
subdomain: settings.subdomain,
|
||||
timedActivity: get("[0].data.timed_activity", responses),
|
||||
lastProjectId: get("[0].data.last_project_id", responses),
|
||||
lastTaskId: get("[0].data.last_task_id", responses),
|
||||
roundTimeEntries: get("[0].data.round_time_entries", responses),
|
||||
|
||||
Reference in New Issue
Block a user