@@ -712,16 +905,14 @@ export function triggerSyllableActions (selectionType: string): void {
//default options
case 'default':
- extraActionsHTML +=
- `
+ extraActionsHTML += `
Re-associate to nearest staff
Delete
`;
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: