-
-
-
{setIsUserWindowOpen(!isUserWindowOpen)}}>
-

-
-
-
-
-
-
-
-
-
-
+ const { onSetContextMenuType } = props;
+ const {
+ isPomodoroEnabled: [isPomodoroEnabled]
+ } = React.useContext(PomodoroContext)
+
+ return (
+
+ {isPomodoroEnabled ? (
+
+ ) : (
+
+ )}
+
+
+
+ );
};
export default Footer;
diff --git a/scripts-2.0/components/PopupMenu/Footer/styles.scss b/scripts-2.0/components/PopupMenu/Footer/styles.scss
index 9969ed1..c676a5b 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,30 +7,48 @@
display: flex;
justify-content: space-between;
- border-top: solid 1px $popupBorderGray;
padding-top: 20px;
margin-top: 28px;
+ color: var(--theme-color);
+ margin-top: 10px;
+ &__button-focus {
+ margin-left:auto;
+ position:relative;
+
+ display: flex;
+ align-items: center;
+ margin-right:15px;
+
- .user-img {
+ }
+
+ &__button-cancel {
+ border: solid 1px $primaryBorderColor;
+ font-family: $defaultFontFamily;
+ font-size: $defaultFontSize;
+ width: 39px;
height: 39px;
- border-radius: 50%;
-
- &:hover {
- border: solid 1px $popupGreen;
- padding: 5px;
- margin: -6px;
- }
+ background-color: transparent;
+ color: #4f4f4f;
+ color: var(--theme-icon-color);
+ border-radius: 8px;
+ cursor: pointer;
+ margin-left: auto;
+ margin-right: 15px;
}
-
&__button-add {
- width: 39px;
+ 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;
}
@@ -38,6 +56,17 @@
svg {
font-size: 17px;
}
+
+ &__icon {
+ flex: 1;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ }
+
+ & span {
+ text-transform: uppercase;
+ }
}
&__user-img {
@@ -68,11 +97,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 +114,7 @@
padding: 10px;
&:hover {
- background-color: $popupGrayThird;
+ background-color: var(--theme-background-hover);
border-radius: 8px;
}
@@ -116,19 +146,19 @@
padding-top: 10px;
&__button-sign-out {
- border: none;
+ border: none;
cursor: pointer;
font-size: 14px;
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/Header/Header.tsx b/scripts-2.0/components/PopupMenu/Header/Header.tsx
new file mode 100644
index 0000000..bd9a723
--- /dev/null
+++ b/scripts-2.0/components/PopupMenu/Header/Header.tsx
@@ -0,0 +1,68 @@
+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";
+
+// @ts-ignore
+import AvatarIconSVG from "../../../icons/user-circle-fas.svg";
+import { User } from "..";
+import "./styles.scss";
+
+import UserPopup from "./UserPopup";
+import { getLogoByTheme } from "../../../helpers/theme";
+
+export interface HeaderInterface {
+ user: User;
+ setIsUserWindowOpen: React.Dispatch
>;
+ isUserWindowOpen: boolean;
+ logoutCallback: () => void;
+ theme?: string;
+}
+
+const Header: React.FC = ({
+ user,
+ isUserWindowOpen,
+ setIsUserWindowOpen,
+ logoutCallback,
+ theme='default'
+}) => {
+ const handleClickUserAvatar = (e) => {
+ if (isUserWindowOpen) return;
+
+ setIsUserWindowOpen(true);
+ e.stopPropagation();
+ };
+
+ return (
+
+
})
+
+
+

+
+
+
+
+
+ );
+};
+
+export default Header;
diff --git a/scripts-2.0/components/PopupMenu/Header/SVGShutDownIcon.tsx b/scripts-2.0/components/PopupMenu/Header/SVGShutDownIcon.tsx
new file mode 100644
index 0000000..40b6bc6
--- /dev/null
+++ b/scripts-2.0/components/PopupMenu/Header/SVGShutDownIcon.tsx
@@ -0,0 +1,19 @@
+import * as React from "react";
+
+const SVGShutdownIcon = () => {
+ return (
+
+ );
+};
+
+export default SVGShutdownIcon;
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..2992f97
--- /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";
+
+
+import SVGShutdownIcon from "./SVGShutDownIcon";
+
+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..8178b66
--- /dev/null
+++ b/scripts-2.0/components/PopupMenu/Header/styles.scss
@@ -0,0 +1,158 @@
+@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: var(--theme-popup-section-separator-color);
+ position: absolute;
+ }
+}
+
+.tc-popup-header {
+ display: flex;
+ color: var(--theme-color);
+ &__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: var(--theme-box-shadow);
+ padding: 10px;
+ width: 265px;
+ background: var(--theme-background-color);
+ color: var(--theme-color);
+ position: absolute;
+ top: 25%;
+ right: -1px;
+ display: none;
+
+ &--visible {
+ display: block;
+ }
+
+ .section-top {
+ padding: 10px;
+
+ &:hover {
+ background-color: var(--theme-background-hover);
+ 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;
+ }
+
+ &__email {
+ white-space: nowrap;
+ 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: var(--theme-background-hover);
+ border-radius: 8px;
+ }
+ }
+
+ .section-bottom {
+ padding: 15px;
+ border: none;
+ cursor: pointer;
+ font-size: 14px;
+ text-align: left;
+ width: 100%;
+ background-color: var(--theme-background-color);
+ color: var(--theme-color);
+ font-family: $defaultFontFamily;
+ display: flex;
+ align-items: center;
+ z-index:1;
+
+ &:hover {
+ background-color: var(--theme-background-hover);
+ border-radius: 8px;
+
+ & button {
+ background-color: $popupGrayThird;
+ }
+ }
+ svg {
+ fill: var(--theme-icon-color);
+ font-size: 18px;
+ margin-right: 15px;
+ }
+
+ @include before-divider-mixin;
+ }
+ }
+}
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 34e9a2d..fcaa2b7 100644
--- a/scripts-2.0/components/PopupMenu/WorkingTimerSection/index.tsx
+++ b/scripts-2.0/components/PopupMenu/WorkingTimerSection/index.tsx
@@ -1,14 +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 { 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,
@@ -16,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);
@@ -38,11 +83,27 @@ const WorkingTimerSection: React.FC = (props) => {
const stopTimer = () => {
stopTimerCallback();
+ setCurrentSelectedType(pomodoroStartedType)
}
return (
-
{translate('currently_running_timer')}:
+
+ {translate('currently_running_timer')}:
+
+
+
+
@@ -56,7 +117,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 d435cbc..eb35b77 100644
--- a/scripts-2.0/components/PopupMenu/WorkingTimerSection/styles.scss
+++ b/scripts-2.0/components/PopupMenu/WorkingTimerSection/styles.scss
@@ -1,18 +1,39 @@
-@import '../../../style/common';
+@import "../../../style/common";
.working-timer {
font-family: $defaultFontFamily;
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);
+ 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: solid 1px $popupBorderGray;
+ border: dashed 2px var(--theme-border-color-secondary);
border-radius: 8px;
width: 360px;
height: 52px;
@@ -21,6 +42,10 @@
align-items: center;
padding: 15px;
+ &--active {
+ border-style: solid;
+ }
+
.entry-empty-info {
color: $popupDisabledFontColor;
}
@@ -80,7 +105,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 deb56d8..276bfa5 100644
--- a/scripts-2.0/components/PopupMenu/index.tsx
+++ b/scripts-2.0/components/PopupMenu/index.tsx
@@ -1,259 +1,390 @@
-const browser = require('webextension-polyfill');
+const browser = require("webextension-polyfill");
import * as React from "react";
-import Header from "../Header";
-import {useEffect, useState, useMemo} from "react";
-import WorkingTimerSection from "./WorkingTimerSection";
-import Footer from "./Footer";
-import './styles.scss';
-import LoginWindow from "../LoginWindow";
-import ContextMenu from "../ContextMenu";
-import Gravatar from "../../helpers/Gravatar";
+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 LoginWindow from "../LoginWindow";
+import Footer from "./Footer";
+import Header from "./Header";
+import "./styles.scss";
+import WorkingTimerSection from "./WorkingTimerSection";
+
+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 [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 [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 setCurrentEntry = (currentEntry) => {
- if (currentEntry === null) {
- setIsTimerWorking(false);
- 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);
- 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 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;
- const stopTimer = () => {
- browser.runtime.sendMessage({
- action: 'track',
- type: "timerStop"
+ 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);
- setIsContextMenuOpen(false);
-
- 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) => {
- resolve(response);
- }).catch((response) => {
- 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;
- }
- });
- }, []);
-
- return (
-
-
-
-
-