From 6eb38837fc189153253a9c8c4a00a25acb008a7c Mon Sep 17 00:00:00 2001 From: Sergiusz Date: Thu, 8 Feb 2024 13:15:18 +0100 Subject: [PATCH 1/3] Implement darkmode --- images/logoTC-with-name-dark.svg | 12 +++++ package.json | 2 +- scripts-2.0/ApiService.js | 17 ++++++ scripts-2.0/PathService.js | 3 ++ scripts-2.0/StorageManager.js | 5 ++ scripts-2.0/background.js | 7 +++ scripts-2.0/components/Billable/styles.scss | 1 + scripts-2.0/components/ContextMenu/index.tsx | 14 ++++- .../components/ContextMenu/styles.scss | 8 ++- scripts-2.0/components/Dropdown/styles.scss | 11 ++-- scripts-2.0/components/Footer/styles.scss | 6 ++- scripts-2.0/components/Header/index.tsx | 31 +++++++---- scripts-2.0/components/Note/styles.scss | 8 +-- .../components/PopupMenu/Footer/styles.scss | 14 ++--- .../PopupMenu/WorkingTimerSection/styles.scss | 2 + scripts-2.0/components/PopupMenu/index.tsx | 28 +++++++--- scripts-2.0/components/PopupMenu/styles.scss | 7 ++- .../components/SearchInput/styles.scss | 4 +- scripts-2.0/components/TagPicker/styles.scss | 3 ++ scripts-2.0/components/TaskPicker/styles.scss | 14 ++--- .../components/TimeSelectors/styles.scss | 5 +- .../components/common/TimePicker/index.tsx | 2 +- .../components/common/TimePicker/styles.scss | 9 ++-- scripts-2.0/helpers/theme.ts | 36 +++++++++++++ scripts-2.0/style/common.scss | 52 +++++++++++++++++++ scripts-2.0/types/theme.ts | 1 + 26 files changed, 250 insertions(+), 52 deletions(-) create mode 100644 images/logoTC-with-name-dark.svg create mode 100644 scripts-2.0/helpers/theme.ts create mode 100644 scripts-2.0/types/theme.ts diff --git a/images/logoTC-with-name-dark.svg b/images/logoTC-with-name-dark.svg new file mode 100644 index 0000000..d1febd1 --- /dev/null +++ b/images/logoTC-with-name-dark.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/package.json b/package.json index c95592b..8f1ee89 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "timecamp-chrome-extension", - "version": "2.85.2", + "version": "2.85.45", "description": "TimeCamp Google Chrome Time Tracking Extension", "main": "index.js", "scripts": { diff --git a/scripts-2.0/ApiService.js b/scripts-2.0/ApiService.js index 5957dfc..6037d7c 100644 --- a/scripts-2.0/ApiService.js +++ b/scripts-2.0/ApiService.js @@ -999,4 +999,21 @@ export default class ApiService { method: "POST" }); } + getTheme(userId) { + return this.authorizeAndCall((token, resolve, reject) => { + this.call({ + url: pathService.getThemeUrl(userId), + method: METHOD_GET, + apiToken: token, + }) + .then((response) => { + let responseData = JSON.parse(response.response); + resolve(responseData); + }) + .catch((response) => { + logger.error(response); + reject(response); + }); + }); + } } diff --git a/scripts-2.0/PathService.js b/scripts-2.0/PathService.js index 69d5078..1cee1d1 100644 --- a/scripts-2.0/PathService.js +++ b/scripts-2.0/PathService.js @@ -172,4 +172,7 @@ export default class PathService { getUsersUrl(){ return this.serverUrl + "third_party/api/users?active_only=true"; } + getThemeUrl(userId) { + return this.serverUrl + `third_party/api/user/${userId}/setting?name=theme`; + } } diff --git a/scripts-2.0/StorageManager.js b/scripts-2.0/StorageManager.js index a486700..4e0a5a9 100644 --- a/scripts-2.0/StorageManager.js +++ b/scripts-2.0/StorageManager.js @@ -4,12 +4,17 @@ import Logger from './Logger'; const logger = new Logger(); const TRELLO_POWER_UP_AD_VISIBLE = 'trello_power_up_ad_visible'; +const TC_THEME = 'tc_theme' export default class StorageManager { static get TRELLO_POWER_UP_AD_VISIBLE() { return TRELLO_POWER_UP_AD_VISIBLE; } + static get TC_THEME() { + return TC_THEME; + } + buildKey(params) { return params.join('_'); } diff --git a/scripts-2.0/background.js b/scripts-2.0/background.js index 27cb2df..0865ed4 100644 --- a/scripts-2.0/background.js +++ b/scripts-2.0/background.js @@ -369,6 +369,13 @@ const TcButton = { reject(error); }); break; + case "getTheme": + apiService.getTheme(request.userId).then((response) => { + resolve(response); + }).catch((error) => { + reject(error); + }); + break; } } catch (e) { logger.error(e); diff --git a/scripts-2.0/components/Billable/styles.scss b/scripts-2.0/components/Billable/styles.scss index 35258e8..0e3edd4 100644 --- a/scripts-2.0/components/Billable/styles.scss +++ b/scripts-2.0/components/Billable/styles.scss @@ -14,6 +14,7 @@ cursor: pointer; padding-left: 26px; user-select: none; + color: var(--theme-color); } &__checkbox { diff --git a/scripts-2.0/components/ContextMenu/index.tsx b/scripts-2.0/components/ContextMenu/index.tsx index 76ee817..e1721ca 100644 --- a/scripts-2.0/components/ContextMenu/index.tsx +++ b/scripts-2.0/components/ContextMenu/index.tsx @@ -24,7 +24,9 @@ import TimeSelectors from "../TimeSelectors"; import DateTime from "../../helpers/DateTime"; import TimeFormatter from "../../TimeFormatter"; import WrongDatesError from "./Error/WrongDatesError"; - +import { TCTheme } from "../../types/theme"; +import { getTheme, retrieveThemeFromStorage } from "../../helpers/theme"; + const pathService = new PathService(); const logger = new Logger(); const dateTime = new DateTime(); @@ -66,6 +68,7 @@ const ContextMenu: React.FC = (props) => { const addTimeEntryCallback = props.addTimeEntryCallback; const onCloseCallback = props.onCloseCallback; const [userId, setUserId] = useState(0); + const [theme, setTheme] = useState("default"); const [isAdmin, setIsAdmin] = useState(false); const [clearTriggerForTimePicker, setClearTriggerForTimePicker] = useState(false); const [externalTaskId, setExternalTaskId] = useState(props.externalTaskId); @@ -111,6 +114,10 @@ const ContextMenu: React.FC = (props) => { }; }, [props]); + useEffect(()=>{ + retrieveThemeFromStorage(setTheme) + },[]) + useMemo(() => { if (service === TRELLO) { @@ -139,6 +146,7 @@ const ContextMenu: React.FC = (props) => { setUserId(parseInt(data.user_id)); setIsAdmin(data.permissions.role_administrator); getBackendIntegrationAdData(data.user_id); + getTheme(data.user_id, setTheme); }); if (billableInputVisibility === null) { @@ -204,6 +212,7 @@ const ContextMenu: React.FC = (props) => { }); }, []); + const getBackendIntegrationAdData = (userId: number) => { browser.runtime.sendMessage({ type: 'getUserSetting', @@ -385,6 +394,7 @@ const ContextMenu: React.FC = (props) => { //data-elevation is fix for trello card modal (it close when click on context menu) return (
= (props) => { -
+
= () => { - // @ts-ignore - return ( -
- TimeCamp logo -
- ); -} +const LOGOS_BY_THEME = { + default: tcLogo, + darkmode: tcLogoDark, +}; + +const Header: React.FC = ({ theme = "default" }) => { + + // @ts-ignore + return ( +
+ TimeCamp logo +
+ ); +}; export default Header; diff --git a/scripts-2.0/components/Note/styles.scss b/scripts-2.0/components/Note/styles.scss index 7115320..f1f9289 100644 --- a/scripts-2.0/components/Note/styles.scss +++ b/scripts-2.0/components/Note/styles.scss @@ -9,7 +9,9 @@ &__label { margin-top: 0; display: inline-block; + color: var(--theme-label-color); } + &__textarea { width: calc(100% - 30px); height: 81px; @@ -17,9 +19,9 @@ padding: 10px 15px 10px 15px; object-fit: contain; border-radius: 8px; - border: solid 1px $primaryBorderColor; - background-color: $primaryBackgroundColor; - color: $primaryTextColor; + border: solid 1px var(--theme-border-color-secondary); + background-color: var(--theme-background-color); + color: var(--theme-color); outline: none; resize: none; box-sizing: unset; diff --git a/scripts-2.0/components/PopupMenu/Footer/styles.scss b/scripts-2.0/components/PopupMenu/Footer/styles.scss index 9969ed1..81f09ab 100644 --- a/scripts-2.0/components/PopupMenu/Footer/styles.scss +++ b/scripts-2.0/components/PopupMenu/Footer/styles.scss @@ -10,6 +10,7 @@ border-top: solid 1px $popupBorderGray; padding-top: 20px; margin-top: 28px; + color: var(--theme-color); .user-img { height: 39px; @@ -68,11 +69,12 @@ } &__user-slide-window { - box-shadow: 0 6px 32px 0 $popupShadowGray; + box-shadow: var(--theme-box-shadow); padding: 10px; height: 114px; width: 265px; - background: $popupBackground; + background-color: var(--theme-background-color); + color: var(--theme-color); position: absolute; top: 25%; right: -1px; @@ -84,7 +86,7 @@ padding: 10px; &:hover { - background-color: $popupGrayThird; + background-color: var(--theme-background-hover); border-radius: 8px; } @@ -122,13 +124,13 @@ text-align: left; width: 100%; height: 35px; - background-color: white; - color: $popupGraySecond; + background-color: var(--theme-background-color); + color: var(--theme-color); font-family: $defaultFontFamily; padding: 9px; &:hover { - background-color: $popupGrayThird; + background-color: var(--theme-background-hover); border-radius: 8px; } diff --git a/scripts-2.0/components/PopupMenu/WorkingTimerSection/styles.scss b/scripts-2.0/components/PopupMenu/WorkingTimerSection/styles.scss index d435cbc..dcd7753 100644 --- a/scripts-2.0/components/PopupMenu/WorkingTimerSection/styles.scss +++ b/scripts-2.0/components/PopupMenu/WorkingTimerSection/styles.scss @@ -5,10 +5,12 @@ font-size: 14px; font-weight: normal; padding-top: 15px; + color: var(--theme-color); &__title { margin: 0 0 10px 7px; font-weight: bold; + color: var(--theme-label-color); } &__entry { diff --git a/scripts-2.0/components/PopupMenu/index.tsx b/scripts-2.0/components/PopupMenu/index.tsx index deb56d8..ea2e889 100644 --- a/scripts-2.0/components/PopupMenu/index.tsx +++ b/scripts-2.0/components/PopupMenu/index.tsx @@ -1,14 +1,15 @@ const browser = require('webextension-polyfill'); import * as React from "react"; +import { useEffect, useMemo, useState } from "react"; +import DateTime from "../../helpers/DateTime"; +import Gravatar from "../../helpers/Gravatar"; +import { getTheme, retrieveThemeFromStorage } from "../../helpers/theme"; +import ContextMenu from "../ContextMenu"; import Header from "../Header"; -import {useEffect, useState, useMemo} from "react"; -import WorkingTimerSection from "./WorkingTimerSection"; +import LoginWindow from "../LoginWindow"; import Footer from "./Footer"; +import WorkingTimerSection from "./WorkingTimerSection"; import './styles.scss'; -import LoginWindow from "../LoginWindow"; -import ContextMenu from "../ContextMenu"; -import Gravatar from "../../helpers/Gravatar"; -import DateTime from "../../helpers/DateTime"; const gravatar = new Gravatar(); const dateTime = new DateTime(); @@ -51,6 +52,17 @@ const PopupMenu: React.FC = (props) => { const [user, setUser] = useState(emptyUser); const [entry, setEntry] = useState(emptyEntry); const [isTimerWorking, setIsTimerWorking] = useState(false); + const [theme, setTheme] = useState<'default' | 'darkmode'>('default') + + useEffect(()=>{ + retrieveThemeFromStorage(setTheme) + browser.runtime.sendMessage({ + type:'getUserData' + }).then(data=>{ + getTheme(data.user_id, setTheme) + + }) + },[]) const setCurrentEntry = (currentEntry) => { if (currentEntry === null) { @@ -214,8 +226,8 @@ const PopupMenu: React.FC = (props) => { return (
-
-
+
+
= (props) => { }} />} - {SEPARATOR} + {SEPARATOR} { + browser.runtime.sendMessage({ + type: "getTheme", + userId: userId, + }) + .then(({ value }) => { + callback(value); + storeThemeInStorage(value) + }) + .catch(() => {}); + }; + + export const storeThemeInStorage = (theme: TCTheme) => { + browser.runtime.sendMessage({ + type: 'saveSettingToStorage', + name: StorageManager.TC_THEME, + value: theme + }).then(()=>{}) + .catch(()=>{}) + } + + export const retrieveThemeFromStorage = (callback) => { + browser.runtime.sendMessage({ + type:'getSettingFromStorage', + name: StorageManager.TC_THEME + }).then((value)=>{ + callback(value) + }).catch(()=>{ + + }) + } diff --git a/scripts-2.0/style/common.scss b/scripts-2.0/style/common.scss index 7cd1285..67c7301 100644 --- a/scripts-2.0/style/common.scss +++ b/scripts-2.0/style/common.scss @@ -56,3 +56,55 @@ $errorTextColor: #3c763d; @import url('https://fonts.googleapis.com/css2?family=Poppins&display=swap'); $defaultFontFamily: 'Poppins', sans-serif; $defaultFontSize: 14px; + +// theme +$themeDefaultBackgroundColor: #ffffff; +$themeDarkBackgroundColor: #222324; + +$themeDarkBackgroundColorSecondary: #323333; + +$themeDefaultBorderColor:#aea6ab; +$themeDefaultBorderColorSecondary: #dbdbdb; + +$themeDarkBorderColor: #646565; + +$themeDefaultBackgroundHover: #f8f8f8; +$themeDarkBackgroundHover: #565757; + +$themeDarkLabelColor: #BBBEBB; + +$themeDefaultBorderDisabledColor:#f2f2f2; +$themeDarkBorderDisabledColor: $themeDarkBorderColor; + +$themeDefaultColorDisabled: $disabledFontColor; +$themeDarkColorDisabled: rgb(111,111,111); + +@mixin theme-variables { + --theme-background-color: #{$themeDefaultBackgroundColor}; + --theme-background-color-secondary: #{$themeDefaultBackgroundColor}; + --theme-background-hover: #{$themeDefaultBackgroundHover}; + --theme-color: #{$primaryTextColor}; + --theme-task-picker-color: #{$taskPickerFontColor}; + --theme-color-disabled: #{$disabledFontColor}; + --theme-box-shadow: 0 6px 32px 0 #{$popupShadowGray}; + --theme-border-color: #{$themeDefaultBorderColor}; + --theme-border-color-secondary: #{$themeDefaultBorderColorSecondary}; + --theme-label-color: #{$primaryTextColor}; + --theme-border-display-color: #{$primaryBorderColor}; + --theme-border-disabled-color: #{$themeDefaultBorderDisabledColor}; + + &[data-theme='darkmode']{ + --theme-background-color: #{$themeDarkBackgroundColor}; + --theme-background-color-secondary:#{$themeDarkBackgroundColorSecondary}; + --theme-color-disabled: #{$themeDarkColorDisabled}; + --theme-color: white; + --theme-task-picker-color: white; + --theme-box-shadow: 0 6px 32px 0 black; + --theme-background-hover: #{$themeDarkBackgroundHover}; + --theme-border-color: #{$themeDarkBorderColor}; + --theme-border-color-secondary: #{$themeDarkBorderColor}; + --theme-label-color: #{$themeDarkLabelColor}; + --theme-border-display-color: transparent; + --theme-border-disabled-color: #{$themeDarkBorderDisabledColor}; + } +} \ No newline at end of file diff --git a/scripts-2.0/types/theme.ts b/scripts-2.0/types/theme.ts new file mode 100644 index 0000000..4787f24 --- /dev/null +++ b/scripts-2.0/types/theme.ts @@ -0,0 +1 @@ +export type TCTheme = "darkmode" | "default"; From 93a32ecc0ee3275881eb623c626d5341728a7c08 Mon Sep 17 00:00:00 2001 From: Sergiusz Date: Thu, 8 Feb 2024 09:09:28 +0100 Subject: [PATCH 2/3] Split timer into entry and timer --- _locales/en/messages.json | 3 + package.json | 2 +- scripts-2.0/components/Billable/styles.scss | 2 +- scripts-2.0/components/ContextMenu/index.tsx | 54 ++++-- .../components/ContextMenu/styles.scss | 10 +- scripts-2.0/components/ContextMenu/types.ts | 4 + scripts-2.0/components/Footer/index.tsx | 80 +++++---- scripts-2.0/components/Footer/styles.scss | 24 ++- scripts-2.0/components/Header/styles.scss | 1 + scripts-2.0/components/InfoBox/InfoBox.tsx | 35 ++++ scripts-2.0/components/InfoBox/index.tsx | 1 + .../infoboxesContent/InfoBoxErrorContent.tsx | 11 ++ .../InfoBoxSuccessContent.tsx | 24 +++ .../InfoBox/infoboxesContent/index.tsx | 7 + scripts-2.0/components/InfoBox/styles.scss | 37 ++++ scripts-2.0/components/InfoBox/types.ts | 4 + .../components/PopupMenu/Footer/index.tsx | 134 +++++--------- .../components/PopupMenu/Footer/styles.scss | 54 ++++-- .../components/PopupMenu/Header/Header.tsx | 62 +++++++ .../components/PopupMenu/Header/UserPopup.tsx | 77 ++++++++ .../components/PopupMenu/Header/index.tsx | 1 + .../components/PopupMenu/Header/styles.scss | 164 ++++++++++++++++++ .../PopupMenu/WorkingTimerSection/index.tsx | 8 +- .../PopupMenu/WorkingTimerSection/styles.scss | 16 +- scripts-2.0/components/PopupMenu/index.tsx | 140 +++++++++------ .../components/TimeSelectors/index.tsx | 53 +++--- scripts-2.0/helpers/Gravatar.js | 5 +- scripts-2.0/icons/index.tsx | 21 ++- scripts-2.0/icons/shutdown-icon.svg | 3 + scripts-2.0/icons/types.tsx | 3 + scripts-2.0/icons/user-circle-fas.svg | 3 + 31 files changed, 785 insertions(+), 258 deletions(-) create mode 100644 scripts-2.0/components/ContextMenu/types.ts create mode 100644 scripts-2.0/components/InfoBox/InfoBox.tsx create mode 100644 scripts-2.0/components/InfoBox/index.tsx create mode 100644 scripts-2.0/components/InfoBox/infoboxesContent/InfoBoxErrorContent.tsx create mode 100644 scripts-2.0/components/InfoBox/infoboxesContent/InfoBoxSuccessContent.tsx create mode 100644 scripts-2.0/components/InfoBox/infoboxesContent/index.tsx create mode 100644 scripts-2.0/components/InfoBox/styles.scss create mode 100644 scripts-2.0/components/InfoBox/types.ts create mode 100644 scripts-2.0/components/PopupMenu/Header/Header.tsx create mode 100644 scripts-2.0/components/PopupMenu/Header/UserPopup.tsx create mode 100644 scripts-2.0/components/PopupMenu/Header/index.tsx create mode 100644 scripts-2.0/components/PopupMenu/Header/styles.scss create mode 100644 scripts-2.0/icons/shutdown-icon.svg create mode 100644 scripts-2.0/icons/user-circle-fas.svg diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 6d82d89..a313082 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -231,6 +231,9 @@ "go_to_timesheet": { "message": "Go to Timesheet" }, + "see_full_timesheet": { + "message": "See full Timesheet" + }, "go_to_subscriton_page": { "message": "Go to subscription page" }, diff --git a/package.json b/package.json index 8f1ee89..e98a479 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "timecamp-chrome-extension", - "version": "2.85.45", + "version": "2.85.74", "description": "TimeCamp Google Chrome Time Tracking Extension", "main": "index.js", "scripts": { diff --git a/scripts-2.0/components/Billable/styles.scss b/scripts-2.0/components/Billable/styles.scss index 0e3edd4..1f4a91c 100644 --- a/scripts-2.0/components/Billable/styles.scss +++ b/scripts-2.0/components/Billable/styles.scss @@ -46,7 +46,7 @@ height: 16px; border-radius: 100%; background: #fff; - display: block;; + display: block; } input:checked ~ .checkmark { diff --git a/scripts-2.0/components/ContextMenu/index.tsx b/scripts-2.0/components/ContextMenu/index.tsx index e1721ca..704a524 100644 --- a/scripts-2.0/components/ContextMenu/index.tsx +++ b/scripts-2.0/components/ContextMenu/index.tsx @@ -40,7 +40,7 @@ export interface ContextMenuInterface { note: string, billable: boolean, startTimerCallback: Function, - addTimeEntryCallback: Function, + addTimeEntryCallback?: Function, onCloseCallback: Function, billableInputVisibility: boolean|null, externalTaskId: string, @@ -94,6 +94,19 @@ const ContextMenu: React.FC = (props) => { const THIRTY_DAYS_IN_MILISEC = 2592000000; + const isTimeEntryInValid = + props.service === "chrome_plugin" && + props.addTimeEntryCallback && + !stopTime; + + const shouldShowAddEntryButton = + props.service !== "chrome_plugin" + ? false + : props.addTimeEntryCallback + ? true + : false; + + useEffect(() => { setNote(props.note); @@ -307,7 +320,7 @@ const ContextMenu: React.FC = (props) => { }; const addTimeEntry = (startTime, stopTime) => { - addTimeEntryCallback( + addTimeEntryCallback?.( taskId, note, service, @@ -345,6 +358,7 @@ const ContextMenu: React.FC = (props) => { setClearTriggerForTimePicker(!clearTriggerForTimePicker); setOpen(false); setTaskId(0); + onCloseCallback() }; const renderTagPicker = () => { @@ -391,6 +405,13 @@ const ContextMenu: React.FC = (props) => { .replace('*linkClose*', ''))); }; + const onStopTimeValueChange = (value) => { + if (value !== null) { + setWrongDatesErrorMessage(''); + } + setStopTime(value); + } + //data-elevation is fix for trello card modal (it close when click on context menu) return (
= (props) => { const now = dateTime.getNow(); setWrongDatesErrorMessage((now < value && stopTime === null) ? translate('Start time cannot be greater than now') : ''); }} - onStopTimeValueChange={(value) => { - if (value !== null) { - setWrongDatesErrorMessage(''); - } - setStopTime(value); - }} + onStopTimeValueChange={ props.addTimeEntryCallback? onStopTimeValueChange : undefined} /> { billableInputVisibility && - {setBillable(newBillable)}} - /> +
+ {setBillable(newBillable)}} + /> +
}
); diff --git a/scripts-2.0/components/ContextMenu/styles.scss b/scripts-2.0/components/ContextMenu/styles.scss index cef1bad..274974e 100644 --- a/scripts-2.0/components/ContextMenu/styles.scss +++ b/scripts-2.0/components/ContextMenu/styles.scss @@ -9,7 +9,6 @@ font-weight: normal; color: $primaryTextColor; width: 440px; - height: 563px; box-sizing: border-box; border: #aea6ab 1px var(--theme-border-color); display: block; @@ -27,9 +26,12 @@ .context-menu-overflow-wrapper { overflow: auto; - height: calc(100% - 72px); padding: 20px; box-sizing: border-box; + + .billable-wrapper { + margin-top:20px; + } } &-extended-height { @@ -40,10 +42,6 @@ margin-top: 15px; } - .billable { - margin-top: 15px; - } - &__info-field { padding: 10px 14px 10px 16px; border-radius: 8px; diff --git a/scripts-2.0/components/ContextMenu/types.ts b/scripts-2.0/components/ContextMenu/types.ts new file mode 100644 index 0000000..f48d91c --- /dev/null +++ b/scripts-2.0/components/ContextMenu/types.ts @@ -0,0 +1,4 @@ +export enum ContextMenuType { + TIMER = "TIMER", + ENTRY = "ENTRY", +} diff --git a/scripts-2.0/components/Footer/index.tsx b/scripts-2.0/components/Footer/index.tsx index 6100973..9ffe720 100644 --- a/scripts-2.0/components/Footer/index.tsx +++ b/scripts-2.0/components/Footer/index.tsx @@ -2,48 +2,62 @@ import * as React from "react"; import translate from "../../Translator"; import Button from "../common/Button"; -import './styles.scss'; -import {IconName} from "../../icons/types"; +import "./styles.scss"; +import { IconName } from "../../icons/types"; import Icon from "../Icon"; export interface FooterInterface { - onClickSave: any, - onClickCancel: any, - idDisabled?: boolean - showAddEntryButton?: boolean + onClickSave: any; + onClickCancel: any; + idDisabled?: boolean; + showAddEntryButton?: boolean; } const Footer: React.FC = (props) => { - const renderStartTimerButton = () => { - return -   - {translate('start_timer')} - - } - - const renderAddEntryButton = () => { - return - {translate('add_time_entry')} - - } - + const renderStartTimerButton = () => { return ( -
-
- - - -
+ + + {" "} + {translate("start_timer")} +    + +
+ +  
+
+ ); + }; + + const renderAddEntryButton = () => { + return ( + + {translate("add_time_entry")} + ); + }; + + return ( +
+
+ + + +
+
+ ); }; export default Footer; diff --git a/scripts-2.0/components/Footer/styles.scss b/scripts-2.0/components/Footer/styles.scss index faa3dc7..6817d10 100644 --- a/scripts-2.0/components/Footer/styles.scss +++ b/scripts-2.0/components/Footer/styles.scss @@ -1,18 +1,17 @@ -@import '../../style/common'; +@import "../../style/common"; .timecamp { .footer { - position: absolute; - bottom: 0; - margin: 0; - padding: 20px 0 20px 0; + bottom: 0; width: 100%; border-top: var(--theme-border-display-color) 1px solid; background-color: inherit; &__buttons { - float: right; + width: 100%; margin: 0 21px 0 0; + padding: 1rem; + display: flex; } &__button-save { @@ -21,7 +20,17 @@ border: none; font-family: $defaultFontFamily; font-size: $defaultFontSize; - width: 133px; + width: unset; + margin-left: auto; + margin-right: 33px; + display: flex; + align-items: center; + justify-content: center; + white-space: nowrap; + + & span { + text-transform: uppercase; + } } &__button-cancel { @@ -30,6 +39,7 @@ font-size: $defaultFontSize; background-color: var(--theme-background-color); color: var(--theme-color); + margin-right: 10px; } &__button-disabled { diff --git a/scripts-2.0/components/Header/styles.scss b/scripts-2.0/components/Header/styles.scss index 4706403..102711f 100644 --- a/scripts-2.0/components/Header/styles.scss +++ b/scripts-2.0/components/Header/styles.scss @@ -3,6 +3,7 @@ .timecamp { .header { margin: 0 0 30px 0; + display: flex; &__logo-svg { top: 5px; diff --git a/scripts-2.0/components/InfoBox/InfoBox.tsx b/scripts-2.0/components/InfoBox/InfoBox.tsx new file mode 100644 index 0000000..977abe7 --- /dev/null +++ b/scripts-2.0/components/InfoBox/InfoBox.tsx @@ -0,0 +1,35 @@ +import * as React from "react"; +import "./styles.scss"; +import { InfoBoxStatus } from "./types"; +import InfoboxesContent from "./infoboxesContent"; + +interface InfoBoxProps { + children?: React.ReactNode; + onClose: () => void; + + status: InfoBoxStatus | undefined; +} + +const contentByStatus = { + [InfoBoxStatus.SUCCESS]: , + [InfoBoxStatus.ERROR]: , +}; + +const InfoBox = ({ onClose, status, children = null }: InfoBoxProps) => { + if (!status) return null; + + const selectedContent = contentByStatus[status] || null; + + return ( +
+ {selectedContent} + {children} + + + X + +
+ ); +}; + +export default InfoBox; diff --git a/scripts-2.0/components/InfoBox/index.tsx b/scripts-2.0/components/InfoBox/index.tsx new file mode 100644 index 0000000..303f07e --- /dev/null +++ b/scripts-2.0/components/InfoBox/index.tsx @@ -0,0 +1 @@ +export { default } from "./InfoBox"; diff --git a/scripts-2.0/components/InfoBox/infoboxesContent/InfoBoxErrorContent.tsx b/scripts-2.0/components/InfoBox/infoboxesContent/InfoBoxErrorContent.tsx new file mode 100644 index 0000000..fcc1e41 --- /dev/null +++ b/scripts-2.0/components/InfoBox/infoboxesContent/InfoBoxErrorContent.tsx @@ -0,0 +1,11 @@ +import * as React from "react"; + +const InfoBoxError = () => { + return ( + + There was problem when creating time entry. + + ); +}; + +export default InfoBoxError; diff --git a/scripts-2.0/components/InfoBox/infoboxesContent/InfoBoxSuccessContent.tsx b/scripts-2.0/components/InfoBox/infoboxesContent/InfoBoxSuccessContent.tsx new file mode 100644 index 0000000..2911544 --- /dev/null +++ b/scripts-2.0/components/InfoBox/infoboxesContent/InfoBoxSuccessContent.tsx @@ -0,0 +1,24 @@ +import * as React from "react"; +import Icon from "../../Icon"; +import { IconName } from "../../../icons/types"; +import PathService from "../../../PathService" + +const serverUrl = new PathService().getBaseUrl(); + +const InfoBoxSuccess = () => { + return ( + + +   Time entry added.  + + Edit in Timesheet. + + + ); +}; + +export default InfoBoxSuccess; diff --git a/scripts-2.0/components/InfoBox/infoboxesContent/index.tsx b/scripts-2.0/components/InfoBox/infoboxesContent/index.tsx new file mode 100644 index 0000000..014fe14 --- /dev/null +++ b/scripts-2.0/components/InfoBox/infoboxesContent/index.tsx @@ -0,0 +1,7 @@ +import InfoBoxError from "./InfoBoxErrorContent"; +import InfoBoxSuccess from "./InfoBoxSuccessContent"; + +export default { + success: InfoBoxSuccess, + error: InfoBoxError, +}; diff --git a/scripts-2.0/components/InfoBox/styles.scss b/scripts-2.0/components/InfoBox/styles.scss new file mode 100644 index 0000000..f3c549e --- /dev/null +++ b/scripts-2.0/components/InfoBox/styles.scss @@ -0,0 +1,37 @@ +.infobox { + display: flex; + gap: 0.5rem; + color: #17b54e; + background-color: #f1f9f3; + border-radius: 8px; + padding: 10px; + align-items: center; + margin-top: 7px; + + & a { + color: #17b54e; + font-weight: 900; + } + + & span { + color: #17b54e; + text-decoration-color: #17b54e; + } + + &__cross { + font-size: 16px; + margin-left: auto; + cursor: pointer; + } + + &[data-status="ERROR"] { + color: #8c0517; + background-color: #e375bb; + & a { + color: #8c0517; + } + & span { + color: #8c0517; + } + } +} diff --git a/scripts-2.0/components/InfoBox/types.ts b/scripts-2.0/components/InfoBox/types.ts new file mode 100644 index 0000000..6777aef --- /dev/null +++ b/scripts-2.0/components/InfoBox/types.ts @@ -0,0 +1,4 @@ +export enum InfoBoxStatus { + SUCCESS = "SUCCESS", + ERROR = "ERROR", +} diff --git a/scripts-2.0/components/PopupMenu/Footer/index.tsx b/scripts-2.0/components/PopupMenu/Footer/index.tsx index d78c7c1..6351d54 100644 --- a/scripts-2.0/components/PopupMenu/Footer/index.tsx +++ b/scripts-2.0/components/PopupMenu/Footer/index.tsx @@ -1,102 +1,58 @@ import * as React from "react"; -import {useEffect, useRef, useState} from "react"; -import './styles.scss'; +import "./styles.scss"; import Button from "../../common/Button"; import Icon from "../../Icon"; -import {IconName} from "../../../icons/types"; -import {User} from "../index"; +import { IconName } from "../../../icons/types"; +import { User } from "../index"; import translate from "../../../Translator"; +import PathService from "../../../PathService"; +import { ContextMenuType } from "../../ContextMenu/types"; export interface FooterInterface { - isUserWindowOpen: boolean, - logoutCallback: Function, - onPlusButtonClickCallback: Function, - user: User + logoutCallback: Function; + onPlusButtonClickCallback: Function; + user: User; + onSetContextMenuType?: React.Dispatch< + React.SetStateAction + >; } -const Footer: React.FC = (props) => { - const node = useRef(null); - const [isUserWindowOpen, setIsUserWindowOpen] = useState(props.isUserWindowOpen); - const [user, setUser] = useState(props.user); - const logoutCallback = props.logoutCallback; - const onPlusButtonClickCallback = props.onPlusButtonClickCallback; - const serverUrl = process.env.SERVER_PROTOCOL + '://' + process.env.SERVER_DOMAIN + '/'; - - useEffect(() => { - setUser(props.user); - }, [props.user]); - - useEffect(() => { - setIsUserWindowOpen(props.isUserWindowOpen); - document.addEventListener("click", onClickOutside); - - return () => { - document.removeEventListener("click", onClickOutside); - }; - }, [props]); - - const onClickOutside = e => { - if (e.target.classList.contains('user-img')) { - return; - } +const serverUrl = new PathService().getBaseUrl(); - const shouldStayOpen = node.current !== null && node.current.contains(e.target); - if (!shouldStayOpen) { - setIsUserWindowOpen(false); - } - }; - - return ( -
- - -
{setIsUserWindowOpen(!isUserWindowOpen)}}> - -
- - +const Footer: React.FC = (props) => { + const { onSetContextMenuType } = props; + return ( +
+ + + + + +
+ ); }; export default Footer; diff --git a/scripts-2.0/components/PopupMenu/Footer/styles.scss b/scripts-2.0/components/PopupMenu/Footer/styles.scss index 81f09ab..48c4cdd 100644 --- a/scripts-2.0/components/PopupMenu/Footer/styles.scss +++ b/scripts-2.0/components/PopupMenu/Footer/styles.scss @@ -1,4 +1,4 @@ -@import '../../../style/common'; +@import "../../../style/common"; .tc-popup-footer { font-family: $defaultFontFamily; @@ -7,31 +7,50 @@ display: flex; justify-content: space-between; - border-top: solid 1px $popupBorderGray; padding-top: 20px; margin-top: 28px; color: var(--theme-color); + margin-top: 10px; - .user-img { - height: 39px; - border-radius: 50%; + &__button-anchor { + display: flex; + justify-content: center; + align-items: flex-end; - &:hover { - border: solid 1px $popupGreen; - padding: 5px; - margin: -6px; + &__href { + color: #2380e3; + font-size: 12px; + } + & svg { + margin-right: 0.3rem; } } - - &__button-add { + &__button-cancel { + border: solid 1px $primaryBorderColor; + font-family: $defaultFontFamily; + font-size: $defaultFontSize; width: 39px; height: 39px; + background-color: transparent; + color: #4f4f4f; + border-radius: 8px; + cursor: pointer; + margin-left: auto; + margin-right: 15px; + } + &__button-add { + width: 133px; + height: 39px; border-radius: 8px; background-color: $popupGreen; border: none; color: white; cursor: pointer; - + display: flex; + justify-content: flex-start; + align-items: center; + padding: 0 0 0 0.5rem; + &:hover { opacity: 0.8; } @@ -39,6 +58,17 @@ svg { font-size: 17px; } + + &__icon { + flex: 1; + display: flex; + justify-content: center; + align-items: center; + } + + & span { + text-transform: uppercase; + } } &__user-img { diff --git a/scripts-2.0/components/PopupMenu/Header/Header.tsx b/scripts-2.0/components/PopupMenu/Header/Header.tsx new file mode 100644 index 0000000..b19d556 --- /dev/null +++ b/scripts-2.0/components/PopupMenu/Header/Header.tsx @@ -0,0 +1,62 @@ +import * as React from "react"; +// @ts-ignore +import tcLogo from "../../../../images/logoTC-with-name.svg"; + +// @ts-ignore +import AvatarIconSVG from "../../../icons/user-circle-fas.svg"; +import { User } from ".."; +import "./styles.scss"; + +import UserPopup from "./UserPopup"; + +export interface HeaderInterface { + user: User; + setIsUserWindowOpen: React.Dispatch>; + isUserWindowOpen: boolean; + logoutCallback: () => void; +} + +const Header: React.FC = ({ + user, + isUserWindowOpen, + setIsUserWindowOpen, + logoutCallback, +}) => { + const handleClickUserAvatar = (e) => { + if (isUserWindowOpen) return; + + setIsUserWindowOpen(true); + e.stopPropagation(); + }; + + return ( +
+ TimeCamp logo +
+
+ +
+
+ + +
+ ); +}; + +export default Header; diff --git a/scripts-2.0/components/PopupMenu/Header/UserPopup.tsx b/scripts-2.0/components/PopupMenu/Header/UserPopup.tsx new file mode 100644 index 0000000..efc0572 --- /dev/null +++ b/scripts-2.0/components/PopupMenu/Header/UserPopup.tsx @@ -0,0 +1,77 @@ +import * as React from "react"; +import translate from "../../../Translator.js"; +import Button from "../../common/Button/"; +import "./styles.scss"; +import PathService from "../../../PathService.js"; + +// @ts-ignore +import ShutdownIconSVG from "../../../icons/shutdown-icon.svg"; + +const serverUrl = new PathService().getBaseUrl(); + +const UserPopup = ({ + setIsUserWindowOpen, + isUserWindowOpen, + user, + logoutCallback, +}) => { + const node = React.useRef(null); + const wrapperClassName = `tc-popup-header__user-slide-window ${isUserWindowOpen ? "tc-popup-header__user-slide-window--visible" : ""}`; + + React.useEffect(() => { + document.addEventListener("click", onClickOutside); + + return () => { + document.removeEventListener("click", onClickOutside); + }; + }, [setIsUserWindowOpen]); + + const onClickOutside = (e) => { + const shouldStayOpen = + node.current !== null && node.current.contains(e.target); + if (!shouldStayOpen) { + setIsUserWindowOpen(false); + } + }; + + return ( + + ); +}; + +export default UserPopup; diff --git a/scripts-2.0/components/PopupMenu/Header/index.tsx b/scripts-2.0/components/PopupMenu/Header/index.tsx new file mode 100644 index 0000000..2764567 --- /dev/null +++ b/scripts-2.0/components/PopupMenu/Header/index.tsx @@ -0,0 +1 @@ +export { default } from "./Header"; diff --git a/scripts-2.0/components/PopupMenu/Header/styles.scss b/scripts-2.0/components/PopupMenu/Header/styles.scss new file mode 100644 index 0000000..286114b --- /dev/null +++ b/scripts-2.0/components/PopupMenu/Header/styles.scss @@ -0,0 +1,164 @@ +@import "../../../style/common"; + +@mixin before-divider-mixin { + position: relative; + &:before { + content: ""; + top: 0; + left: 50%; + height: 1px; + width: 90%; + z-index: 10; + transform: translateX(-50%); + + background: $popupBorderGray; + position: absolute; + } +} + +.tc-popup-header { + display: flex; + + &__logo-svg { + top: 5px; + } + + &__user-logo { + margin-left: auto; + + &__user-img { + cursor: pointer; + + &__avatar { + height: 38px; + border-radius: 50%; + border: 1px solid transparent; + box-shadow: 0 0 0 2px transparent; + &[data-status="active"] { + box-shadow: 0 0 0 2px #4bb063; + } + } + } + } + &__user-slide-window { + box-shadow: 0 6px 32px 0 $popupShadowGray; + padding: 10px; + width: 265px; + background: $popupBackground; + + position: absolute; + top: 25%; + right: -1px; + display: none; + + &--visible { + display: block; + } + + .section-top { + padding: 10px; + + &:hover { + background-color: $popupGrayThird; + border-radius: 8px; + } + + a { + display: flex; + text-decoration: none; + + &:visited { + color: inherit; + } + } + + &__image { + display: flex; + justify-content: center; + align-items: center; + + .user-img { + height: 39px; + border-radius: 50%; + + &:hover { + border: solid 1px $popupGreen; + padding: 5px; + margin: -6px; + } + } + } + + &__user { + padding-left: 10px; + + &__display-name { + font-size: 14px; + font-weight: 400; + color: $primaryTextColor; + } + + &__email { + white-space: nowrap; + color: $secondaryTextColor; + font-size: 10px; + } + } + } + + .section-middle-timecamp { + display: flex; + color: inherit; + text-decoration: none; + align-items: center; + padding: 15px; + padding-left: 9px; + + @include before-divider-mixin; + + & img { + margin-right: 10px; + margin-left: 5px; + } + + &:hover { + background-color: $popupGrayThird; + border-radius: 8px; + } + } + + .section-bottom { + padding: 15px; + + border: none; + cursor: pointer; + font-size: 14px; + text-align: left; + width: 100%; + + color: $popupGraySecond; + font-family: $defaultFontFamily; + background-color: white; + + display: flex; + align-items: center; + + &:hover { + background-color: $popupGrayThird; + border-radius: 8px; + + & button { + background-color: $popupGrayThird; + } + } + img { + color: $popupGray; + font-size: 18px; + + margin-right: 15px; + } + + @include before-divider-mixin; + } + } +} diff --git a/scripts-2.0/components/PopupMenu/WorkingTimerSection/index.tsx b/scripts-2.0/components/PopupMenu/WorkingTimerSection/index.tsx index 34e9a2d..d8fbebb 100644 --- a/scripts-2.0/components/PopupMenu/WorkingTimerSection/index.tsx +++ b/scripts-2.0/components/PopupMenu/WorkingTimerSection/index.tsx @@ -6,7 +6,8 @@ import tcStopButton from "../../../icons/stop-button-small.svg"; import {Entry} from "../index"; import translate from "../../../Translator"; import TimeFormatter, {DURATION_FORMATS} from "../../../TimeFormatter"; - +import Icon from '../../Icon' +import {IconName} from '../../../icons/types' const timeFormatter = new TimeFormatter(); export interface WorkingTimerSectionInterface { @@ -56,7 +57,10 @@ const WorkingTimerSection: React.FC = (props) => {
{entry.taskName}
{entry.breadcrumb &&
{entry.breadcrumb}
} -
{entry.note}
+ {entry.note &&
+     + {entry.note} +
}
diff --git a/scripts-2.0/components/PopupMenu/WorkingTimerSection/styles.scss b/scripts-2.0/components/PopupMenu/WorkingTimerSection/styles.scss index dcd7753..6373c1c 100644 --- a/scripts-2.0/components/PopupMenu/WorkingTimerSection/styles.scss +++ b/scripts-2.0/components/PopupMenu/WorkingTimerSection/styles.scss @@ -1,4 +1,4 @@ -@import '../../../style/common'; +@import "../../../style/common"; .working-timer { font-family: $defaultFontFamily; @@ -14,7 +14,7 @@ } &__entry { - border: solid 1px $popupBorderGray; + border: dashed 2px $popupBorderGray; border-radius: 8px; width: 360px; height: 52px; @@ -23,6 +23,10 @@ align-items: center; padding: 15px; + &--active { + border-style: solid; + } + .entry-empty-info { color: $popupDisabledFontColor; } @@ -82,7 +86,13 @@ .right { display: flex; - flex-direction: column; + flex-direction: row; + align-items: center; + white-space: nowrap; + + & .entry__timer{ + margin-right:10px; + } } } diff --git a/scripts-2.0/components/PopupMenu/index.tsx b/scripts-2.0/components/PopupMenu/index.tsx index ea2e889..417a08a 100644 --- a/scripts-2.0/components/PopupMenu/index.tsx +++ b/scripts-2.0/components/PopupMenu/index.tsx @@ -1,16 +1,20 @@ -const browser = require('webextension-polyfill'); +const browser = require("webextension-polyfill"); import * as React from "react"; import { useEffect, useMemo, useState } from "react"; import DateTime from "../../helpers/DateTime"; import Gravatar from "../../helpers/Gravatar"; import { getTheme, retrieveThemeFromStorage } from "../../helpers/theme"; import ContextMenu from "../ContextMenu"; -import Header from "../Header"; +import Header from "./Header"; import LoginWindow from "../LoginWindow"; import Footer from "./Footer"; import WorkingTimerSection from "./WorkingTimerSection"; import './styles.scss'; +import { ContextMenuType } from "../ContextMenu/types"; +import InfoBox from "../InfoBox"; +import { InfoBoxStatus } from "../InfoBox/types"; + const gravatar = new Gravatar(); const dateTime = new DateTime(); @@ -45,13 +49,7 @@ const PopupMenu: React.FC = (props) => { breadcrumb: null, color: '#aaa', } - - const [isUserWindowOpen, setIsUserWindowOpen] = useState(false); - const [isContextMenuOpen, setIsContextMenuOpen] = useState(false); - const [isUserLogged, setIsUserLogged] = useState(false); - const [user, setUser] = useState(emptyUser); - const [entry, setEntry] = useState(emptyEntry); - const [isTimerWorking, setIsTimerWorking] = useState(false); + const [theme, setTheme] = useState<'default' | 'darkmode'>('default') useEffect(()=>{ @@ -63,12 +61,22 @@ const PopupMenu: React.FC = (props) => { }) },[]) + const [isUserWindowOpen, setIsUserWindowOpen] = useState(false); + const [contextMenuType, setContextMenuType] = useState< + ContextMenuType | undefined + >(undefined); + const [isUserLogged, setIsUserLogged] = useState(false); + const [user, setUser] = useState(emptyUser); + const [entry, setEntry] = useState(emptyEntry); + const [isTimerWorking, setIsTimerWorking] = useState(false); + const [timeEntryAddedInfoBoxStatus, settimeEntryAddedInfoBoxStatus] = + useState(undefined); - const setCurrentEntry = (currentEntry) => { - if (currentEntry === null) { - setIsTimerWorking(false); - return; - } + const setCurrentEntry = (currentEntry) => { + if (currentEntry === null) { + setIsTimerWorking(false); + return; + } let entry = { taskName: currentEntry.description !== undefined ? currentEntry.description : emptyEntry.taskName, @@ -105,6 +113,7 @@ const PopupMenu: React.FC = (props) => { type: 'logOut' }).then(() => { setIsUserWindowOpen(false); + setContextMenuType(undefined); setIsUserLogged(false); setUser(emptyUser); }).catch(() => { @@ -148,7 +157,8 @@ const PopupMenu: React.FC = (props) => { buttonHash: buttonHash }).then((response) => { setIsTimerWorking(true); - setIsContextMenuOpen(false); + setContextMenuType(undefined); + let entry = { taskName: response.name === null ? emptyEntry.taskName : response.name, @@ -188,8 +198,10 @@ const PopupMenu: React.FC = (props) => { service: service, buttonHash: buttonHash }).then((response) => { + settimeEntryAddedInfoBoxStatus(InfoBoxStatus.SUCCESS); resolve(response); }).catch((response) => { + settimeEntryAddedInfoBoxStatus(InfoBoxStatus.ERROR); reject(response); }); }); @@ -224,48 +236,68 @@ const PopupMenu: React.FC = (props) => { }); }, []); - return ( -
-
-
- -
{setIsContextMenuOpen(true);}} - /> -
+ return ( +
+
+
+ settimeEntryAddedInfoBoxStatus(undefined)} + /> + +
{}} + onSetContextMenuType={setContextMenuType} + /> +
- {!isUserLogged &&
- -
} + {!isUserLogged && ( +
+ +
+ )} - {isContextMenuOpen &&
- {setIsContextMenuOpen(false)}} - embedOnPopup={true} - /> -
} + {contextMenuType && ( +
+ { + setContextMenuType(undefined); + }} + embedOnPopup={true} + />
- ); + )} +
+ ); }; export default PopupMenu; diff --git a/scripts-2.0/components/TimeSelectors/index.tsx b/scripts-2.0/components/TimeSelectors/index.tsx index 4257a9d..824abfa 100644 --- a/scripts-2.0/components/TimeSelectors/index.tsx +++ b/scripts-2.0/components/TimeSelectors/index.tsx @@ -16,7 +16,7 @@ export interface TimeSelectorsInterface { stopTime: Date|null, durationFormat?: number|null, onStartTimeValueChange(newValue: any): any - onStopTimeValueChange(newValue: any): any + onStopTimeValueChange?(newValue: any): any } const TimeSelectors: React.FC = (props) => { @@ -61,7 +61,7 @@ const TimeSelectors: React.FC = (props) => { }, [startTime]); useEffect(() => { - props.onStopTimeValueChange(stopTime); + props.onStopTimeValueChange?.(stopTime); validateBoth(); validateStartTime(); calculateAndSetDuration(); @@ -138,29 +138,34 @@ const TimeSelectors: React.FC = (props) => { />
+ { + props.onStopTimeValueChange ? +
+ + { + if (value != undefined) { + value = new Date(value.getTime()); + } + setStopTime(value); + }} + /> +
+
+ + +
+
: null + } + -
- - { - if (value != undefined) { - value = new Date(value.getTime()); - } - setStopTime(value); - }} - /> -
-
- - -
); } diff --git a/scripts-2.0/helpers/Gravatar.js b/scripts-2.0/helpers/Gravatar.js index 45e176d..4dc9e51 100644 --- a/scripts-2.0/helpers/Gravatar.js +++ b/scripts-2.0/helpers/Gravatar.js @@ -1,5 +1,8 @@ import md5 from 'crypto-js/md5'; +//@ts-ignore +import AvatarIconSVG from "../icons/user-circle-fas.svg"; + const AVATAR_URL = 'https://www.gravatar.com/avatar/'; export default class Gravatar { @@ -15,6 +18,6 @@ export default class Gravatar { } getDefaultImageUrl = () => { - return AVATAR_URL + 'aaa?s=120&d=mm'; + return AvatarIconSVG; } } diff --git a/scripts-2.0/icons/index.tsx b/scripts-2.0/icons/index.tsx index 934e190..7f92e81 100644 --- a/scripts-2.0/icons/index.tsx +++ b/scripts-2.0/icons/index.tsx @@ -1,4 +1,4 @@ -import { library } from '@fortawesome/fontawesome-svg-core'; +import { library } from "@fortawesome/fontawesome-svg-core"; import { faSync, faCaretUp, @@ -18,7 +18,6 @@ import { faUndo, faTimes, faAsterisk, - faUserCircle, faSpinner, faBriefcase, faFileInvoiceDollar, @@ -31,8 +30,8 @@ import { faTimesCircle, faPlus, faHeartBroken, - faDollarSign, -} from '@fortawesome/pro-solid-svg-icons'; + faDollarSign +} from "@fortawesome/pro-solid-svg-icons"; import { faTimesCircle as farTimesCircle, @@ -41,8 +40,9 @@ import { faCalendarAlt as farCalendarAlt, faSignOutAlt as farSignOutAlt, faChartPie as farChartPie, - faClock as farClock -} from '@fortawesome/pro-regular-svg-icons'; + faClock as farClock, + faStickyNote as farStickyNote +} from "@fortawesome/pro-regular-svg-icons"; import { faGrinBeamSweat as falGrinBeamSweat, @@ -60,9 +60,10 @@ import { faUserClock as falUserClock, faChevronDown as falChevronDown, faChevronUp as falChevronUp, -} from '@fortawesome/pro-light-svg-icons'; + faExternalLink as falExternalLink, +} from "@fortawesome/pro-light-svg-icons"; -import { faGoogle } from '@fortawesome/free-brands-svg-icons'; +import { faGoogle } from "@fortawesome/free-brands-svg-icons"; /* Add icons to library */ library.add( @@ -85,7 +86,7 @@ library.add( faTimes, faAsterisk, faSpinner, - faUserCircle, + faBriefcase, faFileInvoiceDollar, faCalendarCheck, @@ -121,4 +122,6 @@ library.add( falUserClock, falChevronDown, falChevronUp, + falExternalLink, + farStickyNote ); diff --git a/scripts-2.0/icons/shutdown-icon.svg b/scripts-2.0/icons/shutdown-icon.svg new file mode 100644 index 0000000..601ecdc --- /dev/null +++ b/scripts-2.0/icons/shutdown-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/scripts-2.0/icons/types.tsx b/scripts-2.0/icons/types.tsx index 60200b7..ed595a4 100644 --- a/scripts-2.0/icons/types.tsx +++ b/scripts-2.0/icons/types.tsx @@ -51,4 +51,7 @@ export enum IconName { CHART_PIE = 'chart-pie', CLOCK = 'clock', USER_CLOCK = 'user-clock', + EXTERNAL_LINK='external-link', + POWER_OFF='power-off', + STICKY_NOTE='sticky-note' } diff --git a/scripts-2.0/icons/user-circle-fas.svg b/scripts-2.0/icons/user-circle-fas.svg new file mode 100644 index 0000000..a1e22fd --- /dev/null +++ b/scripts-2.0/icons/user-circle-fas.svg @@ -0,0 +1,3 @@ + + + From c633c6bdd0a332023b214208c20a9897729f814e Mon Sep 17 00:00:00 2001 From: Sergiusz Date: Wed, 21 Feb 2024 15:48:17 +0100 Subject: [PATCH 3/3] Implement pomodoro --- _locales/en/messages.json | 9 + scripts-2.0/StorageManager.js | 22 +- scripts-2.0/components/Checkbox/Checkbox.tsx | 3 +- scripts-2.0/components/ContextMenu/index.tsx | 3 + .../DurationSelector/DurationSelector.tsx | 83 +++ .../components/DurationSelector/styles.scss | 42 ++ scripts-2.0/components/Header/index.tsx | 13 +- .../components/PopupMenu/Footer/index.tsx | 46 +- .../components/PopupMenu/Footer/styles.scss | 22 +- .../components/PopupMenu/Header/Header.tsx | 8 +- .../PopupMenu/Header/SVGShutDownIcon.tsx | 19 + .../components/PopupMenu/Header/UserPopup.tsx | 6 +- .../components/PopupMenu/Header/styles.scss | 32 +- .../PopupMenu/Pomodoro/Pomodoro.tsx | 155 +++++ .../PomodoroCheckbox/PomodoroCheckbox.tsx | 37 ++ .../Pomodoro/PomodoroCheckbox/index.tsx | 1 + .../Pomodoro/PomodoroCheckbox/styles.scss | 17 + .../PopupMenu/Pomodoro/PomodoroContext.ts | 28 + .../PomodoroTypeSelector.tsx | 60 ++ .../PomodoroTypeSelectorContent.tsx | 31 + .../PomodoroTypeSelectorLabel.tsx | 12 + .../Pomodoro/PomodoroTypeSelector/index.tsx | 1 + .../Pomodoro/PomodoroTypeSelector/styles.scss | 60 ++ .../PopupMenu/Pomodoro/SVGGearIcon.tsx | 28 + .../components/PopupMenu/Pomodoro/gear.svg | 10 + .../components/PopupMenu/Pomodoro/index.ts | 1 + .../components/PopupMenu/Pomodoro/styles.scss | 46 ++ .../PopupMenu/WorkingTimerSection/index.tsx | 82 ++- .../PopupMenu/WorkingTimerSection/styles.scss | 21 +- scripts-2.0/components/PopupMenu/index.tsx | 573 ++++++++++-------- .../components/TimeSelectors/index.tsx | 7 +- scripts-2.0/helpers/pomodoro.ts | 66 ++ scripts-2.0/helpers/theme.ts | 16 + scripts-2.0/icons/shutdown-icon.svg | 3 - scripts-2.0/icons/types.tsx | 3 +- scripts-2.0/style/common.scss | 10 +- 36 files changed, 1242 insertions(+), 334 deletions(-) create mode 100644 scripts-2.0/components/DurationSelector/DurationSelector.tsx create mode 100644 scripts-2.0/components/DurationSelector/styles.scss create mode 100644 scripts-2.0/components/PopupMenu/Header/SVGShutDownIcon.tsx create mode 100644 scripts-2.0/components/PopupMenu/Pomodoro/Pomodoro.tsx create mode 100644 scripts-2.0/components/PopupMenu/Pomodoro/PomodoroCheckbox/PomodoroCheckbox.tsx create mode 100644 scripts-2.0/components/PopupMenu/Pomodoro/PomodoroCheckbox/index.tsx create mode 100644 scripts-2.0/components/PopupMenu/Pomodoro/PomodoroCheckbox/styles.scss create mode 100644 scripts-2.0/components/PopupMenu/Pomodoro/PomodoroContext.ts create mode 100644 scripts-2.0/components/PopupMenu/Pomodoro/PomodoroTypeSelector/PomodoroTypeSelector.tsx create mode 100644 scripts-2.0/components/PopupMenu/Pomodoro/PomodoroTypeSelector/PomodoroTypeSelectorContent.tsx create mode 100644 scripts-2.0/components/PopupMenu/Pomodoro/PomodoroTypeSelector/PomodoroTypeSelectorLabel.tsx create mode 100644 scripts-2.0/components/PopupMenu/Pomodoro/PomodoroTypeSelector/index.tsx create mode 100644 scripts-2.0/components/PopupMenu/Pomodoro/PomodoroTypeSelector/styles.scss create mode 100644 scripts-2.0/components/PopupMenu/Pomodoro/SVGGearIcon.tsx create mode 100644 scripts-2.0/components/PopupMenu/Pomodoro/gear.svg create mode 100644 scripts-2.0/components/PopupMenu/Pomodoro/index.ts create mode 100644 scripts-2.0/components/PopupMenu/Pomodoro/styles.scss create mode 100644 scripts-2.0/helpers/pomodoro.ts delete mode 100644 scripts-2.0/icons/shutdown-icon.svg diff --git a/_locales/en/messages.json b/_locales/en/messages.json index a313082..d111e33 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -272,5 +272,14 @@ }, "click_the_button_below_to_login_to_your_timecamp_account": { "message": "Click the button below to login to your TimeCamp account" + }, + "focus_phase":{ + "message":"Focus phase" + }, + "short_break":{ + "message":"Short break" + }, + "long_break":{ + "message":"Long break" } } diff --git a/scripts-2.0/StorageManager.js b/scripts-2.0/StorageManager.js index 4e0a5a9..b106ff7 100644 --- a/scripts-2.0/StorageManager.js +++ b/scripts-2.0/StorageManager.js @@ -4,7 +4,11 @@ import Logger from './Logger'; const logger = new Logger(); const TRELLO_POWER_UP_AD_VISIBLE = 'trello_power_up_ad_visible'; -const TC_THEME = 'tc_theme' +const TC_THEME = 'tc_theme'; +const POMODORO_IS_ENABLED = 'pomodoro_is_enabled' +const POMODORO_TYPE = 'pomodoro_type' +const POMODORO_DURATION_VALUES = 'pomodoro_duration_values' +const POMODORO_IS_OPTIONS_OPEN = 'pomodoro_is_options_open' export default class StorageManager { static get TRELLO_POWER_UP_AD_VISIBLE() { @@ -14,6 +18,22 @@ export default class StorageManager { static get TC_THEME() { return TC_THEME; } + + static get POMODORO_IS_ENABLED(){ + return POMODORO_IS_ENABLED; + } + + static get POMODORO_TYPE(){ + return POMODORO_TYPE; + } + + static get POMODORO_DURATION_VALUES(){ + return POMODORO_DURATION_VALUES; + } + + static get POMODORO_IS_OPTIONS_OPEN(){ + return POMODORO_IS_OPTIONS_OPEN; + } buildKey(params) { return params.join('_'); diff --git a/scripts-2.0/components/Checkbox/Checkbox.tsx b/scripts-2.0/components/Checkbox/Checkbox.tsx index a880cbb..0a2a07e 100644 --- a/scripts-2.0/components/Checkbox/Checkbox.tsx +++ b/scripts-2.0/components/Checkbox/Checkbox.tsx @@ -3,11 +3,12 @@ import './styles.scss'; export interface PropTypes { isChecked: boolean; + onClick?: ()=>void } const Checkbox: React.FC = (props) => { return ( -
+
diff --git a/scripts-2.0/components/ContextMenu/index.tsx b/scripts-2.0/components/ContextMenu/index.tsx index 704a524..502e02e 100644 --- a/scripts-2.0/components/ContextMenu/index.tsx +++ b/scripts-2.0/components/ContextMenu/index.tsx @@ -49,6 +49,7 @@ export interface ContextMenuInterface { taskNotFoundInBackendIntegrationInfo: string, embedOnPopup?: boolean|null, trelloPowerUpAdVisible?: boolean|null, + isPomodoroEnabled?: boolean } const ContextMenu: React.FC = (props) => { @@ -67,6 +68,7 @@ const ContextMenu: React.FC = (props) => { const startTimerCallback = props.startTimerCallback; const addTimeEntryCallback = props.addTimeEntryCallback; const onCloseCallback = props.onCloseCallback; + const isPomodoroEnabled = props.isPomodoroEnabled || false const [userId, setUserId] = useState(0); const [theme, setTheme] = useState("default"); const [isAdmin, setIsAdmin] = useState(false); @@ -510,6 +512,7 @@ const ContextMenu: React.FC = (props) => { stopTime={stopTime} clearFormTrigger={clearTriggerForTimePicker} durationFormat={durationFormat} + isPomodoroEnabled={isPomodoroEnabled} onStartTimeValueChange={(value) => { setStartTime(value); const now = dateTime.getNow(); diff --git a/scripts-2.0/components/DurationSelector/DurationSelector.tsx b/scripts-2.0/components/DurationSelector/DurationSelector.tsx new file mode 100644 index 0000000..d236f11 --- /dev/null +++ b/scripts-2.0/components/DurationSelector/DurationSelector.tsx @@ -0,0 +1,83 @@ +import * as React from "react"; + +import "./styles.scss"; + +const DurationSelector = (props) => { + const inputRef = React.useRef(null); + + return ( +
+ +
+ ); +}; + +export default DurationSelector; diff --git a/scripts-2.0/components/DurationSelector/styles.scss b/scripts-2.0/components/DurationSelector/styles.scss new file mode 100644 index 0000000..88e667f --- /dev/null +++ b/scripts-2.0/components/DurationSelector/styles.scss @@ -0,0 +1,42 @@ +.duration-selector { + small { + margin-left: 3px; + + color: var(--theme-label-color); + } + + &__input-wrapper { + position: relative; + + &::after { + content: "min"; + right: 5px; + top: 50%; + color: #8a8a8a; + position: absolute; + + transform: translateY(-50%); + } + &__input { + display: flex; + justify-content: center; + height: 37px; + + width: 77px; + border-radius: 8px; + border: 1px solid var(--theme-border-color-secondary); + + outline: none; + text-transform: capitalize; + background-color: var(--theme-background-color); + color: var(--theme-color); + margin-top: 3px; + padding-left: 7px; + } + } + + p { + text-transform: capitalize; + color: var(--theme-label-color); + } +} diff --git a/scripts-2.0/components/Header/index.tsx b/scripts-2.0/components/Header/index.tsx index b851ff8..675a995 100644 --- a/scripts-2.0/components/Header/index.tsx +++ b/scripts-2.0/components/Header/index.tsx @@ -1,28 +1,19 @@ import * as React from "react"; -// @ts-ignore -import tcLogo from "../../../images/logoTC-with-name.svg"; -// @ts-ignore -import tcLogoDark from "../../../images/logoTC-with-name-dark.svg"; import "./styles.scss"; +import { getLogoByTheme } from "../../helpers/theme"; export interface HeaderInterface { theme?: string; } -const LOGOS_BY_THEME = { - default: tcLogo, - darkmode: tcLogoDark, -}; - const Header: React.FC = ({ theme = "default" }) => { - // @ts-ignore return (
TimeCamp logo
diff --git a/scripts-2.0/components/PopupMenu/Footer/index.tsx b/scripts-2.0/components/PopupMenu/Footer/index.tsx index 6351d54..43ca6c6 100644 --- a/scripts-2.0/components/PopupMenu/Footer/index.tsx +++ b/scripts-2.0/components/PopupMenu/Footer/index.tsx @@ -1,13 +1,15 @@ import * as React from "react"; -import "./styles.scss"; -import Button from "../../common/Button"; -import Icon from "../../Icon"; import { IconName } from "../../../icons/types"; +import Icon from "../../Icon"; +import Button from "../../common/Button"; import { User } from "../index"; -import translate from "../../../Translator"; -import PathService from "../../../PathService"; +import "./styles.scss"; + import { ContextMenuType } from "../../ContextMenu/types"; +import { PomodoroContext } from "../Pomodoro/PomodoroContext"; +import PomodoroTypeSelector from "../Pomodoro/PomodoroTypeSelector"; + export interface FooterInterface { logoutCallback: Function; onPlusButtonClickCallback: Function; @@ -17,30 +19,26 @@ export interface FooterInterface { >; } -const serverUrl = new PathService().getBaseUrl(); - const Footer: React.FC = (props) => { const { onSetContextMenuType } = props; + const { + isPomodoroEnabled: [isPomodoroEnabled] + } = React.useContext(PomodoroContext) + return ( - - + + + )}
diff --git a/scripts-2.0/components/PopupMenu/Header/styles.scss b/scripts-2.0/components/PopupMenu/Header/styles.scss index 286114b..8178b66 100644 --- a/scripts-2.0/components/PopupMenu/Header/styles.scss +++ b/scripts-2.0/components/PopupMenu/Header/styles.scss @@ -10,15 +10,14 @@ width: 90%; z-index: 10; transform: translateX(-50%); - - background: $popupBorderGray; + background: var(--theme-popup-section-separator-color); position: absolute; } } .tc-popup-header { display: flex; - + color: var(--theme-color); &__logo-svg { top: 5px; } @@ -41,11 +40,11 @@ } } &__user-slide-window { - box-shadow: 0 6px 32px 0 $popupShadowGray; + box-shadow: var(--theme-box-shadow); padding: 10px; width: 265px; - background: $popupBackground; - + background: var(--theme-background-color); + color: var(--theme-color); position: absolute; top: 25%; right: -1px; @@ -59,7 +58,7 @@ padding: 10px; &:hover { - background-color: $popupGrayThird; + background-color: var(--theme-background-hover); border-radius: 8px; } @@ -95,12 +94,10 @@ &__display-name { font-size: 14px; font-weight: 400; - color: $primaryTextColor; } &__email { white-space: nowrap; - color: $secondaryTextColor; font-size: 10px; } } @@ -122,39 +119,36 @@ } &:hover { - background-color: $popupGrayThird; + background-color: var(--theme-background-hover); border-radius: 8px; } } .section-bottom { padding: 15px; - border: none; cursor: pointer; font-size: 14px; text-align: left; width: 100%; - - color: $popupGraySecond; + background-color: var(--theme-background-color); + color: var(--theme-color); font-family: $defaultFontFamily; - background-color: white; - display: flex; align-items: center; + z-index:1; &:hover { - background-color: $popupGrayThird; + background-color: var(--theme-background-hover); border-radius: 8px; & button { background-color: $popupGrayThird; } } - img { - color: $popupGray; + svg { + fill: var(--theme-icon-color); font-size: 18px; - margin-right: 15px; } diff --git a/scripts-2.0/components/PopupMenu/Pomodoro/Pomodoro.tsx b/scripts-2.0/components/PopupMenu/Pomodoro/Pomodoro.tsx new file mode 100644 index 0000000..bf822c4 --- /dev/null +++ b/scripts-2.0/components/PopupMenu/Pomodoro/Pomodoro.tsx @@ -0,0 +1,155 @@ +import * as React from "react"; +import translate from "../../../Translator"; + +import DurationSelector from "../../DurationSelector/DurationSelector"; +import SVGGearIcon from "./SVGGearIcon"; +import "./styles.scss"; + +const browser = require("webextension-polyfill"); + +import StorageManager from "../../../StorageManager"; +import PomodoroCheckbox from "./PomodoroCheckbox"; +import { PomodoroContext } from "./PomodoroContext"; + + +const Pomodoro = () => { + const debouncedRef = React.useRef(undefined); + const debouncedValueRef = React.useRef(""); + + const { + isPomodoroEnabled: [ isPomodoroEnabled, setIsPomodoroEnabled], + isPomodoroOptionsOpen: [ isPomodoroOptionsOpen, setIsPomodoroOptionsOpen], + pomodoroDurations: [ durationValues, setDurationValues] + + } = React.useContext(PomodoroContext) + + const onSetIsPomodoroEnabled = () => { + const currentValue = !isPomodoroEnabled; + + browser.runtime.sendMessage({ + type: "saveSettingToStorage", + name: StorageManager.POMODORO_IS_ENABLED, + value: currentValue, + }); + + setIsPomodoroEnabled(currentValue); + }; + + + const toggleSetIsPomodoroOptionsOpen = () => { + const newValue = !isPomodoroOptionsOpen; + setIsPomodoroOptionsOpen(newValue); + + browser.runtime.sendMessage({ + type: "saveSettingToStorage", + name: StorageManager.POMODORO_IS_OPTIONS_OPEN, + value: newValue, + }); + }; + + const debounce = (value) => { + debouncedValueRef.current = value; + if (debouncedRef.current !== undefined) { + clearTimeout(debouncedRef.current); + } + + debouncedRef.current = setTimeout(() => { + browser.runtime.sendMessage({ + type: "saveSettingToStorage", + name: StorageManager.POMODORO_DURATION_VALUES, + value: debouncedValueRef.current, + }); + }, 300); + }; + + return ( +
+
+ + +
+ +
+
+ {isPomodoroOptionsOpen && ( + +
+
+ { + setDurationValues(values=>({ + ...values, + pomodoroFocusPhase: e + })) + + debounce( + JSON.stringify({ + ...durationValues, + pomodoroFocusPhase: e, + + }), + ); + }} + /> +
+
+ { + setDurationValues(values=>({ + ...values, + pomodoroShortBreak:e + })) + + debounce( + JSON.stringify({ + ...durationValues, + pomodoroShortBreak: e, + }), + ); + }} + /> +
+
+ { + + setDurationValues(values=>({ + ...values, + pomodoroLongBreak: e + })) + + debounce( + JSON.stringify({ + ...durationValues, + pomodoroLongBreak: e, + + }), + ); + }} + /> +
+
+
+ )} +
+ ); +}; + +export default Pomodoro; diff --git a/scripts-2.0/components/PopupMenu/Pomodoro/PomodoroCheckbox/PomodoroCheckbox.tsx b/scripts-2.0/components/PopupMenu/Pomodoro/PomodoroCheckbox/PomodoroCheckbox.tsx new file mode 100644 index 0000000..565e186 --- /dev/null +++ b/scripts-2.0/components/PopupMenu/Pomodoro/PomodoroCheckbox/PomodoroCheckbox.tsx @@ -0,0 +1,37 @@ +import * as React from "react"; +import "./styles.scss"; +export interface PomodoroCheckboxProps { + isChecked: boolean; + onClick?: () => void; +} + +const PomodoroCheckbox: React.FC = (props) => { + return ( + + ); +}; + +export default PomodoroCheckbox; diff --git a/scripts-2.0/components/PopupMenu/Pomodoro/PomodoroCheckbox/index.tsx b/scripts-2.0/components/PopupMenu/Pomodoro/PomodoroCheckbox/index.tsx new file mode 100644 index 0000000..aa5cd0b --- /dev/null +++ b/scripts-2.0/components/PopupMenu/Pomodoro/PomodoroCheckbox/index.tsx @@ -0,0 +1 @@ +export { default } from './PomodoroCheckbox' \ No newline at end of file diff --git a/scripts-2.0/components/PopupMenu/Pomodoro/PomodoroCheckbox/styles.scss b/scripts-2.0/components/PopupMenu/Pomodoro/PomodoroCheckbox/styles.scss new file mode 100644 index 0000000..2d1c8f9 --- /dev/null +++ b/scripts-2.0/components/PopupMenu/Pomodoro/PomodoroCheckbox/styles.scss @@ -0,0 +1,17 @@ +.pomodoro-checkbox{ + &__svg{ + border: 1px solid lightgray; + padding: 3px; + cursor: pointer; + background-color: white + } + + &[data-ischecked='true'] svg{ + background-color:#4BB064; + } + + input{ + display: none; + } +} + diff --git a/scripts-2.0/components/PopupMenu/Pomodoro/PomodoroContext.ts b/scripts-2.0/components/PopupMenu/Pomodoro/PomodoroContext.ts new file mode 100644 index 0000000..17949ed --- /dev/null +++ b/scripts-2.0/components/PopupMenu/Pomodoro/PomodoroContext.ts @@ -0,0 +1,28 @@ +import * as React from "react"; +import { PomodoroDurationValues } from ".."; +import { PomodoroTypes } from "../../../helpers/pomodoro"; + +export const initialPomodoroDurationsState = { + pomodoroFocusPhase: "25:00", + pomodoroShortBreak: "05:00", + pomodoroLongBreak: "15:00", +}; + +export type UseStateContextType = [ + T, + React.Dispatch>, +]; + +export type PomodoroContextType = { + type: UseStateContextType; + pomodoroDurations: UseStateContextType; + isPomodoroOptionsOpen: UseStateContextType; + isPomodoroEnabled: UseStateContextType; +}; + +export const PomodoroContext = React.createContext({ + type: [PomodoroTypes.FOCUS_PHASE, () => {}], + pomodoroDurations: [initialPomodoroDurationsState, () => {}], + isPomodoroOptionsOpen: [false, () => {}], + isPomodoroEnabled: [false, () => {}], +}); diff --git a/scripts-2.0/components/PopupMenu/Pomodoro/PomodoroTypeSelector/PomodoroTypeSelector.tsx b/scripts-2.0/components/PopupMenu/Pomodoro/PomodoroTypeSelector/PomodoroTypeSelector.tsx new file mode 100644 index 0000000..a5501e8 --- /dev/null +++ b/scripts-2.0/components/PopupMenu/Pomodoro/PomodoroTypeSelector/PomodoroTypeSelector.tsx @@ -0,0 +1,60 @@ +import * as React from 'react'; +import Dropdown from '../../../Dropdown'; +import './styles.scss'; + + +const browser = require("webextension-polyfill"); + +import StorageManager from '../../../../StorageManager'; +import FocusPhaseDropdownContent from './PomodoroTypeSelectorContent'; +import FocusPhaseDropdownLabel from './PomodoroTypeSelectorLabel'; +import { PomodoroContext } from '../PomodoroContext'; + +const FocusPhase =( )=>{ + const { + type: [selectedFocusPhase, setSelectedFocusPhase] + } = React.useContext(PomodoroContext) + const [ isDropdownOpen, setIsDropdownOpen] = React.useState(false) + const ulRef = React.useRef(null) + + React.useEffect(()=>{ + const handler = (e)=>{ + if(ulRef.current?.contains(e.target)){ + return + } + setIsDropdownOpen(false); + } + + document.addEventListener('click', handler, true) + + return ()=>{ + document.removeEventListener('click', handler, true) + } + + },[]) + + return { + setIsDropdownOpen(!isDropdownOpen) + }} + children={ { + browser.runtime.sendMessage({ + type: 'saveSettingToStorage', + name: StorageManager.POMODORO_TYPE, + value: e}) + + setSelectedFocusPhase(e) + setIsDropdownOpen(false) + }}/>} + text={} + onBackdropClick={()=>{ + setIsDropdownOpen(false) + }} + /> +} + +export default FocusPhase \ No newline at end of file diff --git a/scripts-2.0/components/PopupMenu/Pomodoro/PomodoroTypeSelector/PomodoroTypeSelectorContent.tsx b/scripts-2.0/components/PopupMenu/Pomodoro/PomodoroTypeSelector/PomodoroTypeSelectorContent.tsx new file mode 100644 index 0000000..ca3c34c --- /dev/null +++ b/scripts-2.0/components/PopupMenu/Pomodoro/PomodoroTypeSelector/PomodoroTypeSelectorContent.tsx @@ -0,0 +1,31 @@ +import * as React from "react"; +import { PomodoroTypes } from "../../../../helpers/pomodoro"; + +type FocusPhaseDropdownContentType = { + onClick: (pomodoroType: PomodoroTypes) => void; +}; + +const FocusPhaseDropdownContent = React.forwardRef< + HTMLUListElement, + FocusPhaseDropdownContentType +>((props, ref) => { + const { onClick } = props; + + return ( +
    + {Object.values(PomodoroTypes).map((focusPhase) => ( +
  • { + onClick(focusPhase); + }} + > + {focusPhase} +
  • + ))} +
+ ); +}); + +export default FocusPhaseDropdownContent; diff --git a/scripts-2.0/components/PopupMenu/Pomodoro/PomodoroTypeSelector/PomodoroTypeSelectorLabel.tsx b/scripts-2.0/components/PopupMenu/Pomodoro/PomodoroTypeSelector/PomodoroTypeSelectorLabel.tsx new file mode 100644 index 0000000..96650b3 --- /dev/null +++ b/scripts-2.0/components/PopupMenu/Pomodoro/PomodoroTypeSelector/PomodoroTypeSelectorLabel.tsx @@ -0,0 +1,12 @@ +import * as React from 'react'; +import { PomodoroTypes } from '../../../../helpers/pomodoro'; + +interface FocusPhaseDropdownLabelProps { + value: PomodoroTypes; +} + +const FocusPhaseDropdownLabel =({value}: FocusPhaseDropdownLabelProps)=>{ + return {value} +} + +export default FocusPhaseDropdownLabel \ No newline at end of file diff --git a/scripts-2.0/components/PopupMenu/Pomodoro/PomodoroTypeSelector/index.tsx b/scripts-2.0/components/PopupMenu/Pomodoro/PomodoroTypeSelector/index.tsx new file mode 100644 index 0000000..ff8d0d5 --- /dev/null +++ b/scripts-2.0/components/PopupMenu/Pomodoro/PomodoroTypeSelector/index.tsx @@ -0,0 +1 @@ +export {default} from './PomodoroTypeSelector' \ No newline at end of file diff --git a/scripts-2.0/components/PopupMenu/Pomodoro/PomodoroTypeSelector/styles.scss b/scripts-2.0/components/PopupMenu/Pomodoro/PomodoroTypeSelector/styles.scss new file mode 100644 index 0000000..8619d7a --- /dev/null +++ b/scripts-2.0/components/PopupMenu/Pomodoro/PomodoroTypeSelector/styles.scss @@ -0,0 +1,60 @@ +.focus-phase { + cursor: relative; + + & > div { + display: flex; + &::after { + width: 100%; + height: 1px; + content: " "; + background: var(--theme-popup-section-separator-color); + position: absolute; + bottom: 10px; + left: 0; + } + } + + &__dropdown-open{ + & > div::after{ + background-color: #4BB063; + } + .Dropdown__icon-box{ + color: #4BB063; + } + } +} + +.focus-phase-dropdown-content { + position: absolute; + bottom: 0; + left: 0; + background: var(--theme-background-color); + box-shadow: var(--theme-box-shadow); + padding: 10px; + border-radius: 8px; + color: var(--theme-color); + z-index: 1; + transform: translateY(-25%); + + & > li { + color: var(--theme-color); + min-width: 106px; + cursor: pointer; + list-style: none; + padding: 5px; + border-radius: 8px; + &:hover { + color:#4BB063; + background: var(--theme-background-hover-modern); + } + & span{ + margin-left:5px; + } + } +} + +.focus-phase-dropdown-label { + margin-right: 5px; + cursor: pointer; + position: relative; +} diff --git a/scripts-2.0/components/PopupMenu/Pomodoro/SVGGearIcon.tsx b/scripts-2.0/components/PopupMenu/Pomodoro/SVGGearIcon.tsx new file mode 100644 index 0000000..cddcd80 --- /dev/null +++ b/scripts-2.0/components/PopupMenu/Pomodoro/SVGGearIcon.tsx @@ -0,0 +1,28 @@ +import * as React from 'react'; + + + +const SVGGearIcon =({ + onClick, + isActive +})=>{ + + const color = isActive? "green" : "currentColor" + + return + + + + + + + + + + + +} + +export default SVGGearIcon \ No newline at end of file diff --git a/scripts-2.0/components/PopupMenu/Pomodoro/gear.svg b/scripts-2.0/components/PopupMenu/Pomodoro/gear.svg new file mode 100644 index 0000000..00be358 --- /dev/null +++ b/scripts-2.0/components/PopupMenu/Pomodoro/gear.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/scripts-2.0/components/PopupMenu/Pomodoro/index.ts b/scripts-2.0/components/PopupMenu/Pomodoro/index.ts new file mode 100644 index 0000000..7c271d9 --- /dev/null +++ b/scripts-2.0/components/PopupMenu/Pomodoro/index.ts @@ -0,0 +1 @@ +export {default} from './Pomodoro' \ No newline at end of file diff --git a/scripts-2.0/components/PopupMenu/Pomodoro/styles.scss b/scripts-2.0/components/PopupMenu/Pomodoro/styles.scss new file mode 100644 index 0000000..aca4803 --- /dev/null +++ b/scripts-2.0/components/PopupMenu/Pomodoro/styles.scss @@ -0,0 +1,46 @@ +.pomodoro { + color: var(--theme-color); + &__checkbox { + display: flex; + align-items: center; + margin: 20px 0 0px 0px; + + input { + cursor: pointer; + } + + &__svg-gear-icon { + cursor: pointer; + transform: translateY(-5%); + } + } + + &__checkbox-paragraph { + display: flex; + cursor: pointer; + + &__label{ + margin-left: 7px; + margin-right: 7px; + user-select: none; + } + } + + &__selectors { + display: flex; + + &__duration-selector { + margin-right: 15px; + } + } + + &__checkbox-breadcrumbs { + margin-left: 20px; + color: var(--theme-label-color); + } + + svg { + fill: red; + font-size: 16px; + } +} diff --git a/scripts-2.0/components/PopupMenu/WorkingTimerSection/index.tsx b/scripts-2.0/components/PopupMenu/WorkingTimerSection/index.tsx index d8fbebb..fcaa2b7 100644 --- a/scripts-2.0/components/PopupMenu/WorkingTimerSection/index.tsx +++ b/scripts-2.0/components/PopupMenu/WorkingTimerSection/index.tsx @@ -1,15 +1,21 @@ import * as React from "react"; -import {useEffect, useState} from "react"; +import { useEffect, useState } from "react"; import './styles.scss'; // @ts-ignore -import tcStopButton from "../../../icons/stop-button-small.svg"; -import {Entry} from "../index"; +import PathService from "../../../PathService"; +import TimeFormatter, { DURATION_FORMATS } from "../../../TimeFormatter"; import translate from "../../../Translator"; -import TimeFormatter, {DURATION_FORMATS} from "../../../TimeFormatter"; -import Icon from '../../Icon' -import {IconName} from '../../../icons/types' +import { PomodoroTypes, subtractTime } from "../../../helpers/pomodoro"; +// @ts-ignore +import tcStopButton from "../../../icons/stop-button-small.svg"; +import { IconName } from '../../../icons/types'; +import Icon from '../../Icon'; +import { Entry } from "../index"; +import { PomodoroContext } from "../Pomodoro/PomodoroContext"; const timeFormatter = new TimeFormatter(); +const serverUrl = new PathService().getBaseUrl(); + export interface WorkingTimerSectionInterface { isTimerWorking: boolean, entry: Entry, @@ -17,21 +23,59 @@ export interface WorkingTimerSectionInterface { } const WorkingTimerSection: React.FC = (props) => { + const { + isPomodoroEnabled:[isPomodoroEnabled], + pomodoroDurations: [pomodoroDurationValues], + type: [pomodoroStartedType, setCurrentSelectedType] + } = React.useContext(PomodoroContext) const [isTimerWorking, setIsTimerWorking] = useState(false); const [entry, setEntry] = useState(props.entry); const [currentTimer, setCurrentTimer] = useState(timeFormatter.formatToDuration(0)); + const stopTimerCallback = props.stopTimerCallback; const durationFormat = DURATION_FORMATS.HHMMSS; + + const pomodoroValuesByType ={ + [PomodoroTypes.FOCUS_PHASE]: pomodoroDurationValues.pomodoroFocusPhase, + [PomodoroTypes.SHORT_BREAK]: pomodoroDurationValues.pomodoroShortBreak, + [PomodoroTypes.LONG_BREAK]: pomodoroDurationValues.pomodoroLongBreak + } + + useEffect(()=>{ + if(!isTimerWorking) { + setCurrentSelectedType(pomodoroStartedType) + } + },[ + pomodoroStartedType + ]) + + useEffect(()=>{ + if(!props.isTimerWorking){ + setCurrentSelectedType(pomodoroStartedType) + } + },[ + props.isTimerWorking + ]) + + useEffect(() => { setIsTimerWorking(props.isTimerWorking); setEntry(props.entry); - const interval = setInterval(() => { + const interval = setInterval(() => { if (isTimerWorking) { - setCurrentTimer( - timeFormatter.format(props.entry.startedAt, durationFormat) - ); + const diff = timeFormatter.format(props.entry.startedAt, durationFormat) + const pomodoroValue = pomodoroValuesByType[pomodoroStartedType] + + const newTimer = !isPomodoroEnabled? diff : subtractTime( diff, pomodoroValue) + + if(newTimer==='00:00:00' || newTimer.includes('-')){ + stopTimer() + return + } + + setCurrentTimer(newTimer); } }, 1000); return () => clearInterval(interval); @@ -39,11 +83,27 @@ const WorkingTimerSection: React.FC = (props) => { const stopTimer = () => { stopTimerCallback(); + setCurrentSelectedType(pomodoroStartedType) } return (
-

{translate('currently_running_timer')}:

+

+ {translate('currently_running_timer')}: + +

+ +

diff --git a/scripts-2.0/components/PopupMenu/WorkingTimerSection/styles.scss b/scripts-2.0/components/PopupMenu/WorkingTimerSection/styles.scss index 6373c1c..eb35b77 100644 --- a/scripts-2.0/components/PopupMenu/WorkingTimerSection/styles.scss +++ b/scripts-2.0/components/PopupMenu/WorkingTimerSection/styles.scss @@ -11,10 +11,29 @@ margin: 0 0 10px 7px; font-weight: bold; color: var(--theme-label-color); + display: flex; + &__timesheet{ + margin-left: auto; + margin-right: 7px; + } + } + + &__button-anchor { + display: flex; + justify-content: center; + align-items: flex-end; + + &__href { + color: #2380e3; + font-size: 12px; + } + & svg { + margin-right: 0.3rem; + } } &__entry { - border: dashed 2px $popupBorderGray; + border: dashed 2px var(--theme-border-color-secondary); border-radius: 8px; width: 360px; height: 52px; diff --git a/scripts-2.0/components/PopupMenu/index.tsx b/scripts-2.0/components/PopupMenu/index.tsx index 417a08a..276bfa5 100644 --- a/scripts-2.0/components/PopupMenu/index.tsx +++ b/scripts-2.0/components/PopupMenu/index.tsx @@ -5,62 +5,118 @@ import DateTime from "../../helpers/DateTime"; import Gravatar from "../../helpers/Gravatar"; import { getTheme, retrieveThemeFromStorage } from "../../helpers/theme"; import ContextMenu from "../ContextMenu"; -import Header from "./Header"; import LoginWindow from "../LoginWindow"; import Footer from "./Footer"; +import Header from "./Header"; +import "./styles.scss"; import WorkingTimerSection from "./WorkingTimerSection"; -import './styles.scss'; import { ContextMenuType } from "../ContextMenu/types"; import InfoBox from "../InfoBox"; import { InfoBoxStatus } from "../InfoBox/types"; +import Pomodoro from "./Pomodoro"; + +import StorageManager from "../../StorageManager"; +import { PomodoroTypes, isObjValid, isPomodoroTypeValid } from "../../helpers/pomodoro"; +import { PomodoroContext, PomodoroContextType, initialPomodoroDurationsState } from "./Pomodoro/PomodoroContext"; const gravatar = new Gravatar(); const dateTime = new DateTime(); -export interface PopupMenuInterface { -} +export interface PopupMenuInterface {} export interface User { - displayName: string; - email: string; - gravatarUrl: string; + displayName: string; + email: string; + gravatarUrl: string; +} + +export interface PomodoroDurationValues { + pomodoroFocusPhase: string; + pomodoroShortBreak: string; + pomodoroLongBreak: string; } export interface Entry { - taskName: string; - note: string; - startedAt: string|null; - breadcrumb: string|null; - color: string|null; + taskName: string; + note: string; + startedAt: string | null; + breadcrumb: string | null; + color: string | null; } const PopupMenu: React.FC = (props) => { - const emptyUser = { - displayName: '', - email: '', - gravatarUrl: gravatar.getDefaultImageUrl(), - } + const emptyUser = { + displayName: "", + email: "", + gravatarUrl: gravatar.getDefaultImageUrl(), + }; + + const emptyEntry = { + taskName: "(no task)", + note: "", + startedAt: null, + breadcrumb: null, + color: "#aaa", + }; + + const [theme, setTheme] = useState<"default" | "darkmode">("default"); + const [isPomodoroEnabled, setIsPomodoroEnabled] = useState(false); + const [pomodoroStartedType, setPomodoroStartedType] = + useState(PomodoroTypes.FOCUS_PHASE); + const [isPomodoroOptionsOpen, setIsPomodoroOptionsOpen] = useState(false); + + + const [ pomodoroDurations, setPomodoroDurations ] = React.useState(initialPomodoroDurationsState) + + + + useEffect(() => { + retrieveThemeFromStorage(setTheme); + browser.runtime + .sendMessage({ + type: "getSettingFromStorage", + name: StorageManager.POMODORO_IS_OPTIONS_OPEN, + }) + .then((value) => { + setIsPomodoroOptionsOpen(value); + }); + browser.runtime + .sendMessage({ + type: "getSettingFromStorage", + name: StorageManager.POMODORO_IS_ENABLED, + }) + .then(setIsPomodoroEnabled); + browser.runtime + .sendMessage({ + type: "getSettingFromStorage", + name: StorageManager.POMODORO_TYPE, + }) + .then((value) => { + if (!isPomodoroTypeValid(value)) return; + setPomodoroStartedType(value); + }); + browser.runtime + .sendMessage({ + type: "getSettingFromStorage", + name: StorageManager.POMODORO_DURATION_VALUES, + }) + .then((values) => { + const jParsed = JSON.parse(values) || {}; + + if (!isObjValid(jParsed)) return; + + setPomodoroDurations(jParsed) + }); + browser.runtime + .sendMessage({ + type: "getUserData", + }) + .then((data) => { + getTheme(data.user_id, setTheme); + }); + }, []); - const emptyEntry = { - taskName: '(no task)', - note: '', - startedAt: null, - breadcrumb: null, - color: '#aaa', - } - - const [theme, setTheme] = useState<'default' | 'darkmode'>('default') - - useEffect(()=>{ - retrieveThemeFromStorage(setTheme) - browser.runtime.sendMessage({ - type:'getUserData' - }).then(data=>{ - getTheme(data.user_id, setTheme) - - }) - },[]) const [isUserWindowOpen, setIsUserWindowOpen] = useState(false); const [contextMenuType, setContextMenuType] = useState< ContextMenuType | undefined @@ -78,225 +134,256 @@ const PopupMenu: React.FC = (props) => { return; } - let entry = { - taskName: currentEntry.description !== undefined ? currentEntry.description : emptyEntry.taskName, - note: currentEntry.note, - startedAt: currentEntry.start, - color: currentEntry.color, - breadcrumb: currentEntry.breadcrumb, - } as Entry; - - setIsTimerWorking(true); - setEntry(entry); - }; - - const updateUser = () => { - browser.runtime.sendMessage({ - type: "getUserData" - }).then((user) => { - createAndSetUserObject(user); - }); - }; - - const createAndSetUserObject = (user) => { - let userObj = { - displayName: user.display_name, - email: user.email, - gravatarUrl: gravatar.getGravatarUrlForEmail(user.email) - } as User; - - setUser(userObj); - }; - - const logOut = () => { - browser.runtime.sendMessage({ - type: 'logOut' - }).then(() => { - setIsUserWindowOpen(false); - setContextMenuType(undefined); - setIsUserLogged(false); - setUser(emptyUser); - }).catch(() => { - }); - }; + let entry = { + taskName: + currentEntry.description !== undefined + ? currentEntry.description + : emptyEntry.taskName, + note: currentEntry.note, + startedAt: currentEntry.start, + color: currentEntry.color, + breadcrumb: currentEntry.breadcrumb, + } as Entry; + + setIsTimerWorking(true); + setEntry(entry); + }; - const stopTimer = () => { - browser.runtime.sendMessage({ - action: 'track', - type: "timerStop" + const updateUser = () => { + browser.runtime + .sendMessage({ + type: "getUserData", + }) + .then((user) => { + createAndSetUserObject(user); + }); + }; + + const createAndSetUserObject = (user) => { + let userObj = { + displayName: user.display_name, + email: user.email, + gravatarUrl: gravatar.getGravatarUrlForEmail(user.email), + } as User; + + setUser(userObj); + }; + + const logOut = () => { + browser.runtime + .sendMessage({ + type: "logOut", + }) + .then(() => { + setIsUserWindowOpen(false); + setContextMenuType(undefined); + setIsUserLogged(false); + setUser(emptyUser); + }) + .catch(() => {}); + }; + + const stopTimer = () => { + browser.runtime.sendMessage({ + action: "track", + type: "timerStop", + }); + browser.runtime + .sendMessage({ + type: "stop", + }) + .then(() => { + setIsTimerWorking(false); + setEntry(emptyEntry); + }) + .catch(() => {}); + }; + + const startTimer = ( + taskId, + note, + service, + externalTaskId, + buttonHash, + startTime, + ) => { + return new Promise((resolve, reject) => { + browser.runtime.sendMessage({ + action: "track", + type: "timerStart", + }); + browser.runtime + .sendMessage({ + type: "startTimer", + startTime: startTime, + externalTaskId: externalTaskId, + taskId: taskId, + description: note, + service: service, + buttonHash: buttonHash, }) - browser.runtime.sendMessage({ - type: 'stop' - }).then(() => { - setIsTimerWorking(false); - setEntry(emptyEntry); - }).catch(() => { - }); - }; - - const startTimer = ( - taskId, - note, - service, - externalTaskId, - buttonHash, - startTime, - ) => { - return new Promise((resolve, reject) => { - browser.runtime.sendMessage({ - action: 'track', - type: "timerStart" - }) - browser.runtime.sendMessage({ - type: 'startTimer', - startTime: startTime, - externalTaskId: externalTaskId, - taskId: taskId, - description: note, - service: service, - buttonHash: buttonHash - }).then((response) => { - setIsTimerWorking(true); - setContextMenuType(undefined); - - - let entry = { - taskName: response.name === null ? emptyEntry.taskName : response.name, - note: note, - startedAt: startTime, - color: emptyEntry.color - } as Entry; - - setEntry(entry); - resolve(response); - }).catch((e) => { - reject(e); - }); + .then((response) => { + setIsTimerWorking(true); + setContextMenuType(undefined); + + let entry = { + taskName: + response.name === null ? emptyEntry.taskName : response.name, + note: note, + startedAt: startTime, + color: emptyEntry.color, + } as Entry; + + setEntry(entry); + resolve(response); + }) + .catch((e) => { + reject(e); }); - }; - - const addTimeEntry = ( - taskId, - note, - service, - externalTaskId, - buttonHash, - billable, - startTime, - stopTime, - ) => { - return new Promise((resolve, reject) => { - browser.runtime.sendMessage({ - type: 'addTimeEntry', - date: dateTime.formatToYmd(startTime), - startTime: dateTime.formatToHis(startTime), - endTime: dateTime.formatToHis(stopTime), - billable: billable, - externalTaskId: externalTaskId, - taskId: taskId, - description: note, - service: service, - buttonHash: buttonHash - }).then((response) => { - settimeEntryAddedInfoBoxStatus(InfoBoxStatus.SUCCESS); - resolve(response); - }).catch((response) => { - settimeEntryAddedInfoBoxStatus(InfoBoxStatus.ERROR); - reject(response); - }); + }); + }; + + const addTimeEntry = ( + taskId, + note, + service, + externalTaskId, + buttonHash, + billable, + startTime, + stopTime, + ) => { + return new Promise((resolve, reject) => { + browser.runtime + .sendMessage({ + type: "addTimeEntry", + date: dateTime.formatToYmd(startTime), + startTime: dateTime.formatToHis(startTime), + endTime: dateTime.formatToHis(stopTime), + billable: billable, + externalTaskId: externalTaskId, + taskId: taskId, + description: note, + service: service, + buttonHash: buttonHash, + }) + .then((response) => { + settimeEntryAddedInfoBoxStatus(InfoBoxStatus.SUCCESS); + resolve(response); + }) + .catch((response) => { + settimeEntryAddedInfoBoxStatus(InfoBoxStatus.ERROR); + reject(response); }); - }; + }); + }; - useEffect(() => { - }, [props]); + useEffect(() => {}, [props]); - useMemo(() => { - browser.runtime.sendMessage({ - type: "isUserLogged" - }).then((isUserLogged) => { - setIsUserLogged(!!isUserLogged) - }); + useMemo(() => { + browser.runtime + .sendMessage({ + type: "isUserLogged", + }) + .then((isUserLogged) => { + setIsUserLogged(!!isUserLogged); + }); - updateUser(); + updateUser(); - browser.runtime.sendMessage({ - type: "currentEntry" - }).then((data) => { - setCurrentEntry(data.currentEntry); - }); + browser.runtime + .sendMessage({ + type: "currentEntry", + }) + .then((data) => { + setCurrentEntry(data.currentEntry); + }); - browser.runtime.onMessage.addListener((request) => { - switch (request.type) { - case 'currentEntryUpdated': - setCurrentEntry(request.currentEntry); - break; - default: - break; - } - }); - }, []); + browser.runtime.onMessage.addListener((request) => { + switch (request.type) { + case "currentEntryUpdated": + setCurrentEntry(request.currentEntry); + break; + default: + break; + } + }); + }, []); + + const pomodoroContextValue :PomodoroContextType = { + type: [pomodoroStartedType, setPomodoroStartedType], + pomodoroDurations: [pomodoroDurations, setPomodoroDurations], + isPomodoroOptionsOpen: [isPomodoroOptionsOpen, setIsPomodoroOptionsOpen], + isPomodoroEnabled: [isPomodoroEnabled, setIsPomodoroEnabled] + + } return ( -
-
-
- settimeEntryAddedInfoBoxStatus(undefined)} - /> - -
{}} - onSetContextMenuType={setContextMenuType} - /> -
- - {!isUserLogged && ( -
- -
- )} - - {contextMenuType && ( -
- { - setContextMenuType(undefined); - }} - embedOnPopup={true} - /> + +
+
+
+ settimeEntryAddedInfoBoxStatus(undefined)} + /> + + +
{}} + onSetContextMenuType={setContextMenuType} + /> +
+ + {!isUserLogged && ( +
+ +
+ )} + + {contextMenuType && ( +
+ { + setContextMenuType(undefined); + }} + embedOnPopup={true} + /> +
+ )}
- )} -
+ ); }; diff --git a/scripts-2.0/components/TimeSelectors/index.tsx b/scripts-2.0/components/TimeSelectors/index.tsx index 824abfa..47c00b0 100644 --- a/scripts-2.0/components/TimeSelectors/index.tsx +++ b/scripts-2.0/components/TimeSelectors/index.tsx @@ -17,6 +17,7 @@ export interface TimeSelectorsInterface { durationFormat?: number|null, onStartTimeValueChange(newValue: any): any onStopTimeValueChange?(newValue: any): any + isPomodoroEnabled?: boolean; } const TimeSelectors: React.FC = (props) => { @@ -116,7 +117,8 @@ const TimeSelectors: React.FC = (props) => { return (
-
+ { + !props.isPomodoroEnabled &&
= (props) => { />
+ } { - props.onStopTimeValueChange ? + props.onStopTimeValueChange && !props.isPomodoroEnabled?
{ + const validatedKeys = ["pomodoroFocusPhase", "pomodoroShortBreak", "pomodoroLongBreak" ] + if(typeof obj !=='object' || obj===null) + { + return false + } + + const valueNotValid = validatedKeys.some(key=> !obj.hasOwnProperty(key) || !/^\d{2}:\d{2}$/.test(obj[key])) + + if(valueNotValid) + { + return false + } + + return true + +} + +export const isPomodoroTypeValid = (pomodoroType: PomodoroTypes) =>{ + if(!Object.values(PomodoroTypes).includes(pomodoroType)) { + return false + } + return true + +} diff --git a/scripts-2.0/helpers/theme.ts b/scripts-2.0/helpers/theme.ts index 36cb590..bd74404 100644 --- a/scripts-2.0/helpers/theme.ts +++ b/scripts-2.0/helpers/theme.ts @@ -1,8 +1,18 @@ import { TCTheme } from "../types/theme"; import StorageManager from "../StorageManager"; +// @ts-ignore +import tcLogo from "../../images/logoTC-with-name.svg"; + +// @ts-ignore +import tcLogoDark from "../../images/logoTC-with-name-dark.svg"; const browser = require('webextension-polyfill'); +const LOGOS_BY_THEME = { + default: tcLogo, + darkmode: tcLogoDark, + }; + export const getTheme = (userId: number, callback) => { browser.runtime.sendMessage({ type: "getTheme", @@ -34,3 +44,9 @@ export const getTheme = (userId: number, callback) => { }) } + + + +export const getLogoByTheme = (theme) =>{ + return LOGOS_BY_THEME[theme] +} \ No newline at end of file diff --git a/scripts-2.0/icons/shutdown-icon.svg b/scripts-2.0/icons/shutdown-icon.svg deleted file mode 100644 index 601ecdc..0000000 --- a/scripts-2.0/icons/shutdown-icon.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/scripts-2.0/icons/types.tsx b/scripts-2.0/icons/types.tsx index ed595a4..8ef1872 100644 --- a/scripts-2.0/icons/types.tsx +++ b/scripts-2.0/icons/types.tsx @@ -53,5 +53,6 @@ export enum IconName { USER_CLOCK = 'user-clock', EXTERNAL_LINK='external-link', POWER_OFF='power-off', - STICKY_NOTE='sticky-note' + STICKY_NOTE='sticky-note', + GEAR='gear' } diff --git a/scripts-2.0/style/common.scss b/scripts-2.0/style/common.scss index 67c7301..d6a6d5c 100644 --- a/scripts-2.0/style/common.scss +++ b/scripts-2.0/style/common.scss @@ -83,28 +83,36 @@ $themeDarkColorDisabled: rgb(111,111,111); --theme-background-color: #{$themeDefaultBackgroundColor}; --theme-background-color-secondary: #{$themeDefaultBackgroundColor}; --theme-background-hover: #{$themeDefaultBackgroundHover}; + --theme-background-hover-modern: #F1F9F3; --theme-color: #{$primaryTextColor}; + --theme-color-secondary: #{$secondaryTextColor}; --theme-task-picker-color: #{$taskPickerFontColor}; --theme-color-disabled: #{$disabledFontColor}; - --theme-box-shadow: 0 6px 32px 0 #{$popupShadowGray}; + --theme-box-shadow: 0 6px 32px 0 rgba(0,0,0,0.17); --theme-border-color: #{$themeDefaultBorderColor}; --theme-border-color-secondary: #{$themeDefaultBorderColorSecondary}; --theme-label-color: #{$primaryTextColor}; --theme-border-display-color: #{$primaryBorderColor}; --theme-border-disabled-color: #{$themeDefaultBorderDisabledColor}; + --theme-icon-color: #{$popupGray}; + --theme-popup-section-separator-color: #{$popupBorderGray}; &[data-theme='darkmode']{ --theme-background-color: #{$themeDarkBackgroundColor}; --theme-background-color-secondary:#{$themeDarkBackgroundColorSecondary}; --theme-color-disabled: #{$themeDarkColorDisabled}; --theme-color: white; + --theme-color-secondary: white; --theme-task-picker-color: white; --theme-box-shadow: 0 6px 32px 0 black; --theme-background-hover: #{$themeDarkBackgroundHover}; + --theme-background-hover-modern: #{$themeDarkBackgroundHover}; --theme-border-color: #{$themeDarkBorderColor}; --theme-border-color-secondary: #{$themeDarkBorderColor}; --theme-label-color: #{$themeDarkLabelColor}; --theme-border-display-color: transparent; --theme-border-disabled-color: #{$themeDarkBorderDisabledColor}; + --theme-icon-color: white; + --theme-popup-section-separator-color: hsla(0,0%,100%,.3); } } \ No newline at end of file