From 6e6b39563a3115b0641b800923752a2722431bf1 Mon Sep 17 00:00:00 2001 From: Corentin Mors Date: Sun, 16 Jun 2024 17:52:40 +0200 Subject: [PATCH] Rework options page structure (#116) --- src/background/index.ts | 5 +- src/background/utils/initGitlabApi.ts | 4 +- src/common/types.ts | 1 + src/config/config.prod.ts | 5 +- src/options/App.tsx | 273 +++++--------------------- src/options/components/Account.tsx | 143 ++++++++++++++ src/popup/components/Nav.tsx | 10 +- 7 files changed, 206 insertions(+), 235 deletions(-) create mode 100644 src/options/components/Account.tsx diff --git a/src/background/index.ts b/src/background/index.ts index 5700e34..856e567 100644 --- a/src/background/index.ts +++ b/src/background/index.ts @@ -3,12 +3,13 @@ import { getLatestDataFromGitLab, setTodoAsDone } from './endpoints'; import { getProjectsList } from './endpoints/getProjectsList'; import { getMembersOfGroup } from './endpoints/getMembersOfGroup'; import { setGlobalError } from '../common/globalError'; +import { readConfiguration } from '../common/configuration'; console.log('background script loaded'); let time: number; // dynamic interval -browser.storage.local.get(['refreshRate']).then((settings) => { - time = settings.refreshRate ? settings.refreshRate : 40; +readConfiguration<{ refreshRate: number }>(['refreshRate']).then((settings) => { + time = settings.refreshRate; browser.alarms.create('fetchGitLab', { when: Date.now() }); diff --git a/src/background/utils/initGitlabApi.ts b/src/background/utils/initGitlabApi.ts index cb9a8cc..9aa85b7 100644 --- a/src/background/utils/initGitlabApi.ts +++ b/src/background/utils/initGitlabApi.ts @@ -14,11 +14,11 @@ export const initGitlabApi = (settings: GetSettingsResponse): GitlabAPI => { const account = settings.accounts[0]; - if (!account.token) { + if (!account.token || account.token === '') { throw new GitLabTokenNotSet(); } - if (!account.address) { + if (!account.address || account.address === '') { throw new GitLabAddressNotSet(); } diff --git a/src/common/types.ts b/src/common/types.ts index 764d837..573066b 100644 --- a/src/common/types.ts +++ b/src/common/types.ts @@ -5,6 +5,7 @@ export interface Configuration { defaultTab: TabId; accounts: Account[]; alertBadgeCounters: number[]; + refreshRate: number; } export interface Account { diff --git a/src/config/config.prod.ts b/src/config/config.prod.ts index 6823e51..cae9670 100644 --- a/src/config/config.prod.ts +++ b/src/config/config.prod.ts @@ -8,9 +8,10 @@ export const config = { { gitlabCE: false, token: '', - address: 'https://gitlab.com', + address: '', draftInToReviewTab: true, projectDirectoryPrefix: '' } - ] + ], + refreshRate: 60 } satisfies Configuration; diff --git a/src/options/App.tsx b/src/options/App.tsx index 42fe954..e486360 100644 --- a/src/options/App.tsx +++ b/src/options/App.tsx @@ -1,184 +1,46 @@ import * as browser from 'webextension-polyfill'; -import { useState, useCallback, useEffect } from 'react'; -import { - Button, - Box, - Checkbox, - TextInput, - Text, - Tooltip, - Octicon, - Link, - FormControl, - Select, - ThemeProvider -} from '@primer/react'; -import { - KeyIcon, - ServerIcon, - PackageDependenciesIcon, - CheckIcon, - InfoIcon, - ClockIcon, - FileDirectoryIcon -} from '@primer/octicons-react'; +import { useState, useEffect } from 'react'; +import { Box, TextInput, Tooltip, Octicon, FormControl, Select, ThemeProvider } from '@primer/react'; +import { CheckIcon, InfoIcon, ClockIcon } from '@primer/octicons-react'; import './style.css'; -import { updateConfiguration, readConfiguration, updateAccountConfiguration } from '../common/configuration'; -import { Account, TabId } from '../common/types'; +import { updateConfiguration, readConfiguration } from '../common/configuration'; +import { Account, Configuration, TabId } from '../common/types'; +import { AccountConfiguration } from './components/Account'; const getSettings = readConfiguration<{ accounts: Account[]; refreshRate: number; defaultTab: TabId; alertBadgeCounters: number[]; -}>(['accounts', 'refreshRate', 'defaultTab', 'alertBadgeCounters']); + mode: 'production' | 'development'; +}>(['accounts', 'refreshRate', 'defaultTab', 'alertBadgeCounters', 'mode']); export const App = () => { - const [gitlabCE, setGitlabCE] = useState(false); - const [gitlabToken, setGitlabToken] = useState(''); - const [gitlabAddress, setGitlabAddress] = useState(''); - const [refreshRate, setRefreshRate] = useState(40); - const [defaultTab, setDefaultTab] = useState('to_review'); - const [alertBadgeCounters, setAlertBadgeCounters] = useState([0]); - const [draftInToReviewTab, setDraftInToReviewTab] = useState(true); - const [projectDirectoryPrefix, setProjectDirectoryPrefix] = useState(''); - const [testSuccess, setTestSuccess] = useState(null); - const [isGitlabTokenInLocalStorage, setIsGitlabTokenInLocalStorage] = useState(false); - const [isGitlabAddressInLocalStorage, setIsGitlabAddressInLocalStorage] = useState(false); - const [isRefreshRateInLocalStorage, setIsRefreshRateInLocalStorage] = useState(false); + const [configuration, setConfiguration] = useState(); useEffect(() => { getSettings.then((settings) => { - setGitlabCE(Boolean(settings.accounts[0].gitlabCE)); - - setGitlabToken(settings.accounts[0].token ?? ''); - setIsGitlabTokenInLocalStorage(Boolean(settings.accounts[0].token)); - - setGitlabAddress(settings.accounts[0].address ?? ''); - setIsGitlabAddressInLocalStorage(Boolean(settings.accounts[0].address)); - - setRefreshRate(settings.refreshRate ?? 40); - setIsRefreshRateInLocalStorage(Boolean(settings.refreshRate)); - - setDefaultTab(settings.defaultTab ?? 'to_review'); - - setAlertBadgeCounters(settings.alertBadgeCounters ? Array.from(settings.alertBadgeCounters) : []); - - setDraftInToReviewTab(settings.accounts[0].draftInToReviewTab ?? true); - - setProjectDirectoryPrefix(settings.accounts[0].projectDirectoryPrefix ?? ''); + if (!settings) { + return; + } + setConfiguration(settings); }); }, []); - const updateGitlabCE = async (event: any) => { - setGitlabCE(event.target.checked); - await updateAccountConfiguration(0, { gitlabCE: event.target.checked }); - }; - - const updateGitlabToken = async (event: any) => { - setGitlabToken(event.target.value); - await updateAccountConfiguration(0, { gitlabToken: event.target.value }); - setIsGitlabTokenInLocalStorage(true); - }; - - const updateGitlabAddress = async (event: any) => { - setGitlabAddress(event.target.value); - await updateAccountConfiguration(0, { gitlabAddress: event.target.value }); - setIsGitlabAddressInLocalStorage(true); - }; - - const updateRefreshRate = async (event: any) => { - setRefreshRate(event.target.value); - await updateConfiguration({ refreshRate: parseInt(event.target.value) }); - setIsRefreshRateInLocalStorage(true); - await browser.runtime.sendMessage({ type: 'updateRefreshRate', interval: event.target.value }); - }; - - const updateDefaultTab = async (event: any) => { - setDefaultTab(event.target.value); - await updateConfiguration({ defaultTab: event.target.value }); + const updateConfigurationInMemory = async (data: Partial) => { + if (!configuration) { + return; + } + setConfiguration({ ...configuration, ...data }); + await updateConfiguration(data); + if (data.refreshRate) { + await browser.runtime.sendMessage({ type: 'updateRefreshRate', interval: data.refreshRate }); + } }; - const updateAlertBadgeCounters = async (event: any) => { - const options = [...event.target.selectedOptions].map((option) => parseInt(option.value)); - setAlertBadgeCounters(options); - await updateConfiguration({ alertBadgeCounters: options }); - }; - - const updateDraftInToReviewTab = async (event: any) => { - setDraftInToReviewTab(event.target.checked); - await updateAccountConfiguration(0, { draftInToReviewTab: event.target.checked }); - }; - - const updateProjectDirectoryPrefix = async (event: any) => { - setProjectDirectoryPrefix(event.target.value); - await updateAccountConfiguration(0, { projectDirectoryPrefix: event.target.value }); - }; - - const testConnection = useCallback(() => { - browser.runtime.sendMessage({ type: 'getLatestDataFromGitLab' }).then((success) => setTestSuccess(success)); - }, []); - return ( - - Using GitLab Community Edition - - (approvals are a premium feature) - - - - Personal GitLab Token{' '} - - - - - - - - - - - GitLab Host Address{' '} - - - - - - Refresh rate in seconds{' '} @@ -188,32 +50,35 @@ export const App = () => { updateConfigurationInMemory({ refreshRate: parseInt(e.target.value) })} /> Default tab - updateConfigurationInMemory({ defaultTab: e.target.value as TabId })} + > + To Review - + Under Review - + Drafts - + Issues - + To-Do List @@ -225,71 +90,31 @@ export const App = () => { - { + const options = [...event.target.selectedOptions].map((option) => parseInt(option.value)); + updateConfigurationInMemory({ alertBadgeCounters: options }); + }} + > + - - - - - View draft MRs in "To Review" tab - - - (merge requests marked as "Draft:" will be ignored if unchecked) - - - - - Projects directory prefix{' '} - - - - - - - - <> - - - {testSuccess === true ? 'Success' : 'Could not connect'} - - + {configuration?.accounts.map((account, index) => ( + + ))} ); diff --git a/src/options/components/Account.tsx b/src/options/components/Account.tsx new file mode 100644 index 0000000..e5fd8f4 --- /dev/null +++ b/src/options/components/Account.tsx @@ -0,0 +1,143 @@ +import * as browser from 'webextension-polyfill'; +import { useCallback, useState } from 'react'; +import { + InfoIcon, + KeyIcon, + CheckIcon, + ServerIcon, + FileDirectoryIcon, + PackageDependenciesIcon +} from '@primer/octicons-react'; +import { Button, Checkbox, FormControl, Link, Octicon, TextInput, Tooltip, Text } from '@primer/react'; +import { Account } from '../../common/types'; +import { updateAccountConfiguration } from '../../common/configuration'; + +interface Props { + accountIndex: number; + account: Account; +} + +export const AccountConfiguration = (props: Props) => { + const [testSuccess, setTestSuccess] = useState(null); + + const [account, setAccount] = useState(props.account); + + const setAccountConfiguration = async (data: Partial) => { + await updateAccountConfiguration(props.accountIndex, data); + setAccount({ ...account, ...data }); + }; + + const testConnection = useCallback(() => { + browser.runtime.sendMessage({ type: 'getLatestDataFromGitLab' }).then((success) => setTestSuccess(success)); + }, []); + + return ( + <> + + Using GitLab Community Edition + setAccountConfiguration({ gitlabCE: e.target.checked })} + checked={account.gitlabCE} + /> + (approvals are a premium feature) + + + + Personal GitLab Token{' '} + + + + + + + setAccountConfiguration({ token: e.target.value })} + aria-label="gitlab-token" + /> + + + + GitLab Host Address{' '} + + + + + setAccountConfiguration({ address: e.target.value })} + aria-label="gitlab-address" + /> + + + View draft MRs in "To Review" tab + setAccountConfiguration({ draftInToReviewTab: e.target.checked })} + checked={account.draftInToReviewTab} + /> + + (merge requests marked as "Draft:" will be ignored if unchecked) + + + + + Projects directory prefix{' '} + + + + + setAccountConfiguration({ projectDirectoryPrefix: e.target.value })} + aria-label="project-directory-prefix" + /> + + + <> + + + {testSuccess === true ? 'Success' : 'Could not connect'} + + + + ); +}; diff --git a/src/popup/components/Nav.tsx b/src/popup/components/Nav.tsx index f2011f7..d9fcf4d 100644 --- a/src/popup/components/Nav.tsx +++ b/src/popup/components/Nav.tsx @@ -14,7 +14,7 @@ export const Nav = (props: Props) => { const { currentTab, setCurrentTab, mrData } = props; const mrDataReviewRatio = - mrData?.mrReviewed || mrData?.mrGiven ? `${mrData.mrReviewed} / ${mrData.mrGiven.length}` : 'Unknown'; + mrData?.mrReviewed || mrData?.mrGiven ? `${mrData.mrReviewed} / ${mrData.mrGiven.length}` : undefined; const issuesCount = mrData?.issues ? mrData.issues.length : 0; interface NavItem { @@ -30,26 +30,26 @@ export const Nav = (props: Props) => { label: 'To Review', navigation: 'to_review', color: '220, 53, 69', - counter: mrData ? mrData.mrToReview : 0 + counter: mrData?.mrToReview }, { label: 'Under Review', navigation: 'under_review', color: '40, 167, 69', - counter: mrData ? mrDataReviewRatio : 0 + counter: mrDataReviewRatio }, { label: 'Drafts', navigation: 'drafts', color: '72, 72, 72', - counter: mrData?.myDrafts?.length ?? 0 + counter: mrData?.myDrafts?.length }, { label: 'Issues', navigation: 'issues', color: '253, 126, 20', counter: issuesCount }, { label: 'To-Do List', navigation: 'todo_list', color: '31, 120, 209', - counter: mrData?.todos ? mrData.todos.length : 0 + counter: mrData?.todos?.length }, { label: 'Pick', navigation: 'pick', color: '200, 200, 200', icon: CodeOfConductIcon } ];