diff --git a/src/background/RemotePontoon.ts b/src/background/RemotePontoon.ts index 2379db52..95ef17a3 100644 --- a/src/background/RemotePontoon.ts +++ b/src/background/RemotePontoon.ts @@ -87,7 +87,12 @@ export function listenToMessagesFromClients() { ); } -export async function refreshData() { +export async function refreshData(context: { + event: 'user interaction' | 'automation'; +}) { + if (context.event === 'user interaction') { + await saveToStorage({ notificationsDataLoadingState: 'loading' }); + } await Promise.all([ updateNotificationsData(), updateLatestTeamActivity(), @@ -126,8 +131,12 @@ async function updateNotificationsData() { for (const notification of userData.notifications.notifications) { notificationsData[notification.id] = notification; } - await saveToStorage({ notificationsData }); + await saveToStorage({ + notificationsData, + notificationsDataLoadingState: 'loaded', + }); } catch (error) { + await saveToStorage({ notificationsDataLoadingState: 'error' }); await deleteFromStorage('notificationsData'); console.error(error); } diff --git a/src/background/dataRefresh.ts b/src/background/dataRefresh.ts index e4324654..c87087f5 100644 --- a/src/background/dataRefresh.ts +++ b/src/background/dataRefresh.ts @@ -28,19 +28,23 @@ export function setupDataRefresh() { refreshDataWithInterval(periodInMinutes), ); callDelayed({ delayInSeconds: 1 }, async () => { - await refreshData(); + await refreshData({ event: 'user interaction' }); console.info('Data refreshed after initialization.'); }); - listenToOptionChange('contextual_identity', () => refreshData()); - listenToOptionChange('pontoon_base_url', () => refreshData()); + listenToOptionChange('contextual_identity', () => + refreshData({ event: 'user interaction' }), + ); + listenToOptionChange('pontoon_base_url', () => + refreshData({ event: 'user interaction' }), + ); registerLiveDataProvider(); } function refreshDataWithInterval(periodInMinutes: number) { callWithInterval('data-refresher-alarm', { periodInMinutes }, () => - refreshData(), + refreshData({ event: 'automation' }), ); } diff --git a/src/background/toolbarButton.ts b/src/background/toolbarButton.ts index ba4c2de3..a541297b 100644 --- a/src/background/toolbarButton.ts +++ b/src/background/toolbarButton.ts @@ -45,7 +45,17 @@ async function registerBadgeChanges() { 'notificationsData', ({ newValue: notificationsData }) => { if (notificationsData) { - updateBadge(notificationsData); + updateBadge({ notificationsData }); + } else { + updateBadge(); + } + }, + ); + listenToStorageChange( + 'notificationsDataLoadingState', + ({ newValue: notificationsDataLoadingState }) => { + if (notificationsDataLoadingState) { + updateBadge({ notificationsDataLoadingState }); } else { updateBadge(); } @@ -106,45 +116,72 @@ function registerButtonPopup(action: OptionValue<'toolbar_button_action'>) { } async function updateBadge( - notificationsData?: Partial['notificationsData'], + data?: Partial< + Pick + >, ) { - if (typeof notificationsData === 'undefined') { - notificationsData = await getOneFromStorage('notificationsData'); - } + const notificationsDataLoadingState = + data?.notificationsDataLoadingState ?? + (await getOneFromStorage('notificationsDataLoadingState')); + const notificationsData = + data?.notificationsData ?? (await getOneFromStorage('notificationsData')); - if (typeof notificationsData !== 'undefined') { + if ( + (typeof notificationsDataLoadingState === 'undefined' || + notificationsDataLoadingState === 'loaded') && + typeof notificationsData === 'object' + ) { if (await getOneOption('display_toolbar_button_badge')) { - const text = `${ - Object.values(notificationsData).filter((n) => n.unread).length - }`; - if (text === '0') { - hideBadge(); - } else { - const color = colors.interactive.red; - await Promise.all([ - browser.browserAction.setBadgeText({ text }), - browser.browserAction.setTitle({ - title: `${DEFAULT_TITLE} (${text})`, - }), - browser.browserAction.setBadgeBackgroundColor({ color }), - ]); - } + const unreadNotificationsCount = Object.values(notificationsData).filter( + (n) => n.unread, + ).length; + await loadedBadge(unreadNotificationsCount); } + } else if (notificationsDataLoadingState === 'loading') { + await loadingBadge(); } else { - const text = '!'; - const color = colors.interactive.red; - await Promise.all([ - browser.browserAction.setBadgeText({ text }), - browser.browserAction.setTitle({ title: `${DEFAULT_TITLE} (${text})` }), - browser.browserAction.setBadgeBackgroundColor({ color }), - ]); + await errorBadge(); } } -async function hideBadge() { +async function loadedBadge(unreadNotificationsCount: number) { + if (unreadNotificationsCount > 0) { + await setBadge({ + text: `${unreadNotificationsCount}`, + title: `${DEFAULT_TITLE} (${unreadNotificationsCount})`, + color: colors.interactive.red, + }); + } else { + await setBadge({ + text: '', // hide badge + title: DEFAULT_TITLE, + color: colors.interactive.gray, + }); + } +} + +async function loadingBadge() { + await setBadge({ + text: '🗘', + title: DEFAULT_TITLE, + color: colors.interactive.gray, + }); +} + +async function errorBadge() { + await setBadge({ + text: '!', + title: DEFAULT_TITLE, + color: colors.interactive.red, + }); +} + +async function setBadge(data: { text: string; title: string; color: string }) { + const { text, title, color } = data; await Promise.all([ - browser.browserAction.setBadgeText({ text: '' }), - browser.browserAction.setTitle({ title: DEFAULT_TITLE }), + browser.browserAction.setBadgeText({ text }), + browser.browserAction.setTitle({ title }), + browser.browserAction.setBadgeBackgroundColor({ color }), ]); } @@ -154,8 +191,7 @@ async function addContextMenu() { title: 'Reload notifications', contexts: ['browser_action'], onclick: async () => { - await hideBadge(); - refreshData(); + await refreshData({ event: 'user interaction' }); }, }); diff --git a/src/commons/webExtensionsApi/index.ts b/src/commons/webExtensionsApi/index.ts index f26453f6..faf02030 100644 --- a/src/commons/webExtensionsApi/index.ts +++ b/src/commons/webExtensionsApi/index.ts @@ -41,6 +41,7 @@ export interface StorageContent { bz_component: string; }; }; + notificationsDataLoadingState: 'loading' | 'loaded' | 'error' | undefined; notificationsData: { [id: number]: { id: number;