diff --git a/__tests__/__renderer__/classes/BaseCalendar.js b/__tests__/__renderer__/classes/BaseCalendar.js index 6701d7e5a..f1d61371a 100644 --- a/__tests__/__renderer__/classes/BaseCalendar.js +++ b/__tests__/__renderer__/classes/BaseCalendar.js @@ -267,6 +267,8 @@ describe('BaseCalendar.js', () => describe('punchDate()', () => { + const workAllDayPreferences = ({...getUserPreferences(), 'working-days-saturday': true, + 'working-days-sunday': true}); const today = new Date(); const nextYear = new Date(); nextYear.setFullYear(today.getFullYear() + 1); @@ -322,7 +324,8 @@ describe('BaseCalendar.js', () => date: new Date(), getCalendar: () => { - const calendar = new ExtendedClass(getUserPreferences(), languageData); + // Setting all days as work days so test works every day + const calendar = new ExtendedClass(workAllDayPreferences, languageData); return calendar; }, expect: () => @@ -352,7 +355,8 @@ describe('BaseCalendar.js', () => date: new Date(), getCalendar: () => { - const calendar = new ExtendedClass(getUserPreferences(), languageData); + // Setting all days as work days so test works every day + const calendar = new ExtendedClass(workAllDayPreferences, languageData); return calendar; }, expect: () => diff --git a/__tests__/__renderer__/window-aux.js b/__tests__/__renderer__/window-aux.js index adca56363..a74994aa2 100644 --- a/__tests__/__renderer__/window-aux.js +++ b/__tests__/__renderer__/window-aux.js @@ -12,8 +12,8 @@ describe('window-aux.js Testing', function() const mockHtmlPath = path.join('file://', __dirname, '../../__mocks__/mock.html'); - const devToolsShortcut = new KeyboardEvent('keyup', {keyCode: 73, ctrlKey: true, shiftKey: true}); - const badDevToolsShortcut = new KeyboardEvent('keyup', {keyCode: 74, ctrlKey: true, shiftKey: true}); + // const devToolsShortcut = new KeyboardEvent('keyup', {keyCode: 73, ctrlKey: true, shiftKey: true}); + // const badDevToolsShortcut = new KeyboardEvent('keyup', {keyCode: 74, ctrlKey: true, shiftKey: true}); const browserWindowOptions = { webPreferences: { enableRemoteModule: true, @@ -22,72 +22,75 @@ describe('window-aux.js Testing', function() }; const timeoutValue = 1500; - describe('bindDevToolsShortcut(window)', function() + // Testcase no longer being used since the move to electron without remote + // but we should make use of it for a mocha testcase to still be sure the proferences window + // and workday waiver have the shortcut working + + // describe('bindDevToolsShortcut(window)', function() + // { + + // test('No bind: should not open anything', async() => + // { + // const testWindow = new BrowserWindow(browserWindowOptions); + // testWindow.loadURL(mockHtmlPath); + // expect(testWindow.webContents.isDevToolsOpened()).not.toBeTruthy(); + + // testWindow.webContents.on('dom-ready', () => + // { + // window.dispatchEvent(devToolsShortcut); + // }); + // testWindow.on('did-fail-load', (event, code, desc, url, isMainFrame) => + // { + // console.log('did-fail-load: ', event, code, desc, url, isMainFrame); + // }); + + // await new Promise(r => setTimeout(r, timeoutValue)); + // expect(testWindow.webContents.isDevToolsOpened()).not.toBeTruthy(); + // }); + + // test('Bind: should open devTools', async() => + // { + // const testWindow = new BrowserWindow(browserWindowOptions); + // testWindow.loadURL(mockHtmlPath); + // expect(testWindow.webContents.isDevToolsOpened()).not.toBeTruthy(); + + // testWindow.webContents.on('dom-ready', () => + // { + // bindDevToolsShortcut(window); + // window.dispatchEvent(devToolsShortcut); + // }); + // testWindow.webContents.on('did-fail-load', (event, code, desc, url, isMainFrame) => + // { + // console.log('did-fail-load: ', event, code, desc, url, isMainFrame); + // }); + + // await new Promise(r => setTimeout(r, timeoutValue)); + // expect(testWindow.webContents.isDevToolsOpened()).toBeTruthy(); + // }); + + // test('Bind: bad shortcut, should not open devTools', async() => + // { + // const testWindow = new BrowserWindow(browserWindowOptions); + // testWindow.loadURL(mockHtmlPath); + // expect(testWindow.webContents.isDevToolsOpened()).not.toBeTruthy(); + + // testWindow.webContents.on('dom-ready', () => + // { + // bindDevToolsShortcut(window); + // window.dispatchEvent(badDevToolsShortcut); + // }); + // testWindow.webContents.on('did-fail-load', (event, code, desc, url, isMainFrame) => + // { + // console.log('did-fail-load: ', event, code, desc, url, isMainFrame); + // }); + + // await new Promise(r => setTimeout(r, timeoutValue)); + // expect(testWindow.webContents.isDevToolsOpened()).not.toBeTruthy(); + // }); + // }); + + describe('showDialogSync(options, successCallback)', function() { - - test('No bind: should not open anything', async() => - { - const testWindow = new BrowserWindow(browserWindowOptions); - testWindow.loadURL(mockHtmlPath); - expect(testWindow.webContents.isDevToolsOpened()).not.toBeTruthy(); - - testWindow.webContents.on('dom-ready', () => - { - window.dispatchEvent(devToolsShortcut); - }); - testWindow.on('did-fail-load', (event, code, desc, url, isMainFrame) => - { - console.log('did-fail-load: ', event, code, desc, url, isMainFrame); - }); - - await new Promise(r => setTimeout(r, timeoutValue)); - expect(testWindow.webContents.isDevToolsOpened()).not.toBeTruthy(); - }); - - test('Bind: should open devTools', async() => - { - const testWindow = new BrowserWindow(browserWindowOptions); - testWindow.loadURL(mockHtmlPath); - expect(testWindow.webContents.isDevToolsOpened()).not.toBeTruthy(); - - testWindow.webContents.on('dom-ready', () => - { - windowAux.bindDevToolsShortcut(window); - window.dispatchEvent(devToolsShortcut); - }); - testWindow.webContents.on('did-fail-load', (event, code, desc, url, isMainFrame) => - { - console.log('did-fail-load: ', event, code, desc, url, isMainFrame); - }); - - await new Promise(r => setTimeout(r, timeoutValue)); - expect(testWindow.webContents.isDevToolsOpened()).toBeTruthy(); - }); - - test('Bind: bad shortcut, should not open devTools', async() => - { - const testWindow = new BrowserWindow(browserWindowOptions); - testWindow.loadURL(mockHtmlPath); - expect(testWindow.webContents.isDevToolsOpened()).not.toBeTruthy(); - - testWindow.webContents.on('dom-ready', () => - { - windowAux.bindDevToolsShortcut(window); - window.dispatchEvent(badDevToolsShortcut); - }); - testWindow.webContents.on('did-fail-load', (event, code, desc, url, isMainFrame) => - { - console.log('did-fail-load: ', event, code, desc, url, isMainFrame); - }); - - await new Promise(r => setTimeout(r, timeoutValue)); - expect(testWindow.webContents.isDevToolsOpened()).not.toBeTruthy(); - }); - }); - - describe('showDialog(options, successCallback)', function() - { - test('Does not crash', async() => { const testWindow = new BrowserWindow(browserWindowOptions); @@ -96,12 +99,12 @@ describe('window-aux.js Testing', function() let spy; testWindow.webContents.on('dom-ready', () => { - spy = jest.spyOn(windowAux, 'showDialog'); + spy = jest.spyOn(windowAux, 'showDialogSync'); const options = { title: 'Time to Leave', }; - windowAux.showDialog(options, () => + windowAux.showDialogSync(options, () => { return; }); @@ -121,7 +124,6 @@ describe('window-aux.js Testing', function() describe('showAlert(message)', function() { - test('Does not crash', async() => { const testWindow = new BrowserWindow(browserWindowOptions); @@ -130,10 +132,7 @@ describe('window-aux.js Testing', function() let spy; testWindow.webContents.on('dom-ready', () => { - const { dialog } = require('electron').remote; - - spy = jest.spyOn(dialog, 'showMessageBoxSync').mockImplementation(() => {}); - + spy = jest.spyOn(windowAux, 'showAlert'); windowAux.showAlert('Test showAlert'); }); testWindow.webContents.on('did-fail-load', (event, code, desc, url, isMainFrame) => diff --git a/__tests__/__renderer__/workday-waiver-aux.js b/__tests__/__renderer__/workday-waiver-aux.js index c0e5c3ead..285023d0e 100644 --- a/__tests__/__renderer__/workday-waiver-aux.js +++ b/__tests__/__renderer__/workday-waiver-aux.js @@ -1,7 +1,7 @@ /* eslint-disable no-undef */ 'use strict'; -import { formatDayId, displayWaiverWindow } from '../../js/workday-waiver-aux.js'; +import { formatDayId, displayWaiverWindow } from '../../renderer/workday-waiver-aux.js'; describe('Workday Waiver Aux', function() { diff --git a/__tests__/__renderer__/workday-waiver.js b/__tests__/__renderer__/workday-waiver.js index cd604cb2e..95ad65781 100644 --- a/__tests__/__renderer__/workday-waiver.js +++ b/__tests__/__renderer__/workday-waiver.js @@ -28,39 +28,102 @@ const { initializeHolidayInfo, refreshDataForTest } = require('../../src/workday-waiver'); -import { showDialog } from '../../js/window-aux.js'; +import { showDialogSync } from '../../js/window-aux.js'; +const { workdayWaiverApi } = require('../../renderer/preload-scripts/workday-waiver-api.js'); +const { + getAllHolidays, + getCountries, + getRegions, + getStates +} = require('../../main/workday-waiver-aux.js'); jest.mock('../../renderer/i18n-translator.js', () => ({ translatePage: jest.fn().mockReturnThis(), getTranslationInLanguageData: jest.fn().mockReturnThis() })); +const waiverStore = new Store({name: 'waived-workdays'}); + +// APIs from the preload script of the workday waiver window +window.mainApi = workdayWaiverApi; + +// Mocking with the actual access to store that main would have +window.mainApi.getWaiverStoreContents = () => { return new Promise((resolve) => resolve(waiverStore.store)); }; +window.mainApi.setWaiver = (key, contents) => +{ + return new Promise((resolve) => + { + waiverStore.set(key, contents); + resolve(true); + }); +}; +window.mainApi.hasWaiver = (key) => { return new Promise((resolve) => resolve(waiverStore.has(key))); }; +window.mainApi.deleteWaiver = (key) => +{ + return new Promise((resolve) => + { + waiverStore.delete(key); + resolve(true); + }); +}; + +window.mainApi.getHolidays = (country, state, city, year) => +{ + return new Promise((resolve) => + { + resolve(getAllHolidays(country, state, city, year)); + }); +}; + +window.mainApi.getCountries = () => +{ + return new Promise((resolve) => + { + resolve(getCountries()); + }); +}; + +window.mainApi.getStates = (country) => +{ + return new Promise((resolve) => + { + resolve(getStates(country)); + }); +}; + +window.mainApi.getRegions = (country, state) => +{ + return new Promise((resolve) => + { + resolve(getRegions(country, state)); + }); +}; + const languageData = {'language': 'en', 'data': {'dummy_string': 'dummy_string_translated'}}; -function prepareMockup() +async function prepareMockup() { - const waivedWorkdays = new Store({ name: 'waived-workdays' }); - waivedWorkdays.clear(); + waiverStore.clear(); const workdayWaiverHtml = path.join(__dirname, '../../src/workday-waiver.html'); const content = fs.readFileSync(workdayWaiverHtml); const parser = new DOMParser(); const htmlDoc = parser.parseFromString(content, 'text/html'); document.body.innerHTML = htmlDoc.body.innerHTML; - populateList(); + await populateList(); refreshDataForTest(languageData); } -function addTestWaiver(day, reason) +async function addTestWaiver(day, reason) { $('#reason').val(reason); setDates(day); - setHours(); + setHours('08:00'); return addWaiver(); } -function testWaiverCount(expected) +async function testWaiverCount(expected) { - const waivedWorkdays = new Store({ name: 'waived-workdays' }); + const waivedWorkdays = await window.mainApi.getWaiverStoreContents(); expect(waivedWorkdays.size).toBe(expected); expect($('#waiver-list-table tbody')[0].rows.length).toBe(expected); } @@ -73,10 +136,9 @@ describe('Test Workday Waiver Window', function() describe('Adding new waivers update the db and the page', function() { - - beforeEach(() => + beforeEach(async() => { - prepareMockup(); + await prepareMockup(); }); test('One Waiver', () => @@ -86,7 +148,6 @@ describe('Test Workday Waiver Window', function() testWaiverCount(1); }); - test('One + two Waivers', () => { //Start with none @@ -128,32 +189,32 @@ describe('Test Workday Waiver Window', function() expect(isSorted).toBe(true); }); - test('Time is not valid', () => + test('Time is not valid', async() => { $('#hours').val('not a time'); - const waiver = addWaiver(); + const waiver = await addWaiver(); expect(waiver).toBeFalsy(); }); - test('End date less than start date', () => + test('End date less than start date', async() => { - setHours(); + setHours('08:00'); $('#start-date').val('2020-07-20'); $('#end-date').val('2020-07-19'); - const waiver = addWaiver(); + const waiver = await addWaiver(); expect(waiver).toBeFalsy(); }); - test('Add waiver with the same date', () => + test('Add waiver with the same date', async() => { addTestWaiver('2020-07-16', 'some reason'); - const waiver = addTestWaiver('2020-07-16', 'some reason'); + const waiver = await addTestWaiver('2020-07-16', 'some reason'); expect(waiver).toBeFalsy(); }); - test('Range does not contain any working day', () => + test('Range does not contain any working day', async() => { - const waiver = addTestWaiver('2020-13-01', 'some reason'); + const waiver = await addTestWaiver('2020-13-01', 'some reason'); expect(waiver).toBeFalsy(); }); }); @@ -197,12 +258,12 @@ describe('Test Workday Waiver Window', function() describe('Delete waiver', () => { - test('Waiver was deleted', () => + test('Waiver was deleted', async() => { - prepareMockup(); + await prepareMockup(); addTestWaiver('2020-07-16', 'some reason'); const deleteBtn = document.querySelectorAll('#waiver-list-table .delete-btn')[0]; - showDialog.mockImplementation((options, cb) => + showDialogSync.mockImplementation((options, cb) => { cb({ response: 0 }); }); @@ -216,52 +277,52 @@ describe('Test Workday Waiver Window', function() { const hd = new Holidays(); - beforeEach(() => + beforeEach(async() => { - prepareMockup(); + await prepareMockup(); }); - test('Country was populated', () => + test('Country was populated', async() => { - const countiesLength = Object.keys(hd.getCountries()).length; + const countriesLength = Object.keys(hd.getCountries()).length; expect($('#country option').length).toBe(0); - populateCountry(); - expect($('#country option').length).toBe(countiesLength + 1); + await populateCountry(); + expect($('#country option').length).toBe(countriesLength + 1); }); - test('States was populated', () => + test('States was populated', async() => { const statesLength = Object.keys(hd.getStates('US')).length; expect($('#state option').length).toBe(0); - populateState('US'); + await populateState('US'); expect($('#state option').length).toBe(statesLength + 1); expect($('#state').css('display')).toBe('inline-block'); expect($('#holiday-state').css('display')).toBe('table-row'); }); - test('States was not populated', () => + test('States was not populated', async() => { expect($('#state option').length).toBe(0); - populateState('CN'); + await populateState('CN'); expect($('#state option').length).toBe(0); expect($('#state').css('display')).toBe('none'); expect($('#holiday-state').css('display')).toBe('none'); }); - test('City was populated', () => + test('City was populated', async() => { const regionsLength = Object.keys(hd.getRegions('US', 'CA')).length; expect($('#city option').length).toBe(0); - populateCity('US', 'CA'); + await populateCity('US', 'CA'); expect($('#city option').length).toBe(regionsLength + 1); expect($('#city').css('display')).toBe('inline-block'); expect($('#holiday-city').css('display')).toBe('table-row'); }); - test('City was not populated', () => + test('City was not populated', async() => { expect($('#city option').length).toBe(0); - populateCity('US', 'AL'); + await populateCity('US', 'AL'); expect($('#city option').length).toBe(0); expect($('#city').css('display')).toBe('none'); expect($('#holiday-city').css('display')).toBe('none'); @@ -288,38 +349,41 @@ describe('Test Workday Waiver Window', function() const state = 'CA'; const city = 'LA'; - beforeEach(() => + beforeEach(async() => { - prepareMockup(); + await prepareMockup(); }); - test('Get holidays with no country', () => + test('Get holidays with no country', async() => { $('#year').append($('').val(year).html(year)); expect($('#year option').length).toBe(1); - expect(getHolidays()).toEqual([]); + const holidays = await getHolidays(); + expect(holidays).toEqual([]); }); - test('Get country holidays', () => + test('Get country holidays', async() => { $('#year').append($('').val(year).html(year)); $('#country').append($('').val(country).html(country)); expect($('#country option').length).toBe(1); hd.init(country); - expect(getHolidays()).toEqual(hd.getHolidays(year)); + const holidays = await getHolidays(); + expect(holidays).toEqual(hd.getHolidays(year)); }); - test('Get country with state holidays', () => + test('Get country with state holidays', async() => { $('#year').append($('').val(year).html(year)); $('#country').append($('').val(country).html(country)); $('#state').append($('').val(state).html(state)); expect($('#state option').length).toBe(1); hd.init(country, state); - expect(getHolidays()).toEqual(hd.getHolidays(year)); + const holidays = await getHolidays(); + expect(holidays).toEqual(hd.getHolidays(year)); }); - test('Get country with state and city holidays', () => + test('Get country with state and city holidays', async() => { $('#year').append($('').val(year).html(year)); $('#country').append($('').val(country).html(country)); @@ -327,7 +391,8 @@ describe('Test Workday Waiver Window', function() $('#city').append($('').val(city).html(city)); expect($('#state option').length).toBe(1); hd.init(country, state, city); - expect(getHolidays()).toEqual(hd.getHolidays(year)); + const holidays = await getHolidays(); + expect(holidays).toEqual(hd.getHolidays(year)); }); }); @@ -337,40 +402,42 @@ describe('Test Workday Waiver Window', function() const country = 'US'; const state = 'CA'; - beforeEach(() => + beforeEach(async() => { - prepareMockup(); + await prepareMockup(); }); - test('Iterate on holidays', () => + test('Iterate on holidays', async() => { $('#year').append($('').val(year).html(year)); $('#country').append($('').val(country).html(country)); $('#state').append($('').val(state).html(state)); - const holidaysLength = getHolidays().length; + const holidays = await getHolidays(); + const holidaysLength = holidays.length; const mockCallback = jest.fn(); - iterateOnHolidays(mockCallback); + await iterateOnHolidays(mockCallback); expect(mockCallback).toBeCalledTimes(holidaysLength); }); - test('Load holidays table', () => + test('Load holidays table', async() => { $('#year').append($('').val(year).html(year)); $('#country').append($('').val(country).html(country)); $('#state').append($('').val(state).html(state)); - loadHolidaysTable(); - const holidaysLength = getHolidays().length; + await loadHolidaysTable(); + const holidays = await getHolidays(); + const holidaysLength = holidays.length; const rowLength = $('#holiday-list-table tbody tr').length; expect($('#holiday-list-table').css('display')).toBe('table'); expect(holidaysLength).toBe(rowLength); }); - test('Holiday info initialize', () => + test('Holiday info initialize', async() => { $('#year').append($('').val(year).html(year)); $('#country').append($('').val(country).html(country)); $('#state').append($('').val(state).html(state)); - initializeHolidayInfo(); + await initializeHolidayInfo(); expect($('#holiday-list-table').css('display')).toBe('none'); expect($('#state').css('display')).toBe('none'); expect($('#holiday-state').css('display')).toBe('none'); @@ -381,9 +448,9 @@ describe('Test Workday Waiver Window', function() describe('Add holiday to list', () => { - beforeEach(() => + beforeEach(async() => { - prepareMockup(); + await prepareMockup(); }); test('Holiday added working day, no conflicts', () => @@ -410,9 +477,9 @@ describe('Test Workday Waiver Window', function() describe('Clearing the table', () => { - beforeEach(() => + beforeEach(async() => { - prepareMockup(); + await prepareMockup(); addTestWaiver('2020-07-20', 'some other reason'); addTestWaiver('2020-07-21', 'yet another reason'); addHolidayToList('test day', 'no reason'); diff --git a/esm-main.js b/esm-main.js index 1e234b7e1..c41c0d069 100644 --- a/esm-main.js +++ b/esm-main.js @@ -7,6 +7,8 @@ const { createNotification } = require('./js/notification'); const { openWaiverManagerWindow } = require('./js/windows.js'); const { setupI18n, getCurrentTranslation, setLanguageChangedCallback } = require('./src/configs/i18next.config.js'); const { handleSquirrelEvent } = require('./js/squirrel.js'); +const { showAlert, showDialogSync } = require('./js/window-aux.js'); + import { appConfig } from './js/app-config.js'; if (appConfig.win32) @@ -25,6 +27,11 @@ ipcMain.on('SET_WAIVER_DAY', (event, waiverDay) => openWaiverManagerWindow(mainWindow); }); +ipcMain.handle('GET_WAIVER_DAY', () => +{ + return global.waiverDay; +}); + ipcMain.handle('USER_DATA_PATH', () => { return new Promise((resolve) => @@ -33,6 +40,16 @@ ipcMain.handle('USER_DATA_PATH', () => }); }); +ipcMain.on('SHOW_ALERT', (event, alertMessage) => +{ + showAlert(alertMessage); +}); + +ipcMain.handle('SHOW_DIALOG', (event, dialogOptions) => +{ + return showDialogSync(dialogOptions); +}); + let launchDate = new Date(); // Logic for recommending user to punch in when they've been idle for too long diff --git a/index.js b/index.js new file mode 100644 index 000000000..a42ded612 --- /dev/null +++ b/index.js @@ -0,0 +1,10 @@ +/*eslint-disable no-global-assign*/ +// File used by index.html, which loads the main window of TTL +'use strict'; + +// Using esm module to be able to mix node 'require' and ES6 'import' statements +// while we don't move to a newer electron+node system that has this by default +// See https://github.com/electron/electron/issues/21457. + +require = require('esm')(module); +require('electron'); diff --git a/js/classes/BaseCalendar.js b/js/classes/BaseCalendar.js index 9810b72a0..c4fd22c4e 100644 --- a/js/classes/BaseCalendar.js +++ b/js/classes/BaseCalendar.js @@ -13,7 +13,7 @@ import { import { formatDayId, displayWaiverWindow -} from '../workday-waiver-aux.js'; +} from '../../renderer/workday-waiver-aux.js'; import { showDay, switchCalendarView } from '../user-preferences.js'; import { getDateStr, getMonthLength } from '../date-aux.js'; import { computeAllTimeBalanceUntilAsync } from '../time-balance.js'; diff --git a/js/classes/FlexibleDayCalendar.js b/js/classes/FlexibleDayCalendar.js index 767ac68bc..f2be28aa6 100644 --- a/js/classes/FlexibleDayCalendar.js +++ b/js/classes/FlexibleDayCalendar.js @@ -9,9 +9,29 @@ import { } from '../time-math.js'; import { getDateStr, getMonthLength } from '../date-aux.js'; import { generateKey } from '../date-db-formatter.js'; -import { showDialog } from '../window-aux.js'; import { BaseCalendar } from './BaseCalendar.js'; +/// Compatiblity block - to be removed in the migration of calendar to non-remote electron +const { remote } = require('electron'); +const { BrowserWindow, dialog } = remote; + +/** + * Opens an electron dialog, based on the options, and performs the successCallback after promise is resolved. + * @param {Object.} options + * @param {function} successCallback + */ + function showDialog(options, successCallback) + { + options['title'] = options['title'] || 'Time to Leave'; + dialog.showMessageBox(BrowserWindow.getFocusedWindow(), options) + .then(successCallback) + .catch(err => + { + console.log(err); + }); + } +//// + class FlexibleDayCalendar extends BaseCalendar { /** diff --git a/js/classes/FlexibleMonthCalendar.js b/js/classes/FlexibleMonthCalendar.js index 79488143f..8bf71c71a 100644 --- a/js/classes/FlexibleMonthCalendar.js +++ b/js/classes/FlexibleMonthCalendar.js @@ -12,11 +12,31 @@ import { generateKey } from '../date-db-formatter.js'; import { formatDayId, displayWaiverWindow -} from '../workday-waiver-aux.js'; -import { showDialog } from '../window-aux.js'; +} from '../../renderer/workday-waiver-aux.js'; import { getMonthName, getDayAbbr } from '../date-to-string-util.js'; import { BaseCalendar } from './BaseCalendar.js'; +/// Compatiblity block - to be removed in the migration of calendar to non-remote electron +const { remote } = require('electron'); +const { BrowserWindow, dialog } = remote; + +/** + * Opens an electron dialog, based on the options, and performs the successCallback after promise is resolved. + * @param {Object.} options + * @param {function} successCallback + */ + function showDialog(options, successCallback) + { + options['title'] = options['title'] || 'Time to Leave'; + dialog.showMessageBox(BrowserWindow.getFocusedWindow(), options) + .then(successCallback) + .catch(err => + { + console.log(err); + }); + } +//// + class FlexibleMonthCalendar extends BaseCalendar { /** diff --git a/js/window-aux.js b/js/window-aux.js index 9f7e99199..6cfb33f0a 100644 --- a/js/window-aux.js +++ b/js/window-aux.js @@ -1,39 +1,18 @@ 'use strict'; -const { remote } = require('electron'); -const { BrowserWindow, dialog } = remote; +const electron = require('electron'); +const BrowserWindow = (electron || electron.remote).BrowserWindow; +const dialog = (electron || electron.remote).dialog; /** - * Binds to the JS "window" the shortcut CTRL+SHIFT+I to toggle Chrome Dev Tools. - * @param {Window} window - */ -function bindDevToolsShortcut(window) -{ - window.addEventListener('keyup', (event) => - { - if (event.ctrlKey && event.shiftKey && (event.keyCode === 73 || event.keyCode === 105)) - { // 'i' or 'I' - BrowserWindow.getFocusedWindow().webContents.toggleDevTools(); - event.preventDefault(); - return false; - } - }, true); -} - -/** - * Opens an electron dialog, based on the options, and performs the successCallback after promise is resolved. + * Opens an electron dialog, based on the options, and returns the promise. * @param {Object.} options - * @param {function} successCallback + * @return {Promise} */ -function showDialog(options, successCallback) +function showDialogSync(options) { options['title'] = options['title'] || 'Time to Leave'; - dialog.showMessageBox(BrowserWindow.getFocusedWindow(), options) - .then(successCallback) - .catch(err => - { - console.log(err); - }); + return dialog.showMessageBox(BrowserWindow.getFocusedWindow(), options) } /** @@ -50,7 +29,6 @@ function showAlert(message) } export { - bindDevToolsShortcut, showAlert, - showDialog + showDialogSync }; diff --git a/js/windows.js b/js/windows.js index 5f4dc186b..1aa77b05b 100644 --- a/js/windows.js +++ b/js/windows.js @@ -35,8 +35,9 @@ function openWaiverManagerWindow(mainWindow, event) resizable: true, icon: appConfig.iconpath, webPreferences: { - enableRemoteModule: true, - nodeIntegration: true + nodeIntegration: true, + preload: path.join(__dirname, '../renderer/preload-scripts/workday-waiver-bridge.js'), + contextIsolation: true } }); waiverWindow.setMenu(null); waiverWindow.loadURL(htmlPath); @@ -46,6 +47,13 @@ function openWaiverManagerWindow(mainWindow, event) waiverWindow = null; mainWindow.webContents.send('WAIVER_SAVED'); }); + waiverWindow.webContents.on('before-input-event', (event, input) => + { + if (input.control && input.shift && input.key.toLowerCase() === 'i') + { + BrowserWindow.getFocusedWindow().webContents.toggleDevTools(); + } + }); } /** diff --git a/main.js b/main.js index f3d9d7250..b149c689d 100644 --- a/main.js +++ b/main.js @@ -2,6 +2,11 @@ /*eslint-disable no-global-assign*/ 'use strict'; +// Executing the code from this file. +// It has dependencies that must be run without the esm module +const { setupWorkdayWaiverHandlers } = require('./main/workday-waiver-aux.js'); +setupWorkdayWaiverHandlers(); + // Using esm module to be able to mix node 'require' and ES6 'import' statements // while we don't move to a newer electron+node system that has this by default // See https://github.com/electron/electron/issues/21457. diff --git a/main/workday-waiver-aux.js b/main/workday-waiver-aux.js new file mode 100644 index 000000000..9311a08fd --- /dev/null +++ b/main/workday-waiver-aux.js @@ -0,0 +1,122 @@ +'use strict'; + +// This file intentionally uses 'require' and is used without esm +// because 'date-holidays' doesn't play nice with esm + +const { ipcMain } = require('electron'); + +const Store = require('electron-store'); +const waiverStore = new Store({name: 'waived-workdays'}); + +const Holidays = require('date-holidays'); +const hd = new Holidays(); + +// Waiver Store handlers + +function getWaiverStore() +{ + return waiverStore.store; +} + +function setupWorkdayWaiverStoreHandlers() +{ + ipcMain.handle('GET_WAIVER_STORE_CONTENTS', () => + { + return getWaiverStore(); + }); + + ipcMain.handle('SET_WAIVER', (_event, key, contents) => + { + waiverStore.set(key, contents); + return true; + }); + + ipcMain.handle('HAS_WAIVER', (_event, key) => + { + return waiverStore.has(key); + }); + + ipcMain.handle('DELETE_WAIVER', (_event, key) => + { + waiverStore.delete(key); + return true; + }); +} + +// Holiday handlers + +function InitHolidays(country, state, city) +{ + if (state !== undefined && city !== undefined) + { + hd.init(country, state, city); + } + else if (state !== undefined && state !== '--' ) + { + hd.init(country, state); + } + else + { + hd.init(country); + } +} + +function getAllHolidays(country, state, city, year) +{ + InitHolidays(country, state, city); + return hd.getHolidays(year); +} + +function getCountries() +{ + return hd.getCountries(); +} + +function getStates(country) +{ + return hd.getStates(country); +} + +function getRegions(country, state) +{ + return hd.getRegions(country, state); +} + +function setupWorkdayHolidaysHandlers() +{ + ipcMain.handle('GET_HOLIDAYS', (_event, country, state, city, year) => + { + return getAllHolidays(country, state, city, year); + }); + + ipcMain.handle('GET_COUNTRIES', () => + { + return getCountries(); + }); + + ipcMain.handle('GET_STATES', (_event, country) => + { + return getStates(country); + }); + + ipcMain.handle('GET_REGIONS', (_event, country, state) => + { + return getRegions(country, state); + }); +} + +// While it's possible to just run these on require and not need the extra function only to set up, we +// have it so they don't run on the tests, which won't include ipcMain and would fail +function setupWorkdayWaiverHandlers() +{ + setupWorkdayWaiverStoreHandlers(); + setupWorkdayHolidaysHandlers(); +} + +module.exports = { + getAllHolidays, + getCountries, + getRegions, + getStates, + setupWorkdayWaiverHandlers +}; diff --git a/renderer/preload-scripts/esm-workday-waiver-bridge.js b/renderer/preload-scripts/esm-workday-waiver-bridge.js new file mode 100644 index 000000000..8f33e1682 --- /dev/null +++ b/renderer/preload-scripts/esm-workday-waiver-bridge.js @@ -0,0 +1,8 @@ +'use strict'; + +const { contextBridge } = require('electron'); +const { workdayWaiverApi } = require('./workday-waiver-api.js'); + +contextBridge.exposeInMainWorld( + 'mainApi', workdayWaiverApi +); diff --git a/renderer/preload-scripts/workday-waiver-api.js b/renderer/preload-scripts/workday-waiver-api.js new file mode 100644 index 000000000..7a9ae3671 --- /dev/null +++ b/renderer/preload-scripts/workday-waiver-api.js @@ -0,0 +1,92 @@ +'use strict'; + +const { ipcRenderer } = require('electron'); +import * as config from '../../src/configs/app.config.js'; +import { getUserPreferencesPromise, showDay } from '../../js/user-preferences.js'; + +function getLanguageData() +{ + return ipcRenderer.invoke('GET_LANGUAGE_DATA'); +} + +function getWaiverDay() +{ + return ipcRenderer.invoke('GET_WAIVER_DAY'); +} + +function showAlert(alertMessage) +{ + ipcRenderer.send('SHOW_ALERT', alertMessage); +} + +function showDialogSync(dialogOptions) +{ + return ipcRenderer.invoke('SHOW_DIALOG', dialogOptions); +} + +function showDayByPreferences(year, month, day, preferences) +{ + return showDay(year, month, day, preferences); +} + +function getWaiverStoreContents() +{ + return ipcRenderer.invoke('GET_WAIVER_STORE_CONTENTS'); +} + +function setWaiver(key, contents) +{ + return ipcRenderer.invoke('SET_WAIVER', key, contents); +} + +function hasWaiver(key) +{ + return ipcRenderer.invoke('HAS_WAIVER', key); +} + +function deleteWaiver(key) +{ + return ipcRenderer.invoke('DELETE_WAIVER', key); +} + +function getHolidays(country, state, city, year) +{ + return ipcRenderer.invoke('GET_HOLIDAYS', country, state, city, year); +} + +function getCountries() +{ + return ipcRenderer.invoke('GET_COUNTRIES'); +} + +function getStates(country) +{ + return ipcRenderer.invoke('GET_STATES', country); +} + +function getRegions(country, state) +{ + return ipcRenderer.invoke('GET_REGIONS', country, state); +} + +const workdayWaiverApi = { + getLanguageMap: () => config.getLanguageMap(), + getUserPreferences: () => getUserPreferencesPromise(), + getLanguageData: () => getLanguageData(), + getWaiverDay: () => getWaiverDay(), + showAlert: (alertMessage) => showAlert(alertMessage), + showDialogSync: (dialogOptions) => showDialogSync(dialogOptions), + showDay: (year, month, day, userPreferences) => showDayByPreferences(year, month, day, userPreferences), + getHolidays: (country, state, city, year) => getHolidays(country, state, city, year), + getCountries: () => getCountries(), + getStates: (country) => getStates(country), + getRegions: (country, state) => getRegions(country, state), + getWaiverStoreContents: () => getWaiverStoreContents(), + setWaiver: (key, contents) => setWaiver(key, contents), + hasWaiver: (key) => hasWaiver(key), + deleteWaiver: (key) => deleteWaiver(key) +}; + +module.exports = { + workdayWaiverApi +}; diff --git a/renderer/preload-scripts/workday-waiver-bridge.js b/renderer/preload-scripts/workday-waiver-bridge.js new file mode 100644 index 000000000..409b9bc2b --- /dev/null +++ b/renderer/preload-scripts/workday-waiver-bridge.js @@ -0,0 +1,9 @@ +/*eslint-disable no-global-assign*/ +'use strict'; + +// Using esm module to be able to mix node 'require' and ES6 'import' statements +// while we don't move to a newer electron+node system that has this by default +// See https://github.com/electron/electron/issues/21457. + +require = require('esm')(module); +module.exports = require('./esm-workday-waiver-bridge.js'); diff --git a/js/workday-waiver-aux.js b/renderer/workday-waiver-aux.js similarity index 100% rename from js/workday-waiver-aux.js rename to renderer/workday-waiver-aux.js diff --git a/src/workday-waiver.html b/src/workday-waiver.html index 761936a8e..2610265de 100644 --- a/src/workday-waiver.html +++ b/src/workday-waiver.html @@ -6,7 +6,11 @@ - + + + diff --git a/src/workday-waiver.js b/src/workday-waiver.js index ce5be3ecf..46cdee0f4 100644 --- a/src/workday-waiver.js +++ b/src/workday-waiver.js @@ -1,23 +1,12 @@ 'use strict'; import { applyTheme } from '../renderer/themes.js'; - -const { ipcRenderer, remote } = require('electron'); -const Store = require('electron-store'); -const Holidays = require('date-holidays'); - -import { getUserPreferences, showDay } from '../js/user-preferences.js'; +import { getTranslationInLanguageData, translatePage } from '../renderer/i18n-translator.js'; import { validateTime, diffDays } from '../js/time-math.js'; import { getDateStr } from '../js/date-aux.js'; -import { bindDevToolsShortcut, showAlert, showDialog } from '../js/window-aux.js'; -import { getTranslationInLanguageData, translatePage } from '../renderer/i18n-translator.js'; - -const $ = require('jquery'); - -const waiverStore = new Store({name: 'waived-workdays'}); -const hd = new Holidays(); let languageData; +let userPreferences; function getTranslation(code) { @@ -35,10 +24,9 @@ function setDates(day) $('#end-date').val(day); } -function setHours() +function setHours(hoursPerDay) { - const usersStyles = getUserPreferences(); - $('#hours').val(usersStyles['hours-per-day']); + $('#hours').val(hoursPerDay); } function toggleAddButton(buttonName, state) @@ -90,14 +78,15 @@ function addRowToListTable(day, reason, hours) $('#'+ id).on('click', deleteEntryOnClick); } -function populateList() +async function populateList() { clearWaiverList(); - for (const elem of waiverStore) + const store = await window.mainApi.getWaiverStoreContents(); + for (const elem of Object.entries(store)) { - const date = elem[0], - reason = elem[1]['reason'], - hours = elem[1]['hours']; + const date = elem[0]; + const reason = elem[1]['reason']; + const hours = elem[1]['hours']; addRowToListTable(date, reason, hours); } sortTable(); @@ -108,7 +97,7 @@ function getDateFromISOStr(isoStr) return isoStr.split('-'); } -function addWaiver() +async function addWaiver() { const [startYear, startMonth, startDay] = getDateFromISOStr($('#start-date').val()); const [endYear, endMonth, endDay] = getDateFromISOStr($('#end-date').val()); @@ -128,7 +117,7 @@ function addWaiver() if (diff < 0) { - showAlert(getTranslation('$WorkdayWaiver.end-date-cannot-be-less')); + window.mainApi.showAlert(getTranslation('$WorkdayWaiver.end-date-cannot-be-less')); return false; } @@ -140,11 +129,12 @@ function addWaiver() const alreadyHaveWaiverStr = getTranslation('$WorkdayWaiver.already-have-waiver'); const removeWaiverStr = getTranslation('$WorkdayWaiver.remove-waiver'); const [tempYear, tempMonth, tempDay] = getDateFromISOStr(tempDateStr); - noWorkingDaysOnRange &= !showDay(tempYear, tempMonth-1, tempDay) && !waiverStore.has(tempDateStr); + const hasWaiver = await window.mainApi.hasWaiver(tempDateStr); + noWorkingDaysOnRange &= !window.mainApi.showDay(tempYear, tempMonth-1, tempDay, userPreferences) && !hasWaiver; - if (waiverStore.has(tempDateStr)) + if (hasWaiver) { - showAlert(`${alreadyHaveWaiverStr} ${tempDateStr}. ${removeWaiverStr}`); + window.mainApi.showAlert(`${alreadyHaveWaiverStr} ${tempDateStr}. ${removeWaiverStr}`); return false; } @@ -153,7 +143,7 @@ function addWaiver() if (noWorkingDaysOnRange) { - showAlert(getTranslation('$WorkdayWaiver.no-working-days-on-range')); + window.mainApi.showAlert(getTranslation('$WorkdayWaiver.no-working-days-on-range')); return false; } @@ -163,9 +153,10 @@ function addWaiver() { const tempDateStr = getDateStr(tempDate); const [tempYear, tempMonth, tempDay] = getDateFromISOStr(tempDateStr); - if (showDay(tempYear, tempMonth-1, tempDay) && !waiverStore.has(tempDateStr)) + const hasWaiver = await window.mainApi.hasWaiver(tempDateStr); + if (window.mainApi.showDay(tempYear, tempMonth-1, tempDay, userPreferences) && !hasWaiver) { - waiverStore.set(tempDateStr, { 'reason' : reason, 'hours' : hours }); + await window.mainApi.setWaiver(tempDateStr, { 'reason' : reason, 'hours' : hours }); addRowToListTable(tempDateStr, reason, hours); } tempDate.setDate(tempDate.getDate() + 1); @@ -185,37 +176,38 @@ function deleteEntryOnClick(event) const options = { title: 'Time to Leave', - message: `${deleteWaiverMessageStr} ${day} ?`, + message: `${deleteWaiverMessageStr} ${day}?`, type: 'info', buttons: [getTranslation('$WorkdayWaiver.yes'), getTranslation('$WorkdayWaiver.no')] }; - showDialog(options, (result) => + window.mainApi.showDialogSync(options).then(async(result) => { const buttonId = result.response; if (buttonId === 1) { return; } - waiverStore.delete(day); + await window.mainApi.deleteWaiver(day); const row = deleteButton.closest('tr'); row.remove(); }); } -function populateCountry() +async function populateCountry() { $('#country').empty(); $('#country').append($('').val('--').html('--')); - $.each(hd.getCountries(), function(i, p) + const countries = await window.mainApi.getCountries(); + $.each(countries, function(i, p) { $('#country').append($('').val(i).html(p)); }); } -function populateState(country) +async function populateState(country) { - const states = hd.getStates(country); + const states = await window.mainApi.getStates(country); if (states) { $('#state').empty(); @@ -233,9 +225,10 @@ function populateState(country) $('#holiday-state').hide(); } } -function populateCity(country, state) + +async function populateCity(country, state) { - const regions = hd.getRegions(country, state); + const regions = await window.mainApi.getRegions(country, state); if (regions) { $('#city').empty(); @@ -271,33 +264,21 @@ function populateYear() function getHolidays() { - const year = $('#year').find(':selected').val(), - country = $('#country').find(':selected') ? $('#country').find(':selected').val() : undefined, - state = $('#state').find(':selected') ? $('#state').find(':selected').val() : undefined, - city = $('#city').find(':selected') ? $('#city').find(':selected').val() : undefined; + const country = $('#country').find(':selected') ? $('#country').find(':selected').val() : undefined; if (country === undefined) { - return []; - } - if (state !== undefined && city !== undefined) - { - hd.init(country, state, city); - } - else if (state !== undefined && state !== '--' ) - { - hd.init(country, state); - } - else - { - hd.init(country); + return new Promise((resolve) => { return resolve([]); }); } - return hd.getHolidays(year); + const state = $('#state').find(':selected') ? $('#state').find(':selected').val() : undefined; + const city = $('#city').find(':selected') ? $('#city').find(':selected').val() : undefined; + const year = $('#year').find(':selected').val(); + return window.mainApi.getHolidays(country, state, city, year); } -function iterateOnHolidays(funct) +async function iterateOnHolidays(func) { - const holidays = getHolidays(); + const holidays = await getHolidays(); for (const holiday of holidays) { @@ -309,7 +290,7 @@ function iterateOnHolidays(funct) for (let i = 0; i <= diff; i++) { const tempDateStr = getDateStr(tempDate); - funct(tempDateStr, reason); + func(tempDateStr, reason); tempDate.setDate(tempDate.getDate() + 1); } } @@ -356,7 +337,7 @@ function clearTable(id) } } -function loadHolidaysTable() +async function loadHolidaysTable() { const holidays = getHolidays(); if (holidays.length === 0) @@ -367,45 +348,54 @@ function loadHolidaysTable() // Clear all rows before adding new ones clearHolidayTable(); - function addHoliday(holidayDate, holidayReason) + // Fill in reasons to check for conflicts + const store = await window.mainApi.getWaiverStoreContents(); + const reasonByDate = {}; + for (const elem of Object.entries(store)) + { + const date = elem[0]; + const reason = elem[1]['reason']; + reasonByDate[date] = reason; + } + + async function addHoliday(holidayDate, holidayReason) { const [tempYear, tempMonth, tempDay] = getDateFromISOStr(holidayDate); // Holiday returns month with 1-12 index, but showDay expects 0-11 - const workingDay = showDay(tempYear, tempMonth - 1, tempDay) ? getTranslation('$WorkdayWaiver.yes') : getTranslation('$WorkdayWaiver.no'); - const conflicts = waiverStore.get(holidayDate); - addHolidayToList(holidayDate, holidayReason, workingDay, conflicts ? conflicts['reason'] : ''); + const workingDay = window.mainApi.showDay(tempYear, tempMonth - 1, tempDay, userPreferences) ? getTranslation('$WorkdayWaiver.yes') : getTranslation('$WorkdayWaiver.no'); + addHolidayToList(holidayDate, holidayReason, workingDay, reasonByDate[holidayDate] ?? ''); } - iterateOnHolidays(addHoliday); + await iterateOnHolidays(addHoliday); // Show table and enable button $('#holiday-list-table').show(); toggleAddButton('holiday-button', true); } -function addHolidaysAsWaiver() +async function addHolidaysAsWaiver() { - function addHoliday(holidayDate, holidayReason) + async function addHoliday(holidayDate, holidayReason) { const importHoliday = $(`#import-${holidayDate}`)[0].checked; if (importHoliday) { - waiverStore.set(holidayDate, { 'reason' : holidayReason, 'hours' : '08:00' }); + await window.mainApi.setWaiver(holidayDate, { 'reason' : holidayReason, 'hours' : '08:00' }); addRowToListTable(holidayDate, holidayReason, '08:00'); sortTable(); } } - iterateOnHolidays(addHoliday); + await iterateOnHolidays(addHoliday); //clear data from table and return the configurations to default - initializeHolidayInfo(); - showAlert(getTranslation('$WorkdayWaiver.loaded-waivers-holidays')); + await initializeHolidayInfo(); + window.mainApi.showAlert(getTranslation('$WorkdayWaiver.loaded-waivers-holidays')); } -function initializeHolidayInfo() +async function initializeHolidayInfo() { toggleAddButton('holiday-button', false); populateYear(); - populateCountry(); + await populateCountry(); $('#holiday-list-table').hide(); $('#state').hide(); $('#holiday-state').hide(); @@ -417,58 +407,58 @@ function initializeHolidayInfo() clearHolidayTable(); } -$(() => +$(async() => { - const preferences = getUserPreferences(); - applyTheme(preferences.theme); + userPreferences = await window.mainApi.getUserPreferences(); + applyTheme(userPreferences.theme); - ipcRenderer.invoke('GET_LANGUAGE_DATA').then(data => - { - languageData = data; - - setDates(remote.getGlobal('waiverDay')); - setHours(); - toggleAddButton('waive-button', $('#reason').val()); + const waiverDay = await window.mainApi.getWaiverDay(); + languageData = await window.mainApi.getLanguageData(); - populateList(); + setDates(waiverDay); + setHours(userPreferences['hours-per-day']); + toggleAddButton('waive-button', $('#reason').val()); - $('#reason, #hours').on('input blur', () => - { - toggleAddButton('waive-button', $('#reason').val() && $('#hours')[0].checkValidity()); - }); + populateList(); - $('#waive-button').on('click', () => - { - addWaiver(); - }); + $('#reason, #hours').on('input blur', () => + { + toggleAddButton('waive-button', $('#reason').val() && $('#hours')[0].checkValidity()); + }); - $('#holiday-button').on('click', () => - { - addHolidaysAsWaiver(); - }); + $('#waive-button').on('click', () => + { + addWaiver(); + }); - initializeHolidayInfo(); - $('#country').on('change', function() - { - populateState($(this).find(':selected').val()); - loadHolidaysTable(); - }); - $('#state').on('change', function() - { - populateCity($('#country').find(':selected').val(), $(this).find(':selected').val()); - loadHolidaysTable(); - }); - $('#city').on('change', function() - { - loadHolidaysTable(); - }); + $('#holiday-button').on('click', async() => + { + await addHolidaysAsWaiver(); + }); - bindDevToolsShortcut(window); - translatePage(languageData.language, languageData.data, 'WorkdayWaiver'); + await initializeHolidayInfo(); + $('#country').on('change', async function() + { + $('#state').val([]); + $('#city').val([]); + await populateState($(this).find(':selected').val()); + loadHolidaysTable(); }); + $('#state').on('change', async function() + { + $('#city').val([]); + await populateCity($('#country').find(':selected').val(), $(this).find(':selected').val()); + loadHolidaysTable(); + }); + $('#city').on('change', function() + { + loadHolidaysTable(); + }); + + translatePage(languageData.language, languageData.data, 'WorkdayWaiver'); }); -module.exports = { +export { addHolidayToList, addWaiver, clearTable,