(inner) undo
- Undo the last editor action.
+ Update the PouchDb database stored in the browser.
+This is based on the data stored in the cache. To save time,
+only entries marked as dirty will be updated.
@@ -1183,7 +2179,7 @@
/**
- * Underlying NeonCore class that communicates with Verovio.
- * @constructor
- * @param {string} mei - Contents of the MEI file.
- * @param {object} vrvToolkit - An instantiated Verovio toolkit.
- */
-function NeonCore (mei, vrvToolkit) {
- /// ///////////
- // Constructor
- /// ///////////
- var vrvOptions = {
- noFooter: 1,
- noHeader: 1,
- pageMarginLeft: 0,
- pageMarginTop: 0,
- font: 'Bravura'
- };
- var undoStack = new Array(0);
- var redoStack = new Array(0);
-
- vrvToolkit.setOptions(vrvOptions);
- loadData(mei);
+
import * as Validation from './Validation.js';
+import PouchDb from 'pouchdb';
+
+const verovio = require('verovio-dev');
+/**
+ * The core component of Neon. This manages the database,
+ * the verovio toolkit, the cache, and undo/redo stacks.
+ */
+class NeonCore {
/**
- * Load MEI data into Verovio.
- * @param {string} data - MEI data.
+ * Constructor for NeonCore
+ * @param {Map<number, string>} meiMap - Map of zero-indexed page no to MEI.
+ * @param {string} title - The title of the page or manuscript.
+ * @returns {object} A NeonCore object.
+ */
+ constructor (meiMap, title) {
+ this.vrvToolkit = new verovio.toolkit();
+ this.vrvToolkit.setOptions({
+ inputFormat: 'mei',
+ noFooter: 1,
+ noHeader: 1,
+ pageMarginLeft: 0,
+ pageMarginTop: 0,
+ font: 'Bravura'
+ });
+
+ Validation.init();
+
+ /**
+ * Stacks of previous MEI files representing actions that can be undone for each page.
+ * @type {Map.<number, Array.<string>>}
+ */
+ this.undoStacks = new Map();
+
+ /**
+ * Stacks of previous MEI files representing actions that can be redone for each page.
+ * @type {Map.<number, Array.<string>>}
+ */
+ this.redoStacks = new Map();
+
+ /**
+ * A cache entry.
+ * @typedef {Object} CacheEntry
+ * @property {boolean} dirty - If the entry has been modified since being fetched from the database.
+ * @property {string} mei - The MEI data for the page.
+ * @property {SVGSVGElement} svg - The rendered SVG for the page.
*/
- function loadData (data) {
- vrvToolkit.loadData(data);
+
+ /**
+ * A cache mapping a page number to a {@link CacheEntry}.
+ * @type {Map.<number, CacheEntry>}
+ */
+ this.neonCache = new Map();
+
+ this.parser = new DOMParser();
+
+ this.db = new PouchDb(title);
+ // Add each MEI to the database
+ this.meiMap = meiMap;
}
/**
- * Get the SVG from Verovio.
- * @returns {string}
- */
- function getSVG () {
- return vrvToolkit.renderToSVG(1);
+ * Initialize the PouchDb database based on the provided MEI.
+ * This should only be run if previous data does not exist.
+ */
+ async initDb () {
+ for (let pair of this.meiMap) {
+ let key = pair[0];
+ let value = pair[1];
+ await this.db.get(key.toString()).catch((err) => {
+ if (err.name === 'not_found') {
+ // Create new document
+ return {
+ _id: key.toString(),
+ data: ''
+ };
+ } else {
+ throw err;
+ }
+ }).then((doc) => {
+ doc.data = value;
+ return this.db.put(doc);
+ }).catch((err) => {
+ console.error(err);
+ });
+ }
}
/**
- * Get the MEI data from Verovio.
- * @returns {string}
- */
- function getMEI () {
- return vrvToolkit.getMEI(0, true);
+ * Load a page into the verovio toolkit. This will fetch the
+ * page from the cache or from the database.
+ * @param {number} pageNo - The zero-indexed page number to load.
+ * @returns {Promise} A promise that resolves to the cache entry.
+ */
+ loadPage (pageNo) {
+ return new Promise((resolve, reject) => {
+ if (this.currentPage !== pageNo) {
+ if (this.neonCache.has(pageNo)) {
+ this.loadData(pageNo, this.neonCache.get(pageNo).mei);
+ resolve(this.neonCache.get(pageNo));
+ } else {
+ this.db.get(pageNo.toString()).then((doc) => {
+ this.loadData(pageNo, doc.data);
+ resolve(this.neonCache.get(pageNo));
+ }).catch((err) => {
+ reject(err);
+ });
+ }
+ } else {
+ resolve(this.neonCache.get(pageNo));
+ }
+ });
}
/**
- * Get MEI element attributes from Verovio.
- * @param {string} elementId - The ID of the MEI element.
- * @returns {object}
- */
- function getElementAttr (elementId) {
- return vrvToolkit.getElementAttr(elementId);
+ * Load data into the verovio toolkit and update the cache.
+ * @param {number} pageNo - The zero-indexed page number.
+ * @param {string} data - The MEI of the page as a string.
+ * @param {boolean} [dirty] - If the cache entry should be marked as dirty. Defaults to false.
+ */
+ loadData (pageNo, data, dirty = false) {
+ Validation.sendForValidation(data);
+ let svg = this.parser.parseFromString(
+ this.vrvToolkit.renderData(data, {}),
+ 'image/svg+xml'
+ ).documentElement;
+ this.neonCache.set(pageNo, {
+ svg: svg,
+ mei: data,
+ dirty: dirty
+ });
+ this.currentPage = pageNo;
}
/**
- * Execute an editor action in Verovio.
- * @param {object} editorAction - The action to execute.
- * @param {boolean} [addToUndo=true] - Whether or not to make this action undoable.
- * @returns {boolean}
- */
- function edit (editorAction, addToUndo = true) {
- let currentMEI = getMEI();
- // console.log(editorAction); // Useful for debugging actions
- let value = vrvToolkit.edit(editorAction);
- if (value && addToUndo) {
- undoStack.push(currentMEI);
- redoStack = new Array(0);
- }
- return value;
+ * Get the SVG for a specific page number.
+ * @param {number} pageNo - The zero-indexed page number.
+ * @returns {Promise} A promise that resolves to the SVG.
+ */
+ getSVG (pageNo) {
+ return new Promise((resolve, reject) => {
+ this.loadPage(pageNo).then((entry) => {
+ resolve(entry.svg);
+ }).catch((err) => { reject(err); });
+ });
}
- function addStateToUndo () {
- undoStack.push(getMEI());
+ /**
+ * Get the MEI for a specific page number.
+ * @param {number} pageNo - The zero-indexed page number.
+ * @returns {Promise} A promise that resolves to the MEI as a string.
+ */
+ getMEI (pageNo) {
+ return new Promise((resolve, reject) => {
+ this.loadPage(pageNo).then((entry) => {
+ resolve(entry.mei);
+ }).catch((err) => { reject(err); });
+ });
}
/**
- * Get additional information on the last editor action from Verovio.
- * @returns {string}
- */
- function info () {
- return vrvToolkit.editInfo();
+ * Get musical element attributes from the verovio toolkit.
+ * @param {string} elementId - The unique ID of the musical element.
+ * @param {number} pageNo - The zero-indexed page number the element is on.
+ * @returns {Promise} A promise that resolves to the attributes in an object.
+ */
+ getElementAttr (elementId, pageNo) {
+ return new Promise((resolve) => {
+ this.loadPage(pageNo).then(() => {
+ resolve(this.vrvToolkit.getElementAttr(elementId));
+ });
+ });
}
/**
- * Undo the last editor action.
- * @returns {boolean}
- */
- function undo () {
- let state = undoStack.pop();
- if (state !== undefined) {
- redoStack.push(getMEI());
- loadData(state);
- return true;
+ * Perform an editor action on a specific page.
+ * @param {object} action - The editor toolkit action object.
+ * @param {string} action.action - The name of the action to perform.
+ * @param {object|array} action.param - The parameters of the action(s)
+ * @param {number} pageNo - The zero-indexed page number to perform the action on.
+ * @returns {boolean} If the action succeeded or not.
+ */
+ async edit (editorAction, pageNo) {
+ if (this.currentPage !== pageNo) {
+ await this.loadPage(pageNo);
+ }
+ let currentMEI = this.getMEI(pageNo);
+ let result = this.vrvToolkit.edit(editorAction);
+ if (result) {
+ if (!this.undoStacks.has(pageNo)) {
+ this.undoStacks.set(pageNo, []);
+ }
+ this.undoStacks.get(pageNo).push(await currentMEI);
+ this.redoStacks.set(pageNo, []);
+
+ // Update cache
+ this.neonCache.set(pageNo, {
+ mei: this.vrvToolkit.getMEI(0, true),
+ svg: this.parser.parseFromString(this.vrvToolkit.renderToSVG(1),
+ 'image/svg+xml').documentElement,
+ dirty: true
+ });
+ }
+ return result;
+ }
+
+ /**
+ * Get the edit info string from the verovio toolkit.
+ * @returns {string}
+ */
+ info () {
+ return this.vrvToolkit.editInfo();
+ }
+
+ /**
+ * Undo the last action performed on a specific page.
+ * @param {number} pageNo - The zero-indexed page number.
+ * @returns {boolean} If an action undone.
+ */
+ undo (pageNo) {
+ if (this.undoStacks.has(pageNo)) {
+ let state = this.undoStacks.get(pageNo).pop();
+ if (state !== undefined) {
+ this.getMEI(0).then((mei) => {
+ this.redoStacks.get(pageNo).push(mei);
+ });
+ this.loadData(pageNo, state, true);
+ return true;
+ }
}
return false;
}
/**
- * Redo an undone action.
- * @returns {boolean}
- */
- function redo () {
- let state = redoStack.pop();
- if (state !== undefined) {
- undoStack.push(getMEI());
- loadData(state);
- return true;
+ * Redo the last action performed on a page.
+ * @param {number} pageNo - The zero-indexed page number.
+ * @returns {boolean} If an action was redone or not.
+ */
+ redo (pageNo) {
+ if (this.redoStacks.has(pageNo)) {
+ let state = this.redoStacks.get(pageNo).pop();
+ if (state !== undefined) {
+ this.getMEI(0).then((mei) => {
+ this.undoStacks.get(pageNo).push(mei);
+ });
+ this.loadData(pageNo, state, true);
+ return true;
+ }
}
return false;
}
- // Constructor reference
- NeonCore.prototype.constructor = NeonCore;
- NeonCore.prototype.loadData = loadData;
- NeonCore.prototype.getSVG = getSVG;
- NeonCore.prototype.getMEI = getMEI;
- NeonCore.prototype.getElementAttr = getElementAttr;
- NeonCore.prototype.edit = edit;
- NeonCore.prototype.info = info;
- NeonCore.prototype.undo = undo;
- NeonCore.prototype.redo = redo;
- NeonCore.prototype.addStateToUndo = addStateToUndo;
+ /**
+ * Update the PouchDb database stored in the browser.
+ * This is based on the data stored in the cache. To save time,
+ * only entries marked as dirty will be updated.
+ */
+ async updateDatabase () {
+ for (let pair of this.neonCache) {
+ let key = pair[0];
+ let value = pair[1];
+ if (value.dirty) {
+ await this.db.get(key.toString()).then((doc) => {
+ doc.data = value.mei;
+ return this.db.put(doc);
+ }).then(() => {
+ console.log('done');
+ value.dirty = false;
+ }).catch((err) => {
+ console.error(err);
+ });
+ }
+ }
+ }
}
export { NeonCore as default };
@@ -163,13 +314,13 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/doc/Contents.js.html b/doc/SingleEdit_Contents.js.html
similarity index 83%
rename from doc/Contents.js.html
rename to doc/SingleEdit_Contents.js.html
index 8bbbb45be..3e63dad06 100644
--- a/doc/Contents.js.html
+++ b/doc/SingleEdit_Contents.js.html
@@ -2,7 +2,7 @@
- JSDoc: Source: Contents.js
+ JSDoc: Source: SingleEdit/Contents.js
@@ -17,7 +17,7 @@
-
Source: Contents.js
+
Source: SingleEdit/Contents.js
@@ -26,29 +26,29 @@
Source: Contents.js
-
/** @module Contents */
+
/** @module SingleEdit/Contents */
-import PunctumIcon from './img/punctum.png';
-import VirgaIcon from './img/virga.png';
-import DiamondIcon from './img/diamond.png';
+import PunctumIcon from '../img/punctum.png';
+import VirgaIcon from '../img/virga.png';
+import DiamondIcon from '../img/diamond.png';
// import WhitePunctumIcon from "./img/white_punct.png";
// import QuilismaIcon from "./img/quilisma.png";
-import CustosIcon from './img/custos.png';
-import CClefIcon from './img/cClef.png';
-import FClefIcon from './img/fClef.png';
-import StaffIcon from './img/staff.png';
+import CustosIcon from '../img/custos.png';
+import CClefIcon from '../img/cClef.png';
+import FClefIcon from '../img/fClef.png';
+import StaffIcon from '../img/staff.png';
// import SmallDivIcon from "./img/smallDiv.png";
// import MinorDivIcon from "./img/minorDiv.png";
// import MajorDivIcon from "./img/majorDiv.png";
// import FinalDivIcon from "./img/finalDiv.png";
-import PesIcon from './img/pes.png';
-import ClivisIcon from './img/clivis.png';
-import ScandicusIcon from './img/scandicus.png';
-import ClimacusIcon from './img/climacus.png';
-import TorculusIcon from './img/torculus.png';
-import PorrectusIcon from './img/porrectus.png';
-import PressusIcon from './img/pressus.png';
-import Icons from './img/icons.svg';
+import PesIcon from '../img/pes.png';
+import ClivisIcon from '../img/clivis.png';
+import ScandicusIcon from '../img/scandicus.png';
+import ClimacusIcon from '../img/climacus.png';
+import TorculusIcon from '../img/torculus.png';
+import PorrectusIcon from '../img/porrectus.png';
+import PressusIcon from '../img/pressus.png';
+import Icons from '../img/icons.svg';
/**
* HTML for each insert tab (neume, grouping, clef, system, and division).
@@ -107,7 +107,6 @@
/** @module SingleEdit/Grouping */
import * as Contents from './Contents.js';
-import * as Warnings from './Warnings.js';
-import * as Notification from './Notification.js';
+import * as Warnings from '../Warnings.js';
+import * as Notification from '../utils/Notification.js';
import { unsetVirgaAction, unsetInclinatumAction } from './SelectOptions.js';
-import InfoBox from './InfoBox.js';
+import InfoModule from '../InfoModule.js';
const $ = require('jquery');
/**
@@ -43,6 +43,7 @@
Source: Grouping.js
/**
* Set the neonView member.
+ * @param {NeonView} view
*/
export function initNeonView (view) {
neonView = view;
@@ -71,7 +72,9 @@
Source: Grouping.js
*/
export function initGroupingListeners () {
$('#mergeSyls').on('click', function () {
- var elementIds = getChildrenIds();
+ var elementIds = getChildrenIds().filter(e =>
+ document.getElementById(e).classList.contains('neume')
+ );
groupingAction('group', 'neume', elementIds);
});
@@ -94,7 +97,7 @@
Source: Grouping.js
var elementIds = getChildrenIds();
groupingAction('ungroup', 'nc', elementIds);
});
- $('#toggle-ligature').on('click', function () {
+ $('#toggle-ligature').on('click', async function () {
var elementIds = getIds();
var isLigature;
let ligatureRegex = /#E99[016]/;
@@ -107,7 +110,7 @@
diff --git a/doc/ResizeStaff.js.html b/doc/SingleEdit_ResizeStaff.js.html
similarity index 67%
rename from doc/ResizeStaff.js.html
rename to doc/SingleEdit_ResizeStaff.js.html
index 0ba5cc919..863b89d9f 100644
--- a/doc/ResizeStaff.js.html
+++ b/doc/SingleEdit_ResizeStaff.js.html
@@ -2,7 +2,7 @@
- JSDoc: Source: ResizeStaff.js
+ JSDoc: Source: SingleEdit/ResizeStaff.js
@@ -17,7 +17,7 @@
-
Source: ResizeStaff.js
+
Source: SingleEdit/ResizeStaff.js
@@ -28,7 +28,7 @@
Source: ResizeStaff.js
/**
* Support for resizing the staff by creating a resizable box around it.
- * @module ResizeStaff
+ * @module SingleEdit/ResizeStaff
*/
import { selectStaff } from './Select.js';
@@ -172,16 +172,18 @@
diff --git a/doc/Select.js.html b/doc/SingleEdit_Select.js.html
similarity index 65%
rename from doc/Select.js.html
rename to doc/SingleEdit_Select.js.html
index a17f6bf60..c10d9a0f8 100644
--- a/doc/Select.js.html
+++ b/doc/SingleEdit_Select.js.html
@@ -2,7 +2,7 @@
- JSDoc: Source: Select.js
+ JSDoc: Source: SingleEdit/Select.js
@@ -17,7 +17,7 @@
-
Source: Select.js
+
Source: SingleEdit/Select.js
@@ -26,138 +26,144 @@
Source: Select.js
-
/** @module Select */
+
/** @module SingleEdit/Select */
-import * as Color from './Color.js';
-import * as Controls from './Controls.js';
+import * as Color from '../utils/Color.js';
+import { updateHighlight } from '../SingleView/DisplayControls.js';
+import { initSelectionButtons } from './EditControls.js';
import * as Grouping from './Grouping.js';
import * as SelectOptions from './SelectOptions.js';
import Resize from './ResizeStaff.js';
+
const d3 = require('d3');
const $ = require('jquery');
+var dragHandler, neonView, info, zoomHandler;
+
/**
- * Handle click selection and mark elements as selected.
- * @constructor
- * @param {DragHandler} dragHandler - An instantiated DragHandler object.
- * @param {module:Zoom~Zoomhandler} zoomHandler
- * @param {NeonView} neonView - The NeonView parent.
- * @param {NeonCore} neonCore
- * @param {InfoBox} infoBox
+ * Get the selection mode chosen by the user.
+ * @returns {string|null}
*/
-export function ClickSelect (dragHandler, zoomHandler, neonView, neonCore, infoBox) {
- selectListeners();
-
- // Selection mode toggle
- function selectListeners () {
- var classesToSelect = '#svg_group use, #svg_group';
- Controls.initSelectionButtons();
-
- // Activating selected neumes
- $(classesToSelect).off('mousedown', handler);
- $(classesToSelect).on('mousedown', handler);
-
- function handler (evt) {
- var editing = false;
- var insertEls = Array.from(d3.selectAll('.insertel')._groups[0]);
- insertEls.forEach(el => {
- if ($(el).hasClass('is-active')) {
- editing = true;
- }
- });
- if (editing || evt.shiftKey) { return; }
- if (this.tagName === 'use') {
- // If this was part of a drag select, drag don't reselect the one component
- if ($(this).parents('.selected').length === 0) {
- selectAll([this], neonCore, neonView, dragHandler, infoBox);
- }
- } else {
- if (!$('#selByStaff').hasClass('is-active')) {
- infoBox.infoListeners();
- return;
- }
- // Check if point is in staff.
- var container = document.getElementsByClassName('definition-scale')[0];
- let pt = container.createSVGPoint();
- pt.x = evt.clientX;
- pt.y = evt.clientY;
- let transformMatrix = container.getScreenCTM();
- pt = pt.matrixTransform(transformMatrix.inverse());
-
- let selectedStaves = Array.from($('.staff')).filter((staff) => {
- let box = getStaffBBox(staff);
- return (box.ulx < pt.x && pt.x < box.lrx) && (box.uly < pt.y && pt.y < box.lry);
- });
- if (selectedStaves.length !== 1) {
- if ($('.selected').length > 0) {
- infoBox.infoListeners();
- }
- unselect();
- return;
- }
+function getSelectionType () {
+ let element = document.getElementsByClassName('sel-by active');
+ if (element.length !== 0) {
+ return element[0].id;
+ } else {
+ return null;
+ }
+}
- var staff = selectedStaves[0];
- if (!$(staff).hasClass('selected')) {
- selectStaff(staff, dragHandler);
- SelectOptions.triggerSplitActions();
- let resize = new Resize(staff.id, neonView, dragHandler);
- resize.drawInitialRect();
- // Start staff dragging
- dragHandler.dragInit();
- }
- staff.dispatchEvent(new MouseEvent('mousedown', {
- screenX: evt.screenX,
- screenY: evt.screenY,
- clientX: evt.clientX,
- clientY: evt.clientY,
- ctrlKey: evt.ctrlKey,
- shiftKey: evt.shiftKey,
- altKey: evt.altKey,
- metaKey: evt.metaKey,
- view: evt.view
- }));
- }
- }
+/**
+ * Set the objects for this module.
+ * @param {DragHandler} dh - The drag handler object
+ * @param {NeonView} nv - The NeonView object
+ */
+export function setSelectHelperObjects (dh, nv) {
+ dragHandler = dh;
+ neonView = nv;
+ info = neonView.InfoModule;
+ zoomHandler = neonView.view.zoomHandler;
+
+ initSelectionButtons();
+ neonView.view.addUpdateCallback(clickSelect);
+ neonView.view.addUpdateCallback(dragSelect);
+}
- // click away listeners
- $('body').on('keydown', (evt) => { // click
- if (evt.type === 'keydown' && evt.key !== 'Escape') return;
- SelectOptions.endOptionsSelection();
+/**
+ * Apply listeners for click selection.
+ */
+export function clickSelect () {
+ $('#svg_group, #svg_group use').off('mousedown', clickHandler);
+ $('#svg_group, #svg_group use').on('mousedown', clickHandler);
+
+ // Click away listeners
+ $('body').on('keydown', (evt) => {
+ if (evt.key === 'Escape') {
if ($('.selected').length > 0) {
- infoBox.infoListeners();
+ info.infoListeners();
}
unselect();
- });
+ }
+ });
- $('use').on('click', function (e) {
- e.stopPropagation();
- });
+ $('use').on('click', (e) => { e.stopPropagation(); });
+ $('#moreEdit').on('click', (e) => { e.stopPropagation(); });
+}
- $('#moreEdit').on('click', function (e) {
- e.stopPropagation();
+/**
+ * Handle click events related to element selection.
+ * @param {object} evt
+ */
+function clickHandler (evt) {
+ let mode = neonView.getUserMode();
+
+ // If in insert mode or panning is active from shift key
+ if (mode === 'insert' || evt.shiftKey) { return; }
+
+ // Check if the element being clicked on is part of a drag Selection
+ if (this.tagName === 'use') {
+ if ($(this).parents('.selected').length === 0) {
+ selectAll([this]);
+ }
+ } else {
+ // Check if the point being clicked on is a staff selection (if applicable)
+ if (getSelectionType() !== 'selByStaff') {
+ info.infoListeners();
+ return;
+ }
+
+ // Check if the point is in a staff.
+ let container = document.getElementsByClassName('definition-scale')[0];
+ let pt = container.createSVGPoint();
+ pt.x = evt.clientX;
+ pt.y = evt.clientY;
+ let transformMatrix = container.getScreenCTM();
+ pt = pt.matrixTransform(transformMatrix.inverse());
+
+ let selectedStaves = Array.from($('.staff')).filter((staff) => {
+ let bbox = getStaffBBox(staff);
+ return (bbox.ulx < pt.x && pt.x < bbox.lrx) && (bbox.uly < pt.y && pt.y < bbox.lry);
});
+ if (selectedStaves.length !== 1) {
+ if ($('.selected').length > 0) {
+ info.infoListeners();
+ }
+ unselect();
+ return;
+ }
+
+ // Select a staff
+ let staff = selectedStaves[0];
+ if (!staff.classList.contains('selected')) {
+ // Select previously unselected staff
+ selectStaff(staff, this.dragHandler);
+ let resize = new Resize(staff.id, neonView, dragHandler);
+ resize.drawInitialRect();
+ this.dragHandler.dragInit();
+ }
+ // Trigger mousedown event on the staff
+ staff.dispatchEvent(new MouseEvent('mousedown', {
+ screenX: evt.screenX,
+ screenY: evt.screenY,
+ clientX: evt.clientX,
+ clientY: evt.clientY,
+ ctrlKey: evt.ctrlKey,
+ shiftKey: evt.shiftKey,
+ altKey: evt.altKey,
+ metaKey: evt.metaKey,
+ view: evt.view
+ }));
}
- ClickSelect.prototype.selectListeners = selectListeners;
}
/**
- * Handle dragging to select musical elements and staves.
- * @constructor
- * @param {DragHandler} dragHandler - Instantiated DragHandler object.
- * @param {module:Zoom~ZoomHandler} zoomHandler - Instantiated ZoomHandler object.
- * @param {NeonView} neonView - NeonView parent.
- * @param {NeonCore} neonCore
- * @param {InfoBox} infoBox
+ * Apply listeners for drag selection.
*/
-export function DragSelect (dragHandler, zoomHandler, neonView, neonCore, infoBox) {
+export function dragSelect () {
var initialX = 0;
-
var initialY = 0;
-
var panning = false;
-
var dragSelecting = false;
-
var canvas = d3.select('#svg_group');
var dragSelectAction = d3.drag()
.on('start', selStart)
@@ -166,23 +172,14 @@
Source: Select.js
canvas.call(dragSelectAction);
dragHandler.resetTo(dragSelectAction);
- /**
- * Start drag selecting musical elements.
- */
function selStart () {
- var editing = false;
- var insertEls = Array.from(d3.selectAll('.insertel')._groups[0]);
- insertEls.forEach(el => {
- if ($(el).hasClass('is-active')) {
- editing = true;
- }
- });
- if (d3.event.sourceEvent.target.nodeName !== 'use' && !editing) {
- if (!d3.event.sourceEvent.shiftKey) {
+ let userMode = neonView.getUserMode();
+ if (d3.event.sourceEvent.target.nodeName !== 'use' && userMode !== 'insert') {
+ if (!d3.event.sourceEvent.shiftKey) { // If not holding down shift key to pan
if (!$('#selByStaff').hasClass('is-active') || pointNotInStaff(d3.mouse(this))) {
unselect();
dragSelecting = true;
- var initialP = d3.mouse(this);
+ let initialP = d3.mouse(this);
initialX = initialP[0];
initialY = initialP[1];
initRect(initialX, initialY);
@@ -195,16 +192,15 @@
Source: Select.js
panning = true;
zoomHandler.startDrag();
}
- editing = false;
}
/**
- * Check if a point is within the bounding boxes of any staves.
- * @param {number[]} point - An array where index 0 corresponds to x and 1 corresponds to y
- * @returns {boolean}
- */
+ * Check if a point is in the bounds of a staff element.
+ * @param {SVGPoint} point
+ * @returns {boolean}
+ */
function pointNotInStaff (point) {
- let staves = Array.from($('.staff'));
+ let staves = Array.from(document.getElementsByClassName('staff'));
let filtered = staves.filter((staff) => {
let box = getStaffBBox(staff);
return (box.ulx < point[0] && point[0] < box.lrx) && (box.uly < point[1] && point[1] < box.lry);
@@ -212,9 +208,6 @@
Source: Select.js
return (filtered.length === 0);
}
- /**
- * Action to run while the drag select continues. Updates the rectangle.
- */
function selecting () {
if (!panning && dragSelecting) {
var currentPt = d3.mouse(this);
@@ -232,9 +225,6 @@
Source: Select.js
}
}
- /**
- * Finish the selection and mark elements within the rectangle as being selected.
- */
function selEnd () {
if (!panning && dragSelecting) {
var rx = parseInt($('#selectRect').attr('x'));
@@ -264,7 +254,7 @@
} else {
SelectOptions.endOptionsSelection();
}
- Controls.updateHighlight();
+ updateHighlight();
}
/**
* Generic select function.
- * @param {SVGSVGElement} el
+ * @param {SVGGraphicsElement} el
*/
function select (el) {
if (!$(el).hasClass('selected')) {
@@ -656,26 +632,26 @@
Source: Select.js
}
}
}
- Controls.updateHighlight();
+ updateHighlight();
}
/**
* Select an nc.
- * @param {SVGSVGElement} el - The nc element to select.
+ * @param {SVGGraphicsElement} el - The nc element to select.
* @param {DragHandler} dragHandler - An instantiated DragHandler.
*/
-function selectNcs (el, dragHandler, neonCore) {
+async function selectNcs (el, dragHandler) {
if (!$(el).parent().hasClass('selected')) {
var parent = el.parentNode;
unselect();
select(parent);
- if (isLigature(parent, neonCore)) {
+ if (await isLigature(parent)) {
var prevNc = $(parent).prev()[0];
- if (isLigature(prevNc, neonCore)) {
+ if (await isLigature(prevNc)) {
select(prevNc);
} else {
var nextNc = $(parent).next()[0];
- if (isLigature(nextNc, neonCore)) {
+ if (await isLigature(nextNc)) {
select(nextNc);
} else {
console.warn('Error: Neither prev or next nc are ligatures');
@@ -692,30 +668,68 @@
Source: Select.js
}
/**
- * Select a staff.
- * @param {SVGSVGElement} el - The staff element to select.
- * @param {DragHandler} dragHandler - An instantiated DragHandler.
+ * Check if neume component is part of a ligature
+ * @param {SVGGraphicsElement} nc - The neume component to check.
+ * @returns {boolean}
*/
-export function selectStaff (el, dragHandler) {
- let staff = $(el);
- if (!staff.hasClass('selected')) {
- unselect();
- staff.addClass('selected');
- Controls.updateHighlight();
- Color.highlight(el, '#d00');
- dragHandler.dragInit();
+async function isLigature (nc) {
+ var attributes = await neonView.getElementAttr(nc.id, 0);
+ return (attributes.ligated === 'true');
+}
+
+/**
+ * Check if the elements have the same parent up two levels.
+ * @param {Array<Element>} elements - The array of elements.
+ * @returns {boolean} - If the elements share the same second level parent.
+ */
+function sharedSecondLevelParent (elements) {
+ let firstElement = elements.pop();
+ let secondParent = firstElement.parentElement.parentElement;
+ for (let element of elements) {
+ let secPar = element.parentElement.parentElement;
+ if (secPar.id !== secondParent.id) {
+ return false;
+ }
}
+ return true;
}
/**
- * Check if neume component is part of a ligature
- * @param {SVGSVGElement} nc - The neume component to check.
- * @param {NeonCore} neonCore - An instantiated NeonCore.
+ * Get the bounding box of a staff based on its staff lines.
+ * @param {SVGGElement} staff
+ * @returns {object}
*/
-function isLigature (nc, neonCore) {
- var attributes = neonCore.getElementAttr(nc.id);
- if (attributes.ligated === 'true') return true;
- return false;
+function getStaffBBox (staff) {
+ let ulx, uly, lrx, lry;
+ Array.from($(staff).children('path')).forEach(path => {
+ let box = path.getBBox();
+ if (uly === undefined || box.y < uly) {
+ uly = box.y;
+ }
+ if (ulx === undefined || box.x < ulx) {
+ ulx = box.x;
+ }
+ if (lry === undefined || box.y + box.height > lry) {
+ lry = box.y + box.height;
+ }
+ if (lrx === undefined || box.x + box.width > lrx) {
+ lrx = box.x + box.width;
+ }
+ });
+ return { 'ulx': ulx, 'uly': uly, 'lrx': lrx, 'lry': lry };
+}
+
+/**
+ * Select not neume elements.
+ * @param {SVGGraphicsElement[]} notNeumes - An array of not neumes elements.
+ */
+function selectNn (notNeumes) {
+ if (notNeumes.length > 0) {
+ notNeumes.forEach(nn => { select(nn); });
+ return false;
+ } else {
+ return true;
+ }
}
@@ -727,13 +741,13 @@
Source: Select.js
diff --git a/doc/SelectOptions.js.html b/doc/SingleEdit_SelectOptions.js.html
similarity index 57%
rename from doc/SelectOptions.js.html
rename to doc/SingleEdit_SelectOptions.js.html
index 5a3283fa0..4548b04d2 100644
--- a/doc/SelectOptions.js.html
+++ b/doc/SingleEdit_SelectOptions.js.html
@@ -2,7 +2,7 @@
- JSDoc: Source: SelectOptions.js
+ JSDoc: Source: SingleEdit/SelectOptions.js
@@ -17,7 +17,7 @@
-
Source: SelectOptions.js
+
Source: SingleEdit/SelectOptions.js
@@ -26,11 +26,11 @@
Source: SelectOptions.js
-
/** @module SelectOptions */
+
/** @module SingleEdit/SelectOptions */
import * as Contents from './Contents.js';
import * as Grouping from './Grouping.js';
-import * as Notification from './Notification.js';
-import InfoBox from './InfoBox.js';
+import * as Notification from '../utils/Notification.js';
+import InfoModule from '../InfoModule.js';
import SplitHandler from './SplitHandler.js';
const $ = require('jquery');
@@ -84,7 +84,7 @@
Source: SelectOptions.js
// TODO: CHANGE NAVABAR-LINK TO PROPER ICON//
/**
* Trigger the extra nc action menu.
- * @param {SVGSVGElement} nc - The last selected elements.
+ * @param {SVGGraphicsElement} nc - The last selected elements.
*/
export function triggerNcActions (nc) {
endOptionsSelection();
@@ -94,13 +94,15 @@
/**
* Trigger extra clef actions.
+ * @param {SVGGraphicsElement} clef - The clef that actions would be applied to.
*/
export function triggerClefActions (clef) {
endOptionsSelection();
@@ -210,13 +218,15 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/doc/index.html b/doc/index.html
index 704119222..7714215b4 100644
--- a/doc/index.html
+++ b/doc/index.html
@@ -43,35 +43,52 @@
-
Neon3
+
Neon3
+
Neume Editor ONline.
Neon3 is a browser-based music notation editor written in JavaScript using the Verovio music engraving library. The editor can be used to manipulate digitally encoded early musical scores in square-note notation.
Neon2 is a re-write of Neon.JS using a modified version of Verovio to render MEI-Neume files according to the MEI 4.0 specifications.
Follow the instructions from above first. The tests for Neon2 use Selenium and so require a web browser (Firefox) and its driver (geckodriver).
On Mac install these with Homebrew:
Then you can run the tests locally using yarn test. We use jest to script our tests.
+brew install geckodriver
+
+
Then you can run the tests locally using yarn test. We use jest to script our tests.
These tests require the server to be running on localhost:8080
-
Verovio
Verovio is present as an npm package under src/verovio-dev with the name verovio-dev. Its contents come from the emscripten/npm-dev folder in a Verovio project folder.
+
Verovio
+
Verovio is present as an npm package under src/verovio-dev with the name verovio-dev. Its contents come from the emscripten/npm-dev folder in a Verovio project folder.
@@ -82,13 +99,13 @@
Verovio
Verovio is present as an npm package under src/verovio-
diff --git a/doc/module-Controls.html b/doc/module-Controls.html
deleted file mode 100644
index 3642a6174..000000000
--- a/doc/module-Controls.html
+++ /dev/null
@@ -1,2491 +0,0 @@
-
-
-
-
- JSDoc: Module: Controls
-
-
-
-
-
-
-
-
-
-
-
/**
* Highlight a staff a certain color.
- * @param {SVGSVGElement} staff - The staff's SVG element.
+ * @param {SVGGElement} staff - The staff's SVG element.
* @param {string} color - The color to highlight the staff.
*/
export function highlight (staff, color) {
@@ -107,7 +107,7 @@
Source: Color.js
/**
* Remove the highlight from a staff.
- * @param {(SVGSVGElement|string)} staff - The staff's SVG element or a JQuery selector.
+ * @param {(SVGGElement|string)} staff - The staff's SVG element or a JQuery selector.
*/
export function unhighlight (staff) {
let children = Array.from($(staff).filter(':not(.selected)').children('.highlighted'));
@@ -147,13 +147,13 @@
Source: Color.js
diff --git a/doc/Cursor.js.html b/doc/utils_Cursor.js.html
similarity index 56%
rename from doc/Cursor.js.html
rename to doc/utils_Cursor.js.html
index 254f36cc1..c768c4b9a 100644
--- a/doc/Cursor.js.html
+++ b/doc/utils_Cursor.js.html
@@ -2,7 +2,7 @@
- JSDoc: Source: Cursor.js
+ JSDoc: Source: utils/Cursor.js
@@ -17,7 +17,7 @@