Show timer in bubble if timed activity exists

This commit is contained in:
manubo
2019-09-19 17:33:41 +02:00
parent 8a4cfccc6f
commit 714e9bd139
7 changed files with 87 additions and 20 deletions

View File

@@ -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 },
}) })
} }

View File

@@ -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,
}, },
}) })

View File

@@ -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 = {

View 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

View 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])
}

View File

@@ -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", () => {

View File

@@ -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: {