diff --git a/frontend/public/images/icons/favicon-tracking-32x32.png b/frontend/public/images/icons/favicon-tracking-32x32.png new file mode 100644 index 000000000..02f5bffd7 Binary files /dev/null and b/frontend/public/images/icons/favicon-tracking-32x32.png differ diff --git a/frontend/src/composables/useTimeTrackingFavicon.ts b/frontend/src/composables/useTimeTrackingFavicon.ts index 0b0a819c7..1b201121d 100644 --- a/frontend/src/composables/useTimeTrackingFavicon.ts +++ b/frontend/src/composables/useTimeTrackingFavicon.ts @@ -4,83 +4,26 @@ import {storeToRefs} from 'pinia' import {useTimeTrackingStore} from '@/stores/timeTracking' -const FAVICON_SIZE = 32 -// Drawn from a PNG rather than the .ico because ICO decoding into a canvas is -// unreliable across browsers. -const BASE_FAVICON = '/images/icons/favicon-32x32.png' -const DOT_COLOR = '#ff4136' +const TRACKING_FAVICON = '/images/icons/favicon-tracking-32x32.png' function getFaviconLink(): HTMLLinkElement | null { return document.querySelector('link[rel="icon"]') } -// Marks the favicon with a small red dot in the lower left corner while a timer +// Swaps in a favicon with a small red dot in the lower left corner while a timer // is running, so an active time tracking session is visible even when the tab // isn't focused. export const useTimeTrackingFavicon = createSharedComposable(() => { const {hasActiveTimer} = storeToRefs(useTimeTrackingStore()) - const link = getFaviconLink() - const originalHref = link?.getAttribute('href') ?? '/favicon.ico' - - let baseImage: HTMLImageElement | null = null - - function loadBaseImage(): Promise { - if (baseImage?.complete) { - return Promise.resolve(baseImage) - } - return new Promise((resolve, reject) => { - const img = new Image() - img.addEventListener('load', () => { - baseImage = img - resolve(img) - }) - img.addEventListener('error', reject) - img.src = BASE_FAVICON - }) - } - - async function drawBadgedFavicon() { - const targetLink = getFaviconLink() - if (targetLink === null) { - return - } - - const img = await loadBaseImage() - const canvas = document.createElement('canvas') - canvas.width = FAVICON_SIZE - canvas.height = FAVICON_SIZE - const ctx = canvas.getContext('2d') - if (ctx === null) { - return - } - - ctx.drawImage(img, 0, 0, FAVICON_SIZE, FAVICON_SIZE) - - const radius = FAVICON_SIZE * 0.28 - const cx = radius - const cy = FAVICON_SIZE - radius - ctx.beginPath() - ctx.arc(cx, cy, radius, 0, Math.PI * 2) - ctx.fillStyle = DOT_COLOR - ctx.fill() - - targetLink.href = canvas.toDataURL('image/png') - } - - function restoreFavicon() { - const targetLink = getFaviconLink() - if (targetLink !== null) { - targetLink.href = originalHref - } - } + const originalHref = getFaviconLink()?.getAttribute('href') ?? '/favicon.ico' function update(active: boolean) { - if (active) { - void drawBadgedFavicon() + const link = getFaviconLink() + if (link === null) { return } - restoreFavicon() + link.href = active ? TRACKING_FAVICON : originalHref } watch(hasActiveTimer, update, {flush: 'post'})