diff --git a/assets/style.css b/assets/style.css
index 5c2b7b38..6a2f4227 100644
--- a/assets/style.css
+++ b/assets/style.css
@@ -60,7 +60,7 @@ a:hover {
}
.navbar-end {
display: flex;
- align-items: center;
+ flex-direction: row;
}
.navbar-main-content-container {
display: flex;
@@ -190,7 +190,7 @@ a:hover {
.navbar-dropdown {
border-top: 1px solid #7cb1b7;
box-shadow: 0px 7px 10px 2px #00000052;
- min-width: 13vw;
+ min-width: 15vw;
}
#default-zoom:hover #extendable-content {
display: block;
@@ -213,6 +213,80 @@ a:hover {
.dropdown-extendable-content-container:hover {
display: block;
}
+/* Styles for debug mode toggle switch */
+.debug-mode-container {
+ display: flex;
+ align-items: center;
+ color: #818181;
+}
+input[type=checkbox].debug-mode-toggle {
+ display: none;
+}
+input[type=checkbox].debug-mode-toggle + label {
+ display: inline-block;
+ height: 35px;
+ width: 70px;
+ position: relative;
+ border: 4px #68A7AD solid;
+ padding: 0;
+ margin: 0;
+ cursor: pointer;
+ box-sizing: border-box;
+ transition: all 0.3s ease;
+ border-radius: 35px;
+}
+input[type=checkbox].debug-mode-toggle + label::before {
+ position: absolute;
+ height: 25px;
+ width: 25px;
+ content: '';
+ transition: all 0.3s ease;
+ z-index: 3;
+ border-radius: 50%;
+}
+input[type=checkbox].debug-mode-toggle + label::after {
+ position: absolute;
+ width: 55px;
+ text-align: center;
+ z-index: 2;
+ text-transform: uppercase;
+ transform: translateY(-50%);
+ text-overflow: ellipsis;
+ overflow: hidden;
+ border-radius: 50%;
+ top: 50%;
+}
+input[type=checkbox].debug-mode-toggle:not(:checked) + label {
+ background-color: transparent;
+ text-align: right;
+}
+input[type=checkbox].debug-mode-toggle:not(:checked)+label:after {
+ content: attr(data-unchecked);
+ left: 15px;
+ opacity: 1;
+ color: #68A7AD;
+}
+input[type=checkbox].debug-mode-toggle:not(:checked) + label:before {
+ left: 1px;
+ top: 1px;
+ background-color: #68A7AD;
+}
+
+input[type=checkbox].debug-mode-toggle:checked + label {
+ background-color: #68A7AD;
+ text-align: left;
+}
+input[type=checkbox].debug-mode-toggle:checked+label:after {
+ content: attr(data-checked);
+ opacity: 1;
+ color: white;
+ right: 15px;
+}
+input[type=checkbox].debug-mode-toggle:checked+label:before {
+ left: 37px;
+ top: 1px;
+ background-color: white;
+}
/* Tov navbar styles styles for smaller devices */
@media screen and (max-width: 1023px) {
diff --git a/assets/template.html b/assets/template.html
index 736876cb..19ca9e86 100644
--- a/assets/template.html
+++ b/assets/template.html
@@ -22,7 +22,16 @@
-
+
+
+ MEI Version:
+
+
+
+ MEI Status:
+
+
+
@@ -45,58 +54,6 @@
-
-
@@ -139,6 +97,14 @@
+
+
Feedback Form
diff --git a/cypress/e2e/errorlog.cy.ts b/cypress/e2e/errorlog.cy.ts
index 2c7533b8..4df0a196 100644
--- a/cypress/e2e/errorlog.cy.ts
+++ b/cypress/e2e/errorlog.cy.ts
@@ -9,6 +9,7 @@ describe('test: error log', () => {
cy.clearLocalStorage();
cy.visit('http://localhost:8080/editor.html?manifest=test');
cy.get('#mei_output', { timeout: 10000 }).should('be.visible');
+ cy.get('#debug-mode-checkbox').click({ timeout: 100, force: true });
});
it('startup: error log should not be visible', () => {
diff --git a/src/DisplayPanel/DisplayControls.ts b/src/DisplayPanel/DisplayControls.ts
index ddb57e84..eae9e7c7 100644
--- a/src/DisplayPanel/DisplayControls.ts
+++ b/src/DisplayPanel/DisplayControls.ts
@@ -235,8 +235,7 @@ export function setBgOpacityFromSlider (background?: string): void {
}
/**
- * Set background image opacity button and slider listeners.
- * @param background - The background image selector.
+ * Set bbox adjustment circle size button and slider listeners.
*/
export function setCircleSizeControls (): void {
const circleSlider = document.getElementById('circleSlider') as HTMLInputElement;
@@ -461,7 +460,7 @@ function setDisplayAllListener(): void {
if (selectAllBtn.classList.contains('selected')) {
selectAllBtn.classList.remove('selected');
selectAllBtn.innerHTML = 'Display All';
- const options = document.querySelectorAll('.checkbox-container > .checkbox');
+ const options = document.querySelectorAll('.checkbox-container:not([style*="display: none;"]) > .checkbox');
Array.from(options).forEach((option: HTMLInputElement) => {
if (option.checked) option.click();
@@ -470,7 +469,7 @@ function setDisplayAllListener(): void {
else {
selectAllBtn.classList.add('selected');
selectAllBtn.innerHTML = 'Hide All';
- const options = document.querySelectorAll('.checkbox-container > .checkbox');
+ const options = document.querySelectorAll('.checkbox-container:not([style*="display: none;"]) > .checkbox');
Array.from(options).forEach((option: HTMLInputElement) => {
if (!option.checked) option.click();
diff --git a/src/NeonView.ts b/src/NeonView.ts
index 32378477..cd78c9ef 100644
--- a/src/NeonView.ts
+++ b/src/NeonView.ts
@@ -13,7 +13,7 @@ import {
TextViewInterface,
ViewInterface
} from './Interfaces';
-import { initErrorLog } from '../src/utils/ErrorLog';
+import { setDebugMode } from './utils/DebugMode';
import { setSavedStatus, listenUnsavedChanges } from './utils/Unsaved';
import LocalSettings, { getSettings } from './utils/LocalSettings';
@@ -109,7 +109,7 @@ class NeonView {
this.info = new this.params.Info(this);
this.modal = new ModalWindow(this);
Validation.init(this); // initialize validation
- initErrorLog(); // initialize notifications logs
+ setDebugMode();
listenUnsavedChanges();
this.setupEdit(this.params);
diff --git a/src/Validation.ts b/src/Validation.ts
index 26b18a46..fc147974 100644
--- a/src/Validation.ts
+++ b/src/Validation.ts
@@ -63,31 +63,13 @@ function statusOnClick(log: string) {
export async function init (neonView: NeonView): Promise {
const fileStatusDiv = document.getElementById('file-status');
if (fileStatusDiv !== null) {
- const meiVersionDiv = document.createElement('div');
- meiVersionDiv.id = 'mei_version_container';
- const meiVersionTitle = document.createElement('span');
- meiVersionTitle.classList.add('file_status_title');
- meiVersionTitle.textContent = 'MEI Version: ';
- const version = document.createElement('span');
- version.id = 'mei_version';
- version.textContent = 'unknown';
- meiVersionDiv.appendChild(meiVersionTitle);
- meiVersionDiv.appendChild(version);
- fileStatusDiv.appendChild(meiVersionDiv);
- versionField = document.getElementById('mei_version');
+ const meiVersion = document.getElementById('mei_version');
+ meiVersion.textContent = 'unknown';
+ versionField = meiVersion;
- const meiStatusDiv = document.createElement('div');
- meiStatusDiv.id = 'validation_status_container';
- const meiStatusTitle = document.createElement('span');
- meiStatusTitle.classList.add('file_status_title');
- meiStatusTitle.textContent = 'MEI Status: ';
- const status = document.createElement('span');
- status.id = 'validation_status';
- status.textContent = 'unknown';
- meiStatusDiv.appendChild(meiStatusTitle);
- meiStatusDiv.appendChild(status);
- fileStatusDiv.appendChild(meiStatusDiv);
- statusField = document.getElementById('validation_status');
+ const meiStatus = document.getElementById('validation_status');
+ meiStatus.textContent = 'unknown';
+ statusField = meiStatus;
worker = new Worker(__ASSET_PREFIX__ + 'workers/Worker.js');
worker.onmessage = updateStatusUI.bind(neonView);
}
diff --git a/src/utils/ConvertMei.ts b/src/utils/ConvertMei.ts
index 0e4b5871..0b93b2a7 100644
--- a/src/utils/ConvertMei.ts
+++ b/src/utils/ConvertMei.ts
@@ -230,10 +230,14 @@ export function convertToVerovio(sbBasedMei: string): string {
// Check syllable without neume
const syllables = Array.from(mei.getElementsByTagName('syllable'));
+ let hasEmptySyllable = false;
+ let hasEmptyNeume = false;
+ let emptySyllableInfo = 'The following syllable(s) have no neumes: \n\n';
+ let emptyNeumeInfo = 'The following neume(s) have no neume components: \n\n';
for (const syllable of syllables) {
if (syllable.getElementsByTagName('neume').length === 0) {
- const id = syllable.getAttribute('xml:id');
- Notification.queueNotification(`This file contains a syllable without neume!
ID: ${id}`, 'warning');
+ hasEmptySyllable = true;
+ emptySyllableInfo += `- <${syllable.tagName}> with xml:id: ${syllable.getAttribute('xml:id')}\n`;
}
// Check neume without neume component
@@ -241,8 +245,8 @@ export function convertToVerovio(sbBasedMei: string): string {
for (const neume of neumes) {
const ncs = Array.from(neume.getElementsByTagName('nc'));
if (ncs.length === 0) {
- const id = neume.getAttribute('xml:id');
- Notification.queueNotification(`This file contains a neume without neume component!
ID: ${id}`, 'warning');
+ hasEmptyNeume = true;
+ emptyNeumeInfo += `- <${neume.tagName}> with xml:id: ${neume.getAttribute('xml:id')}\n`;
} else {
// To be removed in the future:
// If nc has a @curve value, add a element
@@ -257,6 +261,13 @@ export function convertToVerovio(sbBasedMei: string): string {
}
}
+ if (hasEmptySyllable) {
+ Notification.queueNotification('This file contains syllable(s) without neume!', 'warning', emptySyllableInfo);
+ }
+ if (hasEmptyNeume) {
+ Notification.queueNotification('This file contains neume(s) without neume component!', 'warning', emptyNeumeInfo);
+ }
+
// Go section by section just in case
for (const section of mei.getElementsByTagName('section')) {
// In case there are multiple staves here we want to preserve those
@@ -387,7 +398,8 @@ export function convertToVerovio(sbBasedMei: string): string {
// Second pass on all syllables to handle clefs and custos that might remain
const newSyllables = Array.from(mei.getElementsByTagName('syllable'));
-
+ let invalidLinked = false;
+ let invalidLinkedInfo = 'The following linked syllables are not encoded correctly: \n\n';
for (const syllable of mei.querySelectorAll('syllable')) {
for (const clef of syllable.querySelectorAll('clef')) {
syllable.insertAdjacentElement('beforebegin', clef);
@@ -407,18 +419,29 @@ export function convertToVerovio(sbBasedMei: string): string {
// Validate toggle-linked syllable
if (syllable.hasAttribute('precedes') && syllable.hasAttribute('follows')) {
// Check if the syllable has both @precedes and @follows
- const sylId = syllable.getAttribute('xml:id');
- Notification.queueNotification(`This file contains a syllable that has both @precedes and @follows!
ID: ${sylId}`, 'error');
+ invalidLinked = true;
+ invalidLinkedInfo += `- <${syllable.tagName}> (${getSyllableText(syllable)}) with xml:id: ${syllable.getAttribute('xml:id')} has both @precedes and @follows\n`;
}
// Check the precedes syllable
else if (syllable.hasAttribute('precedes')) {
- checkPrecedesSyllable(syllable, syllableIdx, newSyllables);
+ const info = checkPrecedesSyllable(syllable, syllableIdx, newSyllables);
+ if (info) {
+ invalidLinked = true;
+ invalidLinkedInfo += info;
+ }
}
// Check the follows syllable
else if (syllable.hasAttribute('follows')) {
- checkFollowsSyllable(syllable, newSyllables);
+ const info = checkFollowsSyllable(syllable, newSyllables);
+ if (info) {
+ invalidLinked = true;
+ invalidLinkedInfo += info;
+ }
}
}
+ if (invalidLinked) {
+ Notification.queueNotification('This file contains invalid linked syllable(s)', 'error', invalidLinkedInfo);
+ }
const serializer = new XMLSerializer();
return vkbeautify.xml(serializer.serializeToString(meiDoc));
@@ -446,10 +469,7 @@ export function checkOutOfBoundsGlyphs (meiString: string): void {
const isOutOfBounds = ['ulx', 'uly', 'lrx', 'lry'].some((attr) => isAttrOutOfBounds(zone, attr));
if (isOutOfBounds) {
const element = mei.querySelector(`*[facs="${'#'+zone.getAttribute('xml:id')}"]`);
- console.log(zone.getAttribute('xml:id'));
- console.log(element);
info += `- <${element.tagName}> with xml:id: ${element.getAttribute('xml:id')}\n`;
-
}
return isOutOfBounds;
});
@@ -459,7 +479,7 @@ export function checkOutOfBoundsGlyphs (meiString: string): void {
}
}
-function checkPrecedesSyllable (syllable: Element, idx: number, syllables: Element[]): void {
+function checkPrecedesSyllable (syllable: Element, idx: number, syllables: Element[]): string {
// Get xml:id of the next syllable (without the #, if it exists)
const nextSyllableId = syllable.getAttribute('precedes').replace('#', '');
@@ -476,26 +496,17 @@ function checkPrecedesSyllable (syllable: Element, idx: number, syllables: Eleme
// Condition 1: The next (following) syllable cannot be found
if (!nextSyllable) {
- const sylText = getSyllableText(syllable);
- const sylId = syllable.getAttribute('xml:id');
- Notification.queueNotification(`Missing the 2nd part of the toggle-linked syllable (${sylText})
ID: ${sylId}`, 'error');
- return;
+ return `- <${syllable.tagName}> (${getSyllableText(syllable)}) with xml:id: ${syllable.getAttribute('xml:id')} is missing the following part\n`;
}
// Condition 2: The next syllable has been found, but the @follows attribute does NOT EXIST
if (!nextSyllable.hasAttribute('follows')) {
- const sylText = getSyllableText(syllable);
- const sylId = syllable.getAttribute('xml:id');
- Notification.queueNotification(`The 2nd part of the toggle-linked syllable (${sylText}) does not link to any syllable
ID: ${sylId}`, 'error');
- return;
+ return `- The following syllable of <${syllable.tagName}> (${getSyllableText(syllable)}) with xml:id: ${syllable.getAttribute('xml:id')} is not linked to any syllable\n`;
}
// Condition 3: The next syllable's @follows attribute exists, but it is not in the correct format #id
if (nextSyllable.getAttribute('follows') != '#' + syllable.getAttribute('xml:id')) {
- const sylText = getSyllableText(syllable);
- const sylId = syllable.getAttribute('xml:id');
- Notification.queueNotification(`The 2nd part of the toggle-linked syllable (${sylText}) links to the wrong syllable
ID: ${sylId}`, 'error');
- return;
+ return `- The following syllable of <${syllable.tagName}> (${getSyllableText(syllable)}) with xml:id: ${syllable.getAttribute('xml:id')} is linked to the wrong syllable\n`;
}
// Condition 4:
@@ -508,37 +519,27 @@ function checkPrecedesSyllable (syllable: Element, idx: number, syllables: Eleme
.map((syllable) => getSyllableText(syllable));
const sylsText = [sylText, ...unexpectedSylsText].join(' - ');
- const sylId = syllable.getAttribute('xml:id');
- Notification.queueNotification(`Unexpected syllable(s) inside toggle-linked syllable: ${sylsText}
ID: ${sylId}`, 'error');
- return;
+ return `- Unexpected syllable(s) inside toggle-linked <${syllable.tagName}> (${sylsText}) with xml:id: ${syllable.getAttribute('xml:id')} is linked to the wrong syllable\n`;
}
}
-function checkFollowsSyllable (syllable: Element, syllables: Element[]): void {
+function checkFollowsSyllable (syllable: Element, syllables: Element[]): string {
const prevSyllableId = syllable.getAttribute('follows').replace('#', '');
const prevSyllable = syllables.find((syllable) => syllable.getAttribute('xml:id') === prevSyllableId);
// Condition 1: The previous syllable does not exist
if (!prevSyllable) {
- const sylText = getSyllableText(syllable);
- const sylId = syllable.getAttribute('xml:id');
- Notification.queueNotification(`Missing the 1st part of the toggle-linked syllable (${sylText})
ID: ${sylId}`, 'error');
- return;
+ return `- <${syllable.tagName}> (${getSyllableText(syllable)}) with xml:id: ${syllable.getAttribute('xml:id')} is missing the preceding part\n`;
}
// Condition 2: The previous syllable exists, but the @precedes attribute does NOT EXIST
if (!prevSyllable.hasAttribute('precedes')) {
- const sylText = getSyllableText(prevSyllable);
- const sylId = syllable.getAttribute('xml:id');
- Notification.queueNotification(`The 1st part of the toggle-linked syllable (${sylText}) does not link to any syllable
ID: ${sylId}`, 'error');
- return;
+ return `- The preceding syllable of <${syllable.tagName}> (${getSyllableText(syllable)}) with xml:id: ${syllable.getAttribute('xml:id')} is not linked to any syllable\n`;
}
// Condition 3: The previous syllable's @precedes attribute exists, but it is not in the correct format #id
if (prevSyllable.getAttribute('precedes') != '#' + syllable.getAttribute('xml:id')) {
- const sylText = getSyllableText(prevSyllable);
- const sylId = syllable.getAttribute('xml:id');
- Notification.queueNotification(`The 1st part of the toggle-linked syllable (${sylText}) links to the wrong syllable
ID: ${sylId}`, 'error');
+ return `- The preceding syllable of <${syllable.tagName}> (${getSyllableText(prevSyllable)}) with xml:id: ${syllable.getAttribute('xml:id')} is linked to the wrong syllable\n`;
}
}
diff --git a/src/utils/DebugMode.ts b/src/utils/DebugMode.ts
new file mode 100644
index 00000000..9faae802
--- /dev/null
+++ b/src/utils/DebugMode.ts
@@ -0,0 +1,36 @@
+import { initErrorLog, updateErrorLogVisibility } from './ErrorLog';
+import { getSettings, setSettings } from './LocalSettings';
+
+export function setDebugMode(): void {
+ const debugModeCheckbox = document.querySelector('#debug-mode-checkbox');
+
+ const { debugMode } = getSettings();
+
+ debugModeCheckbox.checked = debugMode;
+
+ initErrorLog();
+
+ updateDebugModeStatus();
+
+ debugModeCheckbox.addEventListener('click', () => {
+ updateDebugModeStatus();
+ });
+}
+
+
+export function updateDebugModeStatus(): void {
+ const debugModeCheckbox = document.getElementById('debug-mode-checkbox') as HTMLInputElement;
+ setSettings({ debugMode: debugModeCheckbox.checked });
+
+ const errorLogLabel = document.getElementById('display-errors').parentNode as HTMLElement;
+ const notifPanel = document.querySelector('#error_log');
+ if (debugModeCheckbox.checked) {
+ errorLogLabel.style.display = '';
+ }
+ else {
+ errorLogLabel.style.display = 'none';
+ notifPanel.classList.remove('visible');
+ }
+
+ updateErrorLogVisibility();
+}
\ No newline at end of file
diff --git a/src/utils/ErrorLog.ts b/src/utils/ErrorLog.ts
index 74eb68c5..07006036 100644
--- a/src/utils/ErrorLog.ts
+++ b/src/utils/ErrorLog.ts
@@ -118,7 +118,6 @@ export function initErrorLogControls(): void {
* Initializes click listener on "Show error logs" button in "View" dropdown.
*/
export function initDisplayListener(): void {
- const notifPanel = document.querySelector('#error_log');
const checkboxesContainer = document.querySelector('#display-single-container');
const errorsLabel = document.createElement('label');
const erorrsBtn = document.createElement('input');
@@ -132,43 +131,52 @@ export function initDisplayListener(): void {
errorsLabel.appendChild(erorrsBtn);
checkboxesContainer.append(errorsLabel);
-
const { displayErrLog } = getSettings();
- if (displayErrLog) erorrsBtn.checked = true;
+ erorrsBtn.checked = displayErrLog;
erorrsBtn.addEventListener('click', () => {
- 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;
+ setSettings({ displayErrLog: erorrsBtn.checked });
+ updateErrorLogVisibility();
+ });
+}
- if (erorrsBtn.checked) {
- notifPanel.classList.add('visible');
- setSettings({ displayErrLog: true });
+function openErrorLogWindow(log: string) {
+ const modalWindow = new ModalWindow();
+ modalWindow.setModalWindowView(ModalWindowView.ERROR_LOG, log);
+ modalWindow.openModalWindow();
+}
+
+export function updateErrorLogVisibility(): void {
+ const notifPanel = document.querySelector('#error_log');
+ const { debugMode, displayErrLog, displayInfo, displayBBox, displayText } = getSettings();
- if (displayInfo?.checked && displayBBoxes?.checked &&
- displayText?.checked && displayErrLog?.checked) {
+ const displayAllBtn = document.getElementById('display-all-btn');
+
+ if (debugMode) {
+ if (displayErrLog) {
+ notifPanel.classList.add('visible');
+ if (displayInfo && displayBBox &&
+ displayText && displayErrLog) {
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';
}
}
-
- });
-}
-
-
-function openErrorLogWindow(log: string) {
- const modalWindow = new ModalWindow();
- modalWindow.setModalWindowView(ModalWindowView.ERROR_LOG, log);
- modalWindow.openModalWindow();
+ } else {
+ notifPanel.classList.remove('visible');
+ if (displayInfo && displayBBox && displayText) {
+ displayAllBtn.classList.add('selected');
+ displayAllBtn.innerHTML = 'Hide All';
+ } else {
+ displayAllBtn.classList.remove('selected');
+ displayAllBtn.innerHTML = 'Display All';
+ }
+ }
}
\ No newline at end of file
diff --git a/src/utils/LocalSettings.ts b/src/utils/LocalSettings.ts
index d060fcf9..1911e70e 100644
--- a/src/utils/LocalSettings.ts
+++ b/src/utils/LocalSettings.ts
@@ -26,6 +26,7 @@ export interface Settings {
insertMode: InsertType;
insertTab: InsertTabType;
selectionMode: SelectionType;
+ debugMode: boolean;
displayBBox: boolean;
displayText: boolean;
displayInfo: boolean;
@@ -50,6 +51,7 @@ const DEFAULT_SETTINGS: Settings = {
insertMode: 'punctum',
insertTab: 'primitiveTab',
selectionMode: 'selBySyllable',
+ debugMode: false,
displayBBox: false,
displayText: false,
displayInfo: false,