diff --git a/src/SquareEdit/Contents.ts b/src/SquareEdit/Contents.ts index 02212151..a81e0c9d 100644 --- a/src/SquareEdit/Contents.ts +++ b/src/SquareEdit/Contents.ts @@ -411,6 +411,18 @@ export const groupingMenu = { /* BGINNING OF MODAL WINDOW CONTENT SECTION */ +/** + * HTML for Add Syllable Text modal window + */ +export const addTextModal = `
+ + +
+
Cancel
+
Add
+
+
`; + /** * HTML for Edit Syllable Text modal window */ diff --git a/src/SquareEdit/SelectOptions.ts b/src/SquareEdit/SelectOptions.ts index 07ce047d..dc45e776 100644 --- a/src/SquareEdit/SelectOptions.ts +++ b/src/SquareEdit/SelectOptions.ts @@ -13,9 +13,10 @@ import { RemoveAction, SetAction, SetClefAction, - SetLiquescentAction + SetLiquescentAction, } from '../Types'; import { getStaffBBox } from '../utils/SelectTools'; +import { ModalWindow, ModalWindowView } from '../utils/ModalWindow'; /** * The NeonView parent to call editor actions. @@ -25,7 +26,7 @@ let neonView: NeonView; /** * Initialize NeonView. */ -export function initNeonView (view: NeonView): void { +export function initNeonView(view: NeonView): void { neonView = view; Grouping.initNeonView(view); } @@ -34,14 +35,14 @@ export function initNeonView (view: NeonView): void { * @param id - The id of the neume component. * @returns An action that unsets the inclinatum parameter of a neume component. */ -export function unsetInclinatumAction (id: string): SetAction { +export function unsetInclinatumAction(id: string): SetAction { return { action: 'set', param: { elementId: id, attrType: 'tilt', - attrValue: '' - } + attrValue: '', + }, }; } @@ -49,14 +50,14 @@ export function unsetInclinatumAction (id: string): SetAction { * @param id - The id of the neume component. * @returns An action that unsets the virga parameter of a neume component. */ -export function unsetVirgaAction (id: string): SetAction { +export function unsetVirgaAction(id: string): SetAction { return { action: 'set', param: { elementId: id, attrType: 'tilt', - attrValue: '' - } + attrValue: '', + }, }; } @@ -64,14 +65,14 @@ export function unsetVirgaAction (id: string): SetAction { * @param id - The id of the neume component. * @returns An action that unsets the reversed virga parameter of a neume component. */ -export function unsetVirgaReversedAction (id: string): SetAction { +export function unsetVirgaReversedAction(id: string): SetAction { return { action: 'set', param: { elementId: id, attrType: 'tilt', - attrValue: '' - } + attrValue: '', + }, }; } @@ -79,14 +80,14 @@ export function unsetVirgaReversedAction (id: string): SetAction { * @param id - The id of the neume component. * @returns An action that unsets the liquescent_clockwise parameter of a neume component. */ -export function unsetLiquescentClockwiseAction (id: string): SetAction { +export function unsetLiquescentClockwiseAction(id: string): SetAction { return { action: 'set', param: { elementId: id, attrType: 'curve', - attrValue: '' - } + attrValue: '', + }, }; } @@ -94,25 +95,30 @@ export function unsetLiquescentClockwiseAction (id: string): SetAction { * @param id - The id of the neume component. * @returns An action that unsets the liquescent_anticlockwise parameter of a neume component. */ -export function unsetLiquescentAnticlockwiseAction (id: string): SetLiquescentAction { +export function unsetLiquescentAnticlockwiseAction( + id: string, +): SetLiquescentAction { return { action: 'setLiquescent', param: { elementId: id, - curve: '' - } + curve: '', + }, }; } /** Event handler for delete button press. */ -export function deleteButtonHandler (evt: KeyboardEvent): void { - if (evt.key === 'd' || evt.key === 'Backspace') { removeHandler(); evt.preventDefault(); } +export function deleteButtonHandler(evt: KeyboardEvent): void { + if (evt.key === 'd' || evt.key === 'Backspace') { + removeHandler(); + evt.preventDefault(); + } } /** * End the extra options menu. */ -export function endOptionsSelection (): void { +export function endOptionsSelection(): void { const moreEdit = document.getElementById('moreEdit'); const extraEdit = document.getElementById('extraEdit'); if (moreEdit) { @@ -126,14 +132,13 @@ export function endOptionsSelection (): void { document.body.removeEventListener('keydown', deleteButtonHandler); } - /** * Function to handle removing elements */ -export function removeHandler (): void { +export function removeHandler(): void { const toRemove: RemoveAction[] = []; const selected = Array.from(document.getElementsByClassName('selected')); - selected.forEach(elem => { + selected.forEach((elem) => { if (elem.classList.contains('syl')) { elem = elem.closest('.syllable'); } @@ -143,21 +148,21 @@ export function removeHandler (): void { if (elem.classList.contains('divLine')) { elem = elem.closest('.divLine'); } - toRemove.push( - { - action: 'remove', - param: { - elementId: elem.id - } - } - ); + toRemove.push({ + action: 'remove', + param: { + elementId: elem.id, + }, + }); }); const chainAction: ChainAction = { action: 'chain', - param: toRemove + param: toRemove, }; endOptionsSelection(); - neonView.edit(chainAction, neonView.view.getCurrentPageURI()).then(() => { neonView.updateForCurrentPage(); }); + neonView.edit(chainAction, neonView.view.getCurrentPageURI()).then(() => { + neonView.updateForCurrentPage(); + }); } /** @@ -166,22 +171,22 @@ export function removeHandler (): void { export function changeStaffHandler(): void { const toChange: ChangeStaffAction[] = []; const selected = Array.from(document.getElementsByClassName('selected')); - selected.forEach(elem => { - toChange.push( - { - action: 'changeStaff', - param: { - elementId: elem.id - } - } - ); + selected.forEach((elem) => { + toChange.push({ + action: 'changeStaff', + param: { + elementId: elem.id, + }, + }); }); const chainAction: EditorAction = { action: 'chain', - param: toChange + param: toChange, }; endOptionsSelection(); - neonView.edit(chainAction, neonView.view.getCurrentPageURI()).then(() => { neonView.updateForCurrentPage(); }); + neonView.edit(chainAction, neonView.view.getCurrentPageURI()).then(() => { + neonView.updateForCurrentPage(); + }); } /** @@ -190,29 +195,29 @@ export function changeStaffHandler(): void { export function insertToSyllableHandler(): void { const toInsert: EditorAction[] = []; const selected = Array.from(document.getElementsByClassName('selected')); - selected.forEach(elem => { - toInsert.push( - { - action: 'insertToSyllable', - param: { - elementId: elem.id - } - } - ); + selected.forEach((elem) => { + toInsert.push({ + action: 'insertToSyllable', + param: { + elementId: elem.id, + }, + }); }); const chainAction: ChainAction = { action: 'chain', - param: toInsert + param: toInsert, }; - neonView.edit(chainAction, neonView.view.getCurrentPageURI()).then((result) => { - if (result) { - Notification.queueNotification('Insert Success', 'success'); - } else { - Notification.queueNotification('Insert Failed XoX', 'error'); - } - endOptionsSelection(); - neonView.updateForCurrentPage(); - }); + neonView + .edit(chainAction, neonView.view.getCurrentPageURI()) + .then((result) => { + if (result) { + Notification.queueNotification('Insert Success', 'success'); + } else { + Notification.queueNotification('Insert Failed XoX', 'error'); + } + endOptionsSelection(); + neonView.updateForCurrentPage(); + }); } /** @@ -221,29 +226,29 @@ export function insertToSyllableHandler(): void { export function moveOutsideSyllableHandler(): void { const toMove: EditorAction[] = []; const selected = Array.from(document.getElementsByClassName('selected')); - selected.forEach(elem => { - toMove.push( - { - action: 'moveOutsideSyllable', - param: { - elementId: elem.id - } - } - ); + selected.forEach((elem) => { + toMove.push({ + action: 'moveOutsideSyllable', + param: { + elementId: elem.id, + }, + }); }); const chainAction: ChainAction = { action: 'chain', - param: toMove + param: toMove, }; - neonView.edit(chainAction, neonView.view.getCurrentPageURI()).then((result) => { - if (result) { - Notification.queueNotification('Move Success', 'success'); - } else { - Notification.queueNotification('Move Failed XoX', 'error'); - } - endOptionsSelection(); - neonView.updateForCurrentPage(); - }); + neonView + .edit(chainAction, neonView.view.getCurrentPageURI()) + .then((result) => { + if (result) { + Notification.queueNotification('Move Success', 'success'); + } else { + Notification.queueNotification('Move Failed XoX', 'error'); + } + endOptionsSelection(); + neonView.updateForCurrentPage(); + }); } /** @@ -252,26 +257,45 @@ export function moveOutsideSyllableHandler(): void { export function matchHeightHandler(): void { const selected = Array.from(document.getElementsByClassName('selected')); if (selected.length > 1) { - Notification.queueNotification('Cannot match height to multiple bbox', 'error'); + Notification.queueNotification( + 'Cannot match height to multiple bbox', + 'error', + ); } const toChange: MatchHeightAction = { action: 'matchHeight', param: { - elementId: selected[0].id - } + elementId: selected[0].id, + }, }; - neonView.edit(toChange, neonView.view.getCurrentPageURI()).then((result) => { + neonView.edit(toChange, neonView.view.getCurrentPageURI()).then((result) => { if (result) { Notification.queueNotification('Height Match Success', 'success'); } else { Notification.queueNotification('Height Match Failed XoX', 'error'); } endOptionsSelection(); - neonView.updateForCurrentPage(); + neonView.updateForCurrentPage(); }); } +/** + * Opens Add Syl Text modal window with UI for entering syllable text. + */ +function openAddSylWindow(): void { + // generate modal window + const modalWindow = neonView.modal; + modalWindow.setModalWindowView(ModalWindowView.ADD_TEXT); + modalWindow.openModalWindow(); +} + +export function addAddSylListener(): void { + const addSyl = document.getElementById('addSyl'); + addSyl?.removeEventListener('click', openAddSylWindow); + addSyl?.addEventListener('click', openAddSylWindow); +} + function addDeleteListener(): void { const del = document.getElementById('delete'); @@ -298,7 +322,9 @@ function updateColumnInfo(): void { const staves = document.querySelectorAll('.staff.selected'); let colInfo: number; for (const staff of staves) { - const hasColumn = Array.from(staff.classList).join(' ').match(/\bcolumn(\d+)\b/); + const hasColumn = Array.from(staff.classList) + .join(' ') + .match(/\bcolumn(\d+)\b/); if (hasColumn) { const staffCol = parseInt(hasColumn[1], 10); if (colInfo && colInfo != staffCol) { @@ -310,7 +336,7 @@ function updateColumnInfo(): void { colInfo = staffCol; } } - } + } if (colInfo) colInput.value = colInfo.toString(); } @@ -318,9 +344,15 @@ function updateColumnInfo(): void { * Add column info edit handler for column info editor */ function addColumnInfoStepHandler(): void { - const decrementButton = document.getElementById('col-decrement') as HTMLButtonElement; - const incrementButton = document.getElementById('col-increment') as HTMLButtonElement; - const confirmButton = document.getElementById('col-confirm') as HTMLButtonElement; + const decrementButton = document.getElementById( + 'col-decrement', + ) as HTMLButtonElement; + const incrementButton = document.getElementById( + 'col-increment', + ) as HTMLButtonElement; + const confirmButton = document.getElementById( + 'col-confirm', + ) as HTMLButtonElement; const colInput = document.getElementById('col-input') as HTMLInputElement; decrementButton.addEventListener('click', function () { @@ -335,33 +367,39 @@ function addColumnInfoStepHandler(): void { confirmButton.addEventListener('click', function () { const toSetColumn: EditorAction[] = []; const staves = document.querySelectorAll('.staff.selected'); - staves.forEach(staff => { - toSetColumn.push( - { - action: 'set', - param: { - elementId: staff.id, - attrType: 'type', - attrValue: 'column' + colInput.value, - } - } - ); + staves.forEach((staff) => { + toSetColumn.push({ + action: 'set', + param: { + elementId: staff.id, + attrType: 'type', + attrValue: 'column' + colInput.value, + }, + }); }); const chainAction: ChainAction = { action: 'chain', - param: toSetColumn + param: toSetColumn, }; - neonView.edit(chainAction, neonView.view.getCurrentPageURI()).then((result) => { - if (result) { - Notification.queueNotification('Column Value Update Success', 'success'); - } else { - Notification.queueNotification('Column Value Update Failed XoX', 'error'); - } - endOptionsSelection(); - neonView.updateForCurrentPage(); - }); + neonView + .edit(chainAction, neonView.view.getCurrentPageURI()) + .then((result) => { + if (result) { + Notification.queueNotification( + 'Column Value Update Success', + 'success', + ); + } else { + Notification.queueNotification( + 'Column Value Update Failed XoX', + 'error', + ); + } + endOptionsSelection(); + neonView.updateForCurrentPage(); + }); }); - + function updateNumber(change: number) { const minNum = 1; const maxNum = 5; @@ -369,7 +407,8 @@ function addColumnInfoStepHandler(): void { currentValue += change; currentValue = Math.min(Math.max(currentValue, minNum), maxNum); - if (currentValue.toString() != colInput.value) confirmButton.style.display = ''; + if (currentValue.toString() != colInput.value) + confirmButton.style.display = ''; colInput.value = currentValue.toString(); } } @@ -381,9 +420,13 @@ function addColumnInfoStepHandler(): void { * @param {string} contents - The innerHTML contents * @param {boolean} replace - Is the innerHTML being replaced, or being added to? */ -function setEditControls(editType: 'moreEdit' | 'extraEdit', contents: string, replace = true): void { +function setEditControls( + editType: 'moreEdit' | 'extraEdit', + contents: string, + replace = true, +): void { const edit = document.getElementById(editType); - + if (edit) { edit.parentElement.classList.remove('hidden'); if (replace) edit.innerHTML = contents; @@ -394,131 +437,217 @@ function setEditControls(editType: 'moreEdit' | 'extraEdit', contents: string, r /** * Trigger the extra nc action menu for a selection. */ -export function triggerNcActions (nc: SVGGraphicsElement): void { +export function triggerNcActions(nc: SVGGraphicsElement): void { endOptionsSelection(); setEditControls('moreEdit', Contents.defaultActionContents); setEditControls('extraEdit', Contents.ncActionContents); addDeleteListener(); - document.querySelector('#Punctum.dropdown-item') + document + .querySelector('#Punctum.dropdown-item') .addEventListener('click', () => { const unsetInclinatum = unsetInclinatumAction(nc.id); const unsetVirga = unsetVirgaAction(nc.id); const unsetVirgaReversed = unsetVirgaReversedAction(nc.id); const unsetLiquescentClockwise = unsetLiquescentClockwiseAction(nc.id); - const unsetLiquescentAnticlockwise = unsetLiquescentAnticlockwiseAction(nc.id); - neonView.edit({ action: 'chain', param: [ unsetInclinatum, unsetVirga, unsetVirgaReversed, unsetLiquescentClockwise, unsetLiquescentAnticlockwise ] }, neonView.view.getCurrentPageURI()).then((result) => { - if (result) { - Notification.queueNotification('Shape Changed', 'success'); - } else { - Notification.queueNotification('Shape Change Failed', 'error'); - } - endOptionsSelection(); - neonView.updateForCurrentPage(); - }); + const unsetLiquescentAnticlockwise = unsetLiquescentAnticlockwiseAction( + nc.id, + ); + neonView + .edit( + { + action: 'chain', + param: [ + unsetInclinatum, + unsetVirga, + unsetVirgaReversed, + unsetLiquescentClockwise, + unsetLiquescentAnticlockwise, + ], + }, + neonView.view.getCurrentPageURI(), + ) + .then((result) => { + if (result) { + Notification.queueNotification('Shape Changed', 'success'); + } else { + Notification.queueNotification('Shape Change Failed', 'error'); + } + endOptionsSelection(); + neonView.updateForCurrentPage(); + }); }); - document.querySelector('#Inclinatum.dropdown-item') + document + .querySelector('#Inclinatum.dropdown-item') .addEventListener('click', () => { const unsetVirga = unsetVirgaAction(nc.id); const unsetVirgaReversed = unsetVirgaReversedAction(nc.id); const unsetLiquescentClockwise = unsetLiquescentClockwiseAction(nc.id); - const unsetLiquescentAnticlockwise = unsetLiquescentAnticlockwiseAction(nc.id); + const unsetLiquescentAnticlockwise = unsetLiquescentAnticlockwiseAction( + nc.id, + ); const setInclinatum: SetAction = { action: 'set', param: { elementId: nc.id, attrType: 'tilt', - attrValue: 'se' - } + attrValue: 'se', + }, }; - neonView.edit({ action: 'chain', param: [ unsetVirga, unsetVirgaReversed, unsetLiquescentClockwise, unsetLiquescentAnticlockwise, setInclinatum ] } , neonView.view.getCurrentPageURI()).then((result) => { - if (result) { - Notification.queueNotification('Shape Changed', 'success'); - } else { - Notification.queueNotification('Shape Change Failed', 'error'); - } - endOptionsSelection(); - neonView.updateForCurrentPage(); - }); + neonView + .edit( + { + action: 'chain', + param: [ + unsetVirga, + unsetVirgaReversed, + unsetLiquescentClockwise, + unsetLiquescentAnticlockwise, + setInclinatum, + ], + }, + neonView.view.getCurrentPageURI(), + ) + .then((result) => { + if (result) { + Notification.queueNotification('Shape Changed', 'success'); + } else { + Notification.queueNotification('Shape Change Failed', 'error'); + } + endOptionsSelection(); + neonView.updateForCurrentPage(); + }); }); - document.querySelector('#Virga.dropdown-item') + document + .querySelector('#Virga.dropdown-item') .addEventListener('click', () => { const unsetVirgaReversed = unsetVirgaReversedAction(nc.id); const unsetInclinatum = unsetInclinatumAction(nc.id); const unsetLiquescentClockwise = unsetLiquescentClockwiseAction(nc.id); - const unsetLiquescentAnticlockwise = unsetLiquescentAnticlockwiseAction(nc.id); + const unsetLiquescentAnticlockwise = unsetLiquescentAnticlockwiseAction( + nc.id, + ); const setVirga: SetAction = { action: 'set', param: { elementId: nc.id, attrType: 'tilt', - attrValue: 's' - } + attrValue: 's', + }, }; - neonView.edit({ action: 'chain', param: [ unsetVirgaReversed, unsetInclinatum, unsetLiquescentClockwise, unsetLiquescentAnticlockwise, setVirga ] }, neonView.view.getCurrentPageURI()).then((result) => { - if (result) { - Notification.queueNotification('Shape Changed', 'success'); - } else { - Notification.queueNotification('Shape Change Failed', 'error'); - } - endOptionsSelection(); - neonView.updateForCurrentPage(); - }); + neonView + .edit( + { + action: 'chain', + param: [ + unsetVirgaReversed, + unsetInclinatum, + unsetLiquescentClockwise, + unsetLiquescentAnticlockwise, + setVirga, + ], + }, + neonView.view.getCurrentPageURI(), + ) + .then((result) => { + if (result) { + Notification.queueNotification('Shape Changed', 'success'); + } else { + Notification.queueNotification('Shape Change Failed', 'error'); + } + endOptionsSelection(); + neonView.updateForCurrentPage(); + }); }); - document.querySelector('#VirgaReversed.dropdown-item') + document + .querySelector('#VirgaReversed.dropdown-item') .addEventListener('click', () => { const unsetInclinatum = unsetInclinatumAction(nc.id); const unsetVirga = unsetVirgaAction(nc.id); const unsetLiquescentClockwise = unsetLiquescentClockwiseAction(nc.id); - const unsetLiquescentAnticlockwise = unsetLiquescentAnticlockwiseAction(nc.id); + const unsetLiquescentAnticlockwise = unsetLiquescentAnticlockwiseAction( + nc.id, + ); const setVirgaReversed: SetAction = { action: 'set', param: { elementId: nc.id, attrType: 'tilt', - attrValue: 'n' - } + attrValue: 'n', + }, }; - neonView.edit({ action: 'chain', param: [ unsetInclinatum, unsetVirga, unsetLiquescentClockwise, unsetLiquescentAnticlockwise, setVirgaReversed ] }, neonView.view.getCurrentPageURI()).then((result) => { - if (result) { - Notification.queueNotification('Shape Changed', 'success'); - } else { - Notification.queueNotification('Shape Change Failed', 'error'); - } - endOptionsSelection(); - neonView.updateForCurrentPage(); - }); + neonView + .edit( + { + action: 'chain', + param: [ + unsetInclinatum, + unsetVirga, + unsetLiquescentClockwise, + unsetLiquescentAnticlockwise, + setVirgaReversed, + ], + }, + neonView.view.getCurrentPageURI(), + ) + .then((result) => { + if (result) { + Notification.queueNotification('Shape Changed', 'success'); + } else { + Notification.queueNotification('Shape Change Failed', 'error'); + } + endOptionsSelection(); + neonView.updateForCurrentPage(); + }); }); - document.querySelector('#LiquescentClockwise.dropdown-item') + document + .querySelector('#LiquescentClockwise.dropdown-item') .addEventListener('click', () => { const unsetInclinatum = unsetInclinatumAction(nc.id); const unsetVirga = unsetVirgaAction(nc.id); const unsetVirgaReversed = unsetVirgaReversedAction(nc.id); - const unsetLiquescentAnticlockwise = unsetLiquescentAnticlockwiseAction(nc.id); + const unsetLiquescentAnticlockwise = unsetLiquescentAnticlockwiseAction( + nc.id, + ); const setLiquescentClockwise: SetLiquescentAction = { action: 'setLiquescent', param: { elementId: nc.id, - curve: 'c' - } + curve: 'c', + }, }; - neonView.edit({ action: 'chain', param: [ unsetInclinatum, unsetVirga, unsetVirgaReversed, unsetLiquescentAnticlockwise, setLiquescentClockwise ] }, neonView.view.getCurrentPageURI()).then((result) => { - if (result) { - Notification.queueNotification('Shape Changed', 'success'); - } else { - Notification.queueNotification('Shape Change Failed', 'error'); - } - endOptionsSelection(); - neonView.updateForCurrentPage(); - }); - }); + neonView + .edit( + { + action: 'chain', + param: [ + unsetInclinatum, + unsetVirga, + unsetVirgaReversed, + unsetLiquescentAnticlockwise, + setLiquescentClockwise, + ], + }, + neonView.view.getCurrentPageURI(), + ) + .then((result) => { + if (result) { + Notification.queueNotification('Shape Changed', 'success'); + } else { + Notification.queueNotification('Shape Change Failed', 'error'); + } + endOptionsSelection(); + neonView.updateForCurrentPage(); + }); + }); - document.querySelector('#LiquescentAnticlockwise.dropdown-item') + document + .querySelector('#LiquescentAnticlockwise.dropdown-item') .addEventListener('click', () => { const unsetInclinatum = unsetInclinatumAction(nc.id); const unsetVirga = unsetVirgaAction(nc.id); @@ -528,19 +657,33 @@ export function triggerNcActions (nc: SVGGraphicsElement): void { action: 'setLiquescent', param: { elementId: nc.id, - curve: 'a' - } + curve: 'a', + }, }; - neonView.edit({ action: 'chain', param: [ unsetInclinatum, unsetVirga, unsetVirgaReversed, unsetLiquescentClockwise, setLiquescentAnticlockwise ] }, neonView.view.getCurrentPageURI()).then((result) => { - if (result) { - Notification.queueNotification('Shape Changed', 'success'); - } else { - Notification.queueNotification('Shape Change Failed', 'error'); - } - endOptionsSelection(); - neonView.updateForCurrentPage(); - }); - }); + neonView + .edit( + { + action: 'chain', + param: [ + unsetInclinatum, + unsetVirga, + unsetVirgaReversed, + unsetLiquescentClockwise, + setLiquescentAnticlockwise, + ], + }, + neonView.view.getCurrentPageURI(), + ) + .then((result) => { + if (result) { + Notification.queueNotification('Shape Changed', 'success'); + } else { + Notification.queueNotification('Shape Change Failed', 'error'); + } + endOptionsSelection(); + neonView.updateForCurrentPage(); + }); + }); initOptionsListeners(); } @@ -548,7 +691,7 @@ export function triggerNcActions (nc: SVGGraphicsElement): void { /** * Trigger extra neume actions. */ -export function triggerNeumeActions (): void { +export function triggerNeumeActions(): void { endOptionsSelection(); setEditControls('moreEdit', Contents.defaultNeumeActionContents); @@ -557,112 +700,157 @@ export function triggerNeumeActions (): void { const neume = document.querySelectorAll('.selected'); if (neume.length !== 1) { - console.warn('More than one neume selected! Cannot trigger Neume ClickSelect actions.'); + console.warn( + 'More than one neume selected! Cannot trigger Neume ClickSelect actions.', + ); return; } // TODO add trigger for split action - document.getElementById('split-neume') - .addEventListener('click', () => { - const neume = document.querySelector('.neume.selected') as SVGGElement; - if (neume !== null) { - const split = new SplitNeumeHandler(neonView, neume); - split.startSplit(); - endOptionsSelection(); - } else { - console.error('No staff was selected!'); - endOptionsSelection(); - } - }); + document.getElementById('split-neume').addEventListener('click', () => { + const neume = document.querySelector('.neume.selected') as SVGGElement; + if (neume !== null) { + const split = new SplitNeumeHandler(neonView, neume); + split.startSplit(); + endOptionsSelection(); + } else { + console.error('No staff was selected!'); + endOptionsSelection(); + } + }); - document.querySelector('#Pes.dropdown-item') + document + .querySelector('#Pes.dropdown-item') .addEventListener('click', (e) => { - const contour = neonView.info.getContourByValue((e.target as HTMLElement).id); - triggerChangeGroup(contour); - }); - document.querySelector('#PesSubpunctis.dropdown-item') + const contour = neonView.info.getContourByValue( + (e.target as HTMLElement).id, + ); + triggerChangeGroup(contour); + }); + document + .querySelector('#PesSubpunctis.dropdown-item') .addEventListener('click', (e) => { - const contour = neonView.info.getContourByValue((e.target as HTMLElement).id); - triggerChangeGroup(contour); - }); - document.querySelector('#Clivis.dropdown-item') + const contour = neonView.info.getContourByValue( + (e.target as HTMLElement).id, + ); + triggerChangeGroup(contour); + }); + document + .querySelector('#Clivis.dropdown-item') .addEventListener('click', (e) => { - const contour = neonView.info.getContourByValue((e.target as HTMLElement).id); - triggerChangeGroup(contour); - }); - document.querySelector('#Scandicus.dropdown-item') + const contour = neonView.info.getContourByValue( + (e.target as HTMLElement).id, + ); + triggerChangeGroup(contour); + }); + document + .querySelector('#Scandicus.dropdown-item') .addEventListener('click', (e) => { - const contour = neonView.info.getContourByValue((e.target as HTMLElement).id); - triggerChangeGroup(contour); - }); - document.querySelector('#ScandicusFlexus.dropdown-item') + const contour = neonView.info.getContourByValue( + (e.target as HTMLElement).id, + ); + triggerChangeGroup(contour); + }); + document + .querySelector('#ScandicusFlexus.dropdown-item') .addEventListener('click', (e) => { - const contour = neonView.info.getContourByValue((e.target as HTMLElement).id); - triggerChangeGroup(contour); - }); - document.querySelector('#ScandicusSubpunctis.dropdown-item') + const contour = neonView.info.getContourByValue( + (e.target as HTMLElement).id, + ); + triggerChangeGroup(contour); + }); + document + .querySelector('#ScandicusSubpunctis.dropdown-item') .addEventListener('click', (e) => { - const contour = neonView.info.getContourByValue((e.target as HTMLElement).id); - triggerChangeGroup(contour); - }); - document.querySelector('#Climacus.dropdown-item') + const contour = neonView.info.getContourByValue( + (e.target as HTMLElement).id, + ); + triggerChangeGroup(contour); + }); + document + .querySelector('#Climacus.dropdown-item') .addEventListener('click', (e) => { - const contour = neonView.info.getContourByValue((e.target as HTMLElement).id); - triggerChangeGroup(contour); - }); - document.querySelector('#ClimacusResupinus.dropdown-item') + const contour = neonView.info.getContourByValue( + (e.target as HTMLElement).id, + ); + triggerChangeGroup(contour); + }); + document + .querySelector('#ClimacusResupinus.dropdown-item') .addEventListener('click', (e) => { - const contour = neonView.info.getContourByValue((e.target as HTMLElement).id); - triggerChangeGroup(contour); - }); - document.querySelector('#Torculus.dropdown-item') + const contour = neonView.info.getContourByValue( + (e.target as HTMLElement).id, + ); + triggerChangeGroup(contour); + }); + document + .querySelector('#Torculus.dropdown-item') .addEventListener('click', (e) => { - const contour = neonView.info.getContourByValue((e.target as HTMLElement).id); - triggerChangeGroup(contour); - }); - document.querySelector('#TorculusResupinus.dropdown-item') + const contour = neonView.info.getContourByValue( + (e.target as HTMLElement).id, + ); + triggerChangeGroup(contour); + }); + document + .querySelector('#TorculusResupinus.dropdown-item') .addEventListener('click', (e) => { - const contour = neonView.info.getContourByValue((e.target as HTMLElement).id); - triggerChangeGroup(contour); - }); - document.querySelector('#Porrectus.dropdown-item') + const contour = neonView.info.getContourByValue( + (e.target as HTMLElement).id, + ); + triggerChangeGroup(contour); + }); + document + .querySelector('#Porrectus.dropdown-item') .addEventListener('click', (e) => { - const contour = neonView.info.getContourByValue((e.target as HTMLElement).id); - triggerChangeGroup(contour); - }); - document.querySelector('#PorrectusFlexus.dropdown-item') + const contour = neonView.info.getContourByValue( + (e.target as HTMLElement).id, + ); + triggerChangeGroup(contour); + }); + document + .querySelector('#PorrectusFlexus.dropdown-item') .addEventListener('click', (e) => { - const contour = neonView.info.getContourByValue((e.target as HTMLElement).id); - triggerChangeGroup(contour); - }); - document.querySelector('#PorrectusSubpunctis.dropdown-item') + const contour = neonView.info.getContourByValue( + (e.target as HTMLElement).id, + ); + triggerChangeGroup(contour); + }); + document + .querySelector('#PorrectusSubpunctis.dropdown-item') .addEventListener('click', (e) => { - const contour = neonView.info.getContourByValue((e.target as HTMLElement).id); - triggerChangeGroup(contour); - }); - document.querySelector('#Pressus.dropdown-item') + const contour = neonView.info.getContourByValue( + (e.target as HTMLElement).id, + ); + triggerChangeGroup(contour); + }); + document + .querySelector('#Pressus.dropdown-item') .addEventListener('click', (e) => { - const contour = neonView.info.getContourByValue((e.target as HTMLElement).id); - triggerChangeGroup(contour); - }); + const contour = neonView.info.getContourByValue( + (e.target as HTMLElement).id, + ); + triggerChangeGroup(contour); + }); - function triggerChangeGroup (contour: string): void { + function triggerChangeGroup(contour: string): void { const changeGroupingAction: EditorAction = { action: 'changeGroup', param: { elementId: neume[0].id, - contour: contour - } + contour: contour, + }, }; - neonView.edit(changeGroupingAction, neonView.view.getCurrentPageURI()).then((result) => { - if (result) { - Notification.queueNotification('Grouping Changed', 'success'); - } else { - Notification.queueNotification('Grouping Failed', 'error'); - } - endOptionsSelection(); - neonView.updateForCurrentPage(); - }); + neonView + .edit(changeGroupingAction, neonView.view.getCurrentPageURI()) + .then((result) => { + if (result) { + Notification.queueNotification('Grouping Changed', 'success'); + } else { + Notification.queueNotification('Grouping Failed', 'error'); + } + endOptionsSelection(); + neonView.updateForCurrentPage(); + }); } initOptionsListeners(); @@ -671,7 +859,7 @@ export function triggerNeumeActions (): void { /** * Trigger extra syllable actions. */ -export function triggerSyllableActions (selectionType: string): void { +export function triggerSyllableActions(selectionType: string): void { endOptionsSelection(); setEditControls('moreEdit', Contents.syllableActionsContent); @@ -679,11 +867,18 @@ export function triggerSyllableActions (selectionType: string): void { let extraActionsHTML = ''; // determine the type of selection that was made by the user - switch(selectionType) { + switch (selectionType) { + // only one syllable and syllable has no + case 'noSyl': + extraActionsHTML += `
+ + +
`; + break; + // only one syllable case 'singleSelect': - extraActionsHTML += - `
+ extraActionsHTML += `
@@ -692,8 +887,7 @@ export function triggerSyllableActions (selectionType: string): void { // two syllables on separate staves case 'linkableSelect': - extraActionsHTML += - `
+ extraActionsHTML += `
@@ -702,8 +896,7 @@ export function triggerSyllableActions (selectionType: string): void { // tow or more syllables on one staff case 'multiSelect': - extraActionsHTML += - `
+ extraActionsHTML += `
@@ -712,16 +905,14 @@ export function triggerSyllableActions (selectionType: string): void { //default options case 'default': - extraActionsHTML += - `
+ extraActionsHTML += `
`; break; - } - // set content of additional actions in Display panel + // set content of additional actions in Display panel // and initialize necessary listeners setEditControls('moreEdit', extraActionsHTML, true); addChangeStaffListener(); @@ -729,11 +920,10 @@ export function triggerSyllableActions (selectionType: string): void { Grouping.initGroupingListeners(); } - /** * Trigger extra layer element (accid, divLine, custos, clef) actions. */ -export function triggerLayerElementActions (element: SVGGraphicsElement): void { +export function triggerLayerElementActions(element: SVGGraphicsElement): void { endOptionsSelection(); if (element.classList.contains('custos')) { @@ -743,131 +933,147 @@ export function triggerLayerElementActions (element: SVGGraphicsElement): void { return; } - + const parentIsSyllable = element.parentElement.classList.contains('syllable'); const layerElementActions = parentIsSyllable ? Contents.layerElementInActionContents : Contents.layerElementOutActionContents; - + setEditControls('moreEdit', layerElementActions, false); - + // extra actions for accid and divLine if (element.classList.contains('accid')) { setEditControls('extraEdit', Contents.accidActionContents); accidOptionsListener(element); - } - else if (element.classList.contains('clef')) { + } else if (element.classList.contains('clef')) { setEditControls('extraEdit', Contents.clefActionContents); clefOptionsListener(element); - } - else if (element.classList.contains('divLine')) { + } else if (element.classList.contains('divLine')) { setEditControls('extraEdit', Contents.divLineActionContents); divLineOptionsListener(element); } addChangeStaffListener(); - document.getElementById('insertToSyllable')?.addEventListener('click', insertToSyllableHandler); - document.getElementById('moveOutsideSyllable')?.addEventListener('click', moveOutsideSyllableHandler); + document + .getElementById('insertToSyllable') + ?.addEventListener('click', insertToSyllableHandler); + document + .getElementById('moveOutsideSyllable') + ?.addEventListener('click', moveOutsideSyllableHandler); - addDeleteListener(); addChangeStaffListener(); - document.getElementById('insertToSyllable')?.addEventListener('click', insertToSyllableHandler); - document.getElementById('moveOutsideSyllable')?.addEventListener('click', moveOutsideSyllableHandler); + document + .getElementById('insertToSyllable') + ?.addEventListener('click', insertToSyllableHandler); + document + .getElementById('moveOutsideSyllable') + ?.addEventListener('click', moveOutsideSyllableHandler); } function accidOptionsListener(element: SVGGraphicsElement): void { - document.querySelector('#ChangeToFlat.dropdown-item') + document + .querySelector('#ChangeToFlat.dropdown-item') .addEventListener('click', () => { const changeToFlat: SetAction = { action: 'set', param: { elementId: element.id, attrType: 'accid', - attrValue: 'f' - } + attrValue: 'f', + }, }; parseShapeChangeAction(changeToFlat); }); - document.querySelector('#ChangeToNatural.dropdown-item') + document + .querySelector('#ChangeToNatural.dropdown-item') .addEventListener('click', () => { const changeToNatural: EditorAction = { action: 'set', param: { elementId: element.id, attrType: 'accid', - attrValue: 'n' - } + attrValue: 'n', + }, }; parseShapeChangeAction(changeToNatural); }); initOptionsListeners(); - } function clefOptionsListener(element: SVGGraphicsElement): void { - document.querySelector('#increment-octave') - .addEventListener('click', () => { - const incrementOctave: DisplaceClefOctaveAction = { - action: 'displaceClefOctave', - param: { - elementId: element.id, - direction: 'above' - } - }; + document.querySelector('#increment-octave').addEventListener('click', () => { + const incrementOctave: DisplaceClefOctaveAction = { + action: 'displaceClefOctave', + param: { + elementId: element.id, + direction: 'above', + }, + }; - neonView.edit(incrementOctave, neonView.view.getCurrentPageURI()).then((result) => { + neonView + .edit(incrementOctave, neonView.view.getCurrentPageURI()) + .then((result) => { if (result) { Notification.queueNotification('Clef octave incremented.', 'success'); } else { - Notification.queueNotification('Maximum octave displacement reached. Clef can only be displaced up to 3 octaves.', 'error'); + Notification.queueNotification( + 'Maximum octave displacement reached. Clef can only be displaced up to 3 octaves.', + 'error', + ); } endOptionsSelection(); neonView.updateForCurrentPage(); }); - }); + }); - document.querySelector('#decrement-octave') - .addEventListener('click', () => { - const incrementOctave: DisplaceClefOctaveAction = { - action: 'displaceClefOctave', - param: { - elementId: element.id, - direction: 'below' - } - }; + document.querySelector('#decrement-octave').addEventListener('click', () => { + const incrementOctave: DisplaceClefOctaveAction = { + action: 'displaceClefOctave', + param: { + elementId: element.id, + direction: 'below', + }, + }; - neonView.edit(incrementOctave, neonView.view.getCurrentPageURI()).then((result) => { + neonView + .edit(incrementOctave, neonView.view.getCurrentPageURI()) + .then((result) => { if (result) { Notification.queueNotification('Clef octave decremented.', 'success'); } else { - Notification.queueNotification('Maximum octave displacement reached. Clef can only be displaced up to 3 octaves.', 'error'); + Notification.queueNotification( + 'Maximum octave displacement reached. Clef can only be displaced up to 3 octaves.', + 'error', + ); } endOptionsSelection(); neonView.updateForCurrentPage(); }); - }); + }); - document.querySelector('#CClef.dropdown-item') + document + .querySelector('#CClef.dropdown-item') .addEventListener('click', () => { const setCClef: SetClefAction = { action: 'setClef', param: { elementId: element.id, - shape: 'C' - } + shape: 'C', + }, }; parseShapeChangeAction(setCClef); }); - document.querySelector('#FClef.dropdown-item') + document + .querySelector('#FClef.dropdown-item') .addEventListener('click', () => { const setFClef: SetClefAction = { action: 'setClef', param: { elementId: element.id, - shape: 'F' - } + shape: 'F', + }, }; parseShapeChangeAction(setFClef); }); @@ -876,84 +1082,90 @@ function clefOptionsListener(element: SVGGraphicsElement): void { } function divLineOptionsListener(element: SVGGraphicsElement): void { - document.querySelector('#ChangeToMinima.dropdown-item') + document + .querySelector('#ChangeToMinima.dropdown-item') .addEventListener('click', () => { const ChangeToMinima: SetAction = { action: 'set', param: { elementId: element.id, attrType: 'form', - attrValue: 'minima' - } + attrValue: 'minima', + }, }; parseShapeChangeAction(ChangeToMinima); }); - document.querySelector('#ChangeToMaior.dropdown-item') + document + .querySelector('#ChangeToMaior.dropdown-item') .addEventListener('click', () => { const ChangeToMaior: EditorAction = { action: 'set', param: { elementId: element.id, attrType: 'form', - attrValue: 'maior' - } + attrValue: 'maior', + }, }; parseShapeChangeAction(ChangeToMaior); }); - document.querySelector('#ChangeToMaxima.dropdown-item') + document + .querySelector('#ChangeToMaxima.dropdown-item') .addEventListener('click', () => { const ChangeToMaxima: EditorAction = { action: 'set', param: { elementId: element.id, attrType: 'form', - attrValue: 'maxima' - } + attrValue: 'maxima', + }, }; parseShapeChangeAction(ChangeToMaxima); }); - document.querySelector('#ChangeToFinalis.dropdown-item') + document + .querySelector('#ChangeToFinalis.dropdown-item') .addEventListener('click', () => { const ChangeToFinalis: EditorAction = { action: 'set', param: { elementId: element.id, attrType: 'form', - attrValue: 'finalis' - } + attrValue: 'finalis', + }, }; parseShapeChangeAction(ChangeToFinalis); }); - document.querySelector('#ChangeToCaesura.dropdown-item') + document + .querySelector('#ChangeToCaesura.dropdown-item') .addEventListener('click', () => { const ChangeToCaesura: EditorAction = { action: 'set', param: { elementId: element.id, attrType: 'form', - attrValue: 'caesura' - } + attrValue: 'caesura', + }, }; parseShapeChangeAction(ChangeToCaesura); }); - document.querySelector('#ChangeToVirgula.dropdown-item') + document + .querySelector('#ChangeToVirgula.dropdown-item') .addEventListener('click', () => { const ChangeToVirgula: EditorAction = { action: 'set', param: { elementId: element.id, attrType: 'form', - attrValue: 'virgula' - } + attrValue: 'virgula', + }, }; parseShapeChangeAction(ChangeToVirgula); }); - + initOptionsListeners(); } @@ -975,7 +1187,7 @@ function parseShapeChangeAction(action: SetAction | SetClefAction) { /** * Trigger extra staff actions. */ -export function triggerMultiStaffActions (): void { +export function triggerMultiStaffActions(): void { endOptionsSelection(); setEditControls('moreEdit', Contents.staffMergeActionContents); setEditControls('extraEdit', Contents.columnActionContents); @@ -993,7 +1205,7 @@ export function triggerMultiStaffActions (): void { /** * Enter staff splitting mode */ -export function triggerStaffSplitMode (): void { +export function triggerStaffSplitMode(): void { const staff = document.querySelector('.staff.selected') as SVGGElement; if (staff !== null) { const split = new SplitStaffHandler(neonView, staff); @@ -1008,7 +1220,7 @@ export function triggerStaffSplitMode (): void { /** * Trigger split staff option */ -export function triggerSingleStaffActions (): void { +export function triggerSingleStaffActions(): void { endOptionsSelection(); setEditControls('moreEdit', Contents.staffSplitActionContents); setEditControls('extraEdit', Contents.columnActionContents); @@ -1019,49 +1231,55 @@ export function triggerSingleStaffActions (): void { addColumnInfoStepHandler(); // Add trigger for split action - document.getElementById('split-system') - .addEventListener('click', () => { - triggerStaffSplitMode(); - }); + document.getElementById('split-system').addEventListener('click', () => { + triggerStaffSplitMode(); + }); - document.getElementById('reset-rotate') - .addEventListener('click', () => { - const staff = document.querySelector('.staff.selected') as SVGElement; - // Unused variables: - // const rect = staff.querySelector('#resizeRect'); - // const co = rect.getAttribute('points').split(' '); - // const dy = parseInt(co[0].split(',')[1]) - parseInt(co[1].split(',')[1]); - const points = getStaffBBox(staff as SVGGElement); - const y_change = Math.tan(points.rotate)*(points.lrx - points.ulx); - if (staff !== null) { - const editorAction: EditorAction = { - action: 'resizeRotate', - param: { - elementId: staff.id, - ulx: points.ulx, - uly: points.rotate > 0 ? points.uly + y_change/2 : points.uly - y_change/2, - lrx: points.lrx, - lry: points.rotate > 0 ? points.lry - y_change/2 : points.lry + y_change/2, - rotate: 0 - } - }; - neonView.edit(editorAction, neonView.view.getCurrentPageURI()).then(async (result) => { + document.getElementById('reset-rotate').addEventListener('click', () => { + const staff = document.querySelector('.staff.selected') as SVGElement; + // Unused variables: + // const rect = staff.querySelector('#resizeRect'); + // const co = rect.getAttribute('points').split(' '); + // const dy = parseInt(co[0].split(',')[1]) - parseInt(co[1].split(',')[1]); + const points = getStaffBBox(staff as SVGGElement); + const y_change = Math.tan(points.rotate) * (points.lrx - points.ulx); + if (staff !== null) { + const editorAction: EditorAction = { + action: 'resizeRotate', + param: { + elementId: staff.id, + ulx: points.ulx, + uly: + points.rotate > 0 + ? points.uly + y_change / 2 + : points.uly - y_change / 2, + lrx: points.lrx, + lry: + points.rotate > 0 + ? points.lry - y_change / 2 + : points.lry + y_change / 2, + rotate: 0, + }, + }; + neonView + .edit(editorAction, neonView.view.getCurrentPageURI()) + .then(async (result) => { if (result) { await neonView.updateForCurrentPage(); } }); - endOptionsSelection(); - } else { - console.error('No staff was selected'); - endOptionsSelection(); - } - }); + endOptionsSelection(); + } else { + console.error('No staff was selected'); + endOptionsSelection(); + } + }); } /** * Trigger bbox option. */ -export function triggerBBoxActions (): void { +export function triggerBBoxActions(): void { endOptionsSelection(); setEditControls('moreEdit', Contents.bboxActionContents); addDeleteListener(); @@ -1073,7 +1291,7 @@ export function triggerBBoxActions (): void { /** * Trigger default actions when selecting by syl */ -export function triggerDefaultSylActions (): void { +export function triggerDefaultSylActions(): void { endOptionsSelection(); setEditControls('moreEdit', Contents.defaultSylActionContents); addDeleteListener(); @@ -1083,7 +1301,7 @@ export function triggerDefaultSylActions (): void { /** * Trigger default selection option. */ -export function triggerDefaultActions (): void { +export function triggerDefaultActions(): void { endOptionsSelection(); setEditControls('moreEdit', Contents.defaultActionContents); addDeleteListener(); @@ -1093,34 +1311,30 @@ export function triggerDefaultActions (): void { * Initialize extra dropdown options: * Listen to clicks on dropdowns */ -function initOptionsListeners (): void { - document - .querySelectorAll('.drop_select') - .forEach( - drop => { - // When anything that is not the dropdown is clicked away, the dropdown - // should lose its visibility - const optionsClickaway = () => { - document.body.removeEventListener('click', optionsClickaway); - drop.classList.remove('is-active'); - }; - - drop.addEventListener('click', (evt) => { - // Toggle visibility of dropdown - drop.classList.toggle('is-active'); - - // Remove visibility of other dropdowns when this one is clicked - Array.from(document.querySelectorAll('.drop_select')) - .filter(other => other !== drop) - .forEach(other => other.classList.remove('is-active')); - - // Don't allow other event listeners on the body to interfere with this listener - evt.stopPropagation(); - - if (drop.classList.contains('is-active')) - document.body.addEventListener('click', optionsClickaway); - else - document.body.removeEventListener('click', optionsClickaway); - }); - }); +function initOptionsListeners(): void { + document.querySelectorAll('.drop_select').forEach((drop) => { + // When anything that is not the dropdown is clicked away, the dropdown + // should lose its visibility + const optionsClickaway = () => { + document.body.removeEventListener('click', optionsClickaway); + drop.classList.remove('is-active'); + }; + + drop.addEventListener('click', (evt) => { + // Toggle visibility of dropdown + drop.classList.toggle('is-active'); + + // Remove visibility of other dropdowns when this one is clicked + Array.from(document.querySelectorAll('.drop_select')) + .filter((other) => other !== drop) + .forEach((other) => other.classList.remove('is-active')); + + // Don't allow other event listeners on the body to interfere with this listener + evt.stopPropagation(); + + if (drop.classList.contains('is-active')) + document.body.addEventListener('click', optionsClickaway); + else document.body.removeEventListener('click', optionsClickaway); + }); + }); } diff --git a/src/Types.ts b/src/Types.ts index 703e629f..821c823f 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -12,165 +12,173 @@ export type Attributes = { }; export type ClefAttributes = { - line: string - shape: 'C' | 'F' - 'dis.place'?: 'above' | 'below' - dis?: string + line: string; + shape: 'C' | 'F'; + 'dis.place'?: 'above' | 'below'; + dis?: string; +}; + +export type AddSylAction = { + action: 'addSyl'; + param: { + elementId: string; + sylText: string; + }; }; /** * Drag editing action sent to verovio as described [here](https://github.com/DDMAL/Neon/wiki/Toolkit-Actions). */ export type DragAction = { - action: 'drag', + action: 'drag'; param: { - elementId: string, - x: number, + elementId: string; + x: number; y: number; }; }; /** Resize editing action sent to verovio as described [here](https://github.com/DDMAL/Neon/wiki/Toolkit-Actions). */ export type ResizeAction = { - action: 'resize', + action: 'resize'; param: { - elementId: string, - ulx: number, - uly: number, - lrx: number, + elementId: string; + ulx: number; + uly: number; + lrx: number; lry: number; }; }; export type ResizeRotateAction = { - action: 'resizeRotate', + action: 'resizeRotate'; param: { - elementId: string, - ulx: number, - uly: number, - lrx: number, - lry: number, + elementId: string; + ulx: number; + uly: number; + lrx: number; + lry: number; rotate: number; }; }; export type InsertAction = { - action: 'insert', + action: 'insert'; param: { - elementType: string, - staffId: string | 'auto', - ulx?: number, - uly?: number, - lrx?: number, - lry?: number, + elementType: string; + staffId: string | 'auto'; + ulx?: number; + uly?: number; + lrx?: number; + lry?: number; // TODO: attributes are currently never used yet in Neon - attributes?: { shape: string; } | Record; + attributes?: { shape: string } | Record; }; }; export type InsertToSyllableAction = { - action: 'insertToSyllable', + action: 'insertToSyllable'; param: { elementId: string; }; }; export type MoveOutsideSyllableAction = { - action: 'moveOutsideSyllable', + action: 'moveOutsideSyllable'; param: { elementId: string; }; }; export type DisplaceClefOctaveAction = { - action: 'displaceClefOctave', + action: 'displaceClefOctave'; param: { - elementId: string, + elementId: string; direction: 'above' | 'below'; }; }; export type RemoveAction = { - action: 'remove', + action: 'remove'; param: { elementId: string; }; }; export type GroupingAction = { - action: 'group', + action: 'group'; param: { - groupType: 'neume' | 'nc', + groupType: 'neume' | 'nc'; elementIds: string[]; }; }; export type UngroupingAction = { - action: 'ungroup', + action: 'ungroup'; param: { - groupType: 'neume' | 'nc', + groupType: 'neume' | 'nc'; elementIds: string[]; }; }; export type SetAction = { - action: 'set', + action: 'set'; param: { - elementId: string, - attrType: string, + elementId: string; + attrType: string; attrValue: string; }; }; export type SetLiquescentAction = { - action: 'setLiquescent', + action: 'setLiquescent'; param: { - elementId: string, - curve: string, + elementId: string; + curve: string; }; }; export type MergeAction = { - action: 'merge', + action: 'merge'; param: { elementIds: string[]; }; }; export type SplitAction = { - action: 'split', + action: 'split'; param: { - elementId: string, + elementId: string; x: number; }; }; export type SplitNeumeAction = { - action: 'splitNeume', + action: 'splitNeume'; param: { - elementId: string, + elementId: string; ncId: string; }; }; export type SetTextAction = { - action: 'setText', + action: 'setText'; param: { - elementId: string, + elementId: string; text: string; }; }; export type SetClefAction = { - action: 'setClef', + action: 'setClef'; param: { - elementId: string, + elementId: string; shape: string; }; }; export type ToggleLigatureAction = { - action: 'toggleLigature', + action: 'toggleLigature'; param: { elementIds: string[]; }; @@ -178,39 +186,39 @@ export type ToggleLigatureAction = { // MIGHT BE USELESS: does not exist in Verovio export type ChangeSkewAction = { - action: 'changeSkew', + action: 'changeSkew'; param: { - elementId: string, - dy: number, + elementId: string; + dy: number; rightSide: boolean; }; }; export type ChangeStaffAction = { - action: 'changeStaff', + action: 'changeStaff'; param: { elementId: string; }; }; export type ChangeStaffToAction = { - action: 'changeStaffTo', + action: 'changeStaffTo'; param: { - elementId: string, - staffId: string, + elementId: string; + staffId: string; }; }; export type ChangeGroupAction = { - action: 'changeGroup', + action: 'changeGroup'; param: { - elementId: string, + elementId: string; contour: string; }; }; export type MatchHeightAction = { - action: 'matchHeight', + action: 'matchHeight'; param: { elementId: string; }; @@ -244,36 +252,36 @@ export type EditorAction = | MatchHeightAction; export type ChainAction = { - action: 'chain', + action: 'chain'; param: EditorAction[]; }; /** A message sent to the verovio web worker. */ export type VerovioMessage = { - action: string, - id: string, - mei?: string, - elementId?: string, + action: string; + id: string; + mei?: string; + elementId?: string; editorAction?: EditorAction; }; export type VerovioResponse = { - id: string, - svg?: string, - attributes?: Attributes, - result?: boolean, - mei?: string, + id: string; + svg?: string; + attributes?: Attributes; + result?: boolean; + mei?: string; info?: { - status: string, + status: string; message: string; }; }; /** Modeled after the [W3 Web Annotation Data Model.](https://www.w3.org/TR/annotation-model/) */ export type WebAnnotation = { - id: string, - type: string, - body: string, + id: string; + type: string; + body: string; target: string; }; @@ -281,36 +289,38 @@ export type WebAnnotation = { * see https://github.com/DDMAL/Neon/blob/gh-pages/contexts/1/manifest.JSON-LD */ export type NeonContext = { - schema: string, - title: string, - timestamp: string, + schema: string; + title: string; + timestamp: string; image: { - '@id': string, - '@type': string, - }, + '@id': string; + '@type': string; + }; mei_annotations: { - '@id': string, - '@type': string, + '@id': string; + '@type': string; '@container': string; }; }; /** Required fields in the JSON-LD Neon manifest. */ export type NeonManifest = { - '@context': Array | string, - '@id': string, - title: string, - timestamp: string, - image: string, + '@context': Array | string; + '@id': string; + title: string; + timestamp: string; + image: string; mei_annotations: WebAnnotation[]; }; - export type AllDocs = { - offset?: number, - total_rows?: number, + offset?: number; + total_rows?: number; rows?: { - doc?: PouchDB.Core.ExistingDocument & { type?: string; name?: string; }; + doc?: PouchDB.Core.ExistingDocument & { + type?: string; + name?: string; + }; id: string; key: string; value: { @@ -321,15 +331,15 @@ export type AllDocs = { }; export type Doc = { - _id: string, - name: string, - type: string, + _id: string; + name: string; + type: string; _attachments: { manifest: { - content_type: string, - data: Blob - } - }, + content_type: string; + data: Blob; + }; + }; }; export type uploadsInfo = { @@ -341,18 +351,50 @@ export type uploadsInfo = { export type HTMLSVGElement = HTMLElement & SVGSVGElement; /** "Selection By" type */ -export type SelectionType = 'selByStaff' | 'selByNeume' | 'selByNc' | 'selByLayerElement' | 'selBySyllable' | 'selByBBox' | 'selByLayerElement'; +export type SelectionType = + | 'selByStaff' + | 'selByNeume' + | 'selByNc' + | 'selByLayerElement' + | 'selBySyllable' + | 'selByBBox' + | 'selByLayerElement'; /** Highlight grouping type */ -export type GroupingType = 'column' | 'staff' | 'syllable' | 'neume' | 'layerElement' | 'selection' | 'none'; +export type GroupingType = + | 'column' + | 'staff' + | 'syllable' + | 'neume' + | 'layerElement' + | 'selection' + | 'none'; /** User mode type */ export type UserType = 'insert' | 'edit' | 'viewer'; /** Insert mode type */ -export type InsertType = 'punctum' | 'virga' | 'virgaReversed' | 'diamond' | 'custos' -| 'cClef' | 'fClef' | 'liquescentA' | 'liquescentC' | 'flat' | 'natural' | 'divLineMaxima' -| 'pes' | 'clivis' | 'scandicus' | 'climacus' | 'torculus' | 'porrectus' | 'pressus' | 'staff'; +export type InsertType = + | 'punctum' + | 'virga' + | 'virgaReversed' + | 'diamond' + | 'custos' + | 'cClef' + | 'fClef' + | 'liquescentA' + | 'liquescentC' + | 'flat' + | 'natural' + | 'divLineMaxima' + | 'pes' + | 'clivis' + | 'scandicus' + | 'climacus' + | 'torculus' + | 'porrectus' + | 'pressus' + | 'staff'; /** Insert tab type */ export type InsertTabType = 'primitiveTab' | 'groupingTab' | 'systemTab'; diff --git a/src/utils/ConvertMei.ts b/src/utils/ConvertMei.ts index 08d3e918..3419622c 100644 --- a/src/utils/ConvertMei.ts +++ b/src/utils/ConvertMei.ts @@ -290,10 +290,10 @@ export function convertToVerovio(sbBasedMei: string): string { invalidZonesInfo += `- Zone with xml:id: ${zone.getAttribute('xml:id')} `; if (element) { - element.parentNode.removeChild(element); invalidZonesInfo += `for <${ element.tagName }> with xml:id: ${element.getAttribute('xml:id')}\n`; + element.parentNode.removeChild(element); } else { invalidZonesInfo += 'is not linked with any glyphs\n'; } diff --git a/src/utils/ModalWindow.ts b/src/utils/ModalWindow.ts index 277a6562..3642a77a 100644 --- a/src/utils/ModalWindow.ts +++ b/src/utils/ModalWindow.ts @@ -1,35 +1,41 @@ import NeonView from '../NeonView'; -import { SetTextAction } from '../Types'; +import { AddSylAction, SetTextAction } from '../Types'; import { ModalWindowInterface } from '../Interfaces'; -import { hotkeysModal, editTextModal } from '../SquareEdit/Contents'; -import { newFolderHTML, renameHTML, uploadAreaHTML } from '../Dashboard/DashboardContent'; +import { + hotkeysModal, + editTextModal, + addTextModal, +} from '../SquareEdit/Contents'; +import { + newFolderHTML, + renameHTML, + uploadAreaHTML, +} from '../Dashboard/DashboardContent'; import DragHandler from './DragHandler'; import { selectBBox, unselect } from './SelectTools'; - /** * Defines modal types. * To create new modal window types, add enum option and implement logic inside Modal class. */ export enum ModalWindowView { + ADD_TEXT, EDIT_TEXT, HOTKEYS, ERROR_LOG, // for validation errors against MEI schema and invalid element errors DOCUMENT_UPLOAD, MOVE_TO, NEW_FOLDER, - RENAME + RENAME, } enum ModalWindowState { OPEN, - CLOSED + CLOSED, } - - /** - * Modal class is used to create and control state/content + * Modal class is used to create and control state/content * of modal windows in Neon. */ export class ModalWindow implements ModalWindowInterface { @@ -37,12 +43,11 @@ export class ModalWindow implements ModalWindowInterface { private modalWindowState: ModalWindowState; // open or closed? private neonView: NeonView; // neonView instance context - /** * Set neonView instance context for this modal window instance. * @param neonView neonView context for Modal instance */ - constructor (neonView?: NeonView) { + constructor(neonView?: NeonView) { this.neonView = neonView; this.modalWindowState = ModalWindowState.CLOSED; this.setupEventListeners(); @@ -52,21 +57,32 @@ export class ModalWindow implements ModalWindowInterface { * Set event listeners that apply to all modal windows */ private setupEventListeners() { - document.getElementById('neon-modal-window-header-close').addEventListener('click', this.hideModalWindow.bind(this)); - document.getElementById('neon-modal-window').addEventListener('keydown', this.keydownListener.bind(this)); - document.getElementById('neon-modal-window-container').addEventListener('click', this.focusModalWindow.bind(this)); + document + .getElementById('neon-modal-window-header-close') + .addEventListener('click', this.hideModalWindow.bind(this)); + document + .getElementById('neon-modal-window') + .addEventListener('keydown', this.keydownListener.bind(this)); + document + .getElementById('neon-modal-window-container') + .addEventListener('click', this.focusModalWindow.bind(this)); } /** * Remove event listeners associated with this modal window */ private removeEventListeners() { - document.getElementById('neon-modal-window-header-close').removeEventListener('click', this.hideModalWindow.bind(this)); - document.getElementById('neon-modal-window').removeEventListener('keydown', this.keydownListener.bind(this)); - document.getElementById('neon-modal-window-container').removeEventListener('click', this.focusModalWindow.bind(this)); + document + .getElementById('neon-modal-window-header-close') + .removeEventListener('click', this.hideModalWindow.bind(this)); + document + .getElementById('neon-modal-window') + .removeEventListener('keydown', this.keydownListener.bind(this)); + document + .getElementById('neon-modal-window-container') + .removeEventListener('click', this.focusModalWindow.bind(this)); } - /** * Set the modal view of this Modal instance. * Update the content based on passed view. @@ -77,7 +93,6 @@ export class ModalWindow implements ModalWindowInterface { this.setModalWindowContent(content); } - /** * Return the current modal view as a string */ @@ -85,38 +100,45 @@ export class ModalWindow implements ModalWindowInterface { return this.modalWindowView.toString(); } - /** * Open a model window with content representing the current ModalView. */ openModalWindow(): void { // make sure no other modal content is being displayed - Array.from(document.getElementsByClassName('neon-modal-window-content')).forEach((elem) => { + Array.from( + document.getElementsByClassName('neon-modal-window-content'), + ).forEach((elem) => { elem.classList.remove('visible'); }); - switch(this.modalWindowView) { - + switch (this.modalWindowView) { + case ModalWindowView.ADD_TEXT: + this.openAddSylTextModalWindow(); + break; + case ModalWindowView.EDIT_TEXT: this.openEditSylTextModalWindow(); break; - + case ModalWindowView.HOTKEYS: // set up and diplay hotkey modal content - document.getElementById('neon-modal-window-content-hotkeys').classList.add('visible'); + document + .getElementById('neon-modal-window-content-hotkeys') + .classList.add('visible'); case ModalWindowView.DOCUMENT_UPLOAD: - // add function to pairing button - // break; + // add function to pairing button + // break; case ModalWindowView.MOVE_TO: - - // break; + + // break; case ModalWindowView.NEW_FOLDER: case ModalWindowView.RENAME: default: - document.getElementById('neon-modal-window-container').style.display = 'flex'; + document.getElementById('neon-modal-window-container').style.display = + 'flex'; this.focusModalWindow(); break; } @@ -126,29 +148,34 @@ export class ModalWindow implements ModalWindowInterface { this.modalWindowState = ModalWindowState.OPEN; } - - /** * Hide the Neon modal window */ hideModalWindow(): void { - switch(this.modalWindowView) { + switch (this.modalWindowView) { case ModalWindowView.EDIT_TEXT: - const span = ( document.getElementById('syl_text').querySelectorAll('span.selected-to-edit')[0]); + const span = ( + document + .getElementById('syl_text') + .querySelectorAll('span.selected-to-edit')[0] + ); span.classList.remove('selected-to-edit'); break; - + case ModalWindowView.DOCUMENT_UPLOAD: case ModalWindowView.MOVE_TO: case ModalWindowView.NEW_FOLDER: case ModalWindowView.RENAME: - document.getElementById('neon-modal-window-content-container').innerHTML = ''; + document.getElementById( + 'neon-modal-window-content-container', + ).innerHTML = ''; break; default: break; - } - document.getElementById('neon-modal-window-container').style.display = 'none'; + } + document.getElementById('neon-modal-window-container').style.display = + 'none'; // reset scroll behavior of body document.body.style.overflowX = 'hidden'; document.body.style.overflowY = 'scroll'; @@ -156,27 +183,40 @@ export class ModalWindow implements ModalWindowInterface { this.removeEventListeners(); } - /** * Set content of modal window */ private setModalWindowContent(content?: string): void { - const container = document.getElementById('neon-modal-window-content-container'); + const container = document.getElementById( + 'neon-modal-window-content-container', + ); const title = document.getElementById('neon-modal-window-header-title'); switch (this.modalWindowView) { + case ModalWindowView.ADD_TEXT: + container.innerHTML = addTextModal; + // set modal window title + title.innerText = 'ENTER SYLLABLE TEXT'; + break; + case ModalWindowView.EDIT_TEXT: container.innerHTML = editTextModal; // set modal window title title.innerText = 'EDIT SYLLABLE TEXT'; // span and current text of selected-to-edit syllable and filter out unwanted chars - const span = document.getElementById('syl_text').querySelectorAll('span.selected-to-edit')[0]; + const span = ( + document + .getElementById('syl_text') + .querySelectorAll('span.selected-to-edit')[0] + ); const removeSymbol = /\u{25CA}/u; const orig = span.textContent.replace(removeSymbol, '').trim(); // set value of input field to current syllable text - ( (document.getElementById('neon-modal-window-edit-text-input'))).value = orig; + (( + document.getElementById('neon-modal-window-edit-text-input') + )).value = orig; break; case ModalWindowView.HOTKEYS: @@ -185,10 +225,11 @@ export class ModalWindow implements ModalWindowInterface { break; case ModalWindowView.ERROR_LOG: - container.innerHTML = - `
${content}
+ container.innerHTML = `
${content}
`; @@ -216,48 +257,146 @@ export class ModalWindow implements ModalWindowInterface { default: console.error('Unknown selection type. This should not have occurred.'); - } + } } + /** + * Fill modal window with content for adding syllable text + */ + private openAddSylTextModalWindow = function (): void { + // make sure no other modal content is being displayed + Array.from( + document.getElementsByClassName('neon-modal-window-content'), + ).forEach((elem) => { + elem.classList.remove('visible'); + }); + + // set up Add Syllable Text modal window + document + .getElementById('neon-modal-window-content-edit-text') + .classList.add('visible'); + + // Reset "Cancel" button event listener + document + .getElementById('neon-modal-window-edit-text-cancel') + .removeEventListener('click', this.hideModalWindow); + document + .getElementById('neon-modal-window-edit-text-cancel') + .addEventListener('click', this.hideModalWindow.bind(this)); + + // Reset "Save" button event listener + document + .getElementById('neon-modal-window-edit-text-add') + .removeEventListener('click', this.addSylText.bind(this)); + document + .getElementById('neon-modal-window-edit-text-add') + .addEventListener('click', this.addSylText.bind(this)); + + // display modal window + document.getElementById('neon-modal-window-container').style.display = + 'flex'; + this.focusModalWindow(); + }; /** * Fill modal window with content for updating syllable text */ - private openEditSylTextModalWindow = function(): void { + private openEditSylTextModalWindow = function (): void { // make sure no other modal content is being displayed - Array.from(document.getElementsByClassName('neon-modal-window-content')).forEach( (elem) => { + Array.from( + document.getElementsByClassName('neon-modal-window-content'), + ).forEach((elem) => { elem.classList.remove('visible'); }); // set up Edit Syllable Text modal window - document.getElementById('neon-modal-window-content-edit-text').classList.add('visible'); - + document + .getElementById('neon-modal-window-content-edit-text') + .classList.add('visible'); + // Reset "Cancel" button event listener - document.getElementById('neon-modal-window-edit-text-cancel').removeEventListener('click', this.hideModalWindow); - document.getElementById('neon-modal-window-edit-text-cancel').addEventListener('click', this.hideModalWindow.bind(this)); + document + .getElementById('neon-modal-window-edit-text-cancel') + .removeEventListener('click', this.hideModalWindow); + document + .getElementById('neon-modal-window-edit-text-cancel') + .addEventListener('click', this.hideModalWindow.bind(this)); // Reset "Save" button event listener - document.getElementById('neon-modal-window-edit-text-save').removeEventListener('click', this.updateSylText.bind(this)); - document.getElementById('neon-modal-window-edit-text-save').addEventListener('click', this.updateSylText.bind(this)); + document + .getElementById('neon-modal-window-edit-text-save') + .removeEventListener('click', this.updateSylText.bind(this)); + document + .getElementById('neon-modal-window-edit-text-save') + .addEventListener('click', this.updateSylText.bind(this)); // display modal window - document.getElementById('neon-modal-window-container').style.display = 'flex'; + document.getElementById('neon-modal-window-container').style.display = + 'flex'; this.focusModalWindow(); }; + /** + * Update text of selected-to-edit syllables with user-provided text + */ + private addSylText = function () { + // Get the element ID of the selected syllable + const elementId = + document.getElementsByClassName('syllable selected')[0].id; + + const sylText = (( + document.getElementById('neon-modal-window-edit-text-input') + )).value; + + const editorAction: AddSylAction = { + action: 'addSyl', + param: { + elementId: elementId, + sylText: sylText, + }, + }; + + // send action to verovio for processing + this.neonView + .edit(editorAction, this.neonView.view.getCurrentPageURI()) + .then((response: boolean) => { + if (response) { + // update the SVG + this.neonView.updateForCurrentPage().then(() => { + // An update to the page will reload the entire svg; + // We would like to then reselect the same selected syllable + // if bboxes are enabled + // this.updateSelectedBBox(span, this.dragHandler, this.neonView); + }); + } + }); + + // close modal window + this.hideModalWindow(); + }; + /** * Update text of selected-to-edit syllables with user-provided text */ private updateSylText = function () { // span and current text of selected-to-edit syllable and filter out unwanted chars - const span = document.getElementById('syl_text').querySelectorAll('span.selected-to-edit')[0]; + const span = ( + document + .getElementById('syl_text') + .querySelectorAll('span.selected-to-edit')[0] + ); const removeSymbol = /\u{25CA}/u; const orig = span.textContent.replace(removeSymbol, '').trim(); - const updatedSylText = ( document.getElementById('neon-modal-window-edit-text-input')).value; + const updatedSylText = (( + document.getElementById('neon-modal-window-edit-text-input') + )).value; if (updatedSylText !== null && updatedSylText !== orig) { // create "set text" action - const elementId = [...span.classList.entries()].filter((className) => className[1] !== 'text-select' && className[1] !== 'selected-to-edit')[0][1]; + const elementId = [...span.classList.entries()].filter( + (className) => + className[1] !== 'text-select' && className[1] !== 'selected-to-edit', + )[0][1]; const editorAction: SetTextAction = { action: 'setText', param: { @@ -266,17 +405,19 @@ export class ModalWindow implements ModalWindowInterface { }, }; // send action to verovio for processing - this.neonView.edit(editorAction, this.neonView.view.getCurrentPageURI()).then((response: boolean) => { - if (response) { - // update the SVG - this.neonView.updateForCurrentPage().then(() => { - // An update to the page will reload the entire svg; - // We would like to then reselect the same selected syllable - // if bboxes are enabled - this.updateSelectedBBox(span, this.dragHandler, this.neonView); - }); - } - }); + this.neonView + .edit(editorAction, this.neonView.view.getCurrentPageURI()) + .then((response: boolean) => { + if (response) { + // update the SVG + this.neonView.updateForCurrentPage().then(() => { + // An update to the page will reload the entire svg; + // We would like to then reselect the same selected syllable + // if bboxes are enabled + this.updateSelectedBBox(span, this.dragHandler, this.neonView); + }); + } + }); } else { // reselect if no change is made this.updateSelectedBBox(span, this.dragHandler, this.neonView); @@ -286,27 +427,34 @@ export class ModalWindow implements ModalWindowInterface { }; /** - * Update the bounding box selected when the edit text modal has been clicked - */ - updateSelectedBBox (span: HTMLSpanElement, dragHandler: DragHandler, neonView: NeonView): void { + * Update the bounding box selected when the edit text modal has been clicked + */ + updateSelectedBBox( + span: HTMLSpanElement, + dragHandler: DragHandler, + neonView: NeonView, + ): void { unselect(); - const bboxId = Array.from(span.classList).find(e => e !== 'text-select' && e !== 'selected-to-edit'); + const bboxId = Array.from(span.classList).find( + (e) => e !== 'text-select' && e !== 'selected-to-edit', + ); if ((document.getElementById('displayBBox') as HTMLInputElement).checked) { if (document.getElementById(bboxId)) { - const displayRect = document.getElementById(bboxId).querySelector('.sylTextRect-display') as SVGGraphicsElement; + const displayRect = document + .getElementById(bboxId) + .querySelector('.sylTextRect-display') as SVGGraphicsElement; selectBBox(displayRect, dragHandler, neonView); } } } - /** * Fill modal window with hotkey info content */ /* - private openHotkeyModalWindow = function() { + private openHotkeyModalWindow = function() { // make sure no other modal content is being displayed Array.from(document.getElementsByClassName('neon-modal-window-content')).forEach((elem) => { elem.classList.remove('visible'); @@ -320,30 +468,38 @@ export class ModalWindow implements ModalWindowInterface { }; */ - /** * Define event listeners for modal window based on modalView type */ - private keydownListener = function(e) { + private keydownListener = function (e) { e.stopImmediatePropagation(); // prevent Neon hotkey events from firing when user is typing - switch(this.modalWindowView) { + switch (this.modalWindowView) { + case ModalWindowView.ADD_TEXT: + if (e.key === 'Enter') this.addSylText(); + if (e.key === 'Escape') this.hideModalWindow(); + break; + case ModalWindowView.EDIT_TEXT: if (e.key === 'Enter') this.updateSylText(); + if (e.key === 'Escape') this.hideModalWindow(); + break; default: if (e.key === 'Escape') this.hideModalWindow(); - } + } }; - /** * Event listener that focuses the modal window if user clicks anywhere outside of it */ - private focusModalWindow = function() { - switch(this.modalWindowView) { + private focusModalWindow = function () { + switch (this.modalWindowView) { + case ModalWindowView.ADD_TEXT: case ModalWindowView.EDIT_TEXT: - ( document.getElementById('neon-modal-window-edit-text-input')).select(); + (( + document.getElementById('neon-modal-window-edit-text-input') + )).select(); break; case ModalWindowView.DOCUMENT_UPLOAD: case ModalWindowView.NEW_FOLDER: @@ -353,5 +509,4 @@ export class ModalWindow implements ModalWindowInterface { document.getElementById('neon-modal-window').focus(); } }; - } diff --git a/src/utils/SelectTools.ts b/src/utils/SelectTools.ts index 2fc9fbfd..bd62b8a5 100644 --- a/src/utils/SelectTools.ts +++ b/src/utils/SelectTools.ts @@ -1,14 +1,12 @@ import * as Color from './Color'; -import { - updateHighlight, - getHighlightType, -} from '../DisplayPanel/DisplayControls'; +import { updateHighlight } from '../DisplayPanel/DisplayControls'; import * as Grouping from '../SquareEdit/Grouping'; import { resize } from './Resize'; import { Attributes, SelectionType } from '../Types'; import NeonView from '../NeonView'; import DragHandler from './DragHandler'; import * as SelectOptions from '../SquareEdit/SelectOptions'; +import * as Notification from '../utils/Notification'; import * as d3 from 'd3'; /** @@ -713,7 +711,18 @@ export async function selectAll( switch (groups.length) { case 1: // TODO change context if it is only a neume/nc. - SelectOptions.triggerSyllableActions('singleSelect'); + // Check if the selected syllable contains a element + if (groups[0].querySelector('.syl > rect') === null) { + Notification.queueNotification( + 'The selected syllable does not contain a <syl> element, please insert one using button "Add Syllable Text".', + 'warning', + ); + SelectOptions.triggerSyllableActions('noSyl'); + SelectOptions.addAddSylListener(); + } else { + SelectOptions.triggerSyllableActions('singleSelect'); + } + break; default: