Show timer in bubble if timed activity exists
This commit is contained in:
@@ -46,12 +46,12 @@ export default class Client {
|
|||||||
params: { date: `${formatDate(fromDate)}:${formatDate(toDate)}` },
|
params: { date: `${formatDate(fromDate)}:${formatDate(toDate)}` },
|
||||||
})
|
})
|
||||||
|
|
||||||
bookedHours = service => {
|
activitiesStatus = service => {
|
||||||
if (!service) {
|
if (!service) {
|
||||||
return Promise.resolve({ data: { hours: 0 } })
|
return Promise.resolve({ data: { hours: 0 } })
|
||||||
}
|
}
|
||||||
return this.#client.get("activities/tags", {
|
return this.#client.get("activities/status", {
|
||||||
params: { selection: [service.id], remote_service: service.name },
|
params: { remote_id: service.id, remote_service: service.name },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,11 +33,12 @@ chrome.runtime.onMessage.addListener(action => {
|
|||||||
.createActivity(activity)
|
.createActivity(activity)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
messenger.postMessage(tab, { type: "closePopup" })
|
messenger.postMessage(tab, { type: "closePopup" })
|
||||||
apiClient.bookedHours(service).then(({ data }) => {
|
apiClient.activitiesStatus(service).then(({ data }) => {
|
||||||
messenger.postMessage(tab, {
|
messenger.postMessage(tab, {
|
||||||
type: "showBubble",
|
type: "showBubble",
|
||||||
payload: {
|
payload: {
|
||||||
bookedHours: parseFloat(data[0]?.hours) || 0,
|
bookedHours: parseFloat(data.hours),
|
||||||
|
timedActivity: data.timed_activity,
|
||||||
service,
|
service,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,18 +1,36 @@
|
|||||||
import React from "react"
|
import React from "react"
|
||||||
import PropTypes from "prop-types"
|
import PropTypes from "prop-types"
|
||||||
import logoUrl from "images/moco-159x159.png"
|
import mocoLogo from "images/moco-32x32.png"
|
||||||
|
import mocoTimerLogo from "images/moco-timer-32x32.png"
|
||||||
|
import { parseISO } from "date-fns"
|
||||||
|
import Timer from "./shared/Timer"
|
||||||
|
|
||||||
const Bubble = ({ bookedHours }) => (
|
const Bubble = ({ bookedHours, timedActivity }) => {
|
||||||
<div className="moco-bx-bubble-inner">
|
const logo = timedActivity ? mocoTimerLogo : mocoLogo
|
||||||
<img className="moco-bx-logo" src={chrome.extension.getURL(logoUrl)} />
|
|
||||||
{bookedHours > 0 ? (
|
return (
|
||||||
<span className="moco-bx-booked-hours">{bookedHours.toFixed(2)}</span>
|
<div className="moco-bx-bubble-inner">
|
||||||
) : null}
|
<img className="moco-bx-logo" src={chrome.extension.getURL(logo)} />
|
||||||
</div>
|
{!timedActivity && bookedHours > 0 && (
|
||||||
)
|
<span className="moco-bx-booked-hours">{bookedHours.toFixed(2)}</span>
|
||||||
|
)}
|
||||||
|
{timedActivity && (
|
||||||
|
<Timer
|
||||||
|
startedAt={parseISO(timedActivity.timer_started_at)}
|
||||||
|
offset={timedActivity.seconds}
|
||||||
|
style={{ color: "red", marginBottom: "3px", fontSize: "12px" }}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Bubble.propTypes = {
|
Bubble.propTypes = {
|
||||||
bookedHours: PropTypes.number,
|
bookedHours: PropTypes.number,
|
||||||
|
timedActivity: PropTypes.shape({
|
||||||
|
timer_started_at: PropTypes.string.isRequired,
|
||||||
|
seconds: PropTypes.number.isRequired,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
Bubble.defaultProps = {
|
Bubble.defaultProps = {
|
||||||
|
|||||||
28
src/js/components/shared/Timer.js
Normal file
28
src/js/components/shared/Timer.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import React, { useState } from "react"
|
||||||
|
import PropTypes from "prop-types"
|
||||||
|
import { useInterval } from "./hooks"
|
||||||
|
import { differenceInSeconds, addSeconds, startOfDay, format } from "date-fns"
|
||||||
|
|
||||||
|
Timer.propTypes = {
|
||||||
|
startedAt: PropTypes.instanceOf(Date).isRequired,
|
||||||
|
offset: PropTypes.number,
|
||||||
|
onTick: PropTypes.func,
|
||||||
|
}
|
||||||
|
|
||||||
|
function Timer({ startedAt, offset = 0, onTick, ...domProps }) {
|
||||||
|
const [timerLabel, setTimerLabel] = useState(formattedTimerLabel(startedAt, offset))
|
||||||
|
|
||||||
|
useInterval(() => {
|
||||||
|
setTimerLabel(formattedTimerLabel(startedAt, offset))
|
||||||
|
onTick && onTick()
|
||||||
|
}, 1000)
|
||||||
|
|
||||||
|
return <span {...domProps}>{timerLabel}</span>
|
||||||
|
}
|
||||||
|
|
||||||
|
function formattedTimerLabel(startedAt, offset) {
|
||||||
|
const seconds = differenceInSeconds(new Date(), startedAt) + offset
|
||||||
|
return format(addSeconds(startOfDay(new Date()), seconds), "HH:mm:ss")
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Timer
|
||||||
18
src/js/components/shared/hooks.js
Normal file
18
src/js/components/shared/hooks.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { useEffect, useRef } from "react"
|
||||||
|
|
||||||
|
export function useInterval(callback, delay) {
|
||||||
|
const savedCallback = useRef()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
savedCallback.current = callback
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function tick() {
|
||||||
|
savedCallback.current()
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = setInterval(tick, delay)
|
||||||
|
return () => clearInterval(id)
|
||||||
|
}, [delay])
|
||||||
|
}
|
||||||
@@ -25,7 +25,7 @@ chrome.runtime.onConnect.addListener(function(port) {
|
|||||||
document.removeEventListener("click", clickHandler, true)
|
document.removeEventListener("click", clickHandler, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
function updateBubble({ service, bookedHours } = {}) {
|
function updateBubble({ service, bookedHours, 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")
|
||||||
@@ -47,7 +47,7 @@ chrome.runtime.onConnect.addListener(function(port) {
|
|||||||
// eslint-disable-next-line react/display-name
|
// eslint-disable-next-line react/display-name
|
||||||
(props => (
|
(props => (
|
||||||
<animated.div className="moco-bx-bubble" style={{ ...props, ...service.position }}>
|
<animated.div className="moco-bx-bubble" style={{ ...props, ...service.position }}>
|
||||||
<Bubble key={service.url} bookedHours={bookedHours} />
|
<Bubble key={service.url} bookedHours={bookedHours} timedActivity={timedActivity} />
|
||||||
</animated.div>
|
</animated.div>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -86,8 +86,8 @@ chrome.runtime.onConnect.addListener(function(port) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
messenger.on("showBubble", ({ payload: { service, bookedHours } }) => {
|
messenger.on("showBubble", ({ payload }) => {
|
||||||
updateBubble({ service, bookedHours })
|
updateBubble(payload)
|
||||||
})
|
})
|
||||||
|
|
||||||
messenger.on("hideBubble", () => {
|
messenger.on("hideBubble", () => {
|
||||||
|
|||||||
@@ -25,12 +25,13 @@ export function tabUpdated(tab, { messenger, settings }) {
|
|||||||
|
|
||||||
messenger.once("newService", ({ payload: { service } }) => {
|
messenger.once("newService", ({ payload: { service } }) => {
|
||||||
apiClient
|
apiClient
|
||||||
.bookedHours(service)
|
.activitiesStatus(service)
|
||||||
.then(({ data }) => {
|
.then(({ data }) => {
|
||||||
messenger.postMessage(tab, {
|
messenger.postMessage(tab, {
|
||||||
type: "showBubble",
|
type: "showBubble",
|
||||||
payload: {
|
payload: {
|
||||||
bookedHours: parseFloat(data[0]?.hours) || 0,
|
bookedHours: parseFloat(data.hours),
|
||||||
|
timedActivity: data.timed_activity,
|
||||||
service,
|
service,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -89,6 +90,7 @@ async function openPopup(tab, { service, messenger }) {
|
|||||||
apiClient.activities(fromDate, toDate),
|
apiClient.activities(fromDate, toDate),
|
||||||
apiClient.schedules(fromDate, toDate),
|
apiClient.schedules(fromDate, toDate),
|
||||||
])
|
])
|
||||||
|
|
||||||
const action = {
|
const action = {
|
||||||
type: "openPopup",
|
type: "openPopup",
|
||||||
payload: {
|
payload: {
|
||||||
|
|||||||
Reference in New Issue
Block a user