diff --git a/src/options/components/TogglSection.js b/src/options/components/TogglSection.js new file mode 100644 index 0000000..92cfb21 --- /dev/null +++ b/src/options/components/TogglSection.js @@ -0,0 +1,105 @@ +import React, { useEffect, useState } from 'react'; +import StorageHandler from '../../storage/StorageHandler'; + +/** + * @typedef {Object} TogglConfig + * @property {boolean} enabled + * @property {string} token + * @property {string[]} breakTags + */ + +/** + * @param {TogglConfig} config + */ +async function updateConfig(config) { + await StorageHandler.setTogglConfig(config); + setConfig(config); +} + +/** + * @param {string[]} breakTags + * @returns {string} + */ +const breakTagsToText = (breakTags) => breakTags.join(', '); + +/** + * @param {string} text + * @returns {string[]} + */ +const textToBreakTags = text => text.split(',') + .map(t => t.trim()) + .filter(t => t); + +export default function TogglSection() { + const [enabled, setEnabled] = useState(); + const [token, setToken] = useState(); + const [breakTagsText, setBreakTagsText] = useState(); + const [loading, setLoading] = useState(true); + + const loadConfig = () => { + StorageHandler.getTogglConfig().then(config => { + setEnabled(config.enabled); + setToken(config.token); + setBreakTagsText(breakTagsToText(config.breakTags)); + setLoading(false); + }); + }; + + const save = async () => { + setLoading(true); + await StorageHandler.setTogglConfig({ + enabled, + token, + breakTags: textToBreakTags(breakTagsText), + }); + loadConfig(); + }; + + useEffect(() => { + loadConfig(); + }, []); + + return ( +
+

Toggl Integration Settings

+

+ Allow access to blocked websites when you're tracking a break in + Toggl +

+
+ {!loading && +
save()}> +
+ + setEnabled(!!e.target.checked)} + /> +
+
+ + setToken(e.target.value)} + /> +
+
+ + setBreakTagsText(e.target.value)} + /> +
+ +
+ } +
+ ); +} \ No newline at end of file diff --git a/src/options/index.js b/src/options/index.js index 1b177d3..8bef6c2 100644 --- a/src/options/index.js +++ b/src/options/index.js @@ -6,6 +6,7 @@ import MessageTypes from '../enums/messages'; import DomainListItem from './components/DomainListItem'; import SettingsSection from './components/SettingsSection'; import ExtensionStatus from './components/ExtensionStatus'; +import TogglSection from './components/TogglSection'; class Options extends React.Component { constructor(props) { @@ -153,6 +154,7 @@ class Options extends React.Component {
+ diff --git a/src/options/options.css b/src/options/options.css index 2d35990..e936026 100644 --- a/src/options/options.css +++ b/src/options/options.css @@ -178,3 +178,26 @@ hr { font-size: 20px; } } + +.toggl__header { + font-size: 1.8rem; + color: #1a1a1a; +} + +.toggl__subheader { + margin: 0; +} + +.toggl__link { + margin-left: 0.2em; +} + +.toggl__text-input { + margin-left: 0.4em; + width: 20em; +} + +.toggl__save { + margin-left: 0; + margin-top: 0.2em; +} \ No newline at end of file diff --git a/src/storage/StorageHandler.js b/src/storage/StorageHandler.js index 8d3506d..5e26f8c 100644 --- a/src/storage/StorageHandler.js +++ b/src/storage/StorageHandler.js @@ -20,6 +20,37 @@ export default class StorageHandler { return browser.storage.local.get('sites'); } + + /** + * @typedef {Object} TogglConfig + * @property {boolean} enabled + * @property {string} token + * @property {string[]} breakTags + */ + + /** + * @returns {Promise} + */ + static async getTogglConfig() { + const storage = await browser.storage.local.get('togglConfig'); + return storage.togglConfig || { + enabled: false, + token: '', + breakTags: [ + 'pomodoro-break', + 'break', + ], + }; + } + + /** + * + * @param {TogglConfig} togglConfig + */ + static setTogglConfig(togglConfig) { + return browser.storage.local.set({ togglConfig }); + } + static async isDomainBlocked(urlToMatch) { const websites = await StorageHandler.getWebsiteDomains(); return websites.includes(urlToMatch); diff --git a/src/utils/functions.js b/src/utils/functions.js index bbdab6e..62c2350 100644 --- a/src/utils/functions.js +++ b/src/utils/functions.js @@ -1,14 +1,51 @@ +/** + * @typedef {Object} TogglConfig + * @property {boolean} enabled + * @property {string} token + * @property {string[]} breakTags + */ +import StorageHandler from "../storage/StorageHandler"; + export function openOptionsPage() { browser.runtime.openOptionsPage(); window.close(); } -export function redirectToBlockedPage(requestDetails) { +export async function redirectToBlockedPage(requestDetails) { const original = encodeURIComponent(requestDetails.url); const interceptPage = `/resources/redirect.html?target=${original}`; + + const togglConfig = await StorageHandler.getTogglConfig(); + if (togglConfig.enabled && await isOnBreak(togglConfig)) { + return; + } + browser.tabs.update(requestDetails.tabId, { url: interceptPage }); } export function backgroundResponse(value) { return new Promise(res => res(value)); } + +/** + * @param togglConfig {TogglConfig} + * @returns {Promise} + */ +async function isOnBreak(togglConfig) { + const endpoint = 'https://www.toggl.com/api/v8/time_entries/current'; + const auth = `Basic ${btoa(`${togglConfig.token}:api_token`)}`; + + const response = await fetch(endpoint, { + headers: new Headers({ + Authorization: auth + }) + }); + if (!response.ok) return false; + + const body = await response.json(); + if (!body.data) return false; + if (!(body.data.tags instanceof Array)) return false; + + const breakTags = new Set(togglConfig.breakTags); + return body.data.tags.some(tag => breakTags.has(tag)); +}