diff --git a/assets/template.html b/assets/template.html index de59c7f3a..f3fdae5bb 100644 --- a/assets/template.html +++ b/assets/template.html @@ -39,6 +39,7 @@ + @@ -97,7 +98,7 @@ --> - @@ -134,6 +134,16 @@ + + diff --git a/cypress/e2e/errorlog.cy.ts b/cypress/e2e/errorlog.cy.ts index c62790fb7..a5acb1e97 100644 --- a/cypress/e2e/errorlog.cy.ts +++ b/cypress/e2e/errorlog.cy.ts @@ -7,6 +7,8 @@ describe('test: error log', () => { beforeEach(() => { cy.visit('http://localhost:8080/editor.html?manifest=test'); cy.clearLocalStorage(); + + cy.get('#display-advanced').check({ force: true }); }); it('startup: error log should not be visible', () => { diff --git a/src/DisplayPanel/DisplayPanel.ts b/src/DisplayPanel/DisplayPanel.ts index 98b12b174..433456c8f 100644 --- a/src/DisplayPanel/DisplayPanel.ts +++ b/src/DisplayPanel/DisplayPanel.ts @@ -140,7 +140,7 @@ function displayControlsPanel (handleZoom: ZoomHandler): string { * A class that sets the content of the display panel to the right and * manages controls for viewing. */ -class DisplayPanel implements DisplayInterface { +export class DisplayPanel implements DisplayInterface { view: ViewInterface; meiClass: string; background: string; @@ -216,4 +216,21 @@ class DisplayPanel implements DisplayInterface { } } -export { DisplayPanel as default }; +export function updateDisplayAll (): void { + const displayAllBtn = document.querySelector('#display-all-btn'); + + const btns = document.querySelector('#display-single-container'); + const checkboxes = Array.from(btns.querySelectorAll('input[type=checkbox]')); + const allChecked = checkboxes.every(box => box.checked); + + if (allChecked) { + displayAllBtn.classList.add('selected'); + displayAllBtn.innerHTML = 'Hide All'; + } else { + displayAllBtn.classList.remove('selected'); + displayAllBtn.innerHTML = 'Display All'; + } + +} + +export default DisplayPanel; diff --git a/src/InfoModule.ts b/src/InfoModule.ts index c9ef5b246..b3d051c95 100644 --- a/src/InfoModule.ts +++ b/src/InfoModule.ts @@ -4,6 +4,7 @@ import NeonView from './NeonView'; import { InfoInterface } from './Interfaces'; import { Attributes } from './Types'; import { getSettings, setSettings } from './utils/LocalSettings'; +import { updateDisplayAll } from './DisplayPanel/DisplayPanel'; /** * Map of contours to neume names. @@ -29,12 +30,7 @@ function startInfoVisibility (): void { */ function updateInfoVisibility (): void { const neumeInfo = document.getElementById('neume_info'); - - const displayAllBtn = document.getElementById('display-all-btn'); const displayInfo = document.getElementById('displayInfo') as HTMLInputElement; - const displayBBoxes = document.getElementById('displayBBox') as HTMLInputElement; - const displayText = document.getElementById('displayText') as HTMLInputElement; - const displayErrLog = document.getElementById('display-errors') as HTMLInputElement; // save setting to localStorage setSettings({ displayInfo: displayInfo.checked }); @@ -43,22 +39,11 @@ function updateInfoVisibility (): void { neumeInfo.setAttribute('style', ''); // scroll neume info into view //neumeInfo.scrollIntoView({ behavior: 'smooth' }); - - // if this is the 3rd option to be checked (all three are selected), - // set "Display/Hide All" button to "Hide All". - if (displayInfo?.checked && displayBBoxes?.checked && - displayText?.checked && displayErrLog?.checked) { - displayAllBtn.classList.add('selected'); - displayAllBtn.innerHTML = 'Hide All'; - } } else { neumeInfo.setAttribute('style', 'display: none'); - // if "Display/Hide All" button is in "Hide All" mode, set it to "Display All" mode - if (displayAllBtn.classList.contains('selected')) { - displayAllBtn.classList.remove('selected'); - displayAllBtn.innerHTML = 'Display All'; - } } + + updateDisplayAll(); } /** diff --git a/src/NeonView.ts b/src/NeonView.ts index f97da87d9..290f5cad4 100644 --- a/src/NeonView.ts +++ b/src/NeonView.ts @@ -1,5 +1,4 @@ import NeonCore from './NeonCore'; -import * as Validation from './Validation'; import { parseManifest } from './utils/NeonManifest'; import setBody from './utils/template/Template'; import { ModalWindow } from './utils/ModalWindow'; @@ -13,9 +12,10 @@ import { TextViewInterface, ViewInterface } from './Interfaces'; -import { initErrorLog } from '../src/utils/ErrorLog'; -import { setSavedStatus, listenUnsavedChanges } from './utils/Unsaved'; -import LocalSettings, { getSettings } from './utils/LocalSettings'; +import ErrorLog from '../src/utils/ErrorLog'; +import Unsaved from './utils/Unsaved'; +import LocalSettings from './utils/LocalSettings'; +import AdvancedSettings from './utils/AdvancedSettings'; /** @@ -95,9 +95,9 @@ class NeonView { this.core = new NeonCore(this.manifest); this.info = new this.params.Info(this); this.modal = new ModalWindow(this); - Validation.init(this); // initialize validation - initErrorLog(); // initialize notifications logs - listenUnsavedChanges(); + + AdvancedSettings.init(this); + Unsaved.init(); this.setupEdit(this.params); return this.core.initDb(); @@ -123,7 +123,7 @@ class NeonView { * Redo an action performed on the current page (if there is one). */ redo (): Promise { - setSavedStatus(false); + Unsaved.setSavedStatus(false); return this.core.redo(this.view.getCurrentPageURI()); } @@ -131,7 +131,7 @@ class NeonView { * Undo the last action performed on the current page (if there is one). */ undo (): Promise { - setSavedStatus(false); + Unsaved.setSavedStatus(false); return this.core.undo(this.view.getCurrentPageURI()); } @@ -190,7 +190,7 @@ class NeonView { * Save the current state to the browser database. */ save (): Promise { - setSavedStatus(true); + Unsaved.setSavedStatus(true); return this.core.updateDatabase(); } @@ -219,10 +219,10 @@ class NeonView { /** * Get the page's MEI file as a string. - * @param pageNo - The identifying URI of the page. + * @param pageURI - The identifying URI of the page. */ - getPageMEI (pageNo: string): Promise { - return this.core.getMEI(pageNo); + getPageMEI (pageURI: string): Promise { + return this.core.getMEI(pageURI); } /** diff --git a/src/SingleView/SingleView.ts b/src/SingleView/SingleView.ts index f04dc348c..b6f85dcb3 100644 --- a/src/SingleView/SingleView.ts +++ b/src/SingleView/SingleView.ts @@ -1,6 +1,6 @@ import { updateHighlight, setOpacityFromSlider, setBgOpacityFromSlider } from '../DisplayPanel/DisplayControls'; import NeonView from '../NeonView'; -import DisplayPanel from '../DisplayPanel/DisplayPanel'; +import { DisplayPanel } from '../DisplayPanel/DisplayPanel'; import ZoomHandler from './Zoom'; import { ViewInterface, DisplayConstructable } from '../Interfaces'; diff --git a/src/TextView.ts b/src/TextView.ts index ca1a3334d..442814129 100644 --- a/src/TextView.ts +++ b/src/TextView.ts @@ -4,6 +4,7 @@ import { unselect } from './utils/SelectTools'; import { updateHighlight } from './DisplayPanel/DisplayControls'; import { TextViewInterface } from './Interfaces'; import { getSettings, setSettings } from './utils/LocalSettings'; +import { updateDisplayAll } from './DisplayPanel/DisplayPanel'; /* * Class that manages getting the text for syllables in Neon from the mei file @@ -74,12 +75,7 @@ class TextView implements TextViewInterface { * Update visibility of text bounding boxes */ updateBBoxViewVisibility (): void { - - const displayAllBtn = document.getElementById('display-all-btn'); - const displayInfo = document.getElementById('displayInfo') as HTMLInputElement; const displayBBoxes = document.getElementById('displayBBox') as HTMLInputElement; - const displayText = document.getElementById('displayText') as HTMLInputElement; - const displayErrLog = document.getElementById('display-errors') as HTMLInputElement; // save to localStorage setSettings({ displayBBox: displayBBoxes.checked }); @@ -95,14 +91,6 @@ class TextView implements TextViewInterface { if (this.neonView.getUserMode() !== 'viewer' && this.neonView.TextEdit !== undefined) { this.neonView.TextEdit.initSelectByBBoxButton(); } - - // if this is the 3rd option to be checked (all three are selected), - // set "Display/Hide All" button to "Hide All". - if (displayInfo?.checked && displayBBoxes?.checked && - displayText?.checked && displayErrLog?.checked) { - displayAllBtn.classList.add('selected'); - displayAllBtn.innerHTML = 'Hide All'; - } } else { if (document.getElementById('selByBBox')?.classList.contains('is-active')) { @@ -124,13 +112,9 @@ class TextView implements TextViewInterface { document.getElementById('selByBBox').style.display = 'none'; } catch (e) {} - - // if "Display/Hide All" button is in "Hide All" mode, set it to "Display All" mode - if (displayAllBtn.classList.contains('selected')) { - displayAllBtn.classList.remove('selected'); - displayAllBtn.innerHTML = 'Display All'; - } } + + updateDisplayAll(); updateHighlight(); } @@ -139,12 +123,7 @@ class TextView implements TextViewInterface { * and add the event listeners to make sure the syl highlights when moused over */ updateTextViewVisibility (): void { - - const displayAllBtn = document.getElementById('display-all-btn'); - const displayInfo = document.getElementById('displayInfo') as HTMLInputElement; - const displayBBoxes = document.getElementById('displayBBox') as HTMLInputElement; const displayText = document.getElementById('displayText') as HTMLInputElement; - const displayErrLog = document.getElementById('display-errors') as HTMLInputElement; // save to localStorage setSettings({ displayText: displayText.checked }); @@ -208,23 +187,13 @@ class TextView implements TextViewInterface { // scroll the syllable text bubble into view //sylText.scrollIntoView({ behavior: 'smooth' }); - - // if this is the 3rd option to be checked (all three are selected), - // set "Display/Hide All" button to "Hide All". - if (displayInfo?.checked && displayBBoxes?.checked && - displayText?.checked && displayErrLog?.checked) { - displayAllBtn.classList.add('selected'); - displayAllBtn.innerHTML = 'Hide All'; - } } else { document.getElementById('syl_text').style.display = 'none'; // if "Display/Hide All" button is in "Hide All" mode, set it to "Display All" mode - if (displayAllBtn.classList.contains('selected')) { - displayAllBtn.classList.remove('selected'); - displayAllBtn.innerHTML = 'Display All'; - } } + + updateDisplayAll(); } /** diff --git a/src/Validation.ts b/src/Validation.ts index 6a8b7f95e..a389136d5 100644 --- a/src/Validation.ts +++ b/src/Validation.ts @@ -1,5 +1,5 @@ -import NeonView from "./NeonView"; -import { ModalWindow, ModalWindowView } from "./utils/ModalWindow"; +import NeonView from './NeonView'; +import { ModalWindowView } from './utils/ModalWindow'; const schemaResponse = fetch(__ASSET_PREFIX__ + 'assets/mei-all.rng'); let worker: Worker, schema: string, statusField: HTMLSpanElement; @@ -47,7 +47,7 @@ export async function init (neonView: NeonView): Promise { if (fileStatusDiv !== null) { const statusTitle = document.createElement('div'); statusTitle.textContent = 'MEI Status:'; - statusTitle.id = "validation_status_title"; + statusTitle.id = 'validation_status_title'; const status = document.createElement('span'); status.id = 'validation_status'; status.textContent = 'unknown'; @@ -59,11 +59,23 @@ export async function init (neonView: NeonView): Promise { } } +async function stop (): Promise { + if (worker) { + worker.terminate(); + worker = null; + } + + const fileStatusDiv = document.getElementById('file-status'); + fileStatusDiv.innerHTML = ''; +} + /** * Send the contents of an MEI file to the WebWorker for validation. * @param {string} meiData */ export async function sendForValidation (meiData: string): Promise { + if (!worker) return; + if (statusField === undefined) { return; } @@ -89,3 +101,5 @@ export function blankPage (): void { statusField.textContent = 'No MEI'; statusField.style.color = 'color:gray'; } + +export default { init, stop, sendForValidation }; diff --git a/src/utils/AdvancedSettings.ts b/src/utils/AdvancedSettings.ts new file mode 100644 index 000000000..68b64050b --- /dev/null +++ b/src/utils/AdvancedSettings.ts @@ -0,0 +1,96 @@ +/* + `AdvancedSettings.ts` + Module that handles everything to do with advanced settings. + + Any file that imports `AdvancedSettings` can only access the `init` and `isEnabled` functions. + + I'm using the module design pattern, since that's what + JavaScript does internally with classes anyway. + + We keep track of NeonView since it's a hassle to pass it around all the time. +*/ + +import Validation from '../Validation'; +import { getSettings, setSettings } from './LocalSettings'; +import NeonView from '../NeonView'; +import ErrorLog from './ErrorLog'; +import { updateDisplayAll } from '../DisplayPanel/DisplayPanel'; + + +let view: NeonView; + +function init(neonView: NeonView): void { + view = neonView; + + const checkbox = document.querySelector('#display-advanced'); + + // Listen to click: + checkbox.addEventListener('click', handleClick); + + // Load from localStorage: + const { displayAdvanced } = getSettings(); + checkbox.checked = displayAdvanced; + + toggle(checkbox.checked); + updateDisplayAll(); +} + +function handleClick() { + const checkbox = document.querySelector('#display-advanced'); + + // Before turning on advanced settings, make the user have to confirm the decision + if (checkbox.checked) { + const confirmed = window.confirm( + 'This option is for developers and testers, and is not recommended for general users. ' + + 'Are you sure you wish to proceed?' + ); + + if (!confirmed) { + checkbox.checked = false; + return; + } + } + + toggle(checkbox.checked); + updateDisplayAll(); +} + +function isEnabled(): boolean { + return document.querySelector('#display-advanced').checked; +} + +function toggle(isChecked: boolean) { + setSettings({ displayAdvanced: isChecked }); + + if (isChecked) + enable(); + else + disable(); +} + +function $(query: string) { + return document.querySelector(query); +} + +async function enable() { + ErrorLog.init(); + await Validation.init(view); + + // Retrieve the page MEI as a trivial action to do + // to activate validation of the current page + const uri = view.view.getCurrentPageURI(); + const mei = await view.getPageMEI(uri); + await Validation.sendForValidation(mei); + + // Show MEI actions dropdown + $('#mei-actions-dropdown').style.display = ''; +} + +function disable() { + ErrorLog.stop(); + Validation.stop(); + + $('#mei-actions-dropdown').style.display = 'none'; +} + +export default { init, isEnabled }; diff --git a/src/utils/EditContents.ts b/src/utils/EditContents.ts index ca3fad585..b932ab57c 100644 --- a/src/utils/EditContents.ts +++ b/src/utils/EditContents.ts @@ -34,14 +34,14 @@ export const navbarDropdownMEIActionsMenu: HTMLDivElement = document.createEleme navbarDropdownMEIActionsMenu.classList.add('navbar-item', 'has-dropdown', 'is-hoverable'); const meiActionsDropdownBtn = document.createElement('div'); meiActionsDropdownBtn.classList.add('navbar-btn'); -meiActionsDropdownBtn.innerHTML = `
MEI Actions
`; +meiActionsDropdownBtn.innerHTML = '
MEI Actions
'; const meiActionsNavbarContents = document.createElement('div'); meiActionsNavbarContents.classList.add('navbar-dropdown'); meiActionsNavbarContents.id = 'navbar-dropdown-options'; const meiActionsDropdownContents = [ ['remove-empty-syls', 'Remove Empty Syllables'], ['remove-empty-neumes', 'Remove Empty Neumes'], - ['revert', 'Revert'] + // ['revert', 'Revert'] ]; meiActionsDropdownContents.forEach(content => { diff --git a/src/utils/EditControls.ts b/src/utils/EditControls.ts index fafb8062a..77bf7feaf 100644 --- a/src/utils/EditControls.ts +++ b/src/utils/EditControls.ts @@ -2,13 +2,11 @@ import * as Notification from './Notification'; import NeonView from '../NeonView'; import { convertStaffToSb } from './ConvertMei'; import { EditorAction } from '../Types'; -import ZoomHandler from '../SingleView/Zoom'; /** * Set top navbar event listeners. */ export function initNavbar (neonView: NeonView): void { - // setup navbar listeners const navbarDropdowns = document.querySelectorAll('.navbar-item.has-dropdown.is-hoverable'); Array.from(navbarDropdowns).forEach((dropDown) => { @@ -235,7 +233,7 @@ export function initNavbar (neonView: NeonView): void { }); }); - // Event listener for "Revert" button inside "MEI Actions" dropdown + // Event listener for "Revert" button inside "File" dropdown document.getElementById('revert').addEventListener('click', function () { if (window.confirm('Reverting will cause all changes to be lost. Press OK to continue.')) { neonView.deleteDb().then(() => { diff --git a/src/utils/ErrorLog.ts b/src/utils/ErrorLog.ts index 97f46f70f..c6db533b2 100644 --- a/src/utils/ErrorLog.ts +++ b/src/utils/ErrorLog.ts @@ -1,10 +1,11 @@ import { Notification } from './Notification'; import { errorLogsPanelContents } from '../SquareEdit/Contents'; import { setSettings, getSettings } from './LocalSettings'; +import { updateDisplayAll } from '../DisplayPanel/DisplayPanel'; // TODO: styling -function createLogMessage (notif: Notification): Element { +function createLogMessage(notif: Notification): Element { const notifDiv = document.createElement('div'); notifDiv.classList.add('notification-container'); notifDiv.innerHTML = ` @@ -39,7 +40,7 @@ function createLogMessage (notif: Notification): Element { * Add notification to the persistent error log on the sidebar * @param notif {Notification} Notification to add to the error log */ -export function recordNotification (notif: Notification): void { +export function recordNotification(notif: Notification): void { const log = document.querySelector('#errorLogContents'); log.prepend(createLogMessage(notif)); } @@ -49,7 +50,7 @@ export function recordNotification (notif: Notification): void { * Initialize error log. * Adds necessary HTML to DOM. */ -export function initErrorLog(): void { +export function init(): void { // Error log panel const log = document.querySelector('#error_log'); log.innerHTML = errorLogsPanelContents; @@ -57,11 +58,18 @@ export function initErrorLog(): void { initErrorLogControls(); } +export function stop(): void { + // Hide the error log + document.querySelector('#error_log').classList.remove('visible'); + + // Hide the button + document.querySelector('#display-errors')?.parentElement?.remove(); +} /** * Set up event listeners for error Log panel */ -export function initErrorLogControls(): void { +function initErrorLogControls(): void { const errorPanel = document.querySelector('#error_log'); const heading = document.querySelector('#errorLogHeading'); const dropdownIcon = heading.querySelector('svg > use'); @@ -81,7 +89,7 @@ export function initErrorLogControls(): void { contents.style.overflow = 'visible'; }, 200); dropdownIcon.setAttribute('xlink:href', `${__ASSET_PREFIX__}assets/img/icons.svg#dropdown-down`); - } + } // if error panel is open, close it else { // set classes and styles for a closed panel @@ -97,58 +105,43 @@ export function initErrorLogControls(): void { initDisplayListener(); } +function handleClick(): void { + const notifPanel = document.querySelector('#error_log'); + const displayErrLog = document.getElementById('display-errors') as HTMLInputElement; + + if (displayErrLog.checked) { + notifPanel.classList.add('visible'); + setSettings({ displayErrLog: true }); + } + else { + notifPanel.classList.remove('visible'); + setSettings({ displayErrLog: false }); + } + + updateDisplayAll(); +} /** * Initializes click listener on "Show error logs" button in "View" dropdown. */ -export function initDisplayListener(): void { - const notifPanel = document.querySelector('#error_log'); +function initDisplayListener(): void { const checkboxesContainer = document.querySelector('#display-single-container'); - const errorsLabel = document.createElement('label'); - const erorrsBtn = document.createElement('input'); - - erorrsBtn.classList.add('checkbox'); - errorsLabel.classList.add('checkbox-container', 'side-panel-btn'); - errorsLabel.textContent = 'Errors'; - erorrsBtn.id = 'display-errors'; - erorrsBtn.type = 'checkbox'; - erorrsBtn.checked = false; - errorsLabel.appendChild(erorrsBtn); - checkboxesContainer.append(errorsLabel); - + const errorLabel = document.createElement('label'); + const errorBtn = document.createElement('input'); + + errorBtn.classList.add('checkbox'); + errorLabel.classList.add('checkbox-container', 'side-panel-btn'); + errorLabel.textContent = 'Errors'; + errorBtn.id = 'display-errors'; + errorBtn.type = 'checkbox'; + errorBtn.checked = false; + errorLabel.appendChild(errorBtn); + checkboxesContainer.append(errorLabel); const { displayErrLog } = getSettings(); - if (displayErrLog) erorrsBtn.checked = true; - - erorrsBtn.addEventListener('click', () => { + if (displayErrLog) errorBtn.checked = true; - // setSettings({ displayBBox: displayBBoxes.checked }); - - const displayAllBtn = document.getElementById('display-all-btn'); - const displayInfo = document.getElementById('displayInfo') as HTMLInputElement; - const displayBBoxes = document.getElementById('displayBBox') as HTMLInputElement; - const displayText = document.getElementById('displayText') as HTMLInputElement; - const displayErrLog = document.getElementById('display-errors') as HTMLInputElement; - - - if (erorrsBtn.checked) { - notifPanel.classList.add('visible'); - setSettings({ displayErrLog: true }); - - if (displayInfo?.checked && displayBBoxes?.checked && - displayText?.checked && displayErrLog?.checked) { - displayAllBtn.classList.add('selected'); - displayAllBtn.innerHTML = 'Hide All'; - } - } - else { - notifPanel.classList.remove('visible'); - setSettings({ displayErrLog: false }); - if (displayAllBtn.classList.contains('selected')) { - displayAllBtn.classList.remove('selected'); - displayAllBtn.innerHTML = 'Display All'; - } - } - - }); + errorBtn.addEventListener('click', handleClick); } + +export default { init, stop }; diff --git a/src/utils/LocalSettings.ts b/src/utils/LocalSettings.ts index d097f6435..9fb0c70f5 100644 --- a/src/utils/LocalSettings.ts +++ b/src/utils/LocalSettings.ts @@ -26,6 +26,7 @@ export interface Settings { displayText: boolean; displayInfo: boolean; displayErrLog: boolean; + displayAdvanced: boolean; viewBox: string; } @@ -46,6 +47,7 @@ const DEFAULT_SETTINGS: Settings = { displayText: false, displayInfo: false, displayErrLog: false, + displayAdvanced: false, viewBox: null }; diff --git a/src/utils/Notification.ts b/src/utils/Notification.ts index 21dc62f38..02b6c4552 100644 --- a/src/utils/Notification.ts +++ b/src/utils/Notification.ts @@ -1,3 +1,4 @@ +import AdvancedSettings from './AdvancedSettings'; import { recordNotification } from './ErrorLog'; import { uuidv4 } from './random'; @@ -132,7 +133,8 @@ export function queueNotification (notification: string, type: NotificationType const notif = new Notification(notification, type); notifications.push(notif); - if (notif.type == 'error' || notif.type == 'warning') { + // It would be better if we actually had a persistent store of Neon's notifications. + if ((notif.type == 'error' || notif.type == 'warning') && AdvancedSettings.isEnabled()) { recordNotification(notif); } diff --git a/src/utils/Unsaved.ts b/src/utils/Unsaved.ts index ba92420b8..16b93d862 100644 --- a/src/utils/Unsaved.ts +++ b/src/utils/Unsaved.ts @@ -1,14 +1,15 @@ - +// `Unsaved.ts` +// Module that handles all logic about unsaved changes +// // Initial saved status let saved = true; - /** * Update status of saved status indicator */ -function updateIndicator (): void { +function updateIndicator(): void { const indicator = document.querySelector('#file-saved'); - const path = saved? `${__ASSET_PREFIX__}assets/img/saved-icon.svg` : `${__ASSET_PREFIX__}assets/img/unsaved-icon.svg`; + const path = saved ? `${__ASSET_PREFIX__}assets/img/saved-icon.svg` : `${__ASSET_PREFIX__}assets/img/unsaved-icon.svg`; indicator.setAttribute('src', path); } @@ -18,7 +19,7 @@ function updateIndicator (): void { * * @param status boolean value. true if status is "saved", false if "not saved" */ -export function setSavedStatus (status: boolean = false): void { +export function setSavedStatus(status = false): void { saved = status; updateIndicator(); } @@ -27,11 +28,13 @@ export function setSavedStatus (status: boolean = false): void { /** * Function that defines event listener that checks for unsaved changes on page reload */ -export function listenUnsavedChanges (): void { +function init(): void { window.onbeforeunload = (e: BeforeUnloadEvent) => { if (!saved) { e.preventDefault(); return 'You have unsaved changes!'; } }; -} \ No newline at end of file +} + +export default { init, setSavedStatus };