diff --git a/src/__tests__/__mock__/recurrence.txt b/src/__tests__/__mock__/recurrence.txt index 712e139e..a16ffaac 100644 --- a/src/__tests__/__mock__/recurrence.txt +++ b/src/__tests__/__mock__/recurrence.txt @@ -1,15 +1,15 @@ -2023-12-24 Line 1 rec:1d due:2023-12-25 -2023-12-24 Line 1 rec:w due:2023-12-31 -2023-12-24 Line 1 rec:2m due:2024-02-24 -2023-12-24 Line 1 rec:+1d due:2023-12-26 -2023-12-24 Line 1 rec:7w due:2024-02-11 -2023-12-24 Line 1 due:2023-07-24 rec:+1b -2023-12-24 taxes are due in one year t:2022-03-30 due:2022-04-30 rec:+1y -2023-12-24 Water plants @home +quick due:2023-12-31 t:2023-12-21 rec:1w -2023-12-24 Line 1 rec:+1d t:2023-09-20 -2023-12-24 Line 1 rec:1d pri:A due:2023-12-25 -2023-12-24 (A) Do something rec:d t:2023-12-25 @SomeContext -2023-12-24 Do something rec:0d -2023-12-24 Do something rec:0d due:2023-12-24 -2023-12-24 Do something rec:0d due:2023-12-24 t:2023-12-24 \ No newline at end of file +2023-12-26 Line 1 rec:1d due:2023-12-27 +2023-12-26 Line 1 rec:w due:2024-01-02 +2023-12-26 Line 1 rec:2m due:2024-02-26 +2023-12-26 Line 1 rec:+1d due:2023-12-28 +2023-12-26 Line 1 rec:7w due:2024-02-13 +2023-12-26 Line 1 due:2023-07-24 rec:+1b +2023-12-26 taxes are due in one year t:2022-03-30 due:2022-04-30 rec:+1y +2023-12-26 Water plants @home +quick due:2024-01-02 t:2023-12-23 rec:1w +2023-12-26 Line 1 rec:+1d t:2023-09-20 +2023-12-26 Line 1 rec:1d pri:A due:2023-12-27 +2023-12-26 (A) Do something rec:d t:2023-12-27 @SomeContext +2023-12-26 Do something rec:0d +2023-12-26 Do something rec:0d due:2023-12-26 +2023-12-26 Do something rec:0d due:2023-12-26 t:2023-12-26 \ No newline at end of file diff --git a/src/__tests__/main/Active.tsx b/src/__tests__/main/Active.tsx index 3eaac354..48e4dbd3 100644 --- a/src/__tests__/main/Active.tsx +++ b/src/__tests__/main/Active.tsx @@ -1,4 +1,4 @@ -import { getActiveFile } from '../../main/Modules/File/Active'; +import { getActiveFile } from '../../main/modules/File/Active'; jest.mock('../../main/config', () => ({ configStorage: { diff --git a/src/__tests__/main/Archive.tsx b/src/__tests__/main/Archive.tsx index db5661c2..04533ea2 100644 --- a/src/__tests__/main/Archive.tsx +++ b/src/__tests__/main/Archive.tsx @@ -1,4 +1,4 @@ -import archiveTodos from '../../main/Modules/File/Archive'; +import archiveTodos from '../../main/modules/File/Archive'; import fs from 'fs/promises'; jest.mock('electron', () => ({ diff --git a/src/__tests__/main/Attributes.tsx b/src/__tests__/main/Attributes.tsx index cd984924..b69bd1b3 100644 --- a/src/__tests__/main/Attributes.tsx +++ b/src/__tests__/main/Attributes.tsx @@ -1,4 +1,4 @@ -import { attributes, updateAttributes } from '../../main/Modules/Attributes'; +import { attributes, updateAttributes } from '../../main/modules/Attributes'; const todoObjects = [ { id: 1, created: null, priority: 'A', projects: ['Project 1'], contexts: ['Context 1'], due: '2023-01-01', dueString: '2023-01-01', complete: false, completed: null, t: '2024-02-01', tString: '2024-02-01', rec: null, pm: null, body: null, hidden: false, string: '', notify: false, }, diff --git a/src/__tests__/main/ChangeCompleteState.tsx b/src/__tests__/main/ChangeCompleteState.tsx index c2395927..41cdf7ec 100644 --- a/src/__tests__/main/ChangeCompleteState.tsx +++ b/src/__tests__/main/ChangeCompleteState.tsx @@ -1,10 +1,10 @@ -import { changeCompleteState } from '../../main/Modules/ProcessDataRequest/ChangeCompleteState'; +import { changeCompleteState } from '../../main/modules/ProcessDataRequest/ChangeCompleteState'; import { Item } from 'jstodotxt'; import dayjs from 'dayjs'; const date: string = dayjs(new Date()).format('YYYY-MM-DD'); -jest.mock('../../main/Modules/ProcessDataRequest/CreateRecurringTodo', () => ({ +jest.mock('../../main/modules/ProcessDataRequest/CreateRecurringTodo', () => ({ createRecurringTodo: jest.fn(), })); diff --git a/src/__tests__/main/CreateRecurringTodo.tsx b/src/__tests__/main/CreateRecurringTodo.tsx index 28718727..2d300d8f 100644 --- a/src/__tests__/main/CreateRecurringTodo.tsx +++ b/src/__tests__/main/CreateRecurringTodo.tsx @@ -1,11 +1,11 @@ import fs from 'fs/promises'; -import { writeTodoObjectToFile } from '../../main/Modules/File/Write'; -import { createRecurringTodo } from '../../main/Modules/ProcessDataRequest/CreateRecurringTodo'; -import { getActiveFile } from '../../main/Modules/File/Active'; -import { lines } from '../../main/Modules/ProcessDataRequest/CreateTodoObjects'; +import { writeTodoObjectToFile } from '../../main/modules/File/Write'; +import { createRecurringTodo } from '../../main/modules/ProcessDataRequest/CreateRecurringTodo'; +import { getActiveFile } from '../../main/modules/File/Active'; +import { lines } from '../../main/modules/ProcessDataRequest/CreateTodoObjects'; import dayjs from 'dayjs'; -jest.mock('../../main/Modules/ProcessDataRequest/CreateTodoObjects', () => ({ +jest.mock('../../main/modules/ProcessDataRequest/CreateTodoObjects', () => ({ lines: [''], })); diff --git a/src/__tests__/main/CreateTodoObjects.tsx b/src/__tests__/main/CreateTodoObjects.tsx index 86088db0..1cfb5d4a 100644 --- a/src/__tests__/main/CreateTodoObjects.tsx +++ b/src/__tests__/main/CreateTodoObjects.tsx @@ -1,5 +1,5 @@ import dayjs from 'dayjs'; -import { createTodoObjects } from '../../main/Modules/ProcessDataRequest/CreateTodoObjects'; +import { createTodoObjects } from '../../main/modules/ProcessDataRequest/CreateTodoObjects'; import { configStorage } from '../../main/config'; const dateTodayString: string = dayjs(new Date()).format('YYYY-MM-DD'); diff --git a/src/__tests__/main/Date.tsx b/src/__tests__/main/Date.tsx index a1558ff8..73e8283f 100644 --- a/src/__tests__/main/Date.tsx +++ b/src/__tests__/main/Date.tsx @@ -1,7 +1,7 @@ -import { extractSpeakingDates, replaceSpeakingDatesWithAbsoluteDates } from '../../main/Modules/Date'; +import { extractSpeakingDates, replaceSpeakingDatesWithAbsoluteDates } from '../../main/modules/Date'; import dayjs from 'dayjs'; -jest.mock('../../main/Modules/File/Write', () => ({ +jest.mock('../../main/modules/File/Write', () => ({ writeTodoObjectToFile: jest.fn(), })); diff --git a/src/__tests__/main/Dialog.tsx b/src/__tests__/main/Dialog.tsx index c20f2505..f1bc8949 100644 --- a/src/__tests__/main/Dialog.tsx +++ b/src/__tests__/main/Dialog.tsx @@ -1,6 +1,6 @@ import { dialog } from 'electron'; -import { openFile, createFile } from '../../main/Modules/File/Dialog'; -import { addFile } from '../../main/Modules/File/File'; +import { openFile, createFile } from '../../main/modules/File/Dialog'; +import { addFile } from '../../main/modules/File/File'; import { configStorage } from '../../main/config'; import fs from 'fs/promises'; @@ -33,7 +33,7 @@ jest.mock('../../main/config', () => ({ }, })); -jest.mock('../../main/Modules/File/File', () => ({ +jest.mock('../../main/modules/File/File', () => ({ addFile: jest.fn(), })); diff --git a/src/__tests__/main/File.tsx b/src/__tests__/main/File.tsx index 4da3f3e2..d91b48c0 100644 --- a/src/__tests__/main/File.tsx +++ b/src/__tests__/main/File.tsx @@ -1,7 +1,7 @@ import path from 'path' import { configStorage } from '../../main/config'; -import { createMenu } from '../../main/Modules/Menu'; -import { addFile, removeFile, setFile } from '../../main/Modules/File/File'; +import { createMenu } from '../../main/modules/Menu'; +import { addFile, removeFile, setFile } from '../../main/modules/File/File'; jest.mock('../../main/main', () => ({ mainWindow: { @@ -11,15 +11,15 @@ jest.mock('../../main/main', () => ({ }, })); -jest.mock('../../main/Modules/File/Watcher', () => ({ +jest.mock('../../main/modules/File/Watcher', () => ({ createFileWatcher: jest.fn(), })); -jest.mock('../../main/Modules/Tray', () => ({ +jest.mock('../../main/modules/Tray', () => ({ createTray: jest.fn(), })); -jest.mock('../../main/Modules/Menu', () => ({ +jest.mock('../../main/modules/Menu', () => ({ createMenu: jest.fn(), })); diff --git a/src/__tests__/main/Filters.tsx b/src/__tests__/main/Filters.tsx index 88179547..a616a269 100644 --- a/src/__tests__/main/Filters.tsx +++ b/src/__tests__/main/Filters.tsx @@ -1,4 +1,4 @@ -import { applyFilters } from '../../main/Modules/Filters/Filters'; +import { applyFilters } from '../../main/modules/Filters/Filters'; describe('Should filter todos based on passed filters', () => { const todoObjects = [ diff --git a/src/__tests__/main/Notifications.tsx b/src/__tests__/main/Notifications.tsx index c786cd8a..b15ea639 100644 --- a/src/__tests__/main/Notifications.tsx +++ b/src/__tests__/main/Notifications.tsx @@ -1,6 +1,6 @@ -import { handleNotification } from '../../main/Modules/Notifications'; +import { handleNotification } from '../../main/modules/Notifications'; import { configStorage } from '../../main/config'; -import { badge } from '../../main/Modules/ProcessDataRequest/CreateTodoObjects'; +import { badge } from '../../main/modules/ProcessDataRequest/CreateTodoObjects'; import { Notification } from 'electron'; import dayjs from 'dayjs'; @@ -10,7 +10,7 @@ const dateTomorrowString = dateToday.add(1, 'day').format('YYYY-MM-DD'); const dateInSevenDaysString = dateToday.add(7, 'day').format('YYYY-MM-DD'); const dateInTwentyDaysString = dateToday.add(20, 'day').format('YYYY-MM-DD'); -jest.mock('../../main/Modules/ProcessDataRequest/CreateTodoObjects', () => ({ +jest.mock('../../main/modules/ProcessDataRequest/CreateTodoObjects', () => ({ badge: { count: 0, }, diff --git a/src/__tests__/main/ProcessTodoObjects.tsx b/src/__tests__/main/ProcessTodoObjects.tsx index 7cbf6227..a914804e 100644 --- a/src/__tests__/main/ProcessTodoObjects.tsx +++ b/src/__tests__/main/ProcessTodoObjects.tsx @@ -1,4 +1,4 @@ -import { sortAndGroupTodoObjects, flattenTodoObjects, applySearchString, countTodoObjects } from '../../main/Modules/ProcessDataRequest/ProcessTodoObjects'; +import { sortAndGroupTodoObjects, flattenTodoObjects, applySearchString, countTodoObjects } from '../../main/modules/ProcessDataRequest/ProcessTodoObjects'; jest.mock('../../main/config', () => ({ configStorage: { diff --git a/src/__tests__/main/Write.tsx b/src/__tests__/main/Write.tsx index fa35aeeb..5603bbdf 100644 --- a/src/__tests__/main/Write.tsx +++ b/src/__tests__/main/Write.tsx @@ -1,6 +1,6 @@ import fs from 'fs/promises'; -import { writeTodoObjectToFile, removeLineFromFile } from '../../main/Modules/File/Write'; -import { lines } from '../../main/Modules/ProcessDataRequest/CreateTodoObjects'; +import { writeTodoObjectToFile, removeLineFromFile } from '../../main/modules/File/Write'; +import { lines } from '../../main/modules/ProcessDataRequest/CreateTodoObjects'; import { configStorage } from '../../main/config'; import dayjs from 'dayjs'; @@ -29,7 +29,7 @@ jest.mock('../../main/config', () => ({ }, })); -jest.mock('../../main/Modules/ProcessDataRequest/CreateTodoObjects', () => ({ +jest.mock('../../main/modules/ProcessDataRequest/CreateTodoObjects', () => ({ lines: ['Line 1', 'Line 2', 'Line 3'], })); diff --git a/src/locales/cs.json b/src/locales/cs.json index ca6a6015..bb95c5e3 100644 --- a/src/locales/cs.json +++ b/src/locales/cs.json @@ -23,6 +23,7 @@ "settings.notificationThreshold": "Hranice oznámení", "settings.bulkTodoCreation": "Každý řádek v textovém poli vytvoří samostatnou položku úkolu", "settings.matomo": "Povolit analýzu Matomo", + "settings.zoom": "Přiblížení", "drawer.tabs.attributes": "Atributy", "drawer.tabs.filters": "Filtry", "drawer.tabs.sorting": "Řazení", diff --git a/src/locales/de.json b/src/locales/de.json index feda98c1..28edff93 100644 --- a/src/locales/de.json +++ b/src/locales/de.json @@ -23,6 +23,7 @@ "settings.notificationThreshold": "Benachrichtigungsschwelle", "settings.bulkTodoCreation": "Jede Zeile im Textfeld erstellt eine separate Todo", "settings.matomo": "Matomo-Analyse erlauben", + "settings.zoom": "Vergrößerung", "drawer.tabs.attributes": "Attribute", "drawer.tabs.filters": "Filter", "drawer.tabs.sorting": "Sortierung", diff --git a/src/locales/en-gb.json b/src/locales/en-gb.json index 3648d754..ceeed85e 100644 --- a/src/locales/en-gb.json +++ b/src/locales/en-gb.json @@ -23,6 +23,7 @@ "settings.notificationThreshold": "Notification threshold", "settings.bulkTodoCreation": "Each row in the text field creates a separate to-do item", "settings.matomo": "Allow Matomo Analysis", + "settings.zoom": "Zoom", "drawer.tabs.attributes": "Attributes", "drawer.tabs.filters": "Filters", "drawer.tabs.sorting": "Sorting", diff --git a/src/locales/en.json b/src/locales/en.json index 3648d754..ceeed85e 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -23,6 +23,7 @@ "settings.notificationThreshold": "Notification threshold", "settings.bulkTodoCreation": "Each row in the text field creates a separate to-do item", "settings.matomo": "Allow Matomo Analysis", + "settings.zoom": "Zoom", "drawer.tabs.attributes": "Attributes", "drawer.tabs.filters": "Filters", "drawer.tabs.sorting": "Sorting", diff --git a/src/locales/es.json b/src/locales/es.json index 3132b3f3..0727c185 100644 --- a/src/locales/es.json +++ b/src/locales/es.json @@ -23,6 +23,7 @@ "settings.notificationThreshold": "Umbral de notificación", "settings.bulkTodoCreation": "Cada línea en el campo de texto crea un elemento de tarea separado", "settings.matomo": "Permitir análisis de Matomo", + "settings.zoom": "Zoom", "drawer.tabs.attributes": "Atributos", "drawer.tabs.filters": "Filtros", "drawer.tabs.sorting": "Ordenación", diff --git a/src/locales/fr.json b/src/locales/fr.json index 2c35edbd..187289aa 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -23,6 +23,7 @@ "settings.notificationThreshold": "Umbral de notificación", "settings.bulkTodoCreation": "Chaque ligne dans le champ de texte crée un élément de tâche séparé", "settings.matomo": "Autoriser l'analyse Matomo", + "settings.zoom": "Zoom", "drawer.tabs.attributes": "Attributs", "drawer.tabs.filters": "Filtres", "drawer.tabs.sorting": "Tri", diff --git a/src/locales/hi.json b/src/locales/hi.json index cae1c53c..50c3c98f 100644 --- a/src/locales/hi.json +++ b/src/locales/hi.json @@ -23,6 +23,7 @@ "settings.notificationThreshold": "सूचना सीमा", "settings.bulkTodoCreation": "पाठ क्षेत्र में प्रत्येक पंक्ति स्वतंत्र टूडू आइटम बनाती है", "settings.matomo": "मैटोमो विश्लेषण की अनुमति दें", + "settings.zoom": "ज़ूम", "drawer.tabs.attributes": "गुण", "drawer.tabs.filters": "फ़िल्टर", "drawer.tabs.sorting": "क्रमबद्ध करना", diff --git a/src/locales/hu.json b/src/locales/hu.json index a4b51c0f..0a556171 100644 --- a/src/locales/hu.json +++ b/src/locales/hu.json @@ -23,6 +23,7 @@ "settings.notificationThreshold": "Értesítés küszöbérték", "settings.bulkTodoCreation": "Minden sor külön teendőelemet hoz létre a szövegmezőben", "settings.matomo": "Matomo elemzés engedélyezése", + "settings.zoom": "Nagyítás", "drawer.tabs.attributes": "Tulajdonságok", "drawer.tabs.filters": "Szűrők", "drawer.tabs.sorting": "Rendezés", diff --git a/src/locales/it.json b/src/locales/it.json index 7bb74cc8..e05e5a44 100644 --- a/src/locales/it.json +++ b/src/locales/it.json @@ -23,6 +23,7 @@ "settings.notificationThreshold": "Soglia di notifica", "settings.bulkTodoCreation": "Ogni riga nel campo di testo crea un'attività separata", "settings.matomo": "Consenti l'analisi di Matomo", + "settings.zoom": "Zoom", "drawer.tabs.attributes": "Attributi", "drawer.tabs.filters": "Filtri", "drawer.tabs.sorting": "Ordinamento", diff --git a/src/locales/jp.json b/src/locales/jp.json index 6c973c6b..905dec91 100644 --- a/src/locales/jp.json +++ b/src/locales/jp.json @@ -23,6 +23,7 @@ "settings.notificationThreshold": "通知しきい値", "settings.bulkTodoCreation": "テキストフィールド内の各行は別々のタスクを作成します", "settings.matomo": "Matomoの分析を許可", + "settings.zoom": "ズーム", "drawer.tabs.attributes": "属性", "drawer.tabs.filters": "フィルター", "drawer.tabs.sorting": "ソート", diff --git a/src/locales/ko.json b/src/locales/ko.json index 23ac5199..b3e4ab4e 100644 --- a/src/locales/ko.json +++ b/src/locales/ko.json @@ -23,6 +23,7 @@ "settings.notificationThreshold": "알림 임계값", "settings.bulkTodoCreation": "텍스트 필드의 각 줄은 별도의 할 일 항목을 생성합니다", "settings.matomo": "Matomo 분석 허용", + "settings.zoom": "줌", "drawer.tabs.attributes": "속성", "drawer.tabs.filters": "필터", "drawer.tabs.sorting": "정렬", diff --git a/src/locales/pl.json b/src/locales/pl.json index 289ec71e..0afabf24 100644 --- a/src/locales/pl.json +++ b/src/locales/pl.json @@ -23,6 +23,7 @@ "settings.notificationThreshold": "Prog powiadomień", "settings.bulkTodoCreation": "Każdy wiersz w polu tekstowym tworzy oddzielną pozycję na liście zadań", "settings.matomo": "Zezwalaj na analizę Matomo", + "settings.zoom": "Powiększenie", "drawer.tabs.attributes": "Atrybuty", "drawer.tabs.filters": "Filtry", "drawer.tabs.sorting": "Sortowanie", diff --git a/src/locales/pt.json b/src/locales/pt.json index de8a8e0e..49183a94 100644 --- a/src/locales/pt.json +++ b/src/locales/pt.json @@ -23,6 +23,7 @@ "settings.notificationThreshold": "Prog powiadomień", "settings.bulkTodoCreation": "Cada linha no campo de texto cria um item de tarefa separado", "settings.matomo": "Permitir Análise do Matomo", + "settings.zoom": "Zoom", "drawer.tabs.attributes": "Atributos", "drawer.tabs.filters": "Filtros", "drawer.tabs.sorting": "Ordenação", diff --git a/src/locales/ru.json b/src/locales/ru.json index d9e5a723..27ad641e 100644 --- a/src/locales/ru.json +++ b/src/locales/ru.json @@ -23,6 +23,7 @@ "settings.notificationThreshold": "Порог уведомлений", "settings.bulkTodoCreation": "Каждая строка в текстовом поле создает отдельную задачу", "settings.matomo": "Разрешить анализ Matomo", + "settings.zoom": "Масштаб", "drawer.tabs.attributes": "Атрибуты", "drawer.tabs.filters": "Фильтры", "drawer.tabs.sorting": "Сортировка", diff --git a/src/locales/tr.json b/src/locales/tr.json index 4d8eef0d..ad64321e 100644 --- a/src/locales/tr.json +++ b/src/locales/tr.json @@ -23,6 +23,7 @@ "settings.notificationThreshold": "Bildirim Eşik Değeri", "settings.bulkTodoCreation": "Metin alanındaki her satır ayrı bir görev oluşturur", "settings.matomo": "Matomo Analizine İzin Ver", + "settings.zoom": "Yakınlaştırma", "drawer.tabs.attributes": "Özellikler", "drawer.tabs.filters": "Filtreler", "drawer.tabs.sorting": "Sıralama", diff --git a/src/locales/zh.json b/src/locales/zh.json index c3f43f2a..00d93e9f 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -23,6 +23,7 @@ "settings.notificationThreshold": "通知阈值", "settings.bulkTodoCreation": "文本字段中的每一行都创建一个单独的待办事项", "settings.matomo": "允许Matomo分析", + "settings.zoom": "缩放", "drawer.tabs.attributes": "属性", "drawer.tabs.filters": "过滤器", "drawer.tabs.sorting": "排序", diff --git a/src/main/config.tsx b/src/main/config.tsx index 990f6b42..b5ebecb9 100644 --- a/src/main/config.tsx +++ b/src/main/config.tsx @@ -3,10 +3,10 @@ import path from 'path'; import { app, nativeTheme } from 'electron'; import fs from 'fs'; import { mainWindow } from './main'; -import { createFileWatcher } from './Modules/File/Watcher'; -import { createTray } from './Modules/Tray'; -import processDataRequest from './Modules/ProcessDataRequest/ProcessDataRequest'; -import handleTheme from './Modules/Theme'; +import { createFileWatcher } from './modules/File/Watcher'; +import { createTray } from './modules/Tray'; +import processDataRequest from './modules/ProcessDataRequest/ProcessDataRequest'; +import handleTheme from './modules/Theme'; import crypto from 'crypto'; const anonymousUserId = crypto.randomUUID() || null; @@ -105,15 +105,16 @@ if(!fs.existsSync(customStylesPath)) { filterStorage.onDidAnyChange(async () => { try { - await processDataRequest() + await processDataRequest(); } catch(error) { console.error(error); } }); -configStorage.onDidAnyChange(async () => { +configStorage.onDidAnyChange(async(settings) => { try { - await processDataRequest() + await processDataRequest(); + mainWindow!.webContents.send('settingsChanged', settings); } catch(error) { console.error(error); } @@ -121,23 +122,14 @@ configStorage.onDidAnyChange(async () => { configStorage.onDidChange('files', async (files: FileObject[] | undefined) => { try { - if(files && mainWindow) { + if(files) { createFileWatcher(files); - mainWindow.webContents.send('updateFiles', files); } } catch(error) { console.error(error); } }); -configStorage.onDidChange('matomo', (matomo) => { - mainWindow!.webContents.send('matomo', matomo); -}); - -configStorage.onDidChange('showFileTabs', () => { - mainWindow!.webContents.send('setShowFileTabs'); -}); - configStorage.onDidChange('colorTheme', (colorTheme) => { if(colorTheme === 'system' || colorTheme === 'light' || colorTheme === 'dark') { nativeTheme.themeSource = colorTheme; diff --git a/src/main/main.tsx b/src/main/main.tsx index 5c0bcd33..9bb6772b 100644 --- a/src/main/main.tsx +++ b/src/main/main.tsx @@ -2,12 +2,12 @@ import { app, BrowserWindow } from 'electron'; import path from 'path'; import fs from 'fs'; import { configStorage } from './config'; -import { createMenu } from './Modules/Menu'; -import handleTheme from './Modules/Theme'; +import { createMenu } from './modules/Menu'; +import handleTheme from './modules/Theme'; import { getAssetPath, resolveHtmlPath } from './util'; -import { createFileWatcher, watcher } from './Modules/File/Watcher'; -import { createTray } from './Modules/Tray'; -import './Modules/Ipc'; +import { createFileWatcher, watcher } from './modules/File/Watcher'; +import { createTray } from './modules/Tray'; +import './modules/Ipc'; const environment: string | undefined = process.env.NODE_ENV; const files: FileObject[] = (configStorage.get('files') as FileObject[]) || []; diff --git a/src/main/modules/Ipc.tsx b/src/main/modules/Ipc.tsx index babe94e0..437e5f6e 100644 --- a/src/main/modules/Ipc.tsx +++ b/src/main/modules/Ipc.tsx @@ -14,20 +14,30 @@ async function handleDataRequest(event: IpcMainEvent, searchString: string): Pro await processDataRequest(searchString) } catch(error) { console.error(error); - event.reply('handleError', error); + event.reply('responseFromMainProcess', error); } } -async function handleCreateTodoObject(event: IpcMainEvent, string: string, index: number): Promise { +async function handleUpdateAttributeFields(event: IpcMainEvent, index: number, string: string): Promise { try { - const todoObject = createTodoObject(string, index); - event.reply('createTodoObject', todoObject); + const todoObject = createTodoObject(index, string); + event.reply('updateAttributeFields', todoObject); } catch(error) { console.error(error); - event.reply('handleError', error); + event.reply('responseFromMainProcess', error); } } +function handleUpdateTodoObject(event: IpcMainEvent, index, string, attributeType, attributeValue): Promise { + try { + const todoObject = createTodoObject(index, string, attributeType, attributeValue); + event.reply('updateTodoObject', todoObject); + } catch(error) { + console.error(error); + event.reply('responseFromMainProcess', error); + } +} + async function handleWriteTodoToFile(event: IpcMainEvent, id: number, string: string, state: boolean): Promise { try { let updatedString: string | null = string; @@ -36,7 +46,7 @@ async function handleWriteTodoToFile(event: IpcMainEvent, id: number, string: st event.reply('writeTodoToFile', response); } catch(error) { console.error(error); - event.reply('handleError', error); + event.reply('responseFromMainProcess', error); } } @@ -45,7 +55,7 @@ function handleStoreGetConfig(event: IpcMainEvent, value: string): void { event.returnValue = configStorage.get(value); } catch (error: any) { console.error(error); - event.reply('handleError', error); + event.reply('responseFromMainProcess', error); } } @@ -56,7 +66,7 @@ function handleStoreSetConfig(event: IpcMainEvent, key: string, value: any) { console.log(`Set ${key} to ${value}`); } catch (error: any) { console.error(error); - event.reply('handleError', error); + event.reply('responseFromMainProcess', error); } } @@ -66,7 +76,7 @@ function handleStoreSetFilters(event: IpcMainEvent, value: any): void { console.log(`Filters saved`); } catch (error: any) { console.error(error); - event.reply('handleError', error); + event.reply('responseFromMainProcess', error); } } @@ -76,7 +86,7 @@ function handleStoreSetNotifiedTodoObjects(event: IpcMainEvent, value: any): voi console.log(`notifiedTodoObjects saved`); } catch (error: any) { console.error(error); - event.reply('handleError', error); + event.reply('responseFromMainProcess', error); } } @@ -85,7 +95,7 @@ function handleSetFile(event: IpcMainEvent, index: number): void { setFile(index); } catch (error: any) { console.error(error); - event.reply('handleError', error); + event.reply('responseFromMainProcess', error); } } @@ -94,7 +104,7 @@ function handleRemoveFile(event: IpcMainEvent, index: number): void { removeFile(index); } catch (error: any) { console.error(error); - event.reply('handleError', error); + event.reply('responseFromMainProcess', error); } } @@ -103,7 +113,7 @@ function handleAddFile(event: IpcMainEvent, filePath: string): void { addFile(filePath, null); } catch (error: any) { console.error(error); - event.reply('handleError', error); + event.reply('responseFromMainProcess', error); } } @@ -121,7 +131,7 @@ function handleRevealInFileManager(event: IpcMainEvent, pathToReveal: string): v shell.showItemInFolder(pathToReveal); } catch (error: any) { console.error(error); - event.reply('handleError', error); + event.reply('responseFromMainProcess', error); } } @@ -130,7 +140,7 @@ async function handleOpenFile(event: IpcMainEvent, setDoneFile: boolean): Promis await openFile(setDoneFile); } catch (error: any) { console.error(error); - event.reply('handleError', error); + event.reply('responseFromMainProcess', error); } } @@ -139,7 +149,7 @@ async function handleCreateFile(event: IpcMainEvent, setDoneFile: boolean): Prom await createFile(setDoneFile); } catch (error: any) { console.error(error); - event.reply('handleError', error); + event.reply('responseFromMainProcess', error); } } @@ -148,27 +158,24 @@ async function handleRemoveLineFromFile(event: IpcMainEvent, index: number): Pro await removeLineFromFile(index); } catch (error: any) { console.error(error); - event.reply('handleError', error); + event.reply('responseFromMainProcess', error); } } async function handleArchiveTodos(event: IpcMainEvent): Promise { try { const archivingResult = await archiveTodos(); - event.reply('ArchivingResult', archivingResult); + event.reply('responseFromMainProcess', archivingResult); } catch (error: any) { console.error(error); - event.reply('handleError', error); + event.reply('responseFromMainProcess', error); } } function handleSaveToClipboard(event: IpcMainEvent, string: string): void { try { - clipboard.writeText(string); - event.reply('saveToClipboard', 'Copied to clipboard: ' + string); - } catch (error: any) { console.error(error); event.reply('saveToClipboard', error); @@ -184,7 +191,7 @@ function removeEventListeners(): void { ipcMain.off('removeFile', handleRemoveFile); ipcMain.off('openFile', handleOpenFile); ipcMain.off('createFile', handleCreateFile); - ipcMain.off('createTodoObject', handleCreateTodoObject); + ipcMain.off('updateAttributeFields', handleUpdateAttributeFields); ipcMain.off('requestData', handleDataRequest); ipcMain.off('writeTodoToFile', handleWriteTodoToFile); ipcMain.off('archiveTodos', handleArchiveTodos); @@ -193,6 +200,7 @@ function removeEventListeners(): void { ipcMain.off('saveToClipboard', handleSaveToClipboard); ipcMain.off('revealInFileManager', handleRevealInFileManager); ipcMain.off('removeLineFromFile', handleRemoveLineFromFile); + ipcMain.off('updateTodoObject', handleUpdateTodoObject); } app.on('before-quit', removeEventListeners); @@ -205,7 +213,7 @@ ipcMain.on('setFile', handleSetFile); ipcMain.on('removeFile', handleRemoveFile); ipcMain.on('openFile', handleOpenFile); ipcMain.on('createFile', handleCreateFile); -ipcMain.on('createTodoObject', handleCreateTodoObject); +ipcMain.on('updateAttributeFields', handleUpdateAttributeFields); ipcMain.on('requestData', handleDataRequest); ipcMain.on('writeTodoToFile', handleWriteTodoToFile); ipcMain.on('archiveTodos', handleArchiveTodos); @@ -214,3 +222,5 @@ ipcMain.on('droppedFile', handleDroppedFile); ipcMain.on('saveToClipboard', handleSaveToClipboard); ipcMain.on('revealInFileManager', handleRevealInFileManager); ipcMain.on('removeLineFromFile', handleRemoveLineFromFile); +ipcMain.on('updateTodoObject', handleUpdateTodoObject); + diff --git a/src/main/modules/Menu.tsx b/src/main/modules/Menu.tsx index 255b819a..ea130d39 100644 --- a/src/main/modules/Menu.tsx +++ b/src/main/modules/Menu.tsx @@ -9,10 +9,6 @@ const isMac: boolean = process.platform === 'darwin'; const description = appPackage.description; function createMenu(files: FileObject[]) { - // @ts-ignore - // @ts-ignore - // @ts-ignore - // @ts-ignore const template: Electron.MenuItemConstructorOptions[] = [ { label: 'sleek', @@ -41,7 +37,7 @@ function createMenu(files: FileObject[]) { label: 'Settings', accelerator: 'CmdOrCtrl+,', click: () => { - mainWindow!.webContents.send('setIsSettingsOpen'); + mainWindow!.webContents.send('isSettingsOpen', true); }, }, ...(isMac @@ -119,20 +115,23 @@ function createMenu(files: FileObject[]) { label: 'Toggle drawer', accelerator: 'CmdOrCtrl+B', click: () => { - mainWindow!.webContents.send('setIsDrawerOpen'); + const isDrawerOpen = configStorage.get('isDrawerOpen'); + configStorage.set('isDrawerOpen', !isDrawerOpen); }, }, { label: 'Toggle navigation', accelerator: 'Ctrl+Alt+H', click: () => { - mainWindow!.webContents.send('setIsNavigationOpen'); + const isNavigationOpen = configStorage.get('isNavigationOpen'); + configStorage.set('isNavigationOpen', !isNavigationOpen); }, }, { label: 'Toggle file tabs', click: () => { - mainWindow!.webContents.send('setShowFileTabs'); + const showFileTabs = configStorage.get('showFileTabs'); + configStorage.set('showFileTabs', !showFileTabs); }, }, { @@ -152,7 +151,8 @@ function createMenu(files: FileObject[]) { label: 'Find', accelerator: 'CmdOrCtrl+F', click: () => { - mainWindow!.webContents.send('setIsSearchOpen'); + const isSearchOpen = configStorage.get('isSearchOpen'); + configStorage.set('isSearchOpen', !isSearchOpen); }, }, { @@ -164,7 +164,7 @@ function createMenu(files: FileObject[]) { }, }, { - label: 'Reset search and filters', + label: 'Reset filters', accelerator: 'Ctrl+0', click: async () => { filterStorage.set('filters', {}); diff --git a/src/main/modules/ProcessDataRequest/CreateTodoObjects.tsx b/src/main/modules/ProcessDataRequest/CreateTodoObjects.tsx index 9b81ecdd..3e85c16c 100644 --- a/src/main/modules/ProcessDataRequest/CreateTodoObjects.tsx +++ b/src/main/modules/ProcessDataRequest/CreateTodoObjects.tsx @@ -8,9 +8,18 @@ import dayjs from 'dayjs'; let lines: string[]; export const badge: Badge = { count: 0 }; -function createTodoObject(string: string, index: number): TodoObject { +function createTodoObject(index: number, string: string, attributeType?: string, attributeValue?: string): TodoObject { const content = string.replaceAll(/[\x10\r\n]/g, ' '); const JsTodoTxtObject = new Item(content); + + if(attributeType && attributeValue) { + if(attributeType === 'priority') { + JsTodoTxtObject.setPriority(attributeValue); + } else { + JsTodoTxtObject.setExtension(attributeType, attributeValue); + } + } + const body = JsTodoTxtObject.body(); const speakingDates: DateAttributes = extractSpeakingDates(body); const due = speakingDates['due:']?.date || null; @@ -41,7 +50,7 @@ function createTodoObject(string: string, index: number): TodoObject { rec, hidden, pm, - string: string, + string: JsTodoTxtObject.toString(), }; } @@ -54,7 +63,7 @@ async function createTodoObjects(fileContent: string | null): Promise line.trim() !== ''); const todoObjects: TodoObject[] = await Promise.all(lines.map(async (line, i) => { - const todoObject: TodoObject = await createTodoObject(line, i); + const todoObject: TodoObject = await createTodoObject(i, line); if(todoObject.due && todoObject.body && !todoObject.complete) { handleNotification(todoObject.due, todoObject.body, badge); } diff --git a/src/main/modules/ProcessDataRequest/ProcessDataRequest.tsx b/src/main/modules/ProcessDataRequest/ProcessDataRequest.tsx index 5d997d8a..f6c44201 100644 --- a/src/main/modules/ProcessDataRequest/ProcessDataRequest.tsx +++ b/src/main/modules/ProcessDataRequest/ProcessDataRequest.tsx @@ -25,7 +25,7 @@ async function processDataRequest(search: string): Promise { const activeFile: FileObject | null = getActiveFile(); if(!activeFile) { - throw new Error('No active file'); + return; } const sorting: Sorting[] = configStorage.get('sorting'); const showHidden: boolean = configStorage.get('showHidden'); @@ -52,14 +52,14 @@ async function processDataRequest(search: string): Promise { let flattenedTodoObjects: TodoObject[]; if(fileSorting) { - flattenedTodoObjects = flattenTodoObjects(todoObjects, ''); + todoObjects = flattenTodoObjects(todoObjects, ''); } else { const sortedAndGroupedTodos: TodoObject[] = sortAndGroupTodoObjects(todoObjects, sorting); - flattenedTodoObjects = flattenTodoObjects(sortedAndGroupedTodos, sorting[0].value); + todoObjects = flattenTodoObjects(sortedAndGroupedTodos, sorting[0].value); } const requestedData: RequestedData = { - flattenedTodoObjects, + todoObjects, attributes, headers, filters, diff --git a/src/main/modules/Theme.tsx b/src/main/modules/Theme.tsx index 8e631e3f..b1d3768b 100644 --- a/src/main/modules/Theme.tsx +++ b/src/main/modules/Theme.tsx @@ -5,7 +5,6 @@ import { configStorage } from '../config'; function handleTheme() { const colorTheme: string = configStorage.get('colorTheme'); let shouldUseDarkColors: boolean; - if(colorTheme === 'system') { shouldUseDarkColors = nativeTheme.shouldUseDarkColors; } else if(colorTheme === 'dark') { @@ -15,10 +14,7 @@ function handleTheme() { } else { shouldUseDarkColors = false; } - configStorage.set('shouldUseDarkColors', shouldUseDarkColors); - - mainWindow!.webContents.send('setShouldUseDarkColors', shouldUseDarkColors); } export default handleTheme; diff --git a/src/renderer/App.tsx b/src/renderer/App.tsx index 867b878f..99b0ebed 100644 --- a/src/renderer/App.tsx +++ b/src/renderer/App.tsx @@ -1,5 +1,7 @@ import React, { useEffect, useState, useRef } from 'react'; import { ThemeProvider } from '@mui/material/styles'; +import IpcComponent from './Ipc'; +import MatomoComponent from './Matomo'; import { CssBaseline, Snackbar, Alert, Box } from '@mui/material'; import NavigationComponent from './Navigation'; import TodoDataGrid from './DataGrid/Grid'; @@ -15,145 +17,42 @@ import ContextMenu from './ContextMenu'; import { I18nextProvider } from 'react-i18next'; import { i18n } from './Settings/LanguageSelector'; import Settings from './Settings/Settings'; -import { translatedAttributes } from './Shared'; import Prompt from './Prompt'; +import { translatedAttributes } from './Shared'; import './App.scss'; -const environment = process.env.NODE_ENV; const { ipcRenderer, store } = window.api; const App = () => { - const [files, setFiles] = useState(store.get('files') || null); - const [isDrawerOpen, setIsDrawerOpen] = useState(store.get('isDrawerOpen') || false); - const [isSearchOpen, setIsSearchOpen] = useState(store.get('isSearchOpen') || false); + const [settings, setSettings] = useState(store.get()); const [splashScreen, setSplashScreen] = useState(null); const [snackBarOpen, setSnackBarOpen] = useState(false); const [snackBarContent, setSnackBarContent] = useState(null); const [snackBarSeverity, setSnackBarSeverity] = useState(null); const [dialogOpen, setDialogOpen] = useState(false); const [searchString, setSearchString] = useState(''); - const [flattenedTodoObjects, setTodoObjects] = useState(null); + const [todoObjects, setTodoObjects] = useState(null); const [todoObject, setTodoObject] = useState(null); const [attributeFields, setAttributeFields] = useState(null); const [headers, setHeaders] = useState(null); const [filters, setFilters] = useState(null); const [attributes, setAttributes] = useState(null); - const [sorting, setSorting] = useState(store.get('sorting') || null); - const [zoom, setZoom] = useState(store.get('zoom') || 100); - const [isNavigationOpen, setIsNavigationOpen] = useState(store.get('isNavigationOpen')); - const [shouldUseDarkColors, setShouldUseDarkColors] = useState(store.get('shouldUseDarkColors') || false); - const [showFileTabs, setShowFileTabs] = useState(store.get('showFileTabs')); const [isSettingsOpen, setIsSettingsOpen] = useState(false); const [contextMenuPosition, setContextMenuPosition] = useState(null); const [contextMenuItems, setContextMenuItems] = useState(null); - const [attributeMapping, setAttributeMapping] = useState(translatedAttributes(i18n.t) || {}); const [textFieldValue, setTextFieldValue] = useState(''); const [promptItem, setPromptItem] = useState(null); const [triggerArchiving, setTriggerArchiving] = useState(false); const [showPromptDoneFile, setShowPromptDoneFile] = useState(false); - const [matomo, setMatomo] = useState(store.get('matomo') || false); - const [selectedLanguage, setSelectedLanguage] = useState(() => { - const storedLanguage = store.get('language') || navigator.language; - const supportedLanguages: false | readonly string[] | undefined = i18n.options.supportedLngs; - if(supportedLanguages && supportedLanguages.includes(storedLanguage)) { - return storedLanguage; - } - return 'en'; - }); - const searchFieldRef = useRef(null); - const handleRequestedData = (requestedData: RequestedData) => { - if(requestedData?.headers) setHeaders(requestedData.headers); - if(requestedData?.attributes) setAttributes(requestedData.attributes); - if(requestedData?.filters) setFilters(requestedData.filters); - if(requestedData?.flattenedTodoObjects) setTodoObjects(requestedData.flattenedTodoObjects); - setSplashScreen(null); - }; - - const handleUpdateFiles = (files: FileObject[]) => { - setFiles(files); - }; - - const handleUpdateSorting = (sorting: Sorting) => { - setSorting(sorting) - }; - - const handleSetIsSearchOpen = () => { - setIsSearchOpen(prevIsSearchOpen => !prevIsSearchOpen); - }; - - const handleSetIsNavigationOpen = () => { - setIsNavigationOpen(prevIsNavigationOpen => !prevIsNavigationOpen); - }; - - const handleSetShouldUseDarkColors = (shouldUseDarkColors: boolean) => { - setShouldUseDarkColors(shouldUseDarkColors); - }; - - const handleSetShowFileTabs = () => { - setShowFileTabs(prevShowFileTabs => !prevShowFileTabs); - }; - - const handleSetIsDrawerOpen = () => { - setIsDrawerOpen(prevIsDrawerOpen => !prevIsDrawerOpen); - }; - - const handleSetIsSettingsOpen = () => { - setIsSettingsOpen(prevIsSettingsOpen => !prevIsSettingsOpen); - }; - - const handleDrop = (event: any) => { - event.preventDefault(); - const filePath = event.dataTransfer.files[0].path; - if(typeof filePath === 'string') ipcRenderer.send('droppedFile', filePath); - }; - - const handlePromptConfirm = (item: ContextMenuItem) => { - const { id, todoObject, index } = item; - switch (id) { - case 'delete': - ipcRenderer.send('removeLineFromFile', todoObject?.id); - break; - case 'removeFile': - ipcRenderer.send('removeFile', index); - break; - default: - setContextMenuItems(null); - } - }; - - const handlePromptClose = () => { - setPromptItem(null); - }; - - const handleDragOver = (event: Event) => { - event.preventDefault(); - }; - - const handleAlertClose = () => { - setSnackBarContent(null); - setSnackBarOpen(false); - }; - - const handleResponse = function (response: Error | string) { - const severity = response instanceof Error ? 'error' : 'success'; - setSnackBarSeverity(severity); - setSnackBarContent(response instanceof Error ? response.message : response); - }; - - const handleCreateTodoObject = (todoObject: TodoObject) => { - if(todoObject) { - setAttributeFields(todoObject); - } - }; + const [attributeMapping, setAttributeMapping] = useState(translatedAttributes(i18n.t) || {}); useEffect(() => { if(!headers) { return; } else if(headers.availableObjects === 0) { setSplashScreen('noTodosAvailable'); - setIsDrawerOpen(false); } else if(headers.visibleObjects === 0) { setSplashScreen('noTodosVisible'); } else { @@ -162,259 +61,184 @@ const App = () => { }, [headers]); useEffect(() => { - if(files === null || files?.length === 0) { + if(settings.files?.length === 0) { setTodoObjects(null); setHeaders(null); setSplashScreen('noFiles'); } else { ipcRenderer.send('requestData'); } - }, [files]); - - useEffect(() => { - store.set('sorting', sorting) - }, [sorting]); + }, [settings.files]); useEffect(() => { - store.set('isSearchOpen', isSearchOpen) - }, [isSearchOpen]); - - useEffect(() => { - setContextMenuItems(null); - }, [promptItem]); - - useEffect(() => { - store.set('isNavigationOpen', isNavigationOpen) - }, [isNavigationOpen]); - - useEffect(() => { - if(!snackBarContent) return; - setSnackBarOpen(true); - }, [snackBarContent]); - - useEffect(() => { - store.set('zoom', zoom); - const adjustedFontSize = 16 * (zoom / 100); - document.body.style.fontSize = `${adjustedFontSize}px`; - }, [zoom]); - - useEffect(() => { - const anonymousUserId = store.get('anonymousUserId'); - if(matomo && anonymousUserId) { - const matomoContainer: string = (environment === 'development') ? 'https://www.datenkrake.eu/matomo/js/container_WVsEueTV_dev_a003c77410fd43f247329b3b.js' : 'https://www.datenkrake.eu/matomo/js/container_WVsEueTV.js'; - const _mtm = window._mtm = window._mtm || []; - _mtm.push({'mtm.startTime': (new Date().getTime()), 'event': 'mtm.Start'}); - if(anonymousUserId) _mtm.push({'anonymousUserId': anonymousUserId }); - const - d = document, - g = d.createElement('script'), - s = d.getElementsByTagName('script')[0]; - g.async = true; - g.src = matomoContainer; - s.parentNode?.insertBefore(g, s); + if(!snackBarContent) { + setSnackBarOpen(false); + } else { + setSnackBarOpen(true); } - }, [matomo]); - - const handleDisplayLoadTime = (loadTime) => { - console.log(`App took ${loadTime}ms to load completely`) - }; - - useEffect(() => { - ipcRenderer.on('requestData', handleRequestedData); - ipcRenderer.on('updateFiles', handleUpdateFiles); - ipcRenderer.on('updateSorting', handleUpdateSorting); - ipcRenderer.on('setIsSearchOpen', handleSetIsSearchOpen); - ipcRenderer.on('setIsNavigationOpen', handleSetIsNavigationOpen); - ipcRenderer.on('setShouldUseDarkColors', handleSetShouldUseDarkColors); - ipcRenderer.on('setShowFileTabs', handleSetShowFileTabs); - ipcRenderer.on('setIsDrawerOpen', handleSetIsDrawerOpen); - ipcRenderer.on('setIsSettingsOpen', handleSetIsSettingsOpen); - ipcRenderer.on('matomo', (result) => setMatomo(result)); - ipcRenderer.on('writeTodoToFile', handleResponse); - ipcRenderer.on('droppedFile', handleResponse); - ipcRenderer.on('handleError', handleResponse); - ipcRenderer.on('saveToClipboard', handleResponse); - ipcRenderer.on('triggerArchiving', () => setTriggerArchiving(true)); - ipcRenderer.on('ArchivingResult', handleResponse); - ipcRenderer.on('createTodoObject', handleCreateTodoObject); - ipcRenderer.on('displayLoadTime', handleDisplayLoadTime); - window.addEventListener('drop', handleDrop); - window.addEventListener('dragover', handleDragOver); - return () => { - ipcRenderer.off('requestData', handleRequestedData); - ipcRenderer.off('updateFiles', handleUpdateFiles); - ipcRenderer.off('updateSorting', handleUpdateSorting); - ipcRenderer.off('setIsSearchOpen', handleSetIsSearchOpen); - ipcRenderer.off('setIsNavigationOpen', handleSetIsNavigationOpen); - ipcRenderer.off('setShouldUseDarkColors', handleSetShouldUseDarkColors); - ipcRenderer.off('setShowFileTabs', handleSetShowFileTabs); - ipcRenderer.off('setIsDrawerOpen', handleSetIsDrawerOpen); - ipcRenderer.off('setIsSettingsOpen', handleSetIsSettingsOpen); - ipcRenderer.off('matomo', (result: boolean) => setMatomo(result)); - ipcRenderer.off('writeTodoToFile', handleResponse); - ipcRenderer.off('droppedFile', handleResponse); - ipcRenderer.off('handleError', handleResponse); - ipcRenderer.off('saveToClipboard', handleResponse); - ipcRenderer.off('triggerArchiving', () => setTriggerArchiving(true)); - ipcRenderer.off('ArchivingResult', handleResponse); - ipcRenderer.off('createTodoObject', handleCreateTodoObject); - ipcRenderer.off('displayLoadTime', handleDisplayLoadTime); - window.removeEventListener('drop', handleDrop); - window.removeEventListener('dragover', handleDragOver); - }; - }, []); + }, [snackBarContent]); return ( - - - - - - {files?.length > 0 && ( - <> + <> + + + + + + + + {settings.files?.length > 0 && ( - - )} - - {files?.length > 0 && ( - <> - {showFileTabs ? - : null} - {headers?.availableObjects > 0 ? + )} + + {settings.files?.length > 0 && ( <> - - + {settings.showFileTabs ? + : null} + {headers?.availableObjects > 0 ? + <> + + + + : null } - : null } - - )} - + + + + {dialogOpen ? ( + + ) : null} + setIsSettingsOpen(false)} + settings={settings} + attributeMapping={attributeMapping} + /> + {contextMenuItems && ( + - - - - {dialogOpen ? ( - - ) : null} - setIsSettingsOpen(false)} - setAttributeMapping={setAttributeMapping} - zoom={zoom} - setZoom={setZoom} - selectedLanguage={selectedLanguage} - setSelectedLanguage={setSelectedLanguage} - /> - {contextMenuItems && ( - - )} - - setSnackBarContent(null)} + autoHideDuration={3000} > - {snackBarContent} - - - {files?.length > 0 && ( - - )} - {promptItem && ( - handlePromptConfirm(promptItem)} - headline={promptItem.headline || ''} - text={promptItem.text || ''} - confirmButton={promptItem.label} - /> - )} - - + + {snackBarContent} + + + {settings.files?.length > 0 && ( + + )} + {promptItem && ( + setPromptItem(null)} + promptItem={promptItem} + setContextMenuItems={setContextMenuItems} + headline={promptItem.headline || ''} + text={promptItem.text || ''} + confirmButton={promptItem.label} + /> + )} + + + ); }; diff --git a/src/renderer/Archive.tsx b/src/renderer/Archive.tsx index b579790a..981133e9 100644 --- a/src/renderer/Archive.tsx +++ b/src/renderer/Archive.tsx @@ -3,13 +3,14 @@ import { withTranslation, WithTranslation } from 'react-i18next'; import Prompt from './Prompt'; import { i18n } from './Settings/LanguageSelector'; -const { ipcRenderer, store } = window.api; +const { ipcRenderer} = window.api; interface Props extends WithTranslation { setSnackBarContent: React.Dispatch>; setSnackBarSeverity: React.Dispatch>; triggerArchiving: boolean; setTriggerArchiving: React.Dispatch>; + settings: Settings, showPromptDoneFile: boolean; setShowPromptDoneFile: React.Dispatch>; headers: HeadersObject; @@ -19,6 +20,7 @@ interface Props extends WithTranslation { const Archive: React.FC = ({ triggerArchiving, setTriggerArchiving, + settings, showPromptDoneFile, setShowPromptDoneFile, headers, @@ -40,9 +42,8 @@ const Archive: React.FC = ({ useEffect(() => { if(triggerArchiving) { - const files = store.get('files') - const index = files.findIndex((file: FileObject) => file.active); - const doneFilePath = files[index]?.doneFilePath; + const index = settings.files.findIndex((file: FileObject) => file.active); + const doneFilePath = settings.files[index]?.doneFilePath; if(doneFilePath && headers?.completedTodoObjects > 0) { setShowPromptDoneFile(false); diff --git a/src/renderer/DataGrid/DatePickerInline.tsx b/src/renderer/DataGrid/DatePickerInline.tsx index 57397c8a..89b205fd 100644 --- a/src/renderer/DataGrid/DatePickerInline.tsx +++ b/src/renderer/DataGrid/DatePickerInline.tsx @@ -6,14 +6,14 @@ import { Item } from 'jstodotxt'; import { handleFilterSelect } from '../Shared'; import dayjs from 'dayjs'; -const { ipcRenderer, store } = window.api; +const { ipcRenderer } = window.api; interface Props { type: string; todoObject: TodoObject; date: Date; filters: Filters; - selectedLanguage: string; + settings: Settings; } const DatePickerInline: React.FC = ({ @@ -21,7 +21,7 @@ const DatePickerInline: React.FC = ({ todoObject, date, filters, - selectedLanguage, + settings, }) => { const [open, setOpen] = useState(false); const chipText = type === 'due' ? "due:" : type === 't' ? "t:" : null; @@ -73,7 +73,7 @@ const DatePickerInline: React.FC = ({ }; return ( - + void; + settings: Settings; } const Elements: React.FC = memo(({ todoObject, filters, - handleButtonClick + handleButtonClick, + settings, }) => { const replacements: { [key: string]: (value: string, type: string) => React.ReactNode; @@ -23,6 +25,7 @@ const Elements: React.FC = memo(({ todoObject={todoObject} date={todoObject.due} filters={filters} + settings={settings} /> ), t: (value, type) => ( @@ -31,6 +34,7 @@ const Elements: React.FC = memo(({ todoObject={todoObject} date={todoObject.t} filters={filters} + settings={settings} /> ), contexts: (value, type) => ( diff --git a/src/renderer/DataGrid/Grid.tsx b/src/renderer/DataGrid/Grid.tsx index dc1426b4..f2d1f413 100644 --- a/src/renderer/DataGrid/Grid.tsx +++ b/src/renderer/DataGrid/Grid.tsx @@ -4,7 +4,7 @@ import Row from './Row'; import './Grid.scss'; interface TodoDataGridProps { - flattenedTodoObjects: TodoObject[] | null; + todoObjects: TodoObject[] | null; attributes: Attributes | null; filters: Filters | null; setDialogOpen: (open: boolean) => void; @@ -14,19 +14,21 @@ interface TodoDataGridProps { setContextMenuItems: (items: any[]) => void; setTodoObject: (todoObject: TodoObject) => void; setPromptItem: PromptItem; + settings: Settings; } const TodoDataGrid: React.FC = memo(({ - flattenedTodoObjects, - attributes, - filters, - setDialogOpen, - contextMenuPosition, - setContextMenuPosition, - contextMenuItems, - setContextMenuItems, - setTodoObject, - setPromptItem, + todoObjects, + attributes, + filters, + setDialogOpen, + contextMenuPosition, + setContextMenuPosition, + contextMenuItems, + setContextMenuItems, + setTodoObject, + setPromptItem, + settings, }) => { const [visibleRowCount, setVisibleRowCount] = useState(50); const [loadMoreRows, setLoadMoreRows] = useState(true); @@ -69,7 +71,7 @@ const TodoDataGrid: React.FC = memo(({ const totalHeight = list.scrollHeight; const clientHeight = list.clientHeight; if(totalHeight - scrollPos <= clientHeight * 3) { - const remainingRows: TodoObject[] | null = flattenedTodoObjects?.slice(visibleRowCount, visibleRowCount + 30); + const remainingRows: TodoObject[] | null = todoObjects?.slice(visibleRowCount, visibleRowCount + 30); if(remainingRows?.length === 0) { setLoadMoreRows(false); } else { @@ -79,9 +81,9 @@ const TodoDataGrid: React.FC = memo(({ } }; - if(!flattenedTodoObjects || Object.keys(flattenedTodoObjects).length === 0) return null; + if(!todoObjects || Object.keys(todoObjects).length === 0) return null; - const rows = flattenedTodoObjects.slice(0, visibleRowCount); + const rows = todoObjects.slice(0, visibleRowCount); return ( @@ -98,6 +100,7 @@ const TodoDataGrid: React.FC = memo(({ contextMenuItems={contextMenuItems} setContextMenuItems={setContextMenuItems} setPromptItem={setPromptItem} + settings={settings} /> ))} diff --git a/src/renderer/DataGrid/Row.tsx b/src/renderer/DataGrid/Row.tsx index dc9cadee..38917016 100644 --- a/src/renderer/DataGrid/Row.tsx +++ b/src/renderer/DataGrid/Row.tsx @@ -20,6 +20,7 @@ interface Props extends WithTranslation { setContextMenuPosition: React.Dispatch>; setContextMenuItems: React.Dispatch>; setPromptItem: PromptItem; + settings: Settings, t: typeof i18n.t; } @@ -31,6 +32,7 @@ const Row: React.FC = memo(({ setContextMenuPosition, setContextMenuItems, setPromptItem, + settings, t, }) => { const itemDelete = { @@ -124,6 +126,7 @@ const Row: React.FC = memo(({ todoObject={row} filters={filters} handleButtonClick={handleButtonClick} + settings={settings} /> diff --git a/src/renderer/Drawer/Attributes.tsx b/src/renderer/Drawer/Attributes.tsx index 92d9f4ee..a3b13030 100644 --- a/src/renderer/Drawer/Attributes.tsx +++ b/src/renderer/Drawer/Attributes.tsx @@ -1,12 +1,5 @@ import React, { useState, useEffect, useRef, KeyboardEvent, memo } from 'react'; -import { - Accordion, - AccordionSummary, - AccordionDetails, - Box, - Button, - Badge, -} from '@mui/material'; +import { Accordion, AccordionSummary, AccordionDetails, Box, Button, Badge } from '@mui/material'; import ExpandMoreIcon from '@mui/icons-material/ExpandMore'; import VisibilityOffIcon from '@mui/icons-material/VisibilityOff'; import AirIcon from '@mui/icons-material/Air'; @@ -19,15 +12,15 @@ const { store } = window.api; interface Props extends WithTranslation { attributes: Attributes | null; + attributeMapping; filters: Filters | null; - attributeMapping: { [key: string]: string }; t: typeof i18n.t; } const DrawerAttributes: React.FC = memo(({ attributes, - filters, attributeMapping, + filters, t, }) => { const mustNotify = (items) => items.some((item) => item.notify); diff --git a/src/renderer/Drawer/Drawer.tsx b/src/renderer/Drawer/Drawer.tsx index 5011f17b..87c29ec0 100644 --- a/src/renderer/Drawer/Drawer.tsx +++ b/src/renderer/Drawer/Drawer.tsx @@ -1,4 +1,4 @@ -import React, { useState, useRef, useEffect, KeyboardEvent, memo } from 'react'; +import React, { useState, useRef, useEffect, memo } from 'react'; import { Drawer, Tabs, Tab, Box } from '@mui/material'; import DrawerAttributes from './Attributes'; import DrawerSorting from './Sorting/Sorting'; @@ -7,28 +7,26 @@ import FilterAltIcon from '@mui/icons-material/FilterAlt'; import FilterListIcon from '@mui/icons-material/FilterList'; import TuneIcon from '@mui/icons-material/Tune'; import { withTranslation, WithTranslation } from 'react-i18next'; -import './Drawer.scss'; import { i18n } from '../Settings/LanguageSelector'; +import './Drawer.scss'; const { store } = window.api; interface Props extends WithTranslation { - isDrawerOpen: boolean; - setIsDrawerOpen: React.Dispatch>; + settings: Settings; attributes: Attributes | null; filters: Filters | null; - attributeMapping: TranslatedAttributes | null; searchFieldRef: React.RefObject; + attributeMapping; t: typeof i18n.t; } const DrawerComponent: React.FC = memo(({ - isDrawerOpen, - setIsDrawerOpen, + settings, attributes, filters, - attributeMapping, searchFieldRef, + attributeMapping, t }) => { const [activeTab, setActiveTab] = useState('attributes'); @@ -60,20 +58,20 @@ const DrawerComponent: React.FC = memo(({ const handleKeyDown = (event: KeyboardEvent) => { const isSearchFocused = document.activeElement === searchFieldRef.current; if(!isSearchFocused && event.key === 'Escape') { - setIsDrawerOpen(false); + store.set('isDrawerOpen', false); } }; useEffect(() => { - if(isDrawerOpen) { - document.addEventListener('keydown', handleKeyDown); + if(settings.isDrawerOpen) { + document.addEventListener('keydown', (event) => handleKeyDown(event)); } else { - document.removeEventListener('keydown', handleKeyDown); + document.removeEventListener('keydown', (event) => handleKeyDown(event)); } return () => { - document.removeEventListener('keydown', handleKeyDown); + document.removeEventListener('keydown', (event) => handleKeyDown(event)); }; - }, [isDrawerOpen]); + }, [settings.isDrawerOpen]); useEffect(() => { store.set('drawerWidth', drawerWidth); @@ -83,8 +81,8 @@ const DrawerComponent: React.FC = memo(({ @@ -93,17 +91,17 @@ const DrawerComponent: React.FC = memo(({ } /> } /> - {isDrawerOpen && activeTab === 'attributes' && ( + {settings.isDrawerOpen && activeTab === 'attributes' && ( )} - {isDrawerOpen && activeTab === 'filters' && } - {isDrawerOpen && activeTab === 'sorting' && ( - + {settings.isDrawerOpen && activeTab === 'filters' && } + {settings.isDrawerOpen && activeTab === 'sorting' && ( + )} ); diff --git a/src/renderer/Drawer/Filters.tsx b/src/renderer/Drawer/Filters.tsx index b6aea472..0f79b56b 100644 --- a/src/renderer/Drawer/Filters.tsx +++ b/src/renderer/Drawer/Filters.tsx @@ -1,45 +1,56 @@ -import React, { useState } from 'react'; +import React from 'react'; import { Box, FormGroup, FormControlLabel, Switch } from '@mui/material'; import { withTranslation, WithTranslation } from 'react-i18next'; -import { handleSettingChange } from '../Shared'; import './Filters.scss'; import { i18n } from '../Settings/LanguageSelector'; const { store } = window.api; +const visibleSettings = { + showCompleted: { + style: 'toggle', + }, + showHidden: { + style: 'toggle', + }, + thresholdDateInTheFuture: { + style: 'toggle', + }, + dueDateInTheFuture: { + style: 'toggle', + }, +}; + interface Props extends WithTranslation { + settings: Settings; t: typeof i18n.t; } -const DrawerFilters: React.FC = ({ +const DrawerFilters: React.FC = ({ + settings, t }) => { - const [settings, setSettings] = useState({ - showCompleted: store.get('showCompleted'), - showHidden: store.get('showHidden'), - thresholdDateInTheFuture: store.get('thresholdDateInTheFuture'), - dueDateInTheFuture: store.get('dueDateInTheFuture'), - }); - return ( - {Object.entries(settings).map(([settingName, value]) => ( - - } - label={t(`drawer.filters.${settingName}`)} - /> + {Object.entries(visibleSettings).map(([settingName, settingValue]) => ( + settingValue.style === 'toggle' ? ( + store.set(settingName, event.target.checked)} + name={settingName} + /> + } + label={t(`drawer.filters.${settingName}`)} + /> + ) : null ))} ); }; -export default withTranslation()(DrawerFilters); +export default withTranslation()(DrawerFilters); \ No newline at end of file diff --git a/src/renderer/Drawer/Sorting/DraggableList.tsx b/src/renderer/Drawer/Sorting/DraggableList.tsx index a5c31189..b20ae06d 100644 --- a/src/renderer/Drawer/Sorting/DraggableList.tsx +++ b/src/renderer/Drawer/Sorting/DraggableList.tsx @@ -6,19 +6,15 @@ import './DraggableList.scss'; const { store } = window.api; -interface Settings { - sorting: string[]; -} - -interface Props { +type Props = { + settings: Settings; attributeMapping: TranslatedAttributes; -} +}; const DraggableList: React.FC = ({ - attributeMapping + settings, + attributeMapping, }) => { - const [settings, setSettings] = useState({ sorting: store.get('sorting') }); - const reorder = (list: string[], startIndex: number, endIndex: number): string[] => { const result = Array.from(list); // Use Array.from instead of Array, from const [removed] = result.splice(startIndex, 1); @@ -30,10 +26,6 @@ const DraggableList: React.FC = ({ if(!result.destination) return; const updatedSorting = reorder(settings.sorting, result.source.index, result.destination.index); store.set('sorting', updatedSorting); - setSettings((prevSettings) => ({ - ...prevSettings, - sorting: updatedSorting, - })); }; return ( @@ -47,7 +39,6 @@ const DraggableList: React.FC = ({ index={index} key={item.id} settings={settings} - setSettings={setSettings} attributeMapping={attributeMapping} /> ))} diff --git a/src/renderer/Drawer/Sorting/DraggableListItem.tsx b/src/renderer/Drawer/Sorting/DraggableListItem.tsx index d62964ef..825c1838 100644 --- a/src/renderer/Drawer/Sorting/DraggableListItem.tsx +++ b/src/renderer/Drawer/Sorting/DraggableListItem.tsx @@ -7,20 +7,18 @@ import './DraggableListItem.scss'; const { store } = window.api; -type DraggableListItem = { +type Props = { item: Sorting; index: number; settings: Settings; - setSettings: any; attributeMapping: TranslatedAttributes; }; -const DraggableListItem: React.FC = ({ +const DraggableListItem: React.FC = ({ item, index, settings, - setSettings, - attributeMapping + attributeMapping, }) => { const handleButtonClick = () => { const updatedSorting = settings.sorting.map((sortingItem) => { @@ -30,10 +28,6 @@ const DraggableListItem: React.FC = ({ return sortingItem; }); store.set('sorting', updatedSorting); - setSettings((prevSettings) => ({ - ...prevSettings, - sorting: updatedSorting, - })); }; return ( diff --git a/src/renderer/Drawer/Sorting/Sorting.tsx b/src/renderer/Drawer/Sorting/Sorting.tsx index 9959cabd..69579835 100644 --- a/src/renderer/Drawer/Sorting/Sorting.tsx +++ b/src/renderer/Drawer/Sorting/Sorting.tsx @@ -1,50 +1,51 @@ -import React, { useState } from 'react'; +import React from 'react'; import { Box, FormGroup, FormControlLabel, Switch, Divider } from '@mui/material'; -import { handleSettingChange } from '../../Shared'; import { withTranslation, WithTranslation } from 'react-i18next'; import DraggableList from './DraggableList'; -import './Sorting.scss'; import { i18n } from '../../Settings/LanguageSelector'; +import './Sorting.scss'; const { store } = window.api; -interface Settings { - fileSorting: boolean; -} - interface Props extends WithTranslation { - attributeMapping: TranslatedAttributes; + settings: Settings; + attributeMapping; t: typeof i18n.t; } +const visibleSettings = { + fileSorting: { + style: 'toggle', + }, +}; + const DrawerSorting: React.FC = ({ + settings, attributeMapping, t }) => { - const [settings, setSettings] = useState({ - fileSorting: store.get('fileSorting'), - }); - return ( - {Object.entries(settings).map(([settingName, value]) => ( - - } - label={t(`drawer.sorting.${settingName}`)} - /> - ))} + {Object.entries(visibleSettings).map(([settingName, settingValue]) => ( + settingValue.style === 'toggle' ? ( + store.set(settingName, event.target.checked)} + name={settingName} + /> + } + label={t(`drawer.sorting.${settingName}`)} + /> + ) : null + ))} {!settings.fileSorting && ( - + )} ); diff --git a/src/renderer/Header/FileTabs.tsx b/src/renderer/Header/FileTabs.tsx index 6f278f9e..706d75ba 100644 --- a/src/renderer/Header/FileTabs.tsx +++ b/src/renderer/Header/FileTabs.tsx @@ -8,14 +8,14 @@ import { i18n } from '../Settings/LanguageSelector'; const { ipcRenderer } = window.api; interface Props extends WithTranslation { - files: FileObject[]; + settings: Settings; setContextMenuPosition: (position: { top: number; left: number }) => void; setContextMenuItems: (items: any[]) => void; t: typeof i18n.t; } const FileTabs: React.FC = memo(({ - files, + settings, setContextMenuPosition, setContextMenuItems, t, @@ -28,12 +28,12 @@ const FileTabs: React.FC = memo(({ id: 'changeDoneFilePath', label: t('fileTabs.changeLocation'), index: index, - doneFilePath: files[index].doneFilePath, + doneFilePath: settings.files[index].doneFilePath, }, { id: 'revealInFileManager', label: t('fileTabs.revealFile'), - pathToReveal: files[index].todoFilePath, + pathToReveal: settings.files[index].todoFilePath, }, { id: 'removeFile', @@ -45,9 +45,7 @@ const FileTabs: React.FC = memo(({ ]); }; - if(!files || files.length === 0) return null; - - const index = files.findIndex((file) => file.active); + const index = settings.files.findIndex((file) => file.active); const [fileTab, setFileTab] = useState(index !== -1 ? index : 0); const handleChange = (_event: React.SyntheticEvent, index: number) => { @@ -62,7 +60,7 @@ const FileTabs: React.FC = memo(({ return ( - {files.map((file, index) => ( + {settings.files.map((file, index) => ( file ? ( void; - isSearchOpen: boolean; - setIsSearchOpen: (isSearchOpen: boolean) => void; searchFieldRef: React.RefObject; t: typeof i18n.t; } const Search: React.FC = memo(({ headers, + settings, searchString, setSearchString, - isSearchOpen, - setIsSearchOpen, searchFieldRef, t, }) => { @@ -32,9 +30,7 @@ const Search: React.FC = memo(({ const handleAddTodo = () => { if(searchString) { - ipcRenderer.send('writeTodoToFile', undefined, searchString); - setSearchString(''); - searchFieldRef.current?.focus(); + ipcRenderer.send('writeTodoToFile', -1, searchString); } }; @@ -45,33 +41,31 @@ const Search: React.FC = memo(({ const handleKeyDown = useCallback((event: KeyboardEvent) => { const isSearchFocused = document.activeElement === searchFieldRef.current; - if (isSearchFocused && event.key === 'Escape' && searchString) { + if (searchString && isSearchFocused && event.key === 'Escape') { setSearchString(''); - } else if (isSearchFocused && event.key === 'Escape' && !searchString) { - setIsSearchOpen(false); + } else if (!searchString && isSearchFocused && event.key === 'Escape') { + const isSearchOpen = !settings.isSearchOpen; + store.set('isSearchOpen', isSearchOpen); } else if (searchString && (event.metaKey || event.ctrlKey) && event.key === 'Enter') { - ipcRenderer.send('writeTodoToFile', undefined, searchString); - setSearchString(''); + handleAddTodo(); } - }, [searchFieldRef, searchString, setIsSearchOpen, ipcRenderer]); + }, [searchFieldRef, searchString]); useEffect(() => { const handleSearch = () => { ipcRenderer.send('requestData', searchString); }; - const delayedSearch: NodeJS.Timeout = setTimeout(handleSearch, 200); - return () => { clearTimeout(delayedSearch); }; }, [searchString]); useEffect(() => { - if(isSearchOpen && searchFieldRef.current) { + if(settings.isSearchOpen && searchFieldRef.current) { searchFieldRef.current.focus(); } - }, [isSearchOpen, searchFieldRef]); + }, [settings.isSearchOpen, searchFieldRef]); useEffect(() => { document.addEventListener('keydown', handleKeyDown); @@ -82,8 +76,8 @@ const Search: React.FC = memo(({ return ( <> - {isSearchOpen && ( - + {settings.isSearchOpen && ( + >; + settings: Settings; searchFieldRef: RefObject; } -const ToolBar: React.FC = memo(({ - isSearchOpen, - setIsSearchOpen, +const { store } = window.api; + +const ToolBar: React.FC = memo(({ + settings, searchFieldRef }) => { - const handleClick = () => { - setIsSearchOpen((prevIsSearchOpen) => !prevIsSearchOpen); - }; - - const handleKeyDown = useCallback((event: KeyboardEvent) => { + const handleKeyDown = useCallback( + (event: KeyboardEvent) => { const isSearchFocused = document.activeElement === searchFieldRef.current; - if((event.metaKey || event.ctrlKey) && event.key === 'f' && isSearchOpen && !isSearchFocused) { + if ((event.metaKey || event.ctrlKey) && event.key === 'f' && settings.isSearchOpen && !isSearchFocused) { event.preventDefault(); searchFieldRef.current?.focus(); } }, - [isSearchOpen, searchFieldRef] + [settings.isSearchOpen, searchFieldRef] ); + const handleOnClick = () => { + store.set('isSearchOpen', !settings.isSearchOpen); + } + useEffect(() => { - document.addEventListener('keydown', handleKeyDown); + const handleDocumentKeyDown = (event: KeyboardEvent) => handleKeyDown(event); + document.addEventListener('keydown', handleDocumentKeyDown); + return () => { - document.removeEventListener('keydown', handleKeyDown); + document.removeEventListener('keydown', handleDocumentKeyDown); }; }, [handleKeyDown]); return ( - - + + ); }); -export default ToolBar; +export default ToolBar; \ No newline at end of file diff --git a/src/renderer/Ipc.tsx b/src/renderer/Ipc.tsx new file mode 100644 index 00000000..90e71e1c --- /dev/null +++ b/src/renderer/Ipc.tsx @@ -0,0 +1,91 @@ +import React, { useEffect } from 'react'; + +const { ipcRenderer} = window.api; + +interface Props { + setHeaders; + setAttributes; + setFilters; + setTodoObjects; + setTodoObject; + setAttributeFields; + setSnackBarSeverity; + setSnackBarContent; + setSettings; + setTriggerArchiving; + setSplashScreen; + setIsSettingsOpen; +} + +const IpcComponent: React.FC = ({ + setHeaders, + setAttributes, + setFilters, + setTodoObjects, + setTodoObject, + setAttributeFields, + setSnackBarSeverity, + setSnackBarContent, + setSettings, + setTriggerArchiving, + setSplashScreen, + setIsSettingsOpen, +}) => { + + const handleRequestedData = (requestedData: RequestedData) => { + if(requestedData?.headers) setHeaders(requestedData.headers); + if(requestedData?.attributes) setAttributes(requestedData.attributes); + if(requestedData?.filters) setFilters(requestedData.filters); + if(requestedData?.todoObjects) setTodoObjects(requestedData.todoObjects); + setSplashScreen(null); + }; + + const handleUpdateAttributeFields = (todoObject: TodoObject) => { + if(todoObject) { + setAttributeFields(todoObject); + } + }; + + const handleResponse = function (response: Error | string) { + const severity = response instanceof Error ? 'error' : 'success'; + setSnackBarSeverity(severity); + setSnackBarContent(response instanceof Error ? response.message : response); + }; + + const handleDrop = (event: any) => { + event.preventDefault(); + const filePath = event.dataTransfer.files[0].path; + if(typeof filePath === 'string') ipcRenderer.send('droppedFile', filePath); + }; + + const handleDragOver = (event: Event) => { + event.preventDefault(); + }; + + useEffect(() => { + ipcRenderer.on('requestData', handleRequestedData); + ipcRenderer.on('updateAttributeFields', handleUpdateAttributeFields); + ipcRenderer.on('updateTodoObject', (todoObject) => setTodoObject(todoObject)); + ipcRenderer.on('responseFromMainProcess', handleResponse); + ipcRenderer.on('settingsChanged', (settings) => setSettings(settings)); + ipcRenderer.on('isSettingsOpen', (isSettingsOpen) => setIsSettingsOpen(isSettingsOpen)); + ipcRenderer.on('triggerArchiving', () => setTriggerArchiving(true)); + window.addEventListener('drop', handleDrop); + window.addEventListener('dragover', handleDragOver); + return () => { + ipcRenderer.off('requestData', handleRequestedData); + ipcRenderer.off('updateAttributeFields', handleUpdateAttributeFields); + ipcRenderer.off('updateTodoObject', (todoObject) => setTodoObject(todoObject)); + ipcRenderer.off('responseFromMainProcess', handleResponse); + ipcRenderer.off('settingsChanged', (settings) => setSettings(settings)); + ipcRenderer.off('isSettingsOpen', (isSettingsOpen) => setIsSettingsOpen(isSettingsOpen)); + ipcRenderer.off('triggerArchiving', () => setTriggerArchiving(true)); + window.removeEventListener('drop', handleDrop); + window.removeEventListener('dragover', handleDragOver); + }; + }, []); + + return (<>); +}; + +export default IpcComponent; \ No newline at end of file diff --git a/src/renderer/Matomo.tsx b/src/renderer/Matomo.tsx new file mode 100644 index 00000000..684aceb4 --- /dev/null +++ b/src/renderer/Matomo.tsx @@ -0,0 +1,35 @@ +import React, { useEffect } from 'react'; + +interface Props { + settings: Settings; +} + +const { store } = window.api; + +const environment = process.env.NODE_ENV; + +const MatomoComponent: React.FC = ({ + settings, +}) => { + + useEffect(() => { + const anonymousUserId = (environment === 'development') ? 'Dev' : store.get('anonymousUserId'); + if(settings.matomo && anonymousUserId) { + const matomoContainer: string = (environment === 'development') ? 'https://www.datenkrake.eu/matomo/js/container_WVsEueTV_dev_a003c77410fd43f247329b3b.js' : 'https://www.datenkrake.eu/matomo/js/container_WVsEueTV.js'; + const _mtm = window._mtm = window._mtm || []; + _mtm.push({'mtm.startTime': (new Date().getTime()), 'event': 'mtm.Start'}); + if(anonymousUserId) _mtm.push({'anonymousUserId': anonymousUserId }); + const + d = document, + g = d.createElement('script'), + s = d.getElementsByTagName('script')[0]; + g.async = true; + g.src = matomoContainer; + s.parentNode?.insertBefore(g, s); + } + }, [settings.matomo]); + + return (<>); +}; + +export default MatomoComponent; \ No newline at end of file diff --git a/src/renderer/Navigation.tsx b/src/renderer/Navigation.tsx index c74a1624..4430b0e0 100644 --- a/src/renderer/Navigation.tsx +++ b/src/renderer/Navigation.tsx @@ -9,16 +9,12 @@ import KeyboardArrowRightIcon from '@mui/icons-material/KeyboardArrowRight'; import { Button, Box } from '@mui/material'; import './Navigation.scss'; -const { ipcRenderer } = window.api; +const { ipcRenderer, store } = window.api; interface Props { setIsSettingsOpen: React.Dispatch>; - isDrawerOpen: boolean; - setIsDrawerOpen: React.Dispatch>; setDialogOpen: React.Dispatch>; - files: FileObject[]; - isNavigationOpen: boolean; - setIsNavigationOpen: React.Dispatch>; + settings: Settings; setTriggerArchiving: React.Dispatch>; headers: HeadersObject | null; setTodoObject: React.Dispatch>; @@ -26,11 +22,8 @@ interface Props { const NavigationComponent: React.FC = memo(({ setIsSettingsOpen, - isDrawerOpen, - setIsDrawerOpen, setDialogOpen, - files, - setIsNavigationOpen, + settings, setTriggerArchiving, headers, setTodoObject, @@ -43,7 +36,7 @@ const NavigationComponent: React.FC = memo(({ useEffect(() => { const handleKeyDown = (event: KeyboardEvent) => { - if(files.length > 0 && (event.metaKey || event.ctrlKey) && event.key === 'n') { + if(settings.files?.length > 0 && (event.metaKey || event.ctrlKey) && event.key === 'n') { handleOpen(); } }; @@ -51,18 +44,18 @@ const NavigationComponent: React.FC = memo(({ return () => { document.removeEventListener('keydown', handleKeyDown); }; - }, [files]); + }, [settings.files]); return ( <> sleek - {files?.length > 0 && ( + {settings.files?.length > 0 && ( <> - {headers && headers.completedTodoObjects > 0 && ( @@ -80,11 +73,11 @@ const NavigationComponent: React.FC = memo(({ - - diff --git a/src/renderer/Prompt.tsx b/src/renderer/Prompt.tsx index 1845b94a..aaa5aa73 100644 --- a/src/renderer/Prompt.tsx +++ b/src/renderer/Prompt.tsx @@ -1,12 +1,15 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { Dialog, DialogTitle, DialogContent, DialogActions, Button } from '@mui/material'; import { withTranslation, WithTranslation } from 'react-i18next'; import { i18n } from './Settings/LanguageSelector'; +const { ipcRenderer } = window.api; + interface Props extends WithTranslation { open: boolean; onClose: () => void; - onConfirm: () => void; + promptItem: PromptItem; + setContextMenuItems: React.Dispatch>; headline?: string; text?: string; confirmButton?: string; @@ -20,7 +23,8 @@ interface Props extends WithTranslation { const Prompt: React.FC = ({ open, onClose, - onConfirm, + promptItem, + setContextMenuItems, headline, text, confirmButton, @@ -33,9 +37,25 @@ const Prompt: React.FC = ({ const handleConfirm = (allow: boolean) => { onClose(); if(allow) { - onConfirm(); + const { id, todoObject, index } = promptItem; + switch (id) { + case 'delete': + ipcRenderer.send('removeLineFromFile', todoObject?.id); + break; + case 'removeFile': + ipcRenderer.send('removeFile', index); + break; + default: + setContextMenuItems(null); + } + } + }; + + useEffect(() => { + if(promptItem) { + setContextMenuItems(null); } - }; + }, [promptItem]); return ( diff --git a/src/renderer/Settings/LanguageSelector.tsx b/src/renderer/Settings/LanguageSelector.tsx index 5c0129d6..923f7a6c 100644 --- a/src/renderer/Settings/LanguageSelector.tsx +++ b/src/renderer/Settings/LanguageSelector.tsx @@ -1,6 +1,5 @@ -import React, { useEffect } from 'react'; +import React, { useState, useEffect } from 'react'; import { FormControl, InputLabel, MenuItem, Select } from '@mui/material'; -import { translatedAttributes } from '../Shared'; import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; import de from '../../locales/de.json'; @@ -54,9 +53,8 @@ const options: i18n.InitOptions = { ko: { translation: ko }, hi: { translation: hi }, }, - lng: store.get('language') || navigator.language, fallbackLng: 'en', - supportedLngs: ['de', 'en', 'en-gb', 'it', 'es', 'fr', 'zh', 'pt', 'jp', 'tr', 'hu', 'cs', 'pl', 'ru', 'ko', 'hi'], + supportedLngs: ['de', 'en', 'en-GB', 'it', 'es', 'fr', 'zh', 'pt', 'jp', 'tr', 'hu', 'cs', 'pl', 'ru', 'ko', 'hi'], interpolation: { escapeValue: false, }, @@ -67,6 +65,9 @@ i18n .use(initReactI18next) .init(options) .then(() => { + if(!store.get('language')) { + store.set('language', navigator.language); + } // TODO: check if this is still working i18n.on('missingKey', (key: string) => { console.warn(`Missing translation key: ${key}`); @@ -79,7 +80,7 @@ i18n const friendlyLanguageName: Record = { de: 'Deutsch', en: 'English', - 'en-gb': 'English (UK)', + 'en-GB': 'English (UK)', it: 'Italiano', es: 'Español', fr: 'Français', @@ -96,33 +97,31 @@ const friendlyLanguageName: Record = { }; interface Props { - setAttributeMapping: (attributes: Record) => void; - selectedLanguage: string; - setSelectedLanguage: React.Dispatch>; + settings: Settings; + attributeMapping; } -const LanguageSelector: React.FC = ({ - setAttributeMapping, - selectedLanguage, - setSelectedLanguage, +const LanguageSelector: React.FC = ({ + settings, + attributeMapping, }) => { const supportedLanguages: false | readonly string[] | undefined = i18n.options.supportedLngs; const changeLanguage = (event: React.ChangeEvent<{ value: string }>) => { const language = event.target.value; - setSelectedLanguage(language); + store.set('language', language); }; useEffect(() => { - i18n.changeLanguage(selectedLanguage) - .then(() => { - store.set('language', selectedLanguage); - setAttributeMapping(translatedAttributes(i18n.t)); - }) + i18n.changeLanguage(settings.language) + .then(() => { + store.set('language', settings.language); + //setAttributeMapping(attributeMapping(i18n.t)); + }) .catch((error) => { - console.error('Error loading translation:', error); - }); - }, [selectedLanguage, setAttributeMapping]); + console.error('Error loading translation:', error); + }); + }, [settings.language]); return ( @@ -131,9 +130,9 @@ const LanguageSelector: React.FC = ({ labelId='language' id='language' label='Language' - value={selectedLanguage} + value={settings.language || navigator.language} name='language' - onChange={changeLanguage} + onChange={(event) => changeLanguage(event)} > {Array.isArray(supportedLanguages) ? ( supportedLanguages.map((languageCode: string) => ( diff --git a/src/renderer/Settings/Settings.tsx b/src/renderer/Settings/Settings.tsx index 65ff0901..e9c12543 100644 --- a/src/renderer/Settings/Settings.tsx +++ b/src/renderer/Settings/Settings.tsx @@ -1,8 +1,7 @@ -import React, { useState, memo } from 'react'; +import React, { useEffect, memo } from 'react'; import { Box, FormControl, FormControlLabel, InputLabel, MenuItem, Modal, Select, Switch, Slider } from '@mui/material'; import { withTranslation, WithTranslation } from 'react-i18next'; import LanguageSelector, { i18n } from './LanguageSelector'; -import { handleSettingChange } from '../Shared'; import './Settings.scss'; const { store } = window.api; @@ -10,116 +9,123 @@ const { store } = window.api; interface Props extends WithTranslation { isOpen: boolean; onClose: () => void; - setAttributeMapping: React.Dispatch>; - zoom: number; - setZoom: React.Dispatch>; - selectedLanguage: string; - setSelectedLanguage: React.Dispatch>; + settings: Settings; + attributeMapping; t: typeof i18n.t; } +const visibleSettings = { + appendCreationDate: { + style: 'toggle', + }, + convertRelativeToAbsoluteDates: { + style: 'toggle', + }, + showFileTabs: { + style: 'toggle', + }, + tray: { + style: 'toggle', + }, + bulkTodoCreation: { + style: 'toggle', + }, + matomo: { + style: 'toggle', + }, + notificationsAllowed: { + style: 'toggle', + }, + notificationThreshold: { + style: 'slider', + min: 0, + max: 10, + unit: ' days', + step: 1, + }, + zoom: { + style: 'slider', + min: 75, + max: 125, + unit: '%', + step: 5, + }, + colorTheme: { + style: 'select', + values: ['system', 'light', 'dark'], + }, +}; + const Settings: React.FC = memo(({ isOpen, onClose, - setAttributeMapping, - zoom, - setZoom, - selectedLanguage, - setSelectedLanguage, + settings, + attributeMapping, t, }) => { - const [settings, setSettings] = useState({ - appendCreationDate: store.get('appendCreationDate'), - convertRelativeToAbsoluteDates: store.get('convertRelativeToAbsoluteDates'), - tray: store.get('tray'), - showFileTabs: store.get('showFileTabs'), - colorTheme: store.get('colorTheme'), - bulkTodoCreation: store.get('bulkTodoCreation'), - matomo: store.get('matomo'), - notificationsAllowed: store.get('notificationsAllowed'), - notificationThreshold: store.get('notificationThreshold'), - }); - const handleColorThemeChange = (event: Event) => { - const selectedValue = event.target?.value; - if(selectedValue) { - store.set('colorTheme', selectedValue); - setSettings((prevSettings) => ({ - ...prevSettings, - colorTheme: selectedValue, - })); - } - }; + useEffect(() => { + const adjustedFontSize = 16 * (settings.zoom / 100); + document.body.style.fontSize = `${adjustedFontSize}px`; + }, [settings.zoom]); return (

{t('settings.headline')}

- {Object.entries(settings).map(([settingName, settingValue]) => ( - (settingName !== 'colorTheme' && settingName !== 'notificationThreshold') && ( + {Object.entries(visibleSettings).map(([settingName, settingValue]) => ( + settingValue.style === 'toggle' ? ( store.set(settingName, event.target.checked)} name={settingName} /> } label={t(`settings.${settingName}`)} /> + ) : ( + settingValue.style === 'select' ? ( + + {t(`settings.${settingName}`)} + + + ) : ( + settingValue.style === 'slider' ? ( + + {t(`settings.${settingName}`)} + `${value}${settingValue.unit}`} + min={settingValue.min} + max={settingValue.max} + onChange={(event) => store.set(settingName, event.target.value)} + /> + + ) : null + ) ) ))} - - {settings.notificationsAllowed && ( - - {t('settings.notificationThreshold')} - `${value} days`} - min={1} - max={10} - onChange={handleSettingChange('notificationThreshold', setSettings)} - /> - - )} - - - Zoom - `${value}%`} - min={75} max={125} - onChange={(event) => setZoom(event.target?.value)} - /> - - - - {t('settings.colorTheme')} - - + settings={settings} + attributeMapping={attributeMapping} + />
); diff --git a/src/renderer/Shared.tsx b/src/renderer/Shared.tsx index e31e1263..39e13bcb 100644 --- a/src/renderer/Shared.tsx +++ b/src/renderer/Shared.tsx @@ -1,5 +1,4 @@ import { i18n } from './Settings/LanguageSelector'; -import React from 'react'; const { ipcRenderer, store } = window.api; @@ -50,26 +49,4 @@ export const translatedAttributes = (t: typeof i18n.t) => ({ pm: t('shared.attributeMapping.pm'), created: t('shared.attributeMapping.created'), completed: t('shared.attributeMapping.completed'), -}); - -export const handleSettingChange = (name: keyof typeof Settings, setSettings: React.Dispatch>) => (event: React.ChangeEvent) => { - try { - if(typeof event.target.checked === 'boolean') { - const checked = event.target.checked; - store.set(name, checked); - setSettings((prevSettings) => ({ - ...prevSettings, - [name]: checked, - })); - } else { - const value = event.target.value as any; - store.set(name, value); - setSettings((prevSettings) => ({ - ...prevSettings, - [name]: value, - })); - } - } catch (error: any) { - console.error(error); - } -}; +}); \ No newline at end of file diff --git a/src/renderer/TodoDialog/AutoSuggest.tsx b/src/renderer/TodoDialog/AutoSuggest.tsx index 3888c810..0d00fe8e 100644 --- a/src/renderer/TodoDialog/AutoSuggest.tsx +++ b/src/renderer/TodoDialog/AutoSuggest.tsx @@ -16,7 +16,6 @@ const AutoSuggest: React.FC = ({ textFieldValue, setTextFieldValue, attributes, - handleAdd, }) => { const [suggestions, setSuggestions] = useState([]); const [prefix, setPrefix] = useState(null); diff --git a/src/renderer/TodoDialog/DueDatePicker.tsx b/src/renderer/TodoDialog/DueDatePicker.tsx index bd6df601..0d0cd5f3 100644 --- a/src/renderer/TodoDialog/DueDatePicker.tsx +++ b/src/renderer/TodoDialog/DueDatePicker.tsx @@ -6,30 +6,34 @@ import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import dayjs from 'dayjs'; import './DatePicker.scss'; +const { ipcRenderer } = window.api; + interface Props extends WithTranslation { dueDate: string; - selectedLanguage: string; - refreshTextFieldValue: (type: string, value: any) => void; + settings: Settings; + textFieldValue: string; + todoObject: TodoObject; t: typeof i18n.t; } const DueDatePickerComponent: React.FC = ({ dueDate, - selectedLanguage, - refreshTextFieldValue, + settings, + textFieldValue, + todoObject, t, }) => { const handleChange = (date: dayjs.Dayjs | null) => { try { - refreshTextFieldValue('due', dayjs(date).format('YYYY-MM-DD')); + ipcRenderer.send('updateTodoObject', todoObject?.id, textFieldValue, 'due', dayjs(date).format('YYYY-MM-DD')); } catch(error) { console.error(error); } }; return ( - + void; + textFieldValue: string; + todoObject: TodoObject; } const PomodoroPicker: React.FC = ({ pomodoro, - refreshTextFieldValue, + textFieldValue, + todoObject, }) => { const handleChange = (event: React.ChangeEvent) => { try { - refreshTextFieldValue('pm', event.target.value); + ipcRenderer.send('updateTodoObject', todoObject?.id, textFieldValue, 'pm', event.target.value); } catch(error) { console.error(error); } diff --git a/src/renderer/TodoDialog/PriorityPicker.tsx b/src/renderer/TodoDialog/PriorityPicker.tsx index 5d997e2d..b7fdabfb 100644 --- a/src/renderer/TodoDialog/PriorityPicker.tsx +++ b/src/renderer/TodoDialog/PriorityPicker.tsx @@ -4,24 +4,27 @@ import { withTranslation, WithTranslation } from 'react-i18next'; import { i18n } from '../Settings/LanguageSelector'; import './PriorityPicker.scss'; +const { ipcRenderer } = window.api; const alphabetArray = Array.from({ length: 26 }, (_, index) => String.fromCharCode(65 + index)); const priorities = [{ value: '-', label: '-' }, ...alphabetArray.map((letter) => ({ value: letter, label: letter }))]; interface Props extends WithTranslation { priority: string; - refreshTextFieldValue: (type: string, value: any) => void; + textFieldValue: string; + todoObject: TodoObject; t: typeof i18n.t; } const PriorityPicker: React.FC = ({ priority, - refreshTextFieldValue, + textFieldValue, + todoObject, t, }: Props) => { const handleChange = (event: React.ChangeEvent) => { try { - refreshTextFieldValue('priority', event.target.value); + ipcRenderer.send('updateTodoObject', todoObject?.id, textFieldValue, 'priority', event.target.value); } catch(error) { console.error(error); } diff --git a/src/renderer/TodoDialog/RecurrencePicker.tsx b/src/renderer/TodoDialog/RecurrencePicker.tsx index 996c3f48..b9fda765 100644 --- a/src/renderer/TodoDialog/RecurrencePicker.tsx +++ b/src/renderer/TodoDialog/RecurrencePicker.tsx @@ -6,11 +6,7 @@ import { withTranslation, WithTranslation } from 'react-i18next'; import { i18n } from '../Settings/LanguageSelector'; import './RecurrencePicker.scss'; -interface Props extends WithTranslation { - recurrence: string; - refreshTextFieldValue: (type: string, value: any) => void; - t: typeof i18n.t; -} +const { ipcRenderer } = window.api; const getInterval = (recurrence: string) => { return recurrence && recurrence.startsWith('+') ? recurrence.slice(2, 3) : recurrence ? recurrence.slice(1, 2) : null; @@ -24,9 +20,17 @@ const getStrictIndicator = (recurrence: string) => { return recurrence ? recurrence.startsWith('+') : false; } +interface Props extends WithTranslation { + recurrence: string; + textFieldValue: string; + todoObject: TodoObject; + t: typeof i18n.t; +} + const RecurrencePicker: React.FC = ({ recurrence, - refreshTextFieldValue, + textFieldValue, + todoObject, t }) => { const recurrenceFieldRef = useRef(null); @@ -36,7 +40,7 @@ const RecurrencePicker: React.FC = ({ const handleChange = (_event: React.ChangeEvent | undefined, recurrence: string) => { try { - refreshTextFieldValue('rec', recurrence); + ipcRenderer.send('updateTodoObject', todoObject?.id, textFieldValue, 'rec', recurrence); } catch(error) { console.error(error); } diff --git a/src/renderer/TodoDialog/ThresholdDatePicker.tsx b/src/renderer/TodoDialog/ThresholdDatePicker.tsx index 4f2aaf4b..fa4fdbcd 100644 --- a/src/renderer/TodoDialog/ThresholdDatePicker.tsx +++ b/src/renderer/TodoDialog/ThresholdDatePicker.tsx @@ -6,30 +6,34 @@ import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import dayjs from 'dayjs'; import './DatePicker.scss'; +const { ipcRenderer } = window.api; + interface Props extends WithTranslation { thresholdDate: string; - selectedLanguage: string; - refreshTextFieldValue: (type: string, value: any) => void; + settings: Settings; + textFieldValue: string; + todoObject: TodoObject; t: typeof i18n.t; } const ThresholdDatePickerComponent: React.FC = ({ thresholdDate, - selectedLanguage, - refreshTextFieldValue, + settings, + textFieldValue, + todoObject, t, }) => { const handleChange = (date: dayjs.Dayjs | null) => { try { - refreshTextFieldValue('t', dayjs(date).format('YYYY-MM-DD')); + ipcRenderer.send('updateTodoObject', todoObject?.id, textFieldValue, 't', dayjs(date).format('YYYY-MM-DD')); } catch(error) { console.error(error); } }; return ( - + >; setSnackBarSeverity: React.Dispatch>; setSnackBarContent: React.Dispatch>; - shouldUseDarkColors: boolean; textFieldValue: string, setTextFieldValue: React.Dispatch>, - selectedLanguage: React.Dispatch>, + settings: Settings, t: typeof i18n.t; } @@ -41,10 +39,9 @@ const TodoDialog: React.FC = memo(({ setAttributeFields, setSnackBarSeverity, setSnackBarContent, - shouldUseDarkColors, textFieldValue, setTextFieldValue, - selectedLanguage, + settings, t }) => { const bulkTodoCreation = store.get('bulkTodoCreation'); @@ -78,35 +75,36 @@ const TodoDialog: React.FC = memo(({ setDialogOpen(false); }; - const refreshTextFieldValue = (type, value) => { - let updatedString = (textFieldValue || '').replaceAll(/[\x10\r\n]/g, ` ${String.fromCharCode(16)} `); - const JsTodoTxtObject = new Item(updatedString); - if (type === 'priority') { - JsTodoTxtObject.setPriority(value); - } else { - JsTodoTxtObject.setExtension(type, value); - } - updatedString = JsTodoTxtObject.toString().replaceAll(` ${String.fromCharCode(16)} `, String.fromCharCode(16)); - setTextFieldValue(updatedString); - }; - - const handleKeyDown = (event) => { + const handleKeyDown = (event: any) => { if(event.metaKey && event.key === 'Enter') { event.preventDefault(); handleAdd(); } }; + const updateAttributeFields = (todoObject: TodoObject) => { + if(todoObject) { + setPriority(todoObject?.priority || '-'); + setDueDate(todoObject?.due || null); + setThresholdDate(todoObject?.t || null); + setRecurrence(todoObject?.rec || null); + setPomodoro(todoObject?.pm || 0); + } + } + useEffect(() => { - if(textFieldValue) ipcRenderer.send('createTodoObject', textFieldValue) + setTextFieldValue(todoObject?.string || ''); + updateAttributeFields(todoObject); + }, [todoObject]); + + useEffect(() => { + if(textFieldValue) ipcRenderer.send('updateAttributeFields', todoObject?.id, textFieldValue) }, [textFieldValue]); useEffect(() => { - setPriority(attributeFields?.priority || '-'); - setDueDate(attributeFields?.due || null); - setThresholdDate(attributeFields?.t || null); - setRecurrence(attributeFields?.rec || null); - setPomodoro(attributeFields?.pm || 0); + if(attributeFields) { + updateAttributeFields(attributeFields); + } }, [attributeFields]); useEffect(() => { @@ -120,7 +118,7 @@ const TodoDialog: React.FC = memo(({ id="TodoDialog" open={dialogOpen} onClose={handleClose} - className={shouldUseDarkColors ? 'darkTheme' : 'lightTheme'} + className={settings.shouldUseDarkColors ? 'darkTheme' : 'lightTheme'} onKeyDown={handleKeyDown} > @@ -134,25 +132,30 @@ const TodoDialog: React.FC = memo(({ /> diff --git a/src/types.tsx b/src/types.tsx index 6bbd69f2..729c05b5 100644 --- a/src/types.tsx +++ b/src/types.tsx @@ -59,10 +59,14 @@ declare global { showHidden: boolean; thresholdDateInTheFuture: boolean; dueDateInTheFuture: boolean; - showCompleted: boolean; - showHidden: boolean; - thresholdDateInTheFuture: boolean; - dueDateInTheFuture: boolean; + isNavigationOpen: boolean; + isDrawerOpen: boolean; + matomo: boolean; + files: FileObject[]; + shouldUseDarkColors: boolean; + language: string; + isSearchOpen: boolean; + fileSorting: boolean; } interface FileObject {