diff --git a/src/_locales/de/messages.json b/src/_locales/de/messages.json index b81c34e..42ee437 100644 --- a/src/_locales/de/messages.json +++ b/src/_locales/de/messages.json @@ -128,6 +128,53 @@ "notificationsSettings": { "message": "Benachrichtigungen" }, + "pomodoroMode": { + "message": "Pomodoro" + }, + "pomodoroSettings": { + "message": "Pomodoro Einstellungen" + }, + "pomodoro": { + "message": "Pomodoro", + "description": "Die Pomodoro-Methode ist eine Zeitmanagementtechnik, die auf dem Wechsel von konzentrierten Arbeits- und Ruhephasen basiert. Bei der klassischen Pomodoro-Methode dauert die Arbeitsphase 25 Minuten und die Ruhephase 5 Minuten." + }, + "pomodoroExplanationIcon": { + "message": "Wenn der Pomodoro-Modus aktiviert ist und der Betriebsmodus aktiv ist, sehen Sie dieses Symbol", + "description": "Während der Ruhezeiten sehen Sie dieses Erweiterungssymbol" + }, + "pomodoroExplanationTime": { + "message": "Während des Pomodoro-Modus berücksichtigt die Erweiterung weiterhin die Zeit, die Sie auf den Seiten verbracht haben, alle Grenzen und Benachrichtigungen funktionieren." + }, + "pomodoroExplanationStop": { + "message": "Nach dem Drücken von 'Stop' werden die Betriebs- und Ruhezeiten auf Null zurückgesetzt." + }, + "pomodoroWork": { + "message": "Zeitraum der Arbeit" + }, + "pomodoroRest": { + "message": "Ruhezeit" + }, + "pomodoroFrequency": { + "message": "Anzahl der Wiederholungen" + }, + "start": { + "message": "Starten Sie" + }, + "stop": { + "message": "Stop" + }, + "pomodoroIsEnabled": { + "message": "Der Pomodoro-Modus ist aktiviert" + }, + "pomodoroSoundAfter": { + "message": "Ton nach vollständiger Periode" + }, + "clickToPreview": { + "message": "Zum Anhören klicken" + }, + "sound": { + "message": "Ton" + }, "aboutSettings": { "message": "Über die Erweiterung" }, @@ -316,6 +363,10 @@ "message": "Möchten Sie Kurzfilme, Kommentare, empfohlene Videos, Abonnements und andere YouTube-Ablenkungen blockieren? Wir haben eine weitere Erweiterung erstellt, mit der Sie YouTube ohne Ablenkung ansehen können.", "description": "Install" }, + "completelyBlocked": { + "message": "Vollständig blockiert", + "description": "Vollständig blockieren" + }, "tryMyOtherApps": { "message": "Probieren Sie meine anderen Apps aus" }, diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index 653f226..101629d 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -3,7 +3,7 @@ "message": "Web Activity Time Tracker - Block Sites" }, "extDescription": { - "message": "Time tracking of your web activity, limit and block distracting websites. Monitor your web usage." + "message": "Track time of your web activity, limit and block distracting websites. Monitor your web usage." }, "settings": { "message": "Settings" @@ -128,6 +128,53 @@ "notificationsSettings": { "message": "Notifications" }, + "pomodoroMode": { + "message": "Pomodoro" + }, + "pomodoroSettings": { + "message": "Pomodoro Settings" + }, + "pomodoro": { + "message": "Pomodoro", + "description": "The Pomodoro method is a time management technique based on alternating periods of focused work and rest. According to the classics of the Pomodoro method, the work period lasts 25 minutes, the rest period is 5 minutes." + }, + "pomodoroExplanationIcon": { + "message": "When Pomodoro mode is on and operation mode is active, you will see this icon", + "description": "During rest periods, you will see this extension icon" + }, + "pomodoroExplanationTime": { + "message": "During Pomodoro mode, the extension continues to take into account the time you spent on the sites, all limits and notifications work." + }, + "pomodoroExplanationStop": { + "message": "After clicking 'Stop', work and rest time is reset to zero." + }, + "pomodoroWork": { + "message": "Period of work" + }, + "pomodoroRest": { + "message": "Period of rest" + }, + "pomodoroFrequency": { + "message": "Number of repetitions" + }, + "start": { + "message": "Run" + }, + "stop": { + "message": "Stop" + }, + "pomodoroIsEnabled": { + "message": "Pomodoro Mode is enabled" + }, + "pomodoroSoundAfter": { + "message": "Sound after complete period" + }, + "clickToPreview": { + "message": "Click to listen" + }, + "sound": { + "message": "Sound" + }, "aboutSettings": { "message": "About" }, @@ -316,6 +363,10 @@ "message": "Only time intervals greater than", "description": "are shown" }, + "completelyBlocked": { + "message": "Completely Blocked", + "description": "Completely Block" + }, "tryMyOtherApps": { "message": "Try my other apps" }, diff --git a/src/_locales/ru/messages.json b/src/_locales/ru/messages.json index 13e88aa..ce17a72 100644 --- a/src/_locales/ru/messages.json +++ b/src/_locales/ru/messages.json @@ -128,6 +128,53 @@ "notificationsSettings": { "message": "Уведомления" }, + "pomodoroMode": { + "message": "Pomodoro" + }, + "pomodoroSettings": { + "message": "Настройки Pomodoro" + }, + "pomodoro": { + "message": "Pomodoro", + "description": "Метод Pomodoro — это техника тайм-менеджмента, завязанная на чередовании периодов сфокусированной работы и отдыха. По классике метода помодоро период работы длится 25 минут, период отдыха 5 минут." + }, + "pomodoroExplanationIcon": { + "message": "Когда режим Pomodoro включен и активен режим работы, то вы будете видеть эту иконку", + "description": "В периоды отдыха вы будете видеть эту иконку расширения" + }, + "pomodoroExplanationTime": { + "message": "Во время режима Pomodoro, расширение продолжает учитывать время, которые вы провели на сайтах,работают все лимиты и уведомления." + }, + "pomodoroExplanationStop": { + "message": "После нажатия 'Остановить', время работы и отдыха обнуляется." + }, + "pomodoroWork": { + "message": "Период работы" + }, + "pomodoroRest": { + "message": "Период отдыха" + }, + "pomodoroFrequency": { + "message": "Количество повторов" + }, + "start": { + "message": "Запустить" + }, + "stop": { + "message": "Остановить" + }, + "pomodoroIsEnabled": { + "message": "Режим Pomodoro включен" + }, + "pomodoroSoundAfter": { + "message": "Воспроизведение после завершения периода" + }, + "clickToPreview": { + "message": "Нажмите для прослушивания" + }, + "sound": { + "message": "Мелодия" + }, "aboutSettings": { "message": "\u041E программе" }, @@ -310,13 +357,17 @@ "message": "По часам" }, "intervals": { - "message": "Интервальны", + "message": "Интервалы", "description": "Отображаются только временные интервалы, превышающие 5 секунд" }, "intervalsChart": { "message": "Отображаются только временные интервалы, превышающие", "description": "" }, + "completelyBlocked": { + "message": "Полностью заблокирован", + "description": "Полностью заблокировать" + }, "tryMyOtherApps": { "message": "Попробуйте другие мои приложения" }, diff --git a/src/_locales/zh_CN/messages.json b/src/_locales/zh_CN/messages.json index c66b8eb..ff249eb 100644 --- a/src/_locales/zh_CN/messages.json +++ b/src/_locales/zh_CN/messages.json @@ -128,6 +128,53 @@ "notificationsSettings": { "message": "通知" }, + "pomodoroMode": { + "message": "Pomodoro" + }, + "pomodoroSettings": { + "message": "Pomodoro 设置" + }, + "pomodoro": { + "message": "Pomodoro", + "description": "Pomodoro 工作法是一种时间管理技术,以交替集中工作和休息为基础。根据经典的 Pomodoro 方法,工作时间为 25 分钟,休息时间为 5 分钟。" + }, + "pomodoroExplanationIcon": { + "message": "启用 Pomodoro 模式并激活操作模式后,您将看到以下图标", + "description": "在休息时间,您会看到这个扩展图标" + }, + "pomodoroExplanationTime": { + "message": "在 Pomodoro 模式下,扩展会继续考虑你在网站上花费的时间、所有限制和通知。" + }, + "pomodoroExplanationStop": { + "message": "按下 '停止' 键后,运行时间和休息时间将重置为零" + }, + "pomodoroWork": { + "message": "工作期限" + }, + "pomodoroRest": { + "message": "休息时间" + }, + "pomodoroFrequency": { + "message": "重复次数" + }, + "start": { + "message": "启动" + }, + "stop": { + "message": "停止" + }, + "pomodoroIsEnabled": { + "message": "番茄钟模式已启用" + }, + "pomodoroSoundAfter": { + "message": "完整时段后的声音" + }, + "clickToPreview": { + "message": "点击收听" + }, + "sound": { + "message": "声音" + }, "aboutSettings": { "message": "关于" }, @@ -316,6 +363,10 @@ "message": "您想屏蔽短片、评论、推荐视频、订阅和其他 YouTube 干扰内容吗? 我们创建了另一个扩展程序,可帮助您不受干扰地观看 YouTube", "description": "安装" }, + "completelyBlocked": { + "message": "完全封锁", + "description": "完全封锁" + }, "tryMyOtherApps": { "message": "试试我的其他应用程序" }, diff --git a/src/assets/css/dark.css b/src/assets/css/dark.css index e4ce5bd..6d7f4e2 100644 --- a/src/assets/css/dark.css +++ b/src/assets/css/dark.css @@ -8,7 +8,7 @@ .dark .headerBlock .icons-block a:hover{ filter: invert(40%) sepia(94%) saturate(3371%) hue-rotate(227deg) brightness(99%) contrast(92%); } -.dark .headerBlock .icons-block a img { +.dark .headerBlock .icons-block a.filter img { filter: invert(100%) sepia(17%) saturate(0%) hue-rotate(24deg) brightness(103%) contrast(102%); } .dark .header-block { @@ -63,4 +63,10 @@ } .dark .container p.value{ color: black; +} +.dark .review-block p{ + color:#303030; +} +.dark .pomodoro-popup-block p{ + color:#303030; } \ No newline at end of file diff --git a/src/assets/css/dashboard.css b/src/assets/css/dashboard.css index 446b624..e3b11c9 100644 --- a/src/assets/css/dashboard.css +++ b/src/assets/css/dashboard.css @@ -21,7 +21,7 @@ body { } .description { - font-size: 13px; + font-size: 14px; color: grey; margin: 7px 0 15px 0; font-weight: normal; @@ -49,7 +49,7 @@ body { margin-left: 10px; } -input[type='text'] { +input[type='number'],input[type='text'] { height: 36px; padding: 0 0 0 5px; width: 400px; diff --git a/src/assets/css/general.css b/src/assets/css/general.css index 7f4a4ad..bac2baf 100644 --- a/src/assets/css/general.css +++ b/src/assets/css/general.css @@ -29,6 +29,9 @@ .mb-20 { margin-bottom: 20px; } +.ml-5 { + margin-left: 5px; +} .ml-10 { margin-left: 10px; } @@ -57,6 +60,10 @@ text-align: center; } +.mt-10 { + margin-top: 10px; +} + .mt-15 { margin-top: 15px; } diff --git a/src/assets/icons/pomodoro-rest.png b/src/assets/icons/pomodoro-rest.png new file mode 100644 index 0000000..5c8a25e Binary files /dev/null and b/src/assets/icons/pomodoro-rest.png differ diff --git a/src/assets/icons/pomodoro.png b/src/assets/icons/pomodoro.png new file mode 100644 index 0000000..1bca502 Binary files /dev/null and b/src/assets/icons/pomodoro.png differ diff --git a/src/assets/icons/pomodoro.svg b/src/assets/icons/pomodoro.svg new file mode 100644 index 0000000..d3ec461 --- /dev/null +++ b/src/assets/icons/pomodoro.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/src/assets/icons/start.svg b/src/assets/icons/start.svg new file mode 100644 index 0000000..144f821 --- /dev/null +++ b/src/assets/icons/start.svg @@ -0,0 +1,17 @@ + + + + + play + Created with Sketch Beta. + + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/icons/stop.svg b/src/assets/icons/stop.svg new file mode 100644 index 0000000..8d35481 --- /dev/null +++ b/src/assets/icons/stop.svg @@ -0,0 +1,17 @@ + + + + + stop + Created with Sketch Beta. + + + + + + + + + + + \ No newline at end of file diff --git a/src/assets/pomodoro-sounds/1.mp3 b/src/assets/pomodoro-sounds/1.mp3 new file mode 100644 index 0000000..2c4babb Binary files /dev/null and b/src/assets/pomodoro-sounds/1.mp3 differ diff --git a/src/assets/pomodoro-sounds/10.mp3 b/src/assets/pomodoro-sounds/10.mp3 new file mode 100644 index 0000000..371a5b8 Binary files /dev/null and b/src/assets/pomodoro-sounds/10.mp3 differ diff --git a/src/assets/pomodoro-sounds/11.mp3 b/src/assets/pomodoro-sounds/11.mp3 new file mode 100644 index 0000000..e000b73 Binary files /dev/null and b/src/assets/pomodoro-sounds/11.mp3 differ diff --git a/src/assets/pomodoro-sounds/12.mp3 b/src/assets/pomodoro-sounds/12.mp3 new file mode 100644 index 0000000..b949f38 Binary files /dev/null and b/src/assets/pomodoro-sounds/12.mp3 differ diff --git a/src/assets/pomodoro-sounds/13.mp3 b/src/assets/pomodoro-sounds/13.mp3 new file mode 100644 index 0000000..a255ca5 Binary files /dev/null and b/src/assets/pomodoro-sounds/13.mp3 differ diff --git a/src/assets/pomodoro-sounds/2.mp3 b/src/assets/pomodoro-sounds/2.mp3 new file mode 100644 index 0000000..2e526e3 Binary files /dev/null and b/src/assets/pomodoro-sounds/2.mp3 differ diff --git a/src/assets/pomodoro-sounds/3.mp3 b/src/assets/pomodoro-sounds/3.mp3 new file mode 100644 index 0000000..185ddb4 Binary files /dev/null and b/src/assets/pomodoro-sounds/3.mp3 differ diff --git a/src/assets/pomodoro-sounds/4.mp3 b/src/assets/pomodoro-sounds/4.mp3 new file mode 100644 index 0000000..4fcab3f Binary files /dev/null and b/src/assets/pomodoro-sounds/4.mp3 differ diff --git a/src/assets/pomodoro-sounds/5.mp3 b/src/assets/pomodoro-sounds/5.mp3 new file mode 100644 index 0000000..8009774 Binary files /dev/null and b/src/assets/pomodoro-sounds/5.mp3 differ diff --git a/src/assets/pomodoro-sounds/6.mp3 b/src/assets/pomodoro-sounds/6.mp3 new file mode 100644 index 0000000..21d0827 Binary files /dev/null and b/src/assets/pomodoro-sounds/6.mp3 differ diff --git a/src/assets/pomodoro-sounds/7.mp3 b/src/assets/pomodoro-sounds/7.mp3 new file mode 100644 index 0000000..901fc3d Binary files /dev/null and b/src/assets/pomodoro-sounds/7.mp3 differ diff --git a/src/assets/pomodoro-sounds/8.mp3 b/src/assets/pomodoro-sounds/8.mp3 new file mode 100644 index 0000000..8843950 Binary files /dev/null and b/src/assets/pomodoro-sounds/8.mp3 differ diff --git a/src/assets/pomodoro-sounds/9.mp3 b/src/assets/pomodoro-sounds/9.mp3 new file mode 100644 index 0000000..59845ea Binary files /dev/null and b/src/assets/pomodoro-sounds/9.mp3 differ diff --git a/src/background.ts b/src/background.ts index 3a12b2c..6feb8a6 100644 --- a/src/background.ts +++ b/src/background.ts @@ -6,17 +6,26 @@ import { Settings } from './functions/settings'; import { StorageParams } from './storage/storage-params'; import { injecStorage } from './storage/inject-storage'; import { todayLocalDate } from './utils/date'; +import { checkPomodoro } from './functions/pomodoro'; +import { Messages } from './utils/messages'; +import { injectTabsRepositorySingleton } from './repository/inject-tabs-repository'; logger.log('Start background script'); +let pomodoroTimer: number; self.onerror = err => { console.error('Unhandled error:', err); }; -Browser.storage.onChanged.addListener((changes, namespace) => { +Browser.storage.onChanged.addListener(async (changes, namespace) => { for (var key in changes) { if (Object.values(StorageParams).includes(key as StorageParams)) - Settings.getInstance().reloadSetting(key as StorageParams); + await Settings.getInstance().reloadSetting(key as StorageParams); + + if (key == StorageParams.IS_POMODORO_ENABLED) { + const value = changes[StorageParams.IS_POMODORO_ENABLED].newValue; + pomodoro(value); + } } }); @@ -54,5 +63,30 @@ Browser.windows.onFocusChanged.addListener(() => { logger.log('onFocusChanged'); }); +async function pomodoro(value?: boolean) { + if (value == undefined) { + const settingsStorage = injecStorage(); + value = await settingsStorage.getValue(StorageParams.IS_POMODORO_ENABLED); + } + if (value == true) pomodoroTimer = setInterval(checkPomodoro, 1000); + else clearInterval(pomodoroTimer); +} + +pomodoro(); scheduleJobs(); initTracker(); + +Browser.runtime.onMessage.addListener(async message => { + if (message == Messages.ClearAllData) { + const storage = injecStorage(); + const repo = await injectTabsRepositorySingleton(); + repo.removeAllTabs(); + await storage.saveTabs([]); + } + if (message.message == Messages.Restore) { + const storage = injecStorage(); + await storage.saveTabs(message.data); + const repo = await injectTabsRepositorySingleton(); + repo.initAsync(); + } +}); diff --git a/src/block.html b/src/block.html index f0075ee..1b72d23 100644 --- a/src/block.html +++ b/src/block.html @@ -1,4 +1,4 @@ - + @@ -11,6 +11,5 @@ - diff --git a/src/components/Dashboad.vue b/src/components/Dashboad.vue index b94beb1..9658ff2 100644 --- a/src/components/Dashboad.vue +++ b/src/components/Dashboad.vue @@ -71,7 +71,19 @@ function openChart(type: TypeOfChart) { height: 390px; } .chart-btn { - background-color: rgb(202, 202, 202); + background-color: rgb(192, 192, 192); + color: #fff; + border-radius: 3px; + height: 36px; + line-height: 35px; + padding: 0 20px; + border: 0; + font-size: 14px; + font-weight: 500; + cursor: pointer; + min-width: 80px; + text-align: center; + width: 200px; } .chart-btn.active { diff --git a/src/components/Pomodoro.vue b/src/components/Pomodoro.vue new file mode 100644 index 0000000..cf58928 --- /dev/null +++ b/src/components/Pomodoro.vue @@ -0,0 +1,254 @@ + + + + + + + diff --git a/src/components/PomodoroInfo.vue b/src/components/PomodoroInfo.vue new file mode 100644 index 0000000..23f776e --- /dev/null +++ b/src/components/PomodoroInfo.vue @@ -0,0 +1,72 @@ + + + + + + + diff --git a/src/components/PomodoroSoundsSelector.vue b/src/components/PomodoroSoundsSelector.vue new file mode 100644 index 0000000..e9b8c8d --- /dev/null +++ b/src/components/PomodoroSoundsSelector.vue @@ -0,0 +1,69 @@ + + + + + + + diff --git a/src/components/Review.vue b/src/components/Review.vue index b12d619..c1cf061 100644 --- a/src/components/Review.vue +++ b/src/components/Review.vue @@ -82,6 +82,7 @@ async function openStore() { display: inline-block; margin: 8px; font-size: 17px; + font-weight: 500; } .review-block img { padding: 9px 0 0 0; diff --git a/src/functions/playSound.ts b/src/functions/playSound.ts new file mode 100644 index 0000000..61eb7f2 --- /dev/null +++ b/src/functions/playSound.ts @@ -0,0 +1,7 @@ +import Browser from 'webextension-polyfill'; +import { PomodoroSounds } from '../utils/pomodoro'; + +export function playSound(sound: PomodoroSounds) { + const myAudio = new Audio(Browser.runtime.getURL(`assets/pomodoro-sounds/${sound}`)); + myAudio.play(); +} diff --git a/src/functions/pomodoro.ts b/src/functions/pomodoro.ts index 49c74bf..afb639f 100644 --- a/src/functions/pomodoro.ts +++ b/src/functions/pomodoro.ts @@ -1,64 +1,152 @@ import { addSeconds } from 'date-fns'; import { injecStorage } from '../storage/inject-storage'; import { StorageParams } from '../storage/storage-params'; -import { Time, timeToSeconds } from '../utils/time'; import { useBadge, BadgeIcon, BadgeColor } from './useBadge'; +import { Settings } from './settings'; +import Browser from 'webextension-polyfill'; +import { logger } from '../utils/logger'; +import { Messages } from '../utils/messages'; +import { isDateEqual } from '../utils/date'; +import { createOffscreen } from '../offscreen/index'; export async function checkPomodoro() { - function isTargetPeriod(isRest: boolean) { + type PomodoroPeriod = { + period: Period; + isTargetPeriod: boolean; + isTargetPeriodFinishedNow: boolean; + }; + + enum Period { + work = 'WORK', + rest = 'REST', + finished = 'FINISH', + } + + function isTargetPeriod(period: Period): PomodoroPeriod { + let isPomodoroTargetPeriodEnd; for (let index = 1; index <= frequency; index++) { - const plusWorkingTime = timeToSeconds(workTime) * (isRest ? index : index--); - const plusRestTime = timeToSeconds(restTime) * index--; + let ind = period == Period.work ? index - 1 : index; + const plusWorkingTime = workTime * ind; + const plusRestTime = (restTime + 1) * (index - 1); const isPomodoroTargetPeriodStart = addSeconds(startTime, plusWorkingTime + plusRestTime); - const isPomodoroTargetPeriodEnd = addSeconds( - startTime, - plusWorkingTime + plusRestTime + timeToSeconds(workTime), - ); + isPomodoroTargetPeriodEnd = addSeconds(startTime, plusWorkingTime + plusRestTime + workTime); const isTargetPeriod = - now.getTime() >= isPomodoroTargetPeriodStart.getTime() && - now.getTime() <= isPomodoroTargetPeriodEnd.getTime(); + now >= isPomodoroTargetPeriodStart && + (now <= isPomodoroTargetPeriodEnd || addSeconds(now, -1) <= isPomodoroTargetPeriodEnd); - if (isTargetPeriod) return true; + if (isTargetPeriod) { + console.log( + now, + isPomodoroTargetPeriodEnd, + isDateEqual(now, isPomodoroTargetPeriodEnd) || + isDateEqual(addSeconds(now, -1), isPomodoroTargetPeriodEnd), + period, + ); + return { + period: period, + isTargetPeriod: isTargetPeriod, + isTargetPeriodFinishedNow: + isDateEqual(now, isPomodoroTargetPeriodEnd) || + isDateEqual(addSeconds(now, -1), isPomodoroTargetPeriodEnd), + }; + } } - return false; + return { + period: Period.finished, + isTargetPeriod: false, + isTargetPeriodFinishedNow: false, + }; + } + + async function play(period: Period) { + function getSound() { + switch (period) { + case Period.work: + return StorageParams.POMODORO_AUDIO_AFTER_WORK; + case Period.rest: + return StorageParams.POMODORO_AUDIO_AFTER_REST; + case Period.finished: + return StorageParams.POMODORO_AUDIO_AFTER_FINISHED; + } + } + logger.log(`[Pomodoro] ${period}`); + const sound = await storage.getValue(getSound()); + await createOffscreen(); + await Browser.runtime.sendMessage({ + message: Messages.PlayAudio, + sound: sound, + offscreen: true, + }); } const storage = injecStorage(); - const isPomodoroEnabled = (await storage.getValue(StorageParams.IS_POMODORO_ENABLED)) as boolean; + const isPomodoroEnabled = (await Settings.getInstance().getSetting( + StorageParams.IS_POMODORO_ENABLED, + )) as boolean; if (!isPomodoroEnabled) return; - const startTime = (await storage.getValue(StorageParams.POMODORO_START_TIME)) as Date; - const workTime = (await storage.getValue(StorageParams.POMODORO_INTERVAL_WORK)) as Time; - const restTime = (await storage.getValue(StorageParams.POMODORO_INTERVAL_REST)) as Time; - const frequency = (await storage.getValue(StorageParams.POMODORO_FREQUENCY)) as number; + const startTime = new Date( + (await Settings.getInstance().getSetting(StorageParams.POMODORO_START_TIME)) as string, + ); + const workTime = (await Settings.getInstance().getSetting( + StorageParams.POMODORO_INTERVAL_WORK, + )) as number; + const restTime = (await Settings.getInstance().getSetting( + StorageParams.POMODORO_INTERVAL_REST, + )) as number; + const frequency = (await Settings.getInstance().getSetting( + StorageParams.POMODORO_FREQUENCY, + )) as number; const now = new Date(); - const pomodoroEndTime = addSeconds( - startTime, - timeToSeconds(workTime) * frequency + timeToSeconds(restTime) * frequency, - ); + const pomodoroEndTime = addSeconds(startTime, workTime * frequency + restTime * frequency); + + const activeTab = await Browser.tabs.query({ active: true }); + + if (now >= pomodoroEndTime) { + if (isDateEqual(now, pomodoroEndTime)) { + logger.log(`[Pomodoro] Pomodoro finished`); + await play(Period.finished); + } - if (pomodoroEndTime > now) { await storage.saveValue(StorageParams.IS_POMODORO_ENABLED, false); await storage.saveValue(StorageParams.POMODORO_START_TIME, null); + await useBadge({ + tabId: activeTab[0].id, + text: null, + color: BadgeColor.none, + icon: BadgeIcon.default, + }); return; } - const isWork = isTargetPeriod(false); - const isRest = isTargetPeriod(true); + let target = isTargetPeriod(Period.work); + const isWork = target.isTargetPeriod; - if (isWork) + if (isWork) { await useBadge({ - text: '', + tabId: activeTab[0].id, + text: null, color: BadgeColor.none, icon: BadgeIcon.pomodoroWorkingTime, }); - if (isRest) - await useBadge({ - text: '', - color: BadgeColor.none, - icon: BadgeIcon.pomodoroRestTime, - }); + } else { + target = isTargetPeriod(Period.rest); + if (target.isTargetPeriod) { + await useBadge({ + tabId: activeTab[0].id, + text: null, + color: BadgeColor.none, + icon: BadgeIcon.pomodoroRestTime, + }); + } + } + + if (target.isTargetPeriodFinishedNow) await play(target.period); + + return { + isWork, + }; } diff --git a/src/functions/useBadge.ts b/src/functions/useBadge.ts index ddd9a6d..8854b25 100644 --- a/src/functions/useBadge.ts +++ b/src/functions/useBadge.ts @@ -1,16 +1,16 @@ import Browser from 'webextension-polyfill'; export interface BadgeState { - text: string; + text: string | null; color: BadgeColor; tabId?: number; icon?: BadgeIcon; } export enum BadgeIcon { - timer = '/assets/icons/128x128.png', - pomodoroWorkingTime = '/assets/icons/empty.png', - pomodoroRestTime = '/assets/icons/empty.png', + default = '/128x128.png', + pomodoroWorkingTime = '/assets/icons/pomodoro.png', + pomodoroRestTime = '/assets/icons/pomodoro-rest.png', } export enum BadgeColor { @@ -21,13 +21,22 @@ export enum BadgeColor { } export async function useBadge(badge: BadgeState): Promise { - await Browser.action.setBadgeBackgroundColor({ color: badge.color }); + if (badge.color != BadgeColor.none) + await Browser.action.setBadgeBackgroundColor({ color: badge.color }); await Browser.action.setBadgeText({ tabId: badge.tabId, text: badge.text, }); - if (badge.icon) + if (badge.icon) { await Browser.action.setIcon({ path: badge.icon, }); + await Browser.action.setBadgeText({ + tabId: badge.tabId, + text: badge.text, + }); + } else + await Browser.action.setIcon({ + path: BadgeIcon.default, + }); } diff --git a/src/manifest.json b/src/manifest.json index 6523135..736dc37 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -19,7 +19,8 @@ "idle", "unlimitedStorage", "alarms", - "notifications" + "notifications", + "offscreen" ], "offline_enabled": true, "background": { @@ -28,5 +29,9 @@ "action": { "default_popup": "src/popup.html", "default_title": "Web Activity Time Tracker" - } + }, + "web_accessible_resources": [{ + "resources": ["assets/pomodoro-sounds/*.mp3"], + "matches": [""] + }] } diff --git a/src/offscreen.html b/src/offscreen.html new file mode 100644 index 0000000..9c5de41 --- /dev/null +++ b/src/offscreen.html @@ -0,0 +1,14 @@ + + + + + + + Offscreen + + + + + + + diff --git a/src/offscreen.ts b/src/offscreen.ts new file mode 100644 index 0000000..b4c0574 --- /dev/null +++ b/src/offscreen.ts @@ -0,0 +1,23 @@ +import Browser from 'webextension-polyfill'; +import { Messages } from './utils/messages'; + +console.log('ofscreen'); + +Browser.runtime.onMessage.addListener(msg => { + console.log('ofscreen message'); + if (msg.message == Messages.PlayAudio) { + if (msg.offscreen == undefined) return; + + playAudio(msg.sound); + } +}); + +function playAudio(sound: string) { + const audio = document.querySelector('audio'); + if (!audio) return; + + const path = Browser.runtime.getURL(`../assets/pomodoro-sounds/${sound}`); + audio.src = path; + audio.volume = 1; + audio.play(); +} diff --git a/src/offscreen/index.ts b/src/offscreen/index.ts new file mode 100644 index 0000000..6f8fd85 --- /dev/null +++ b/src/offscreen/index.ts @@ -0,0 +1,12 @@ +import Browser from 'webextension-polyfill'; + +export async function createOffscreen() { + const path = 'src/offscreen.html'; + const offscreenUrl = Browser.runtime.getURL(path); + if (await chrome.offscreen.hasDocument()) return; + await chrome.offscreen.createDocument({ + url: offscreenUrl, + reasons: ['AUDIO_PLAYBACK'], + justification: 'Play audio sounds', + }); +} diff --git a/src/pages/Dashboard.vue b/src/pages/Dashboard.vue index 27599a6..a3108e8 100644 --- a/src/pages/Dashboard.vue +++ b/src/pages/Dashboard.vue @@ -90,6 +90,27 @@ +
+ + + +
+
+ +
+
+
+ @@ -84,6 +88,7 @@ + @@ -93,6 +98,7 @@ import { useI18n } from 'vue-i18n'; import TabList from '../components/TabList.vue'; import ByDays from '../components/ByDays.vue'; import Review from '../components/Review.vue'; +import PomodoroInfo from '../components/PomodoroInfo.vue'; import { openPage } from '../utils/open-page'; import { SettingsTab, TypeOfList } from '../utils/enums'; import { injecStorage } from '../storage/inject-storage'; @@ -137,7 +143,7 @@ function updateTab() { } .headerBlock .header { font-size: 16px; - padding: 0 10px; + padding: 0 0 0 5px; display: inline-block; font-weight: 600; color: #4a4a4a; @@ -153,7 +159,7 @@ function updateTab() { } .headerBlock .icons-block { float: right; - margin: 7px 10px 0 0; + margin: 7px 0 0 0; } .headerBlock .icons-block a:hover { @@ -172,6 +178,5 @@ function updateTab() { } .headerBlock .icons-block .dark-mode-icon { vertical-align: middle; - margin-right: 10px; } diff --git a/src/storage/storage-params.ts b/src/storage/storage-params.ts index 612b2ae..09c001f 100644 --- a/src/storage/storage-params.ts +++ b/src/storage/storage-params.ts @@ -1,6 +1,7 @@ import { Tab } from '../entity/tab'; import { TimeInterval } from '../entity/time-interval'; -import { HOUR } from '../utils/time'; +import { PomodoroSounds } from '../utils/pomodoro'; +import { HOUR, MINUTE_IN_SECONDS } from '../utils/time'; export enum StorageParams { BLACK_LIST = 'black_list', @@ -20,6 +21,14 @@ export enum StorageParams { INSTALL_DATE = 'install-date', PROMO_CLEAR_YOUTUBE_ON_LIMITS = 'promo-clear-youtube-on-limits', PROMO_CLEAR_YOUTUBE_ON_BLOCK = 'promo-clear-youtube-on-block', + IS_POMODORO_ENABLED = 'is-pomodoro-enabled', + POMODORO_START_TIME = 'pomodoro-start-time', + POMODORO_INTERVAL_WORK = 'pomodoro-interval-work', + POMODORO_AUDIO_AFTER_WORK = 'pomodoro-audio-after-work', + POMODORO_AUDIO_AFTER_REST = 'pomodoro-audio-after-rest', + POMODORO_AUDIO_AFTER_FINISHED = 'pomodoro-audio-after-finished', + POMODORO_INTERVAL_REST = 'pomodoro-interval-rest', + POMODORO_FREQUENCY = 'pomodoro-frequency', } export enum StorageDeserializeParam { @@ -63,6 +72,13 @@ export const DAILY_SUMMARY_NOTIFICATION_TIME_DEFAULT = (20 * HOUR) / 1000; export const DAILY_NOTIFICATION_DEFAULT = true; export const SHOW_CHANGELOG_DEFAULT = false; export const SHOW_PROMO_CLEAR_YOUTUBE_DEFAULT = false; +export const IS_POMODORO_ENABLED_DEFAULT = false; +export const POMODORO_INTERVAL_WORK_DEFAULT = 25 * MINUTE_IN_SECONDS; +export const POMODORO_INTERVAL_REST_DEFAULT = 5 * MINUTE_IN_SECONDS; +export const POMODORO_FREQUENCY_DEFAULT = 3; +export const POMODORO_AUDIO_AFTER_WORK_DEFAULT = PomodoroSounds['Sound 3']; +export const POMODORO_AUDIO_AFTER_REST_DEFAULT = PomodoroSounds['Sound 8']; +export const POMODORO_AUDIO_AFTER_FINISHED_DEFAULT = PomodoroSounds['Sound 10']; export function getDefaultValue(param: StorageParams) { switch (param) { @@ -93,5 +109,19 @@ export function getDefaultValue(param: StorageParams) { case StorageParams.PROMO_CLEAR_YOUTUBE_ON_BLOCK: case StorageParams.PROMO_CLEAR_YOUTUBE_ON_LIMITS: return SHOW_PROMO_CLEAR_YOUTUBE_DEFAULT; + case StorageParams.IS_POMODORO_ENABLED: + return IS_POMODORO_ENABLED_DEFAULT; + case StorageParams.POMODORO_INTERVAL_WORK: + return POMODORO_INTERVAL_WORK_DEFAULT; + case StorageParams.POMODORO_INTERVAL_REST: + return POMODORO_INTERVAL_REST_DEFAULT; + case StorageParams.POMODORO_FREQUENCY: + return POMODORO_FREQUENCY_DEFAULT; + case StorageParams.POMODORO_AUDIO_AFTER_WORK: + return POMODORO_AUDIO_AFTER_WORK_DEFAULT; + case StorageParams.POMODORO_AUDIO_AFTER_REST: + return POMODORO_AUDIO_AFTER_REST_DEFAULT; + case StorageParams.POMODORO_AUDIO_AFTER_FINISHED: + return POMODORO_AUDIO_AFTER_FINISHED_DEFAULT; } } diff --git a/src/tracker.ts b/src/tracker.ts index af2bc57..7e8a4a2 100644 --- a/src/tracker.ts +++ b/src/tracker.ts @@ -39,7 +39,7 @@ async function trackTime() { if (isValidPage(activeTab)) { const activeDomain = extractHostname(activeTab!.url); - if (await isInBlackList(activeDomain)) { + if ((await isInBlackList(activeDomain)) && (await canChangeBadge())) { await useBadge({ tabId: activeTab?.id, text: 'n/a', @@ -126,18 +126,20 @@ async function mainTracker( const viewInBadge = await Settings.getInstance().getSetting(StorageParams.VIEW_TIME_IN_BADGE); - if (viewInBadge) - await useBadge({ - tabId: activeTab?.id, - text: convertSummaryTimeToBadgeString(tab.days.at(-1)!.summary), - color: BadgeColor.blue, - }); - else - await useBadge({ - tabId: activeTab?.id, - text: '', - color: BadgeColor.red, - }); + if (await canChangeBadge()) { + if (viewInBadge) + await useBadge({ + tabId: activeTab?.id, + text: convertSummaryTimeToBadgeString(tab.days.at(-1)!.summary), + color: BadgeColor.blue, + }); + else + await useBadge({ + tabId: activeTab?.id, + text: null, + color: BadgeColor.none, + }); + } } else await closeOpenInterval(); } @@ -161,17 +163,6 @@ async function saveTabs() { await storage.saveTabs(tabs); } -Browser.runtime.onMessage.addListener(async message => { - if (message == Messages.ClearAllData) { - const storage = injecStorage(); - const repo = await injectTabsRepositorySingleton(); - repo.removeAllTabs(); - await storage.saveTabs([]); - } - if (message.message == Messages.Restore) { - const storage = injecStorage(); - await storage.saveTabs(message.data); - const repo = await injectTabsRepositorySingleton(); - repo.initAsync(); - } -}); +async function canChangeBadge() { + return !(await Settings.getInstance().getSetting(StorageParams.IS_POMODORO_ENABLED)) as boolean; +} diff --git a/src/utils/converter.ts b/src/utils/converter.ts index 06ac8f5..a80a5a7 100644 --- a/src/utils/converter.ts +++ b/src/utils/converter.ts @@ -2,7 +2,7 @@ import i18n, { getMessagesFromLocale } from '../plugins/i18n'; import { HOUR, HOUR_IN_SECONDS, MINUTE, MINUTE_IN_SECONDS, Time } from './time'; export function convertHHMMToSeconds(hours: number, minutes: number) { - return hours * 3600 + minutes * 60; + return hours * HOUR_IN_SECONDS + minutes * MINUTE_IN_SECONDS; } export function convertHHMMToMilliSeconds(hours: number, minutes: number) { @@ -10,9 +10,9 @@ export function convertHHMMToMilliSeconds(hours: number, minutes: number) { } export function convertSecondsToHHMM(seconds: number): Time { - const hours = Math.floor(seconds / 3600); - const totalSeconds = seconds % 3600; - const mins = Math.floor(totalSeconds / 60); + const hours = Math.floor(seconds / HOUR_IN_SECONDS); + const totalSeconds = seconds % HOUR_IN_SECONDS; + const mins = Math.floor(totalSeconds / MINUTE_IN_SECONDS); return { hours: hours, diff --git a/src/utils/date.ts b/src/utils/date.ts index 2fa2cda..3be4450 100644 --- a/src/utils/date.ts +++ b/src/utils/date.ts @@ -29,3 +29,14 @@ export function ranges() { }, ]; } + +export function isDateEqual(first: Date, second: Date) { + return ( + first.getFullYear() == second.getFullYear() && + first.getMonth() == second.getMonth() && + first.getDate() == second.getDate() && + first.getHours() == second.getHours() && + first.getMinutes() == second.getMinutes() && + first.getSeconds() == second.getSeconds() + ); +} diff --git a/src/utils/enums.ts b/src/utils/enums.ts index 2e94d73..bc270f2 100644 --- a/src/utils/enums.ts +++ b/src/utils/enums.ts @@ -17,6 +17,7 @@ export enum SettingsTab { WhiteList, Limits, Notifications, + Pomodoro, About, } diff --git a/src/utils/extension-tabs.ts b/src/utils/extension-tabs.ts index 7ff66e6..f89c8fe 100644 --- a/src/utils/extension-tabs.ts +++ b/src/utils/extension-tabs.ts @@ -16,6 +16,8 @@ export function getStringTab(tab: SettingsTab) { return 'whitelist'; case SettingsTab.Notifications: return 'notifications'; + case SettingsTab.Pomodoro: + return 'pomodoro'; } } @@ -35,5 +37,7 @@ export function getEnumValueTab(tab: string) { return SettingsTab.WhiteList; case 'notifications': return SettingsTab.Notifications; + case 'pomodoro': + return SettingsTab.Pomodoro; } } diff --git a/src/utils/messages.ts b/src/utils/messages.ts index 61d7e3c..4f7f239 100644 --- a/src/utils/messages.ts +++ b/src/utils/messages.ts @@ -2,4 +2,5 @@ export enum Messages { RescheduleJobs = 'reschedule-jobs', ClearAllData = 'clear-data', Restore = 'restore-data', + PlayAudio = 'play-audio', } diff --git a/src/utils/pomodoro.ts b/src/utils/pomodoro.ts new file mode 100644 index 0000000..c1a546d --- /dev/null +++ b/src/utils/pomodoro.ts @@ -0,0 +1,22 @@ +import { StorageParams } from '../storage/storage-params'; + +export type PomodoroAudioParams = + | StorageParams.POMODORO_AUDIO_AFTER_WORK + | StorageParams.POMODORO_AUDIO_AFTER_REST + | StorageParams.POMODORO_AUDIO_AFTER_FINISHED; + +export enum PomodoroSounds { + 'Sound 1' = '1.mp3', + 'Sound 2' = '2.mp3', + 'Sound 3' = '3.mp3', + 'Sound 4' = '4.mp3', + 'Sound 5' = '5.mp3', + 'Sound 6' = '6.mp3', + 'Sound 7' = '7.mp3', + 'Sound 8' = '8.mp3', + 'Sound 9' = '9.mp3', + 'Sound 10' = '10.mp3', + 'Sound 11' = '11.mp3', + 'Sound 12' = '12.mp3', + 'Sound 13' = '13.mp3', +} diff --git a/vite.config.ts b/vite.config.ts index c2fb681..b8ef92a 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -22,6 +22,7 @@ function generateManifest() { // https://vitejs.dev/config/ export default defineConfig(({ mode }) => ({ build: { + assetsInlineLimit: 1024, rollupOptions: { output: { assetFileNames: assetInfo => { @@ -54,10 +55,13 @@ export default defineConfig(({ mode }) => ({ webExtension({ manifest: generateManifest, watchFilePaths: ['package.json', 'manifest.json'], - additionalInputs: ['src/block.html', 'src/welcome.html'], + additionalInputs: ['src/block.html', 'src/welcome.html', 'src/offscreen.html'], }), copy({ - targets: [{ src: 'src/_locales', dest: 'dist' }], + targets: [ + { src: 'src/_locales', dest: 'dist' }, + { src: 'src/assets/pomodoro-sounds', dest: 'dist/assets' }, + ], }), ], optimizeDeps: {