diff --git a/.travis.yml b/.travis.yml index da4faaacf..fe80dcf3a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,17 +1,28 @@ language: node_js node_js: 10 -os: osx env: - - HOMEBREW_NO_AUTO_UPDATE="1" + - NODE_OPTIONS=--max_old_space_size=2048 +os: osx +osx_image: xcode10.2 if: (branch = master OR branch = develop) +cache: + directories: + - $HOME/Library/Caches/Homebrew addons: firefox: latest -before_install: - - brew install geckodriver + chrome: stable + homebrew: + update: true + packages: + - geckodriver + casks: + - chromedriver install: + - sudo safaridriver --enable - yarn install script: - yarn build + - yarn upgrade verovio-dev - yarn start & - yarn test notifications: diff --git a/README.md b/README.md index 180ad4ea3..a885ca989 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Neon v 3.1.1 +Neon v4.0.0 ===== [![Build_Status](https://travis-ci.org/DDMAL/Neon.svg?branch=master)](https://travis-ci.org/DDMAL/Neon) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) @@ -46,11 +46,12 @@ Neon has two main modes: viewer and editor. To learn how to use both, [read the Test ---- -Follow the instructions from above first. The tests for Neon use [Selenium](https://docs.seleniumhq.org/) and so require a web browser ([Firefox](https://mozilla.org/firefox)) and its driver ([geckodriver](https://github.com/mozilla/geckodriver)). -On Mac install these with Homebrew: -``` +Follow the instructions from above first. The tests for Neon use [Selenium](https://docs.seleniumhq.org/) and use Firefox, Safari, and Chrome. Their respective webdrivers are required. Safari 12 or greater is required. On Mac, Firefox and Chrome can be installed by: +```shell brew cask install firefox +brew cask install google-chrome brew install geckodriver +brew cask install chromedriver ``` Then you can run the tests locally using `yarn test`. We use [jest](https://facebook.github.io/jest/) to script our tests. diff --git a/doc/DisplayPanel_DisplayControls.js.html b/doc/DisplayPanel_DisplayControls.js.html new file mode 100644 index 000000000..ffefe72ec --- /dev/null +++ b/doc/DisplayPanel_DisplayControls.js.html @@ -0,0 +1,259 @@ + + + + + JSDoc: Source: DisplayPanel/DisplayControls.js + + + + + + + + + + +
+ +

Source: DisplayPanel/DisplayControls.js

+ + + + + + +
+
+
/** @module DisplayPanel/DisplayControls */
+
+import * as Color from '../utils/Color.js';
+import Icons from '../img/icons.svg';
+
+const $ = require('jquery');
+
+var lastGlyphOpacity, lastImageOpacity;
+
+/**
+ * Initialize listeners and controls for display panel.
+ * @param {string} meiClassName - The class used to signifiy the MEI element(s).
+ * @param {string} background - The class used to signify the background.
+ */
+export function initDisplayControls (meiClassName, background) {
+  setOpacityControls(meiClassName);
+  setBackgroundOpacityControls(background);
+  setHighlightControls();
+  setBurgerControls();
+
+  $('#toggleDisplay').on('click', () => {
+    if ($('#displayContents').is(':hidden')) {
+      $('#displayContents').css('display', '');
+      $('#toggleDisplay').attr('xlink:href', Icons + '#dropdown-down');
+    } else {
+      $('#displayContents').css('display', 'none');
+      $('#toggleDisplay').attr('xlink:href', Icons + '#dropdown-side');
+    }
+  });
+}
+
+/**
+ * Set zoom control listener for button and slider
+ * @param {ZoomHandler} zoomHandler - The zoomHandler, if it exists.
+ */
+export function setZoomControls (zoomHandler) {
+  if (zoomHandler === undefined) {
+    return;
+  }
+  $('#zoomSlider').val(100);
+  $('#reset-zoom').click(() => {
+    $('#zoomOutput').val(100);
+    $('#zoomSlider').val(100);
+    zoomHandler.resetZoomAndPan();
+  });
+
+  $(document).on('input change', '#zoomSlider', () => {
+    $('#zoomOutput').val($('#zoomSlider').val());
+    zoomHandler.zoomTo($('#zoomOutput').val() / 100.0);
+  });
+
+  $('body').on('keydown', (evt) => {
+    let currentZoom = parseInt($('#zoomOutput').val());
+    if (evt.key === '+') { // increase zoom by 20
+      let newZoom = Math.min(currentZoom + 20, parseInt($('#zoomSlider').attr('max')));
+      zoomHandler.zoomTo(newZoom / 100.0);
+      $('#zoomOutput').val(newZoom);
+      $('#zoomSlider').val(newZoom);
+    } else if (evt.key === '-') { // decrease zoom by 20
+      let newZoom = Math.max(currentZoom - 20, parseInt($('#zoomSlider').attr('min')));
+      zoomHandler.zoomTo(newZoom / 100.0);
+      $('#zoomOutput').val(newZoom);
+      $('#zoomSlider').val(newZoom);
+    } else if (evt.key === '0') {
+      $('#zoomOutput').val(100);
+      $('#zoomSlider').val(100);
+      zoomHandler.resetZoomAndPan();
+    }
+  });
+}
+
+/**
+ * Set rendered MEI opacity button and slider listeners.
+ * @param {string} meiClassName
+ */
+function setOpacityControls (meiClassName) {
+  lastGlyphOpacity = 100;
+  $('#opacitySlider').val(100);
+  $('#reset-opacity').click(function () {
+    // Definition scale is the root element of what is generated by verovio
+    let lowerOpacity = lastGlyphOpacity < 95 ? lastGlyphOpacity / 100.0 : 0;
+    let newOpacity = $('#opacitySlider').val() === '100' ? lowerOpacity : 1;
+    $('.' + meiClassName).css('opacity', newOpacity);
+
+    lastGlyphOpacity = Number($('#opacitySlider').val());
+    $('#opacitySlider').val(newOpacity * 100);
+    $('#opacityOutput').val(newOpacity * 100);
+  });
+
+  $(document).on('input change', '#opacitySlider', () => {
+    $('#opacityOutput').val($('#opacitySlider').val());
+    lastGlyphOpacity = Number($('#opacitySlider').val());
+    $('.' + meiClassName).css('opacity', $('#opacityOutput').val() / 100.0);
+  });
+}
+
+/**
+ * Update MEI opacity to value from the slider.
+ * @param {string} meiClassName
+ */
+export function setOpacityFromSlider (meiClassName) {
+  $('#opacityOutput').val($('#opacitySlider').val());
+  $('.' + meiClassName).css('opacity', $('#opacityOutput').val() / 100.0);
+}
+
+/**
+ * Set background image opacity button and slider listeners.
+ * @param {string} background
+ */
+function setBackgroundOpacityControls (background) {
+  lastImageOpacity = 100;
+  $('#bgOpacitySlider').val(100);
+  $('#reset-bg-opacity').click(function () {
+    let lowerOpacity = lastImageOpacity < 95 ? lastImageOpacity / 100.0 : 0;
+    let newOpacity = $('#bgOpacitySlider').val() === '100' ? lowerOpacity : 1;
+    document.getElementsByClassName(background)[0].style.opacity = newOpacity;
+
+    lastImageOpacity = Number($('#bgOpacitySlider').val());
+    $('#bgOpacitySlider').val(newOpacity * 100);
+    $('#bgOpacityOutput').val(newOpacity * 100);
+  });
+
+  $(document).on('input change', '#bgOpacitySlider', function () {
+    $('#bgOpacityOutput').val(parseInt($('#bgOpacitySlider').val()));
+    lastImageOpacity = Number($('#bgOpacitySlider').val());
+    document.getElementsByClassName(background)[0].style.opacity = $('#bgOpacityOutput').val() / 100.0;
+  });
+}
+
+/**
+ * Set listener on staff highlighting checkbox.
+ */
+export function setHighlightControls () {
+  $('#highlight-button').on('click', (evt) => {
+    evt.stopPropagation();
+    $('#highlight-dropdown').toggleClass('is-active');
+    if ($('#highlight-dropdown').hasClass('is-active')) {
+      $('body').one('click', highlightClickaway);
+      $('#highlight-staff').on('click', () => {
+        $('#highlight-dropdown').removeClass('is-active');
+        $('.highlight-selected').removeClass('highlight-selected');
+        $('#highlight-staff').addClass('highlight-selected');
+        $('#highlight-type').html('&nbsp;- Staff');
+        Color.setGroupingHighlight('staff');
+      });
+      $('#highlight-syllable').on('click', () => {
+        $('#highlight-dropdown').removeClass('is-active');
+        $('.highlight-selected').removeClass('highlight-selected');
+        $('#highlight-syllable').addClass('highlight-selected');
+        $('#highlight-type').html('&nbsp;- Syllable');
+        Color.setGroupingHighlight('syllable');
+      });
+      $('#highlight-neume').on('click', () => {
+        $('#highlight-dropdown').removeClass('is-active');
+        $('.highlight-selected').removeClass('highlight-selected');
+        $('#highlight-neume').addClass('highlight-selected');
+        $('#highlight-type').html('&nbsp;- Neume');
+        Color.setGroupingHighlight('neume');
+      });
+      $('#highlight-none').on('click', () => {
+        $('#highlight-dropdown').removeClass('is-active');
+        $('.highlight-selected').removeClass('highlight-selected');
+        $('#highlight-type').html('&nbsp;- Off');
+        Color.unsetGroupingHighlight();
+      });
+    } else {
+      $('body').off('click', highlightClickaway);
+    }
+  });
+}
+
+/**
+ * Reset the highlight for different types based on the 'highlight-selected' class in the DOM.
+ */
+export function updateHighlight () {
+  let highlightId = $('.highlight-selected').attr('id');
+  switch (highlightId) {
+    case 'highlight-staff':
+      Color.setGroupingHighlight('staff');
+      break;
+    case 'highlight-syllable':
+      Color.setGroupingHighlight('syllable');
+      break;
+    case 'highlight-neume':
+      Color.setGroupingHighlight('neume');
+      break;
+    default:
+      Color.unsetGroupingHighlight();
+  }
+}
+
+/**
+ * Set listener on burger menu for smaller screens.
+ */
+function setBurgerControls () {
+  $('#burgerMenu').on('click', () => {
+    $(this).toggleClass('is-active');
+    $('#navMenu').toggleClass('is-active');
+  });
+}
+
+/**
+ * Clickaway listener for the highlight dropdown.
+ */
+function highlightClickaway () {
+  $('body').off('click', highlightClickaway);
+  $('#highlight-dropdown').removeClass('is-active');
+}
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/doc/DisplayPanel_DisplayPanel.js.html b/doc/DisplayPanel_DisplayPanel.js.html new file mode 100644 index 000000000..7ad45ade6 --- /dev/null +++ b/doc/DisplayPanel_DisplayPanel.js.html @@ -0,0 +1,136 @@ + + + + + JSDoc: Source: DisplayPanel/DisplayPanel.js + + + + + + + + + + +
+ +

Source: DisplayPanel/DisplayPanel.js

+ + + + + + +
+
+
/** @module DisplayPanel/DisplayPanel */
+
+import * as DisplayControls from './DisplayControls.js';
+import Icons from '../img/icons.svg';
+
+/**
+ * A class that sets the content of the display panel to the right and
+ * manages controls for viewing.
+ */
+class DisplayPanel {
+  /**
+   * Constructor for DisplayPanel.
+   * @param {SingleView | DivaView} view - The View parent.
+   * @param {string} className - The class name for the rendered SVG object(s).
+   * @param {string} background - The class name associated with the background.
+   * @param {ZoomHandler} [zoomHandler] - The ZoomHandler object, if SingleView.
+   */
+  constructor (view, className, background, zoomHandler = undefined) {
+    this.view = view;
+    this.className = className;
+    this.background = background;
+    this.zoomHandler = zoomHandler;
+
+    let displayPanel = document.getElementById('display_controls');
+    displayPanel.innerHTML = displayControlsPanel(this.zoomHandler);
+    this.view.addUpdateCallback(this.updateVisualization.bind(this));
+  }
+
+  /**
+   * Apply event listeners related to the DisplayPanel.
+   */
+  setDisplayListeners () {
+    if (this.zoomHandler) {
+      // Zoom handler stuff
+      DisplayControls.setZoomControls(this.zoomHandler);
+    }
+    DisplayControls.initDisplayControls(this.className, this.background);
+  }
+
+  /**
+   * Update SVG based on visualization settings
+   */
+  updateVisualization () {
+    DisplayControls.setOpacityFromSlider(this.className);
+    DisplayControls.updateHighlight();
+  }
+}
+
+/**
+ * Return the HTML for the display panel.
+ * @param {ZoomHandler} handleZoom - Includes zoom controls if defined.
+ * @returns {string}
+ */
+function displayControlsPanel (handleZoom) {
+  let contents =
+  "<p class='panel-heading' id='displayHeader'>Display" +
+  "<svg class='icon is-pulled-right'><use id='toggleDisplay' xlink:href='" + Icons + "#dropdown-down'></use></svg></p>" +
+  "<div id='displayContents'>";
+  if (handleZoom !== undefined) {
+    contents +=
+    "<a class='panel-block has-text-centered'><button class='button' id='reset-zoom'>Zoom</button>" +
+    "<input class='slider is-fullwidth' id='zoomSlider' step='5' min='25' max='400' value='100' type='range'/>" +
+    "<output id='zoomOutput' for='zoomSlider'>100</output></a>";
+  }
+  contents +=
+  "<a class='panel-block has-text-centered'><button class='button' id='reset-opacity'>Glyph Opacity</button>" +
+  "<input class='slider is-fullwidth' id='opacitySlider' step='5' min='0' max='100' value='100' type='range'/>" +
+  "<output id='opacityOutput' for='opacitySlider'>100</output></a>" +
+  "<a class='panel-block has-text-centered'><button class='button' id='reset-bg-opacity'>Image Opacity</button>" +
+  "<input class='slider is-fullwidth' id='bgOpacitySlider' step='5' min='0' max='100' value='100' type='range'/>" +
+  "<output id='bgOpacityOutput' for='bgOpacitySlider'>100</output></a>" +
+  "<div class='panel-block' id='extensible-block'>" +
+  "<div class='dropdown' id='highlight-dropdown'><div class='dropdown-trigger'>" +
+  "<button class='button' id='highlight-button' aria-haspopup='true' aria-controls='highlight-menu' style='width: auto'>" +
+  "<span>Highlight</span><span id='highlight-type'>&nbsp;- Off</span>" +
+  "<svg class='icon'><use id='toggleDisplay' xlink:href='" + Icons + "#dropdown-down'></use>" +
+  "</svg></button></div><div class='dropdown-menu' id='highlight-menu' role='menu'>" +
+  "<div class='dropdown-content'><a class='dropdown-item' id='highlight-staff'>Staff</a>" +
+  "<a class='dropdown-item' id='highlight-syllable'>Syllable</a>" +
+  "<a class='dropdown-item' id='highlight-neume'>Neume</a><hr class='dropdown-divider'/>" +
+  "<a class='dropdown-item' id='highlight-none'>None</a></div></div></div></div></div>";
+  return contents;
+}
+
+export { DisplayPanel as default };
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/doc/DivaView.html b/doc/DivaView.html new file mode 100644 index 000000000..8dc51c98e --- /dev/null +++ b/doc/DivaView.html @@ -0,0 +1,1704 @@ + + + + + JSDoc: Class: DivaView + + + + + + + + + + +
+ +

Class: DivaView

+ + + + + + +
+ +
+ +

DivaView(neonView, Display, manifest)

+ +
View module that uses the diva.js viewer to render the pages of a IIIF manifests +and then display the rendered MEI files over the proper pages.
+ + +
+ +
+
+ + + + +

Constructor

+ + + +

new DivaView(neonView, Display, manifest)

+ + + + + + +
+ Constructor for DivaView. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
neonView + + +NeonView + + + + NeonView parent
Display + + +function + + + + A constructor for a DisplayPanel
manifest + + +string + + + + Link to the IIIF manifest.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + +

Methods

+ + + + + + + +

addUpdateCallback(cb)

+ + + + + + +
+ Add a callback function that will be run whenever an SVG is updated. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
cb + + +function + + + + The callback function.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

adjustZoom(zoomLevel)

+ + + + + + +
+ Adjust the rendered SVG(s) to be the correct size after zooming. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
zoomLevel + + +number + + + + The new diva.js zoom level.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

(async) changePage(pageIndexes)

+ + + + + + +
+ Called when the visible page changes in the diva.js viewer. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
pageIndexes + + +number +| + +Array.<number> + + + + The zero-index or -indexes of the page(s) visible.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

didLoad()

+ + + + + + +
+ Function called when diva.js finishes loading. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

getCurrentPage() → {number}

+ + + + + + +
+ Get the active page in the diva.js viewer. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +number + + +
+
+ + + + + + + + + + + + + +

getCurrentPageURI() → {string}

+ + + + + + +
+ Get the active page URI in the diva.js viewer. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +string + + +
+
+ + + + + + + + + + + + + +

getPageName() → {string}

+ + + + + + +
+ Get the name of the active page/canvas combined with the manuscript name. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +string + + +
+
+ + + + + + + + + + + + + +

initDivaEvents()

+ + + + + + +
+ Set the listeners for certain events internal to diva.js +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

parseManifest(manifest)

+ + + + + + +
+ Use the IIIF manifest to create a map between IIIF canvases and page indexes. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
manifest + + +object + + + + The IIIF manifest
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

removeUpdateCallback(cb)

+ + + + + + +
+ Remove a callback function previously added to the list of functions to call. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
cb + + +function + + + + The callback function to remove.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

setViewEventHandlers()

+ + + + + + +
+ Set listeners on the body element for global events. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

updateSVG(svg, pageNo)

+ + + + + + +
+ Update the SVG being displayed for the specified page. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
svg + + +SVGSVGElement + + + + The updated SVG.
pageNo + + +number + + + + The zero-indexed page number.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ + + + + + + \ No newline at end of file diff --git a/doc/DivaView.js.html b/doc/DivaView.js.html new file mode 100644 index 000000000..9a935c920 --- /dev/null +++ b/doc/DivaView.js.html @@ -0,0 +1,280 @@ + + + + + JSDoc: Source: DivaView.js + + + + + + + + + + +
+ +

Source: DivaView.js

+ + + + + + +
+
+
/**
+ * View module that uses the diva.js viewer to render the pages of a IIIF manifests
+ * and then display the rendered MEI files over the proper pages.
+ */
+class DivaView {
+  /**
+   * Constructor for DivaView.
+   * @param {NeonView} neonView - NeonView parent
+   * @param {function} Display - A constructor for a DisplayPanel
+   * @param {string} manifest - Link to the IIIF manifest.
+   */
+  constructor (neonView, Display, manifest) {
+    this.neonView = neonView;
+    this.updateCallbacks = [];
+    this.divaReady = false;
+    this.diva = new Diva('container', {
+      objectData: manifest
+    });
+    document.getElementById('container').style.minHeight = '100%';
+    this.indexMap = new Map();
+    this.diva.disableDragScrollable();
+    this.displayPanel = new Display(this, 'neon-container', 'diva-viewer-canvas');
+    this.loadDelay = 500; // in milliseconds
+    this.initDivaEvents();
+    this.setViewEventHandlers();
+  }
+
+  /**
+   * Set the listeners for certain events internal to diva.js
+   */
+  initDivaEvents () {
+    Diva.Events.subscribe('ManifestDidLoad', this.parseManifest.bind(this), this.diva.settings.ID);
+    Diva.Events.subscribe('ObjectDidLoad', this.didLoad.bind(this), this.diva.settings.ID);
+    Diva.Events.subscribe('VisiblePageDidChange', this.changePage.bind(this), this.diva.settings.ID);
+    Diva.Events.subscribe('ZoomLevelDidChange', this.adjustZoom.bind(this), this.diva.settings.ID);
+  }
+
+  /**
+   * Called when the visible page changes in the diva.js viewer.
+   * @param {number | number[]} pageIndexes - The zero-index or -indexes of the page(s) visible.
+   */
+  async changePage (pageIndexes) {
+    if (typeof pageIndexes !== 'object') {
+      pageIndexes = [pageIndexes];
+    }
+    Array.from(document.getElementsByClassName('active-page')).forEach(elem => {
+      elem.classList.remove('active-page');
+    });
+    for (let page of pageIndexes) {
+      window.setTimeout(checkAndLoad.bind(this), this.loadDelay, page);
+    }
+
+    function checkAndLoad (page) {
+      if (page === this.getCurrentPage()) {
+        let pageURI = this.indexMap.get(page);
+        this.neonView.getPageSVG(pageURI).then(svg => {
+          this.updateSVG(svg, page);
+          let containerId = 'neon-container-' + page;
+          let container = document.getElementById(containerId);
+          if (container !== null) {
+            container.classList.add('active-page');
+          }
+          this.updateCallbacks.forEach(callback => callback());
+        }).catch(err => {
+          if (err.name !== 'not_found' && err.name !== 'missing_mei') {
+            console.error(err);
+          }
+        });
+      }
+    }
+  }
+
+  /**
+   * Get the active page in the diva.js viewer.
+   * @returns {number}
+   */
+  getCurrentPage () {
+    return this.diva.getActivePageIndex();
+  }
+
+  /**
+   * Get the active page URI in the diva.js viewer.
+   * @returns {string}
+   */
+  getCurrentPageURI () {
+    return this.indexMap.get(this.getCurrentPage());
+  }
+
+  /**
+   * Adjust the rendered SVG(s) to be the correct size after zooming.
+   * @param {number} zoomLevel - The new diva.js zoom level.
+   */
+  adjustZoom (zoomLevel) {
+    (new Promise((resolve) => {
+      Array.from(document.getElementsByClassName('neon-container'))
+        .forEach(elem => { elem.style.display = 'none'; });
+      setTimeout(resolve, this.diva.settings.zoomDuration + 100);
+    })).then(() => {
+      this.changePage(this.diva.getActivePageIndex());
+      Array.from(document.getElementsByClassName('neon-container'))
+        .forEach(elem => {
+          let svg = elem.firstChild;
+          let pageNo = parseInt(elem.id.match(/\d+/)[0]);
+          this.updateSVG(svg, pageNo);
+          elem.style.display = '';
+        });
+    });
+  }
+
+  /**
+   * Update the SVG being displayed for the specified page.
+   * @param {SVGSVGElement} svg - The updated SVG.
+   * @param {number} pageNo - The zero-indexed page number.
+   */
+  updateSVG (svg, pageNo) {
+    let inner = document.getElementById('diva-1-inner');
+    let dimensions = this.diva.getPageDimensionsAtCurrentZoomLevel(pageNo);
+    let offset = this.diva.settings.viewHandler._viewerCore.getPageRegion(pageNo, {
+      includePadding: true,
+      incorporateViewport: true
+    });
+    let marginLeft = window.getComputedStyle(inner, null)
+      .getPropertyValue('margin-left');
+
+    let containerId = 'neon-container-' + pageNo.toString();
+    let container = document.getElementById(containerId);
+    if (container === null) {
+      container = document.createElement('div');
+      container.id = containerId;
+      container.classList.add('neon-container');
+      inner.appendChild(container);
+    }
+
+    while (container.firstChild) {
+      container.removeChild(container.firstChild);
+    }
+
+    svg.setAttribute('width', dimensions.width);
+    svg.setAttribute('height', dimensions.height);
+    container.style.position = 'absolute';
+    container.style.top = `${offset.top}px`;
+    container.style.left = `${offset.left - parseInt(marginLeft)}px`;
+
+    container.appendChild(svg);
+  }
+
+  /**
+   * Function called when diva.js finishes loading.
+   */
+  didLoad () {
+    this.divaReady = true;
+    this.displayPanel.setDisplayListeners();
+    document.getElementById('loading').style.display = 'none';
+    console.log(this.diva);
+  }
+
+  /**
+   * Add a callback function that will be run whenever an SVG is updated.
+   * @param {function} cb - The callback function.
+   */
+  addUpdateCallback (cb) {
+    this.updateCallbacks.push(cb);
+  }
+
+  /**
+   * Remove a callback function previously added to the list of functions to call.
+   * @param {function} cb - The callback function to remove.
+   */
+  removeUpdateCallback (cb) {
+    let index = this.updateCallbacks.findItem((elem) => {
+      return elem === cb;
+    });
+    if (index !== -1) {
+      this.updateCallbacks.splice(index, 1);
+    }
+  }
+
+  /**
+   * Set listeners on the body element for global events.
+   */
+  setViewEventHandlers () {
+    document.body.addEventListener('keydown', (evt) => {
+      switch (evt.key) {
+        case 'h':
+          for (let container of document.getElementsByClassName('neon-container')) {
+            container.style.visibility = 'hidden';
+          }
+          break;
+        default: break;
+      }
+    });
+
+    document.body.addEventListener('keyup', (evt) => {
+      switch (evt.key) {
+        case 'h':
+          for (let container of document.getElementsByClassName('neon-container')) {
+            container.style.visibility = '';
+          }
+          break;
+        default: break;
+      }
+    });
+  }
+
+  /**
+   * Use the IIIF manifest to create a map between IIIF canvases and page indexes.
+   * @param {object} manifest - The IIIF manifest
+   */
+  parseManifest (manifest) {
+    this.indexMap.clear();
+    for (let sequence of manifest.sequences) {
+      for (let canvas of sequence.canvases) {
+        this.indexMap.set(sequence.canvases.indexOf(canvas), canvas['@id']);
+      }
+    }
+  }
+
+  /**
+   * Get the name of the active page/canvas combined with the manuscript name.
+   * @returns {string}
+   */
+  getPageName () {
+    let manuscriptName = this.diva.settings.manifest.itemTitle;
+    let pageName = this.diva.settings.manifest.pages[this.getCurrentPage()].l;
+    return manuscriptName + ' \u2014 ' + pageName;
+  }
+}
+
+export { DivaView as default };
+
+
+
+ + + + +
+ + + +
+ + + + + + + diff --git a/doc/DragHandler.html b/doc/DragHandler.html index 83b7cc172..a0f4a4edd 100644 --- a/doc/DragHandler.html +++ b/doc/DragHandler.html @@ -142,7 +142,7 @@
Parameters:
Source:
@@ -250,7 +250,320 @@

(inner) drag
Source:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

(inner) endOptionsSelection()

+ + + + + + +
+ Remove the additonal editor options that exist. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

(inner) reset()

+ + + + + + +
+ Reset the actino on the selector to the one set by resetTo. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

(inner) resetTo(reset)

+ + + + + + +
+ A d3 action that should be put on the selector once the drag action finishes. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
reset + + +oject + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
@@ -296,13 +609,13 @@

(inner) drag
- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:07 GMT-0400 (GMT-04:00)
diff --git a/doc/InfoModule.js.html b/doc/InfoModule.js.html index 54adada6d..066e8263a 100644 --- a/doc/InfoModule.js.html +++ b/doc/InfoModule.js.html @@ -106,7 +106,7 @@

Source: InfoModule.js

var ncs = element.children('.nc'); var contour = await this.getContour(ncs); if (contour === 'Clivis') { - var attr = await this.neonView.getElementAttr($(ncs[0])[0].id, this.neonView.view.getCurrentPage()); + var attr = await this.neonView.getElementAttr($(ncs[0])[0].id, this.neonView.view.getCurrentPageURI()); if (attr.ligated) { contour = 'Ligature'; } @@ -118,11 +118,11 @@

Source: InfoModule.js

'Pitch(es): ' + pitches; break; case 'custos': - attributes = await this.neonView.getElementAttr(id, this.neonView.view.getCurrentPage()); + attributes = await this.neonView.getElementAttr(id, this.neonView.view.getCurrentPageURI()); body += 'Pitch: ' + (attributes.pname).toUpperCase() + attributes.oct; break; case 'clef': - attributes = await this.neonView.getElementAttr(id, this.neonView.view.getCurrentPage()); + attributes = await this.neonView.getElementAttr(id, this.neonView.view.getCurrentPageURI()); body += 'Shape: ' + attributes.shape + '<br/>' + 'Line: ' + attributes.line; break; @@ -140,27 +140,27 @@

Source: InfoModule.js

} /** - * Get the individual pitches of a neume. - * @param {array.<SVGGraphicsElement>} ncs - neume components in the neume. - */ + * Get the individual pitches of a neume. + * @param {array.<SVGGraphicsElement>} ncs - neume components in the neume. + */ async getPitches (ncs) { var pitches = ''; for (let nc of ncs) { - var attributes = await this.neonView.getElementAttr(nc.id, this.neonView.view.getCurrentPage()); + var attributes = await this.neonView.getElementAttr(nc.id, this.neonView.view.getCurrentPageURI()); pitches += attributes.pname + attributes.oct + ' '; } return pitches; } /** - * Get the contour of a neume. - * @param {array.<SVGGraphicsElement>} ncs - neume components in the neume. - */ + * Get the contour of a neume. + * @param {array.<SVGGraphicsElement>} ncs - neume components in the neume. + */ async getContour (ncs) { var contour = ''; var previous = null; for (let nc of ncs) { - var attributes = await this.neonView.getElementAttr(nc.id, this.neonView.view.getCurrentPage()); + var attributes = await this.neonView.getElementAttr(nc.id, this.neonView.view.getCurrentPageURI()); if (previous !== null) { if (previous.oct > attributes.oct) { contour += 'd'; @@ -185,10 +185,10 @@

Source: InfoModule.js

} /** - * Show and update the info box. - * @param {string} title - The info box title. - * @param {string} body - The info box contents. - */ + * Show and update the info box. + * @param {string} title - The info box title. + * @param {string} body - The info box contents. + */ updateInfoModule (title, body) { if ($('#displayInfo').is(':checked')) { $('.message').css('display', ''); @@ -202,10 +202,10 @@

Source: InfoModule.js

} /** - * Convert a pitch name (a-g) to a number (where c is 1, d is 2 and b is 7). - * @param {string} pname - The pitch name. - * @returns {number} - */ + * Convert a pitch name (a-g) to a number (where c is 1, d is 2 and b is 7). + * @param {string} pname - The pitch name. + * @returns {number} + */ pitchNameToNum (pname) { switch (pname) { case 'c': @@ -228,10 +228,10 @@

Source: InfoModule.js

} /** - * Find the contour of an neume grouping based on the grouping name. - * @param {string} value - the value name. - * @returns {string} - */ + * Find the contour of an neume grouping based on the grouping name. + * @param {string} value - the value name. + * @returns {string} + */ getContourByValue (value) { for (let [cont, v] of InfoModule.neumeGroups.entries()) { if (v === value) { @@ -282,13 +282,13 @@

Source: InfoModule.js


diff --git a/doc/InsertHandler.html b/doc/InsertHandler.html index f53a0b5d1..99a48bd8f 100644 --- a/doc/InsertHandler.html +++ b/doc/InsertHandler.html @@ -28,7 +28,7 @@

Class: InsertHandler

-

InsertHandler(neonView)

+

InsertHandler(neonView, sel)

@@ -41,7 +41,7 @@

InsertHandl -

new InsertHandler(neonView)

+

new InsertHandler(neonView, sel)

@@ -105,6 +105,29 @@
Parameters:
+ + + + sel + + + + + +string + + + + + + + + + + A CSS selector representing where to put the listeners. + + + @@ -142,7 +165,7 @@
Parameters:
Source:
@@ -299,7 +322,7 @@
Parameters:
Source:
@@ -436,7 +459,7 @@
Parameters:
Source:
@@ -524,7 +547,7 @@

(inner) Source:
@@ -612,7 +635,7 @@

(inner)
Source:
@@ -767,7 +790,7 @@

Parameters:
Source:
@@ -813,13 +836,13 @@
Parameters:

- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:07 GMT-0400 (GMT-04:00)
diff --git a/doc/NeonCore.html b/doc/NeonCore.html index 98e2f91df..fe3798c3b 100644 --- a/doc/NeonCore.html +++ b/doc/NeonCore.html @@ -28,7 +28,7 @@

Class: NeonCore

-

NeonCore(meiMap, title) → {object}

+

NeonCore(manifest) → {object}

The core component of Neon. This manages the database, the verovio toolkit, the cache, and undo/redo stacks.
@@ -46,7 +46,7 @@

Constructor

-

new NeonCore(meiMap, title) → {object}

+

new NeonCore(manifest) → {object}

@@ -90,36 +90,13 @@
Parameters:
- meiMap + manifest -Map.<number, string> - - - - - - - - - - Map of zero-indexed page no to MEI. - - - - - - - title - - - - - -Promise +object @@ -129,7 +106,7 @@
Parameters:
- The title of the page or manuscript. + The manifest to load. @@ -170,7 +147,7 @@
Parameters:
Source:
@@ -242,13 +219,13 @@

Members

-

neonCache :Map.<number, CacheEntry>

+

neonCache :Map.<string, CacheEntry>

- A cache mapping a page number to a CacheEntry. + A cache mapping a page URI to a CacheEntry.
@@ -257,7 +234,7 @@
Type:
  • -Map.<number, CacheEntry> +Map.<string, CacheEntry>
  • @@ -296,7 +273,7 @@
    Type:
    Source:
    @@ -314,7 +291,7 @@
    Type:
    -

    redoStacks :Map.<number, Array.<string>>

    +

    redoStacks :Map.<string, Array.<string>>

    @@ -329,7 +306,7 @@
    Type:
    • -Map.<number, Array.<string>> +Map.<string, Array.<string>>
    • @@ -368,7 +345,7 @@
      Type:
      Source:
      @@ -386,14 +363,13 @@
      Type:
      -

      toolkits :Map.<number, object>

      +

      undoStacks :Map.<string, Array.<string>>

      - A map associating page numbers with their respective Verovio toolkit -instances. This is used to decrease latency in loading files. + Stacks of previous MEI files representing actions that can be undone for each page.
      @@ -402,7 +378,7 @@
      Type:
      • -Map.<number, object> +Map.<string, Array.<string>>
      • @@ -441,7 +417,7 @@
        Type:
        Source:
        @@ -459,13 +435,13 @@
        Type:
        -

        undoStacks :Map.<number, Array.<string>>

        +

        verovioWrapper :object

        - Stacks of previous MEI files representing actions that can be undone for each page. + A wrapper for the Verovio Web Worker.
        @@ -474,7 +450,7 @@
        Type:
        • -Map.<number, Array.<string>> +object
        • @@ -513,7 +489,7 @@
          Type:
          Source:
          @@ -541,7 +517,7 @@

          Methods

          -

          (async) edit(action, pageNo) → {boolean}

          +

          edit(action, pageURI) → {Promise}

          @@ -683,13 +659,13 @@
          Properties
          - pageNo + pageURI -number +string @@ -699,7 +675,7 @@
          Properties
          - The zero-indexed page number to perform the action on. + The URI of the selected page. @@ -740,7 +716,7 @@
          Properties
          Source:
          @@ -769,7 +745,7 @@
          Returns:
          - If the action succeeded or not. + Resolves to boolean if the action succeeded or not.
          @@ -780,7 +756,7 @@
          Returns:
          -boolean +Promise
          @@ -798,7 +774,7 @@
          Returns:
          -

          getElementAttr(elementId, pageNo) → {Promise}

          +

          getElementAttr(elementId, pageURI) → {Promise}

          @@ -865,13 +841,13 @@
          Parameters:
          - pageNo + pageURI -number +string @@ -881,7 +857,7 @@
          Parameters:
          - The zero-indexed page number the element is on. + The URI of the selected page. @@ -922,7 +898,7 @@
          Parameters:
          Source:
          @@ -980,7 +956,7 @@
          Returns:
          -

          getMEI(pageNo) → {Promise}

          +

          getMEI(pageURI) → {Promise}

          @@ -1024,13 +1000,13 @@
          Parameters:
          - pageNo + pageURI -number +string @@ -1040,7 +1016,7 @@
          Parameters:
          - The zero-indexed page number. + The URI of the selected page. @@ -1081,7 +1057,7 @@
          Parameters:
          Source:
          @@ -1139,7 +1115,7 @@
          Returns:
          -

          getSVG(pageNo) → {Promise}

          +

          getSVG(pageURI) → {Promise}

          @@ -1183,13 +1159,13 @@
          Parameters:
          - pageNo + pageURI -number +string @@ -1199,7 +1175,7 @@
          Parameters:
          - The zero-indexed page number. + The URI of the selected page. @@ -1240,7 +1216,7 @@
          Parameters:
          Source:
          @@ -1298,7 +1274,7 @@
          Returns:
          -

          info() → {string}

          +

          info() → {Promise}

          @@ -1350,7 +1326,7 @@

          infoSource:
          @@ -1378,6 +1354,10 @@

          info + Promise that resolves to info string + +
          @@ -1386,7 +1366,7 @@
          Returns:
          -string +Promise
          @@ -1404,7 +1384,7 @@
          Returns:
          -

          (async) initDb()

          +

          (async) initDb(force) → {boolean}

          @@ -1412,8 +1392,9 @@

          (async) initDb<
          - Initialize the PouchDb database based on the provided MEI. -This should only be run if previous data does not exist. + Initialize the PouchDb database based on the provided manifest. +If a newer version already exists in the database, this will +not update the database unless forced.
          @@ -1424,6 +1405,63 @@

          (async) initDb< +

          Parameters:
          + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
          NameTypeDefaultDescription
          force + + +boolean + + + + + + false + + If a database update should be forced.
          + + @@ -1457,7 +1495,7 @@

          (async) initDb<
          Source:
          @@ -1482,6 +1520,24 @@

          (async) initDb< +

          Returns:
          + + + + +
          +
          + Type +
          +
          + +boolean + + +
          +
          + + @@ -1493,7 +1549,7 @@

          (async) initDb< -

          loadData(pageNo, data, dirtyopt)

          +

          loadData(pageURI, data, dirtyopt) → {Promise}

          @@ -1541,13 +1597,13 @@
          Parameters:
          - pageNo + pageURI -number +string @@ -1569,7 +1625,7 @@
          Parameters:
          - The zero-indexed page number. + The URI of the selected page. @@ -1684,7 +1740,7 @@
          Parameters:
          Source:
          @@ -1709,6 +1765,28 @@
          Parameters:
          +
          Returns:
          + + +
          + promise that resolves when this action is done +
          + + + +
          +
          + Type +
          +
          + +Promise + + +
          +
          + + @@ -1720,7 +1798,7 @@
          Parameters:
          -

          loadPage(pageNo) → {Promise}

          +

          loadPage(pageURI) → {Promise}

          @@ -1765,13 +1843,13 @@
          Parameters:
          - pageNo + pageURI -number +string @@ -1781,7 +1859,7 @@
          Parameters:
          - The zero-indexed page number to load. + The URI of the selected page. @@ -1822,7 +1900,7 @@
          Parameters:
          Source:
          @@ -1880,7 +1958,7 @@
          Returns:
          -

          redo(pageNo) → {boolean}

          +

          redo(pageURI) → {Promise}

          @@ -1924,13 +2002,13 @@
          Parameters:
          - pageNo + pageURI -number +string @@ -1981,7 +2059,7 @@
          Parameters:
          Source:
          @@ -2021,7 +2099,7 @@
          Returns:
          -boolean +Promise
          @@ -2039,7 +2117,7 @@
          Returns:
          -

          undo(pageNo) → {boolean}

          +

          undo(pageURI) → {Promise}

          @@ -2083,13 +2161,13 @@
          Parameters:
          - pageNo + pageURI -number +string @@ -2099,7 +2177,7 @@
          Parameters:
          - The zero-indexed page number. + The URI of the selected page. @@ -2140,7 +2218,7 @@
          Parameters:
          Source:
          @@ -2180,9 +2258,187 @@
          Returns:
          +Promise + + +
          +
          + + + + + + + + + + + + + +

          updateCache(pageURI, dirty) → {Promise}

          + + + + + + +
          + Update contents of the cache using information in verovio toolkit. +
          + + + + + + + + + +
          Parameters:
          + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
          NameTypeDescription
          pageURI + + +string + + + + Page to be updated in cache.
          dirty + + boolean + + If the entry should be marked as dirty
          + + + + + + +
          + + + + + + + + + + + + + + + + + + + + + + + + + + +
          Source:
          +
          + + + + + + + +
          + + + + + + + + + + + + + + + +
          Returns:
          + + + + +
          +
          + Type +
          +
          + +Promise + +
          @@ -2252,7 +2508,7 @@

          (async) Source:
          @@ -2298,13 +2554,13 @@

          (async)
          - Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:08 GMT-0400 (GMT-04:00)
          diff --git a/doc/NeonCore.js.html b/doc/NeonCore.js.html index 58c4a826b..9bf4d5693 100644 --- a/doc/NeonCore.js.html +++ b/doc/NeonCore.js.html @@ -27,9 +27,9 @@

          Source: NeonCore.js

          import * as Validation from './Validation.js';
          +import VerovioWrapper from './VerovioWrapper.js';
           import PouchDb from 'pouchdb';
          -
          -const verovio = require('verovio-dev');
          +const uuid = require('uuid/v4');
           
           /**
            * The core component of Neon. This manages the database,
          @@ -38,32 +38,26 @@ 

          Source: NeonCore.js

          class NeonCore { /** * Constructor for NeonCore - * @param {Map<number, string>} meiMap - Map of zero-indexed page no to MEI. - * @param {Promise} title - The title of the page or manuscript. + * @param {object} manifest - The manifest to load. * @returns {object} A NeonCore object. */ - constructor (meiMap, title) { - this.verovioOptions = { - inputFormat: 'mei', - noFooter: 1, - noHeader: 1, - pageMarginLeft: 0, - pageMarginTop: 0, - font: 'Bravura', - useFacsimile: true - }; - + constructor (manifest) { + /** + * A wrapper for the Verovio Web Worker. + * @type {object} + */ + this.verovioWrapper = new VerovioWrapper(); Validation.init(); /** * Stacks of previous MEI files representing actions that can be undone for each page. - * @type {Map.<number, Array.<string>>} + * @type {Map.<string, 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>>} + * @type {Map.<string, Array.<string>>} */ this.redoStacks = new Map(); @@ -76,110 +70,205 @@

          Source: NeonCore.js

          */ /** - * A cache mapping a page number to a {@link CacheEntry}. - * @type {Map.<number, CacheEntry>} + * A cache mapping a page URI to a {@link CacheEntry}. + * @type {Map.<string, CacheEntry>} */ this.neonCache = new Map(); - this.parser = new DOMParser(); + this.parser = new window.DOMParser(); - this.db = new PouchDb(title); - - /** - * A map associating page numbers with their respective Verovio toolkit - * instances. This is used to decrease latency in loading files. - * @type {Map.<number, object>} - */ - this.toolkits = new Map(); + this.db = new PouchDb('Neon'); this.blankPages = []; // Add each MEI to the database - this.meiMap = meiMap; + this.manifest = manifest; + this.annotations = manifest.mei_annotations; + this.lastPageLoaded = ''; } /** - * Initialize the PouchDb database based on the provided MEI. - * This should only be run if previous data does not exist. + * Initialize the PouchDb database based on the provided manifest. + * If a newer version already exists in the database, this will + * not update the database unless forced. + * @param {boolean} force - If a database update should be forced. + * @returns {boolean} */ - async initDb () { - for (let pair of this.meiMap) { - let key = pair[0]; - let value = pair[1]; - await this.db.get(key.toString()).catch((err) => { + async initDb (force = false) { + // Check for existing manifest + let response = await new Promise((resolve, reject) => { + this.db.get(this.manifest['@id']).catch(err => { if (err.name === 'not_found') { - // Create new document - return { - _id: key.toString(), - data: '' + // This is a new document. + let doc = { + _id: this.manifest['@id'], + timestamp: this.manifest.timestamp, + image: this.manifest.image, + title: this.manifest.title, + annotations: [] }; + this.annotations.forEach(annotation => { + doc.annotations.push(annotation.id); + }); + return doc; } else { - throw err; + console.error(err); + return reject(err); + } + }).then(async doc => { + // Check if doc timestamp is newer than manifest + let docTime = (new Date(doc.timestamp)).getTime(); + let manTime = (new Date(this.manifest.timestamp)).getTime(); + if (docTime > manTime) { + if (!force) { + // Fill annotations list with db annotations + this.annotations = []; + doc.annotations.forEach(async id => { + await this.db.get(id).then(annotation => { + this.annotations.push({ + id: annotation._id, + type: 'Annotation', + body: annotation.body, + target: annotation.target + }); + }).catch(err => { + console.error(err); + }); + }); + return resolve(false); + } + } + for (let annotation of this.annotations) { + // Add annotations to database + await this.db.get(annotation.id).catch(err => { + if (err.name === 'not_found') { + return { + _id: annotation.id + }; + } else { + console.error(err); + return reject(err); + } + }).then(newAnnotation => { + newAnnotation.body = annotation.body; + newAnnotation.target = annotation.target; + return this.db.put(newAnnotation); + }).catch(err => { + reject(err); + console.error(err); + }); } - }).then((doc) => { - doc.data = value; return this.db.put(doc); - }).catch((err) => { + }).then(() => { + return resolve(true); + }).catch(err => { + reject(err); console.error(err); }); - } + }); + + return response; } /** * 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. + * @param {string} pageURI - The URI of the selected page. * @returns {Promise} A promise that resolves to the cache entry. */ - loadPage (pageNo) { + loadPage (pageURI) { return new Promise((resolve, reject) => { - if (this.neonCache.has(pageNo)) { - resolve(this.neonCache.get(pageNo)); - } else if (this.blankPages.includes(pageNo)) { - let e = new Error('No MEI file for page ' + pageNo); + if (this.lastPageLoaded === pageURI && this.neonCache.has(pageURI)) { + resolve(this.neonCache.get(pageURI)); + } else if (this.neonCache.has(pageURI)) { + this.loadData(pageURI, this.neonCache.get(pageURI).mei).then(() => { + resolve(this.neonCache.get(pageURI)); + }); + } else if (this.blankPages.includes(pageURI)) { + Validation.blankPage(); + let e = new Error('No MEI file for page ' + pageURI); e.name = 'missing_mei'; reject(e); } else { - this.db.get(pageNo.toString()).then((doc) => { - this.loadData(pageNo, doc.data); - resolve(this.neonCache.get(pageNo)); - }).catch((err) => { - if (err.name === 'not_found') { - this.blankPages.push(pageNo); - } - reject(err); + // Find annotation + let annotation = this.annotations.find(elem => { + return elem.target === pageURI; }); + if (annotation) { + window.fetch(annotation.body).then(response => { + if (response.ok) { + return response.text(); + } else { + throw new Error(response.statusText); + } + }).then(data => { + this.loadData(pageURI, data).then(() => { + resolve(this.neonCache.get(pageURI)); + }); + }).catch(err => { + reject(err); + }); + } else { + Validation.blankPage(); + this.blankPages.push(pageURI); + } } }); } /** * Load data into the verovio toolkit and update the cache. - * @param {number} pageNo - The zero-indexed page number. + * @param {string} pageURI - The URI of the selected page. * @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. + * @returns {Promise} promise that resolves when this action is done */ - loadData (pageNo, data, dirty = false) { + loadData (pageURI, data, dirty = false) { Validation.sendForValidation(data); - let svg = this.parser.parseFromString( - this.getToolkit(pageNo).renderData(data, {}), - 'image/svg+xml' - ).documentElement; - this.neonCache.set(pageNo, { - svg: svg, - mei: data, - dirty: dirty + this.lastPageLoaded = pageURI; + /* A promise is returned that will resolve to the result of the action. + * However the value that is must return comes from the Web Worker and + * information passed between the worker and main context much be in a + * message. So an event handler is put on verovioWrapper for when a message + * is receieved from the worker. Then a message is sent to the worker to + * take an action. A response is sent back and the previously mentioned + * event handler handles the response. Since it is defined within the + * promise it has access to the necessary resolve function. + */ + return new Promise((resolve, reject) => { + let message = { + id: uuid(), + action: 'renderData', + mei: data + }; + function handle (evt) { + if (evt.data.id === message.id) { + let svg = this.parser.parseFromString( + evt.data.svg, + 'image/svg+xml' + ).documentElement; + this.neonCache.set(pageURI, { + svg: svg, + mei: data, + dirty: dirty + }); + evt.target.removeEventListener('message', handle); + resolve(); + } + } + this.verovioWrapper.addEventListener('message', handle.bind(this)); + this.verovioWrapper.postMessage(message); }); } /** * Get the SVG for a specific page number. - * @param {number} pageNo - The zero-indexed page number. + * @param {string} pageURI - The URI of the selected page. * @returns {Promise} A promise that resolves to the SVG. */ - getSVG (pageNo) { + getSVG (pageURI) { return new Promise((resolve, reject) => { - this.loadPage(pageNo).then((entry) => { + this.loadPage(pageURI).then((entry) => { resolve(entry.svg); }).catch((err) => { reject(err); }); }); @@ -187,12 +276,12 @@

          Source: NeonCore.js

          /** * Get the MEI for a specific page number. - * @param {number} pageNo - The zero-indexed page number. + * @param {string} pageURI - The URI of the selected page. * @returns {Promise} A promise that resolves to the MEI as a string. */ - getMEI (pageNo) { + getMEI (pageURI) { return new Promise((resolve, reject) => { - this.loadPage(pageNo).then((entry) => { + this.loadPage(pageURI).then((entry) => { resolve(entry.mei); }).catch((err) => { reject(err); }); }); @@ -201,13 +290,24 @@

          Source: NeonCore.js

          /** * 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. + * @param {string} pageURI - The URI of the selected page. * @returns {Promise} A promise that resolves to the attributes in an object. */ - getElementAttr (elementId, pageNo) { + getElementAttr (elementId, pageURI) { return new Promise((resolve) => { - this.loadPage(pageNo).then(() => { - resolve(this.getToolkit(pageNo).getElementAttr(elementId)); + this.loadPage(pageURI).then(() => { + let message = { + id: uuid(), + action: 'getElementAttr', + elementId: elementId + }; + this.verovioWrapper.addEventListener('message', function handle (evt) { + if (evt.data.id === message.id) { + evt.target.removeEventListener('message', handle); + resolve(evt.data.attributes); + } + }); + this.verovioWrapper.postMessage(message); }); }); } @@ -217,77 +317,169 @@

          Source: NeonCore.js

          * @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. + * @param {string} pageURI - The URI of the selected page. + * @returns {Promise} Resolves to boolean if the action succeeded or not. */ - async edit (editorAction, pageNo) { - if (this.currentPage !== pageNo) { - await this.loadPage(pageNo); + edit (editorAction, pageURI) { + let promise; + if (this.lastPageLoaded === pageURI) { + promise = Promise.resolve(this.neonCache.get(pageURI)); + } else { + promise = this.loadPage(pageURI); } - let currentMEI = this.getMEI(pageNo); - let result = this.getToolkit(pageNo).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.getToolkit(pageNo).getMEI(0, true), - svg: this.parser.parseFromString(this.getToolkit(pageNo).renderToSVG(1), - 'image/svg+xml').documentElement, - dirty: true + return new Promise((resolve, reject) => { + promise.then(entry => { + let currentMEI = entry.mei; + let message = { + id: uuid(), + action: 'edit', + editorAction: editorAction + }; + function handle (evt) { + if (evt.data.id === message.id) { + if (evt.data.result) { + if (!this.undoStacks.has(pageURI)) { + this.undoStacks.set(pageURI, []); + } + this.undoStacks.get(pageURI).push(currentMEI); + this.redoStacks.set(pageURI, []); + } + evt.target.removeEventListener('message', handle); + this.updateCache(pageURI).then(() => { resolve(evt.data.result); }); + } + } + this.verovioWrapper.addEventListener('message', handle.bind(this)); + this.verovioWrapper.postMessage(message); }); - } - return result; + }); + } + + /** + * Update contents of the cache using information in verovio toolkit. + * @param {string} pageURI - Page to be updated in cache. + * @param {boolean} dirty - If the entry should be marked as dirty + * @returns {Promise} + */ + updateCache (pageURI, dirty) { + return new Promise((resolve, reject) => { + // Must get MEI and then get SVG then finish. + var mei, svgText; + let meiPromise = new Promise((resolve, reject) => { + let message = { + id: uuid(), + action: 'getMEI' + }; + this.verovioWrapper.addEventListener('message', function handle (evt) { + if (evt.data.id === message.id) { + mei = evt.data.mei; + evt.target.removeEventListener('message', handle); + resolve(); + } + }); + this.verovioWrapper.postMessage(message); + }); + let svgPromise = new Promise((resolve, reject) => { + let message = { + id: uuid(), + action: 'renderToSVG' + }; + this.verovioWrapper.addEventListener('message', function handle (evt) { + if (evt.data.id === message.id) { + svgText = evt.data.svg; + evt.target.removeEventListener('message', handle); + resolve(); + } + }); + this.verovioWrapper.postMessage(message); + }); + + meiPromise.then(() => { return svgPromise; }).then(() => { + let svg = this.parser.parseFromString( + svgText, + 'image/svg+xml' + ).documentElement; + this.neonCache.set(pageURI, { + mei: mei, + svg: svg, + dirty: dirty + }); + resolve(); + }); + }); } /** * Get the edit info string from the verovio toolkit. - * @returns {string} + * @returns {Promise} Promise that resolves to info string */ - info (pageNo) { - return this.getToolkit(pageNo).editInfo(); + info (pageURI) { + let promise; + if (this.lastPageLoaded === pageURI) { + promise = Promise.resolve(); + } else { + promise = this.loadPage(pageURI); + } + return new Promise((resolve, reject) => { + promise.then(() => { + let message = { + id: uuid(), + action: 'editInfo' + }; + this.verovioWrapper.addEventListener('message', function handle (evt) { + if (evt.data.id === message.id) { + evt.target.removeEventListener('message', handle); + resolve(evt.data.info); + } + }); + this.verovioWrapper.postMessage(message); + }); + }); } /** * Undo the last action performed on a specific page. - * @param {number} pageNo - The zero-indexed page number. - * @returns {boolean} If an action undone. + * @param {string} pageURI - The URI of the selected page. + * @returns {Promise} 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; + undo (pageURI) { + return new Promise((resolve, reject) => { + if (this.undoStacks.has(pageURI)) { + let state = this.undoStacks.get(pageURI).pop(); + if (state !== undefined) { + this.getMEI(pageURI).then(mei => { + this.redoStacks.get(pageURI).push(mei); + return this.loadData(pageURI, state, true); + }).then(() => { + resolve(true); + }); + return; + } } - } - return false; + resolve(false); + }); } /** * 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. + * @param {string} pageURI - The zero-indexed page number. + * @returns {Promise} 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; + redo (pageURI) { + return new Promise((resolve, reject) => { + if (this.redoStacks.has(pageURI)) { + let state = this.redoStacks.get(pageURI).pop(); + if (state !== undefined) { + this.getMEI(pageURI).then((mei) => { + this.undoStacks.get(pageURI).push(mei); + return this.loadData(pageURI, state, true); + }).then(() => { + resolve(true); + }); + return; + } } - } - return false; + resolve(false); + }); } /** @@ -296,29 +488,58 @@

          Source: NeonCore.js

          * only entries marked as dirty will be updated. */ async updateDatabase () { + let updateTimestamp = false; 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; + updateTimestamp ^= true; + let index = this.annotations.findIndex(elem => { return elem.target === key; }); + // try to update server with PUT (if applicable + // only attempt if not a data URI + let uri; + if (!this.annotations[index].body.match(/^data:/)) { + await window.fetch(this.annotations[index].body, + { + method: 'PUT', + headers: { 'Content-Type': 'application/mei+xml' }, + body: value.mei + } + ).then(response => { + if (response.ok) { + uri = this.annotations[index].body; + } else { + uri = 'data:application/mei+xml;base64,' + window.btoa(value.mei); + } + }).catch(err => { + console.error(err); + console.warn('Falling back to data URI'); + uri = 'data:application/mei+xml;base64,' + window.btoa(value.mei); + }); + } else { + uri = 'data:application/mei+xml;base64,' + window.btoa(value.mei); + } + // Update URI in annotations, database + this.annotations[index].body = uri; + await this.db.get(this.annotations[index].id).then(doc => { + doc.body = uri; return this.db.put(doc); }).then(() => { - console.log('done'); value.dirty = false; - }).catch((err) => { + }).catch(err => { console.error(err); }); } } - } - getToolkit (pageNo) { - if (!this.toolkits.has(pageNo)) { - this.toolkits.set(pageNo, new verovio.toolkit()); - this.toolkits.get(pageNo).setOptions(this.verovioOptions); + if (updateTimestamp) { + await this.db.get(this.manifest['@id']).then(doc => { + doc.timestamp = (new Date()).toISOString(); + return this.db.put(doc); + }).catch(err => { + console.error(err); + }); } - return this.toolkits.get(pageNo); } } @@ -333,13 +554,13 @@

          Source: NeonCore.js


          - Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:07 GMT-0400 (GMT-04:00)
          diff --git a/doc/NeonView.html b/doc/NeonView.html index 70e4152a6..1d7e6c445 100644 --- a/doc/NeonView.html +++ b/doc/NeonView.html @@ -134,7 +134,7 @@
          Properties
          - mode + manifest @@ -158,14 +158,14 @@
          Properties
          - + The contents of a Neon manifest. - options + View @@ -189,14 +189,14 @@
          Properties
          - + Constructor for a View module - View + Display @@ -220,14 +220,14 @@
          Properties
          - Constructor for a View module + Constructor for DisplayPanel module - Display + Info @@ -251,14 +251,14 @@
          Properties
          - Constructor for DisplayPanel module + Constructor for InfoModule module - Info + NeumeEdit @@ -273,6 +273,8 @@
          Properties
          + <optional>
          + @@ -282,14 +284,14 @@
          Properties
          - Constructor for InfoModule module + Constructor for NeumeEdit module - Edit + TextView @@ -315,14 +317,14 @@
          Properties
          - Constructor for EditMode module + Constructor for TextView module - TextView + TextEdit @@ -348,7 +350,7 @@
          Properties
          - Constructor for TextView module + Constructor for TextEdit module @@ -396,7 +398,7 @@
          Properties
          Source:
          @@ -504,7 +506,7 @@

          deleteDbSource:
          @@ -562,7 +564,7 @@
          Returns:
          -

          edit(action, pageNo) → {Promise}

          +

          edit(action, pageURI) → {Promise}

          @@ -704,13 +706,13 @@
          Properties
          - pageNo + pageURI -number +string @@ -720,7 +722,7 @@
          Properties
          - The zero-indexed page number to perform the action on. + The URI of the page to perform the action on @@ -761,7 +763,7 @@
          Properties
          Source:
          @@ -819,7 +821,117 @@
          Returns:
          -

          getElementAttr(elementID, pageNo) → {Promise}

          +

          export() → {Promise}

          + + + + + + +
          + Updates browser database and creates JSON-LD save file. +
          + + + + + + + + + + + + + +
          + + + + + + + + + + + + + + + + + + + + + + + + + + +
          Source:
          +
          + + + + + + + +
          + + + + + + + + + + + + + + + +
          Returns:
          + + +
          + A promise that resolves when the save action is finished. +
          + + + +
          +
          + Type +
          +
          + +Promise + + +
          +
          + + + + + + + + + + + + + +

          getElementAttr(elementID, pageURI) → {Promise}

          @@ -886,13 +998,13 @@
          Parameters:
          - pageNo + pageURI -number +string @@ -902,7 +1014,7 @@
          Parameters:
          - The zero-indexed page number the ID is found on. + The URI of the page the element is found on @@ -943,7 +1055,7 @@
          Parameters:
          Source:
          @@ -1102,7 +1214,7 @@
          Parameters:
          Source:
          @@ -1261,7 +1373,7 @@
          Parameters:
          Source:
          @@ -1420,7 +1532,7 @@
          Parameters:
          Source:
          @@ -1478,7 +1590,7 @@
          Returns:
          -

          getUserMode()

          +

          getUserMode() → {string}

          @@ -1530,7 +1642,7 @@

          getUserMod
          Source:
          @@ -1555,6 +1667,24 @@

          getUserMod +

          Returns:
          + + + + +
          +
          + Type +
          +
          + +string + + +
          +
          + + @@ -1566,7 +1696,7 @@

          getUserMod -

          redo()

          +

          redo() → {Promise}

          @@ -1643,6 +1773,28 @@

          redo + a promise that resolves to a success boolean + + + + +
          +
          + Type +
          +
          + +Promise + + +
          +
          + + @@ -1662,7 +1814,7 @@

          save - Save the current state of the MEI file(s) to the browser database. + Save the current state to the browser database. @@ -1706,7 +1858,7 @@

          saveSource:
          @@ -1735,7 +1887,7 @@
          Returns:
          - A promise that resolves when the save action is finished. + A promise that resolves when the action is finished.
          @@ -1816,7 +1968,7 @@

          startSource:
          @@ -1852,7 +2004,7 @@

          startundo()

          +

          undo() → {Promise}

          @@ -1904,7 +2056,7 @@

          undoSource:
          @@ -1929,6 +2081,28 @@

          undo + a promise that reoslves to a success boolean + + + + +
          +
          + Type +
          +
          + +Promise + + +
          +
          + + @@ -1993,7 +2167,7 @@

          u
          Source:
          @@ -2023,6 +2197,101 @@

          u + + + + + + +

          updateForCurrentPagePromise()

          + + + + + + +
          + Same as updateForCurrentPage but returns a promise. +
          + + + + + + + + + + + + + +
          + + + + + + + + + + + + + + + + + + + + + + + + + + +
          Source:
          +
          + + + + + +
          See:
          +
          +
            +
          • NeonView.updateForCurrentPage
          • +
          +
          + + + +
          + + + + + + + + + + + + + + + + + + + + @@ -2039,13 +2308,13 @@

          u
          - Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:08 GMT-0400 (GMT-04:00)
          diff --git a/doc/NeonView.js.html b/doc/NeonView.js.html index 459239c59..a68b9f22e 100644 --- a/doc/NeonView.js.html +++ b/doc/NeonView.js.html @@ -27,7 +27,9 @@

          Source: NeonView.js

          import NeonCore from './NeonCore.js';
          -import * as Notification from './utils/Notification.js';
          +
          +import { parseManifest } from './utils/NeonManifest.js';
          +import { prepareEditMode } from './utils/EditControls';
           
           /**
            * NeonView class. Manages the other modules of Neon and communicates with
          @@ -37,49 +39,40 @@ 

          Source: NeonView.js

          /** * Constructor for NeonView. Sets mode and passes constructors. * @param {object} params - * @param {string} params.mode - * @param {object} params.options + * @param {string} params.manifest - The contents of a Neon manifest. * @param {object} params.View - Constructor for a View module * @param {object} params.Display - Constructor for DisplayPanel module * @param {object} params.Info - Constructor for InfoModule module - * @param {object} [params.Edit] - Constructor for EditMode module + * @param {object} [params.NeumeEdit] - Constructor for NeumeEdit module * @param {object} [params.TextView] - Constructor for TextView module + * @param {object} [params.TextEdit] - Constructor for TextEdit module */ constructor (params) { - if (params.mode === 'single' || params.mode === 'iiif') { - this.mode = params.mode; - } else { - console.error('Invalid mode'); - } - - if (this.mode === 'single') { - this.view = new params.View(this, params.Display, params.options.image); - } else { - this.view = new params.View(this, params.Display, params.options.manifest); + if (!parseManifest(params.manifest)) { + console.error('Unable to parse the manifest'); } + this.manifest = params.manifest; - this.core = new NeonCore(params.options.meiMap, params.options.name); + this.view = new params.View(this, params.Display, params.manifest.image); + this.name = params.manifest.title; + this.core = new NeonCore(params.manifest); this.display = this.view.display; - this.InfoModule = params.Info; this.info = new params.Info(this); - if (params.Edit !== undefined) { + if (params.NeumeEdit !== undefined || (params.TextEdit !== undefined && params.TextView !== undefined)) { // Set up display for edit button - let parent = document.getElementById('dropdown_toggle'); - let editItem = document.createElement('a'); - editItem.classList.add('navbar-item'); - let editButton = document.createElement('button'); - editButton.classList.add('button'); - editButton.id = 'edit_mode'; - editButton.textContent = 'Edit MEI'; - editItem.appendChild(editButton); - parent.appendChild(editItem); - - this.editor = new params.Edit(this); + prepareEditMode(this); + } + + if (params.NeumeEdit !== undefined) { + this.NeumeEdit = new params.NeumeEdit(this); } if (params.TextView !== undefined) { this.textView = new params.TextView(this); + if (params.TextEdit !== undefined) { + this.TextEdit = new params.TextEdit(this); + } } } @@ -104,34 +97,45 @@

          Source: NeonView.js

          */ updateForCurrentPage () { let pageNo = this.view.getCurrentPage(); - // load pages - this.core.getSVG(pageNo).then((svg) => { - this.view.updateSVG(svg, pageNo); - }); + this.view.changePage(pageNo); + } + + /** + * Same as updateForCurrentPage but returns a promise. + * @see NeonView.updateForCurrentPage + */ + updateForCurrentPagePromise () { + let pageNo = this.view.getCurrentPage(); + return Promise.resolve(this.view.changePage(pageNo)); } /** * Redo an action performed on the current page (if any) + * @returns {Promise} a promise that resolves to a success boolean */ redo () { - return this.core.redo(this.view.getCurrentPage()); + return this.core.redo(this.view.getCurrentPageURI()); } /** * Undo the last action performed on the current page (if any) + * @returns {Promise} a promise that reoslves to a success boolean */ undo () { - return this.core.undo(this.view.getCurrentPage()); + return this.core.undo(this.view.getCurrentPageURI()); } /** * Get the mode Neon is in: viewer, insert, or edit. + * @returns {string} */ getUserMode () { - if (this.editor === undefined) { - return 'viewer'; + if (this.NeumeEdit !== undefined) { + return this.NeumeEdit.getUserMode(); + } else if (this.TextEdit !== undefined) { + return 'edit'; } else { - return this.editor.getUserMode(); + return 'viewer'; } } @@ -140,33 +144,47 @@

          Source: NeonView.js

          * @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. + * @param {string} pageURI - The URI of the page to perform the action on * @returns {Promise} A promise that resolves to the result of the action. */ - edit (action, pageNo) { - let editPromise = new Promise((resolve) => { - resolve(this.core.edit(action, pageNo)); - }); - return editPromise; + edit (action, pageURI) { + return this.core.edit(action, pageURI); } /** * Get the attributes for a specific musical element. * @param {string} elementID - The unique ID of the element. - * @param {number} pageNo - The zero-indexed page number the ID is found on. + * @param {string} pageURI - The URI of the page the element is found on * @returns {Promise} A promise that resolves to the available attributes. */ - getElementAttr (elementID, pageNo) { - let elementPromise = new Promise((resolve, reject) => { - resolve(this.core.getElementAttr(elementID, pageNo)); - }); - return elementPromise; + getElementAttr (elementID, pageURI) { + return this.core.getElementAttr(elementID, pageURI); } /** - * Save the current state of the MEI file(s) to the browser database. + * Updates browser database and creates JSON-LD save file. * @returns {Promise} A promise that resolves when the save action is finished. */ + export () { + // return this.core.updateDatabase(); + return (new Promise((resolve, reject) => { + this.core.updateDatabase().then(() => { + this.manifest.mei_annotations = this.core.annotations; + this.manifest.timestamp = (new Date()).toISOString(); + let data = new window.Blob([JSON.stringify(this.manifest, null, 2)], { type: 'application/ld+json' }); + let reader = new window.FileReader(); + reader.addEventListener('load', () => { + resolve(reader.result); + }); + reader.readAsDataURL(data); + }).catch(err => { reject(err); }); + })); + } + + /** + * Save the current state to the browser database. + * @returns {Promise} A promise that resolves when the action is finished. + */ save () { return this.core.updateDatabase(); } @@ -225,13 +243,13 @@

          Source: NeonView.js


          - Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:07 GMT-0400 (GMT-04:00)
          diff --git a/doc/SingleEditMode.html b/doc/SingleEditMode.html index c5205342d..08c57445a 100644 --- a/doc/SingleEditMode.html +++ b/doc/SingleEditMode.html @@ -147,7 +147,7 @@
          Parameters:
          Source:
          @@ -255,7 +255,7 @@

          getUserMod
          Source:
          @@ -361,7 +361,7 @@

          initEditM
          Source:
          @@ -407,13 +407,13 @@

          initEditM
          - Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:08 GMT-0400 (GMT-04:00)
          diff --git a/doc/SingleEdit_ResizeStaff.js.html b/doc/SingleEdit_ResizeStaff.js.html deleted file mode 100644 index 9163fffaa..000000000 --- a/doc/SingleEdit_ResizeStaff.js.html +++ /dev/null @@ -1,227 +0,0 @@ - - - - - JSDoc: Source: SingleEdit/ResizeStaff.js - - - - - - - - - - -
          - -

          Source: SingleEdit/ResizeStaff.js

          - - - - - - -
          -
          -
          /**
          - * Support for resizing the staff by creating a resizable box around it.
          - * @module SingleEdit/ResizeStaff
          - */
          -
          -import { selectStaff } from './Select.js';
          -
          -const d3 = require('d3');
          -
          -/**
          - * The sides of the rectangle
          - */
          -const Side = {
          -  Top: 0,
          -  Bottom: 1,
          -  Left: 2,
          -  Right: 3
          -};
          -
          -/**
          - * Handle the resizing of the selected staff.
          - * @constructor
          - * @param {string} staffId - The ID of the staff to resize.
          - * @param {NeonView} neonView - The NeonView parent for editing and refreshing.
          - * @param {DragHandler} dragHandler - A drag handler object.
          - */
          -function Resize (staffId, neonView, dragHandler) {
          -  var staff = document.getElementById(staffId);
          -  /**
          -     * The upper-left x-coordinate of the staff.
          -     * @type {number}
          -     */
          -  var ulx;
          -  /**
          -     * The upper-left y-coordinate of the staff.
          -     * @type {number}
          -     */
          -  var uly;
          -  /**
          -     * The lower-right x-coordinate of the staff.
          -     * @type {number}
          -     */
          -  var lrx;
          -  /**
          -     * The lower-right y-coordinate of the staff.
          -     * @type {number}
          -     */
          -  var lry;
          -
          -  /**
          -     * Draw the initial rectangle around the staff
          -     * and add the listeners to support dragging to resize.
          -     */
          -  function drawInitialRect () {
          -    if (staff === null) return;
          -    let paths = Array.from(staff.getElementsByTagName('path'));
          -
          -    paths.forEach(path => {
          -      let box = path.getBBox();
          -      if (ulx === undefined || ulx > box.x) {
          -        ulx = box.x;
          -      }
          -      if (uly === undefined || uly > box.y) {
          -        uly = box.y;
          -      }
          -      if (lrx === undefined || lrx < box.x + box.width) {
          -        lrx = box.x + box.width;
          -      }
          -      if (lry === undefined || lry < box.y + box.height) {
          -        lry = box.y + box.height;
          -      }
          -    });
          -
          -    d3.select('#' + staff.id).append('rect')
          -      .attr('x', ulx)
          -      .attr('y', uly)
          -      .attr('width', lrx - ulx)
          -      .attr('height', lry - uly)
          -      .attr('id', 'resizeRect')
          -      .attr('stroke', 'black')
          -      .attr('stroke-width', 15)
          -      .attr('fill', 'none')
          -      .style('cursor', 'move');
          -
          -    d3.select('#resizeRect').call(
          -      d3.drag()
          -        .on('start', resizeStart)
          -        .on('drag', resizeDrag)
          -        .on('end', resizeEnd)
          -    );
          -
          -    var side;
          -    var initialPoint;
          -
          -    function resizeStart () {
          -      initialPoint = d3.mouse(this);
          -      {
          -        let dist = Math.abs(initialPoint[0] - ulx);
          -        side = Side.Left;
          -        if (dist > Math.abs(initialPoint[0] - lrx)) {
          -          dist = Math.abs(initialPoint[0] - lrx);
          -          side = Side.Right;
          -        }
          -        if (dist > Math.abs(initialPoint[1] - uly)) {
          -          dist = Math.abs(initialPoint[1] - uly);
          -          side = Side.Top;
          -        }
          -        if (dist > Math.abs(initialPoint[1] - lry)) {
          -          dist = Math.abs(initialPoint[1] - lry);
          -          side = Side.Bottom;
          -        }
          -      }
          -    }
          -
          -    function resizeDrag () {
          -      let currentPoint = d3.mouse(this);
          -      switch (side) {
          -        case Side.Left:
          -          ulx = currentPoint[0];
          -          break;
          -        case Side.Right:
          -          lrx = currentPoint[0];
          -          break;
          -        case Side.Top:
          -          uly = currentPoint[1];
          -          break;
          -        case Side.Bottom:
          -          lry = currentPoint[1];
          -          break;
          -        default:
          -          console.error("Something that wasn't a side of the rectangle was dragged. This shouldn't happen.");
          -      }
          -      redraw();
          -    }
          -
          -    function resizeEnd () {
          -      let editorAction = {
          -        'action': 'resize',
          -        'param': {
          -          'elementId': staff.id,
          -          'ulx': ulx,
          -          'uly': uly,
          -          'lrx': lrx,
          -          'lry': lry
          -        }
          -      };
          -      neonView.edit(editorAction, 0).then((result) => {
          -        if (result) {
          -          neonView.updateForCurrentPage();
          -        }
          -        staff = document.getElementById(staffId);
          -        ulx = undefined;
          -        uly = undefined;
          -        lrx = undefined;
          -        lry = undefined;
          -        selectStaff(staff, dragHandler);
          -        drawInitialRect();
          -      });
          -    }
          -  }
          -
          -  /**
          -     * Redraw the rectangle with the new bounds
          -     */
          -  function redraw () {
          -    d3.select('#resizeRect')
          -      .attr('x', ulx)
          -      .attr('y', uly)
          -      .attr('width', lrx - ulx)
          -      .attr('height', lry - uly);
          -  }
          -
          -  Resize.prototype.constructor = Resize;
          -  Resize.prototype.drawInitialRect = drawInitialRect;
          -}
          -export { Resize as default };
          -
          -
          -
          - - - - -
          - - - -
          - -
          - Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) -
          - - - - - diff --git a/doc/SingleEdit_Select.js.html b/doc/SingleEdit_Select.js.html deleted file mode 100644 index b503bb1a5..000000000 --- a/doc/SingleEdit_Select.js.html +++ /dev/null @@ -1,756 +0,0 @@ - - - - - JSDoc: Source: SingleEdit/Select.js - - - - - - - - - - -
          - -

          Source: SingleEdit/Select.js

          - - - - - - -
          -
          -
          /** @module SingleEdit/Select */
          -
          -import * as Color from '../utils/Color.js';
          -import { updateHighlight } from '../DisplayPanel/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;
          -
          -/**
          - * Get the selection mode chosen by the user.
          - * @returns {string|null}
          - */
          -function getSelectionType () {
          -  let element = document.getElementsByClassName('sel-by active');
          -  if (element.length !== 0) {
          -    return element[0].id;
          -  } else {
          -    return null;
          -  }
          -}
          -
          -/**
          - * 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.info;
          -  zoomHandler = neonView.view.zoomHandler;
          -
          -  initSelectionButtons();
          -  neonView.view.addUpdateCallback(clickSelect);
          -  neonView.view.addUpdateCallback(dragSelect);
          -}
          -
          -/**
          - * 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) {
          -        info.infoListeners();
          -      }
          -      unselect();
          -    }
          -  });
          -
          -  $('use').on('click', (e) => { e.stopPropagation(); });
          -  $('#moreEdit').on('click', (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 window.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
          -    }));
          -  }
          -}
          -
          -/**
          - * Apply listeners for drag selection.
          - */
          -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)
          -    .on('drag', selecting)
          -    .on('end', selEnd);
          -  canvas.call(dragSelectAction);
          -  dragHandler.resetTo(dragSelectAction);
          -
          -  function selStart () {
          -    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;
          -          let initialP = d3.mouse(this);
          -          initialX = initialP[0];
          -          initialY = initialP[1];
          -          initRect(initialX, initialY);
          -        }
          -      } else {
          -        panning = true;
          -        zoomHandler.startDrag();
          -      }
          -    } else if (d3.event.sourceEvent.shiftKey) {
          -      panning = true;
          -      zoomHandler.startDrag();
          -    }
          -  }
          -
          -  /**
          -   * Check if a point is in the bounds of a staff element.
          -   * @param {SVGPoint} point
          -   * @returns {boolean}
          -   */
          -  function pointNotInStaff (point) {
          -    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);
          -    });
          -    return (filtered.length === 0);
          -  }
          -
          -  function selecting () {
          -    if (!panning && dragSelecting) {
          -      var currentPt = d3.mouse(this);
          -      var curX = currentPt[0];
          -      var curY = currentPt[1];
          -
          -      var newX = curX < initialX ? curX : initialX;
          -      var newY = curY < initialY ? curY : initialY;
          -      var width = curX < initialX ? initialX - curX : curX - initialX;
          -      var height = curY < initialY ? initialY - curY : curY - initialY;
          -
          -      updateRect(newX, newY, width, height);
          -    } else if (panning) {
          -      zoomHandler.dragging();
          -    }
          -  }
          -
          -  function selEnd () {
          -    if (!panning && dragSelecting) {
          -      var rx = parseInt($('#selectRect').attr('x'));
          -      var ry = parseInt($('#selectRect').attr('y'));
          -      var lx = parseInt($('#selectRect').attr('x')) + parseInt($('#selectRect').attr('width'));
          -      var ly = parseInt($('#selectRect').attr('y')) + parseInt($('#selectRect').attr('height'));
          -
          -      var nc;
          -      if ($('#selByStaff').hasClass('is-active')) {
          -        nc = d3.selectAll('#svg_group use, .staff')._groups[0];
          -      } else {
          -        nc = d3.selectAll('#svg_group use')._groups[0];
          -      }
          -      var els = Array.from(nc);
          -
          -      var elements = els.filter(function (d) {
          -        if (d.tagName === 'use') {
          -          let box = d.parentNode.getBBox();
          -          let ulx = box.x;
          -          let uly = box.y;
          -          let lrx = box.x + box.width;
          -          let lry = box.y + box.height;
          -          return !(((rx < ulx && lx < ulx) || (rx > lrx && lx > lrx)) || ((ry < uly && ly < uly) || (ry > lry && ly > lry)));
          -        } else {
          -          let box = getStaffBBox(d);
          -          return !(((rx < box.ulx && lx < box.ulx) || (rx > box.lrx && lx > box.lrx)) || ((ry < box.uly && ly < box.uly) || (ry > box.lry && ly > box.lry)));
          -        }
          -      });
          -
          -      selectAll(elements);
          -
          -      dragHandler.dragInit();
          -      d3.selectAll('#selectRect').remove();
          -      dragSelecting = false;
          -    }
          -    panning = false;
          -  }
          -
          -  /**
          -     * Create an initial dragging rectangle.
          -     * @param {number} ulx - The upper left x-position of the new rectangle.
          -     * @param {number} uly - The upper left y-position of the new rectangle.
          -     */
          -  function initRect (ulx, uly) {
          -    canvas.append('rect')
          -      .attr('x', ulx)
          -      .attr('y', uly)
          -      .attr('width', 0)
          -      .attr('height', 0)
          -      .attr('id', 'selectRect')
          -      .attr('stroke', 'black')
          -      .attr('stroke-width', 7)
          -      .attr('fill', 'none');
          -  }
          -
          -  /**
          -     * Update the dragging rectangle.
          -     * @param {number} newX - The new ulx.
          -     * @param {number} newY - The new uly.
          -     * @param {number} currentWidth - The width of the rectangle in pixels.
          -     * @param {number} currentHeight - The height of the rectangle in pixels.
          -     */
          -  function updateRect (newX, newY, currentWidth, currentHeight) {
          -    d3.select('#selectRect')
          -      .attr('x', newX)
          -      .attr('y', newY)
          -      .attr('width', currentWidth)
          -      .attr('height', currentHeight);
          -  }
          -}
          -
          -/**
          - * Select a staff element.
          - * @param {SVGGElement} el - The staff element in the DOM.
          - * @param {DragHandler} dragHandler - The drag handler in use.
          - */
          -export function selectStaff (el, dragHandler) {
          -  let staff = $(el);
          -  if (!staff.hasClass('selected')) {
          -    unselect();
          -    staff.addClass('selected');
          -    updateHighlight();
          -    Color.highlight(el, '#d00');
          -    dragHandler.dragInit();
          -  }
          -}
          -
          -/**
          - * Handle selecting an array of elements based on the selection type.
          - * @param {SVGGraphicsElement[]} elements - The elements to select. Either <g> or <use>.
          - */
          -async function selectAll (elements) {
          -  var syls = [];
          -
          -  var neumes = [];
          -
          -  var ncs = [];
          -
          -  var notNeumes = [];
          -
          -  elements.forEach(el => {
          -    var firstParent = el.parentNode;
          -
          -    if ($(firstParent).hasClass('nc')) {
          -      ncs.push(firstParent);
          -
          -      let neume = firstParent.parentNode;
          -      if (!neumes.includes(neume)) {
          -        neumes.push(neume);
          -      }
          -
          -      var syl = neume.parentNode;
          -      if (!syls.includes(syl)) {
          -        syls.push(syl);
          -      }
          -    } else {
          -      notNeumes.push(firstParent);
          -    }
          -  });
          -
          -  // Determine selection mode
          -  var selectMode = null;
          -  Array.from($('.sel-by')).forEach(tab => {
          -    if ($(tab).hasClass('is-active')) {
          -      selectMode = $(tab)[0].id;
          -    }
          -  });
          -
          -  if (selectMode === 'selByStaff') {
          -    let toSelect = [];
          -    elements.forEach(el => {
          -      if (el.tagName === 'use') {
          -        let staff = $(el).parents('.staff')[0];
          -        if (!toSelect.includes(staff)) {
          -          toSelect.push(staff);
          -        }
          -      } else {
          -        if (!toSelect.includes(el)) {
          -          toSelect.push(el);
          -        }
          -      }
          -    });
          -    toSelect.forEach(elem => {
          -      $(elem).addClass('selected');
          -    });
          -
          -    updateHighlight();
          -    toSelect.forEach(elem => {
          -      Color.highlight(elem, '#d00');
          -    });
          -    if (toSelect.length === 1) {
          -      SelectOptions.triggerSplitActions();
          -      let resize = new Resize(toSelect[0].id, neonView, dragHandler);
          -      resize.drawInitialRect();
          -    } else if (toSelect.length === 2) {
          -      let bb1 = getStaffBBox(toSelect[0]);
          -      let bb2 = getStaffBBox(toSelect[1]);
          -      var avgHeight = (bb1.lry - bb1.uly + bb2.lry - bb2.uly) / 2;
          -      if (Math.abs(bb1.uly - bb2.uly) < avgHeight) {
          -        SelectOptions.triggerStaffActions();
          -      }
          -    }
          -  } else if (selectMode === 'selBySyl') {
          -    let noClefOrCustos = selectNn(notNeumes);
          -    syls.forEach(s => { select(s); });
          -    if (!noClefOrCustos) {
          -      if (notNeumes.length === 1 && ncs.length === 0) {
          -        let el = notNeumes[0];
          -        // if ($(el).hasClass("custos")){
          -        //     SelectOptions.triggerNcActions([el]);
          -        // }
          -        if ($(el).hasClass('clef')) {
          -          SelectOptions.triggerClefActions([el]);
          -        }
          -      }
          -    } else if (syls.length > 1) {
          -      if (sharedSecondLevelParent(syls)) {
          -        Grouping.triggerGrouping('syl');
          -      }
          -    } else if (syls.length === 1) {
          -      var syl = syls[0];
          -      var nmChildren = $(syl).children('.neume');
          -      if (nmChildren.length === 1) {
          -        let neume = nmChildren[0];
          -        let ncChildren = neume.children;
          -        if (ncChildren.length === 1) {
          -          unselect();
          -          select(ncChildren[0]);
          -          SelectOptions.triggerNcActions(ncChildren[0]);
          -        } else if (ncChildren.length === 2) {
          -          unselect();
          -          if (await isLigature(ncChildren[0])) {
          -            selectNcs(ncChildren[0], dragHandler);
          -            if (sharedSecondLevelParent(Array.from(document.getElementsByClassName('selected')))) {
          -              Grouping.triggerGrouping('ligature');
          -            }
          -          } else {
          -            select(neume);
          -            SelectOptions.triggerNeumeActions();
          -          }
          -        } else {
          -          unselect();
          -          select(neume);
          -          SelectOptions.triggerNeumeActions();
          -        }
          -      } else {
          -        SelectOptions.triggerSylActions();
          -      }
          -    }
          -  } else if (selectMode === 'selByNeume') {
          -    unselect();
          -    let noClefOrCustos = selectNn(notNeumes);
          -    neumes.forEach(n => { select(n); });
          -    if (!noClefOrCustos) {
          -      if (notNeumes.length === 1 && ncs.length === 0) {
          -        let el = notNeumes[0];
          -        // if ($(el).hasClass("custos")){
          -        //     SelectOptions.triggerNcActions([el]);
          -        // }
          -        if ($(el).hasClass('clef')) {
          -          SelectOptions.triggerClefActions([el]);
          -        }
          -      }
          -    } else if (neumes.length > 1) {
          -      let syllable = neumes[0].parentElement;
          -      let group = false;
          -      for (var i = 1; i < neumes.length; i++) {
          -        if (syllable !== neumes[i].parentElement) {
          -          group = true;
          -          break;
          -        }
          -      }
          -      if (group) {
          -        if (sharedSecondLevelParent(neumes)) {
          -          Grouping.triggerGrouping('neume');
          -        }
          -      } else {
          -        let sylNeumes = Array.from(syllable.children).filter(child => $(child).hasClass('neume'));
          -        let result = true;
          -        sylNeumes.forEach(neume => { result = result && neumes.includes(neume); });
          -        if (result) {
          -          unselect();
          -          select(syllable);
          -          SelectOptions.triggerSylActions();
          -        }
          -      }
          -    } else if (neumes.length === 1) {
          -      let neume = neumes[0];
          -      let ncChildren = neume.children;
          -      if (ncChildren.length === 1) {
          -        unselect();
          -        select(ncChildren[0]);
          -        SelectOptions.triggerNcActions(ncChildren[0]);
          -      } else if (ncChildren.length === 2 && await isLigature(ncChildren[0])) {
          -        unselect();
          -        select(ncChildren[0]);
          -        select(ncChildren[1]);
          -        Grouping.triggerGrouping('ligature');
          -      } else {
          -        SelectOptions.triggerNeumeActions();
          -      }
          -    }
          -  } else if (selectMode === 'selByNc') {
          -    let noClefOrCustos = selectNn(notNeumes);
          -    if (ncs.length === 1 && noClefOrCustos) {
          -      selectNcs(ncs[0].children[0], dragHandler);
          -      return;
          -    }
          -    var prev = $(ncs[0]).prev();
          -    if (ncs.length !== 0 && await isLigature(ncs[0]) && prev.length !== 0 && await isLigature($(ncs[0]).prev()[0])) {
          -      ncs.push($(ncs[0]).prev()[0]);
          -    }
          -    ncs.forEach(nc => { select(nc); });
          -    if (!noClefOrCustos) {
          -      if (notNeumes.length === 1 && ncs.length === 0) {
          -        var el = notNeumes[0];
          -        // if ($(el).hasClass("custos")){
          -        //     SelectOptions.triggerNcActions([el]);
          -        // }
          -        if ($(el).hasClass('clef')) {
          -          SelectOptions.triggerClefActions([el]);
          -        }
          -      }
          -    } else if (ncs.length === 2) {
          -      let firstChild = ncs[0].children[0];
          -      let secondChild = ncs[1].children[0];
          -      var firstX = firstChild.x.baseVal.value; // $(ncs[0]).children()[0].x.baseVal.value;
          -      var secondX = secondChild.x.baseVal.value; // $(ncs[1]).children()[0].x.baseVal.value;
          -      var firstY = 0;
          -      var secondY = 0;
          -
          -      if (firstX === secondX) {
          -        firstY = secondChild.y.baseVal.value;
          -        secondY = firstChild.y.baseVal.value;
          -      } else {
          -        firstY = firstChild.y.baseVal.value;
          -        secondY = secondChild.y.baseVal.value;
          -      }
          -
          -      if (secondY > firstY) {
          -        if (ncs[0].parentNode.id === ncs[1].parentNode.id) {
          -          let isFirstLigature = await isLigature(ncs[0]);
          -          let isSecondLigature = await isLigature(ncs[1]);
          -          if ((isFirstLigature && isSecondLigature) || (!isFirstLigature && !isSecondLigature)) {
          -            Grouping.triggerGrouping('ligature');
          -          }
          -          /* else{
          -                        Grouping.triggerGrouping("ligatureNc");
          -                    } */
          -        } else {
          -          if (ncs[0].parentElement !== ncs[1].parentElement) {
          -            if (sharedSecondLevelParent(ncs)) {
          -              Grouping.triggerGrouping('nc');
          -            }
          -          }
          -        }
          -      } else {
          -        if (ncs[0].parentElement !== ncs[1].parentElement) {
          -          if (sharedSecondLevelParent(ncs)) {
          -            Grouping.triggerGrouping('nc');
          -          }
          -        }
          -      }
          -    } else if (ncs.length > 1 && noClefOrCustos) {
          -      let neume = ncs[0].parentElement;
          -      let group = false;
          -      for (i = 1; i < ncs.length; i++) {
          -        if (ncs[i].parentElement !== neume) {
          -          group = true;
          -          break;
          -        }
          -      }
          -      if (group) {
          -        if (sharedSecondLevelParent(ncs)) {
          -          Grouping.triggerGrouping('nc');
          -        }
          -      } else {
          -        let neumeNcs = Array.from(neume.children).filter(nc => $(nc).hasClass('nc'));
          -        let result = true;
          -        neumeNcs.forEach(nc => { result = result && ncs.includes(nc); });
          -        if (result) {
          -          unselect();
          -          select(neume);
          -          SelectOptions.triggerNeumeActions();
          -        }
          -      }
          -    } else if (ncs.length === 1) {
          -      SelectOptions.triggerNcActions(ncs[0]);
          -    }
          -  }
          -  if ($('.selected').length > 0) {
          -    info.stopListeners();
          -  }
          -  dragHandler.dragInit();
          -}
          -
          -/**
          - * Unselect all selected elements and run undo any extra
          - * actions.
          - */
          -export function unselect () {
          -  var selected = $('.selected');
          -  for (var i = 0; i < selected.length; i++) {
          -    if ($(selected[i]).hasClass('staff')) {
          -      $(selected[i]).removeClass('selected');
          -      Color.unhighlight(selected[i]);
          -    } else {
          -      $(selected[i]).removeClass('selected').attr('fill', null);
          -    }
          -  }
          -  $('.syl-select').css('color', '');
          -  $('.syl-select').css('font-weight', '');
          -  $('.syl-select').removeClass('syl-select');
          -
          -  d3.select('#resizeRect').remove();
          -
          -  if (!$('#selByStaff').hasClass('is-active')) {
          -    Grouping.endGroupingSelection();
          -  } else {
          -    SelectOptions.endOptionsSelection();
          -  }
          -  updateHighlight();
          -}
          -
          -/**
          - * Generic select function.
          - * @param {SVGGraphicsElement} el
          - */
          -function select (el) {
          -  if (!$(el).hasClass('selected')) {
          -    $(el).attr('fill', '#d00');
          -    $(el).addClass('selected');
          -
          -    var sylId;
          -    if ($(el).hasClass('syllable')) {
          -      sylId = el.id;
          -    } else if ($(el).parents('.syllable').length) {
          -      sylId = $(el).parents('.syllable').attr('id');
          -    }
          -    if (sylId !== undefined) {
          -      if ($('span').filter('.' + sylId).length) {
          -        $('span').filter('.' + sylId).css('color', '#d00');
          -        $('span').filter('.' + sylId).css('font-weight', 'bold');
          -        $('span').filter('.' + sylId).addClass('syl-select');
          -      }
          -    }
          -  }
          -  updateHighlight();
          -}
          -
          -/**
          - * Select an nc.
          - * @param {SVGGraphicsElement} el - The nc element to select.
          - * @param {DragHandler} dragHandler - An instantiated DragHandler.
          - */
          -async function selectNcs (el, dragHandler) {
          -  if (!$(el).parent().hasClass('selected')) {
          -    var parent = el.parentNode;
          -    unselect();
          -    select(parent);
          -    if (await isLigature(parent)) {
          -      var prevNc = $(parent).prev()[0];
          -      if (await isLigature(prevNc)) {
          -        select(prevNc);
          -      } else {
          -        var nextNc = $(parent).next()[0];
          -        if (await isLigature(nextNc)) {
          -          select(nextNc);
          -        } else {
          -          console.warn('Error: Neither prev or next nc are ligatures');
          -        }
          -      }
          -      Grouping.triggerGrouping('ligature');
          -    } else if ($(parent).hasClass('nc')) {
          -      SelectOptions.triggerNcActions(parent);
          -    } else {
          -      console.warn('No action triggered!');
          -    }
          -    dragHandler.dragInit();
          -  }
          -}
          -
          -/**
          - * Check if neume component is part of a ligature
          - * @param {SVGGraphicsElement} nc - The neume component to check.
          - * @returns {boolean}
          - */
          -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;
          -}
          -
          -/**
          - * Get the bounding box of a staff based on its staff lines.
          - * @param {SVGGElement} staff
          - * @returns {object}
          - */
          -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;
          -  }
          -}
          -
          -
          -
          - - - - -
          - - - -
          - -
          - Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) -
          - - - - - diff --git a/doc/SingleEdit_SingleEditMode.js.html b/doc/SingleEdit_SingleEditMode.js.html deleted file mode 100644 index 0b56bbfba..000000000 --- a/doc/SingleEdit_SingleEditMode.js.html +++ /dev/null @@ -1,106 +0,0 @@ - - - - - JSDoc: Source: SingleEdit/SingleEditMode.js - - - - - - - - - - -
          - -

          Source: SingleEdit/SingleEditMode.js

          - - - - - - -
          -
          -
          import { bindInsertTabs, initEditModeControls, initNavbar, initInsertEditControls } from './EditControls.js';
          -import DragHandler from '../SingleView/DragHandler.js';
          -import * as Select from './Select.js';
          -import InsertHandler from './InsertHandler.js';
          -import * as SelectOptions from './SelectOptions.js';
          -
          -/**
          - * An Edit Module for a single page of a manuscript.
          - * Works with the SingleView module.
          - */
          -class SingleEditMode {
          -  /**
          -   * Constructor for an EditMode object.
          -   * @param {NeonView} neonView - The NeonView parent.
          -   */
          -  constructor (neonView) {
          -    this.neonView = neonView;
          -    initEditModeControls(this);
          -  }
          -
          -  /**
          -   * Initialize the start of edit mode when first leaving viewer mode.
          -   */
          -  initEditMode () {
          -    this.dragHandler = new DragHandler(this.neonView);
          -    initNavbar(this.neonView);
          -    Select.setSelectHelperObjects(this.dragHandler, this.neonView);
          -    Select.clickSelect();
          -    this.insertHandler = new InsertHandler(this.neonView);
          -    bindInsertTabs(this.insertHandler);
          -    document.getElementById('neumeTab').click();
          -    Select.dragSelect();
          -    SelectOptions.initNeonView(this.neonView);
          -    initInsertEditControls(this.neonView);
          -    let editMenu = document.getElementById('editMenu');
          -    editMenu.style.backgroundColor = '#ffc7c7';
          -    editMenu.style.fontWeight = 'bold';
          -  }
          -
          -  /**
          -   * Get the user mode that Neon is in. Either insert, edit, or viewer.
          -   * @returns {string}
          -   */
          -  getUserMode () {
          -    if (this.insertHandler !== undefined) {
          -      if (this.insertHandler.isInsertMode()) {
          -        return 'insert';
          -      }
          -      return 'edit';
          -    }
          -    return 'viewer';
          -  }
          -}
          -
          -export { SingleEditMode as default };
          -
          -
          -
          - - - - -
          - - - -
          - -
          - Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) -
          - - - - - diff --git a/doc/SingleEdit_SplitHandler.js.html b/doc/SingleEdit_SplitHandler.js.html deleted file mode 100644 index 3402746bd..000000000 --- a/doc/SingleEdit_SplitHandler.js.html +++ /dev/null @@ -1,137 +0,0 @@ - - - - - JSDoc: Source: SingleEdit/SplitHandler.js - - - - - - - - - - -
          - -

          Source: SingleEdit/SplitHandler.js

          - - - - - - -
          -
          -
          import * as Notification from '../utils/Notification.js';
          -const $ = require('jquery');
          -
          -/**
          - * Handler splitting a staff into two staves through Verovio.
          - * @constructor
          - * @param {NeonView} neonView - The NeonView parent.
          - */
          -function SplitHandler (neonView) {
          -  function startSplit () {
          -    splitDisable();
          -
          -    $('body').on('click', '#svg_output', handler);
          -
          -    // Handle keypresses
          -    $('body').on('keydown', keydownListener);
          -    $('body').on('keyup', resetHandler);
          -    $('body').on('click', clickawayHandler);
          -
          -    Notification.queueNotification('Click Where to Split');
          -  }
          -
          -  function keydownListener (evt) {
          -    if (evt.key === 'Escape') {
          -      splitDisable();
          -    } else if (evt.key === 'Shift') {
          -      $('body').off('click', '#svg_output', handler);
          -    }
          -  }
          -
          -  function clickawayHandler (evt) {
          -    if (evt.target.id !== 'svg_group' && $('#svg_group').find(evt.target).length === 0 && evt.target.tagName !== 'path' &&
          -            evt.target.id !== 'split-system') {
          -      splitDisable();
          -      $('body').off('click', '#svg_output', handler);
          -    }
          -  }
          -
          -  function resetHandler (evt) {
          -    if (evt.key === 'Shift') {
          -      $('body').on('click', '#svg_output', handler);
          -    }
          -  }
          -
          -  function handler (evt) {
          -    let id = $('.selected')[0].id;
          -
          -    var container = document.getElementsByClassName('definition-scale')[0];
          -    var pt = container.createSVGPoint();
          -    pt.x = evt.clientX;
          -    pt.y = evt.clientY;
          -
          -    // Transform to SVG coordinate system.
          -    var transformMatrix = container.getScreenCTM().inverse();
          -    var cursorPt = pt.matrixTransform(transformMatrix);
          -    console.log(cursorPt.x);
          -    // Find staff point corresponds to if one exists
          -    // TODO
          -
          -    let editorAction = {
          -      'action': 'split',
          -      'param': {
          -        'elementId': id,
          -        'x': parseInt(cursorPt.x)
          -      }
          -    };
          -
          -    neonView.edit(editorAction, 0).then((result) => {
          -      if (result) {
          -        neonView.updateForCurrentPage();
          -      }
          -      splitDisable();
          -    });
          -  }
          -
          -  function splitDisable () {
          -    $('body').off('keydown', keydownListener);
          -    $('body').off('keyup', resetHandler);
          -    $('body').off('click', clickawayHandler);
          -    $('body').off('click', handler);
          -  }
          -
          -  SplitHandler.prototype.constructor = SplitHandler;
          -  SplitHandler.prototype.startSplit = startSplit;
          -}
          -export { SplitHandler as default };
          -
          -
          -
          - - - - -
          - - - -
          - -
          - Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) -
          - - - - - diff --git a/doc/SingleView.html b/doc/SingleView.html index 1036e93e9..822c8ca9b 100644 --- a/doc/SingleView.html +++ b/doc/SingleView.html @@ -192,7 +192,7 @@

          Parameters:
          Source:
          @@ -349,7 +349,145 @@
          Parameters:
          Source:
          + + + + + + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + +

(async) changePage(page)

+ + + + + + +
+ Change to a certain page +Since there is only one page, this is essentially a wrapper for updateSVG +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
page + + +number + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
@@ -437,7 +575,7 @@

getCurr
Source:
@@ -491,6 +629,218 @@

Returns:
+

getCurrentPageURI() → {string}

+ + + + + + +
+ Returns the page URI. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +string + + +
+
+ + + + + + + + + + + + + +

getPageName() → {string}

+ + + + + + +
+ A human readable name for the page. Used for downloads. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +string + + +
+
+ + + + + + + + + + + + +

removeUpdateCallback(cb)

@@ -592,7 +942,7 @@
Parameters:
Source:
@@ -680,7 +1030,7 @@

r
Source:
@@ -768,7 +1118,7 @@

s
Source:
@@ -905,7 +1255,7 @@

Parameters:
Source:
@@ -951,13 +1301,13 @@
Parameters:

- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:08 GMT-0400 (GMT-04:00)
diff --git a/doc/SingleView_DragHandler.js.html b/doc/SingleView_DragHandler.js.html deleted file mode 100644 index cf1b2114e..000000000 --- a/doc/SingleView_DragHandler.js.html +++ /dev/null @@ -1,154 +0,0 @@ - - - - - JSDoc: Source: SingleView/DragHandler.js - - - - - - - - - - -
- -

Source: SingleView/DragHandler.js

- - - - - - -
-
-
const d3 = require('d3');
-const $ = require('jquery');
-
-/**
- * Handle the dragging of musical elements and communicate actions.
- * @constructor
- * @param {NeonView} neonView - The NeonView parent object.
- */
-function DragHandler (neonView) {
-  var dragStartCoords;
-  var dragEndCoords;
-  var resetToAction;
-
-  /**
-     * Initialize the dragging action and handler for selected elements.
-     */
-  function dragInit () {
-    // Adding listeners
-    var dragBehaviour = d3.drag().on('start', dragStarted)
-      .on('drag', dragging)
-      .on('end', dragEnded);
-
-    var activeNc = d3.selectAll('.selected');
-    var selection = Array.from(activeNc._groups[0]);
-
-    dragStartCoords = new Array(activeNc.size());
-    dragEndCoords = new Array(activeNc.size());
-
-    activeNc.call(dragBehaviour);
-
-    var editorAction;
-
-    // Drag effects
-    function dragStarted () {
-      dragStartCoords = d3.mouse(this);
-      if (this.classList.contains('staff')) {
-        d3.select('#svg_group').call(dragBehaviour);
-      }
-    }
-
-    function dragging () {
-      var relativeY = d3.event.y - dragStartCoords[1];
-      var relativeX = d3.event.x - dragStartCoords[0];
-      selection.forEach((el) => {
-        d3.select(el).attr('transform', function () {
-          return 'translate(' + [relativeX, relativeY] + ')';
-        });
-      });
-    }
-
-    function dragEnded () {
-      dragEndCoords = [d3.event.x, d3.event.y];
-      let paramArray = [];
-      selection.forEach((el) => {
-        let singleAction = { action: 'drag',
-          param: { elementId: el.id,
-            x: parseInt(dragEndCoords[0] - dragStartCoords[0]),
-            y: parseInt(dragEndCoords[1] - dragStartCoords[1]) * -1 }
-        };
-        paramArray.push(singleAction);
-      });
-      editorAction = {
-        'action': 'chain',
-        'param': paramArray
-      };
-
-      var xDiff = Math.abs(dragStartCoords[0] - dragEndCoords[0]);
-      var yDiff = Math.abs(dragStartCoords[1] - dragEndCoords[1]);
-
-      if (xDiff > 5 || yDiff > 5) {
-        neonView.edit(editorAction, 0).then(() => {
-          neonView.updateForCurrentPage();
-          endOptionsSelection();
-          reset();
-          dragInit();
-        });
-      } else {
-        reset();
-        dragInit();
-      }
-    }
-  }
-
-  function resetTo (reset) {
-    resetToAction = reset;
-  }
-
-  function reset () {
-    if (resetToAction !== undefined) {
-      d3.select('#svg_group').call(resetToAction);
-    }
-  }
-
-  function endOptionsSelection () {
-    $('#moreEdit').empty();
-    $('#moreEdit').addClass('is-invisible');
-  }
-
-  DragHandler.prototype.dragInit = dragInit;
-  DragHandler.prototype.resetTo = resetTo;
-}
-
-export { DragHandler as default };
-
-
-
- - - - -
- - - -
- -
- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) -
- - - - - diff --git a/doc/SingleView_SingleView.js.html b/doc/SingleView_SingleView.js.html index 8c5f26a6c..d801b6d96 100644 --- a/doc/SingleView_SingleView.js.html +++ b/doc/SingleView_SingleView.js.html @@ -33,6 +33,15 @@

Source: SingleView/SingleView.js

const d3 = require('d3'); const $ = require('jquery'); +/* A view module must contain the following functions: + * updateSVG(svg, pageNo) - a function that updates the dipslayed SVG with + * the provided SVG for the given zero-indexed page number. + * add/removeUpdateCallback - functions that add or remove callback functions + * that occur when the page updates. + * getCurrentPage(URI) - functions that return the current page index (URI). + * getPageName - function that returns a user-readable name for the page. + */ + /** * A view module for displaying a single page of a manuscript. */ @@ -74,6 +83,8 @@

Source: SingleView/SingleView.js

this.setViewEventHandlers(); this.displayPanel.setDisplayListeners(); + this.pageURI = image; + document.getElementById('loading').style.display = 'none'; } @@ -97,6 +108,16 @@

Source: SingleView/SingleView.js

this.updateCallbacks.forEach(callback => callback()); } + /** + * Change to a certain page + * Since there is only one page, this is essentially a wrapper for updateSVG + * @param {number} page + */ + async changePage (page) { + let svg = await this.neonView.getPageSVG(this.getCurrentPageURI()); + this.updateSVG(svg); + } + /** * Add a callback to the list of those be called when the page updates. * @param {function} cb - The callback function to add to the list. @@ -134,6 +155,14 @@

Source: SingleView/SingleView.js

return 0; } + /** + * Returns the page URI. + * @returns {string} + */ + getCurrentPageURI () { + return this.pageURI; + } + /** * Set event handlers for the view and display panel. */ @@ -159,6 +188,9 @@

Source: SingleView/SingleView.js

case 'Shift': d3.select('#svg_group').on('.drag', null); Cursor.updateCursorTo(''); + if (this.neonView.getUserMode !== 'viewer') { + this.neonView.NeumeEdit.setSelectListeners(); + } if (this.neonView.getUserMode() === 'insert') { Cursor.updateCursor(); } @@ -189,6 +221,14 @@

Source: SingleView/SingleView.js

} }); } + + /** + * A human readable name for the page. Used for downloads. + * @returns {string} + */ + getPageName () { + return this.neonView.name; + } } export { SingleView as default }; @@ -202,13 +242,13 @@

Source: SingleView/SingleView.js


- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:07 GMT-0400 (GMT-04:00)
diff --git a/doc/SingleView_Zoom.js.html b/doc/SingleView_Zoom.js.html index 2f0cec617..f08515551 100644 --- a/doc/SingleView_Zoom.js.html +++ b/doc/SingleView_Zoom.js.html @@ -264,13 +264,13 @@

Source: SingleView/Zoom.js


- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:07 GMT-0400 (GMT-04:00)
diff --git a/doc/SplitHandler.html b/doc/SplitHandler.html deleted file mode 100644 index 4aabf3689..000000000 --- a/doc/SplitHandler.html +++ /dev/null @@ -1,219 +0,0 @@ - - - - - JSDoc: Class: SplitHandler - - - - - - - - - - -
- -

Class: SplitHandler

- - - - - - -
- -
- -

SplitHandler(neonView)

- - -
- -
-
- - - - - - -

new SplitHandler(neonView)

- - - - - - -
- Handler splitting a staff into two staves through Verovio. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
neonView - - -NeonView - - - - The NeonView parent.
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - -
- -
- - - - -
- - - -
- -
- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) -
- - - - - \ No newline at end of file diff --git a/doc/SingleEdit_Contents.js.html b/doc/SquareEdit_Contents.js.html similarity index 72% rename from doc/SingleEdit_Contents.js.html rename to doc/SquareEdit_Contents.js.html index defa26e0d..6f4bc5371 100644 --- a/doc/SingleEdit_Contents.js.html +++ b/doc/SquareEdit_Contents.js.html @@ -2,7 +2,7 @@ - JSDoc: Source: SingleEdit/Contents.js + JSDoc: Source: SquareEdit/Contents.js @@ -17,7 +17,7 @@
-

Source: SingleEdit/Contents.js

+

Source: SquareEdit/Contents.js

@@ -26,7 +26,7 @@

Source: SingleEdit/Contents.js

-
/** @module SingleEdit/Contents */
+            
/** @module SquareEdit/Contents */
 
 import PunctumIcon from '../img/punctum.png';
 import VirgaIcon from '../img/virga.png';
@@ -55,7 +55,7 @@ 

Source: SingleEdit/Contents.js

* @type {object} */ export const insertTabHtml = { - neumeTab: "<p class='control'>" + + primitiveTab: "<p class='control'>" + "<button id='punctum' class='button insertel smallel' title='punctum'><img src='" + PunctumIcon + "' class='image'/></button></p>" + "<p class='control'>" + "<button id='virga' class='button insertel smallel' title='virga'><img src='" + VirgaIcon + "' class='image'/></button></p>" + @@ -66,7 +66,11 @@

Source: SingleEdit/Contents.js

"<p class='control'>" + "<button id='quilisma' class='button insertel smallel' title='quilisma'><img src='" + QuilismaIcon + "' class='image'/></button></p>" + */ "<p class='control'>" + - "<button id='custos' class='button insertel smallel' title='custos'><img src='" + CustosIcon + "' class='image'/></button></p>", + "<button id='custos' class='button insertel smallel' title='custos'><img src='" + CustosIcon + "' class='image'/></button></p>" + + "<p class='control'>" + + "<button id='cClef' class='button insertel smallel' title=' C Clef'><img src='" + CClefIcon + "' class='image' /></button></p>" + + "<p class='control'>" + + "<button id='fClef' class='button insertel smallel' title='F Clef'><img src='" + FClefIcon + "' class='image'/></button></p>", groupingTab: "<p class='control'>" + "<button id='pes' class='button insertel smallel' title='pes'><img src='" + PesIcon + "' class='image'/></button></p>" + "<p class='control'>" + @@ -81,10 +85,6 @@

Source: SingleEdit/Contents.js

"<button id='porrectus' class='button insertel smallel' title='porrectus'><img src='" + PorrectusIcon + "' class='image'/></button></p>" + "<p class='control'>" + "<button id='pressus' class='button insertel smallel' title='pressus'><img src='" + PressusIcon + "' class='image'/></button></p>", - clefTab: "<p class='control'>" + - "<button id='cClef' class='button insertel smallel' title=' C Clef'><img src='" + CClefIcon + "' class='image' /></button></p>" + - "<p class='control'>" + - "<button id='fClef' class='button insertel smallel' title='F Clef'><img src='" + FClefIcon + "' class='image'/></button></p>", systemTab: "<p class='control'>" + "<button id='staff' class='button insertel longel' title='system'><img src='" + StaffIcon + "' class='image' /></button></p>" + '<p>Click upper left and lower right corners of new staff.</p>' @@ -98,24 +98,6 @@

Source: SingleEdit/Contents.js

// "<button id='finalDiv' class='button insertel tallel'><img src='" + FinalDivIcon + "' class='image'/></button></p>" }; -/** - * Contents of navbar menu after switching to edit mode. - * @type {string} - */ -export const navbarDropdownMenu = - "<div class='navbar-item has-dropdown is-hoverable'><a class='navbar-link'>File</a>" + - "<div id='navbar-dropdown-options' class='navbar-dropdown'>" + - "<a id='save' class='navbar-item'>Save File</a>" + - "<a id='getmei' class='navbar-item' href='' download=''> Download MEI </a>" + - "<a id='revert' class='navbar-item'> Revert </a>"; - -/** - * Finalize option in the navbar for rodan - * @type {string} - */ -export const navbarFinalize = - "<a id='finalize' class='navbar-item'> Finalize MEI </a>"; - /** * Structure of insert panel with basic grouping tabs. * @type {string} @@ -125,9 +107,8 @@

Source: SingleEdit/Contents.js

"<svg class='icon is-pulled-right'><use id='toggleInsert' xlink:href='" + Icons + "#dropdown-down'></use></svg></p>" + "<div id='insertContents' style='overflow-y: hidden;'>" + "<p class='panel-tabs'>" + - "<a id='neumeTab' class='insertTab'>Neume</a>" + + "<a id='primitiveTab' class='insertTab'>Primitive Elements</a>" + "<a id='groupingTab' class='insertTab'>Grouping</a>" + - "<a id='clefTab' class='insertTab'>Clef</a>" + "<a id='systemTab' class='insertTab'>System</a></p>" + // "<a id='divisionTab' class='insertTab'>Division</a></p>" + "<a class='panel-block has-text-centered'>" + @@ -152,14 +133,8 @@

Source: SingleEdit/Contents.js

"<button class='button sel-by' id='selByNc'>Neume Component</button></p>" + "<p class='control'>" + "<button class='button sel-by' id='selByStaff'>Staff</button></p></div></a>" + - "<a class='panel-block'>" + "<div class='field is-grouped buttons'>" + "<p class='control'>" + - "<button class='button' id='undo'>Undo</button></p>" + - "<p class='control'>" + - "<button class='button' id='redo'>Redo</button></p>" + - "<p class='control'>" + - "<button class='button' id='delete'>Delete</button></p></div></a>" + "<a id='moreEdit' class='panel-block is-invisible'>" + "<a id='neumeEdit' class='panel-block is-invisible'></div>"; @@ -178,7 +153,9 @@

Source: SingleEdit/Contents.js

"<div class='dropdown-content'>" + "<a id='Punctum' class='dropdown-item'>Punctum</a>" + "<a id='Virga' class='dropdown-item'>Virga</a>" + - "<a id='Inclinatum' class='dropdown-item'>Inclinatum</a></div></div></div>"; + "<a id='Inclinatum' class='dropdown-item'>Inclinatum</a></div></div></div>" + + "<p class='control'>" + + "<button class='button' id='delete'>Delete</button></p></div>"; /** * Contents of extra neume action menu. @@ -209,7 +186,8 @@

Source: SingleEdit/Contents.js

"<a id='Pressus' class='dropdown-item grouping'>Pressus</a>" + '</div></div></div>' + "<div><p class='control'>" + - "<button class='button' id='ungroupNcs'>Ungroup</button></p></div>"; + "<button class='button' id='ungroupNcs'>Ungroup</button>" + + "<button class='button' id='delete'>Delete</button></p></div>"; /** * Contents of extra staff action menu. @@ -218,7 +196,16 @@

Source: SingleEdit/Contents.js

export const staffActionContents = '<label>Merge Systems:&nbsp;</label>' + "<div><p class='control'>" + - "<button id='merge-systems' class='button'>Merge</button></p></div>"; + "<button id='merge-systems' class='button'>Merge</button>" + + "<button class='button' id='delete'>Delete</button></p></div>"; + +/** + * Contents of default action menu. + * @type {string} + */ +export const defaultActionContents = + "<div><p class='control'>" + + "<button class='button' id='delete'>Delete</button></p></div>"; /** * Contents of split action menu. @@ -227,7 +214,8 @@

Source: SingleEdit/Contents.js

export const splitActionContents = '<label>Split System:&nbsp;</label>' + "<div><p class='control'>" + - "<button id='split-system' class='button'>Split</button></p></div>"; + "<button id='split-system' class='button'>Split</button>" + + "<button class='button' id='delete'>Delete</button></p></div>"; /** * Contents of extra clef action menu. @@ -243,7 +231,8 @@

Source: SingleEdit/Contents.js

"<div class='dropdown-menu' id='dropdown-menu' role='menu'>" + "<div class='dropdown-content'>" + "<a id='CClef' class='dropdown-item'>C Clef</a>" + - "<a id='FClef' class='dropdown-item'>F Clef</a></div></div></div>"; + "<a id='FClef' class='dropdown-item'>F Clef</a></div></div>" + + "<button class='button' id='delete'>Delete</button></div>"; /** * HTML for grouping selection menu. @@ -252,21 +241,30 @@

Source: SingleEdit/Contents.js

export const groupingMenu = { 'nc': "<div class='field is-grouped'>" + "<div><p class='control'>" + - "<button class='button' id='groupNcs'>Group Neume Components</button></p></div>", + "<button class='button' id='groupNcs'>Group Neume Components</button>" + + "<button class='button' id='delete'>Delete</button></p></div>", 'neume': "<div class='field is-grouped'>" + "<div><p class='control'>" + - "<button class='button' id='groupNeumes'>Group Neumes</button></p></div>", + "<button class='button' id='groupNeumes'>Group Neumes</button>" + + "<button class='button' id='delete'>Delete</button></p></div>", 'syl': "<div class='field is-grouped'>" + "<div><p class='control'>" + - "<button class='button' id='mergeSyls'>Merge Syllables</button></p></div>", + "<button class='button' id='mergeSyls'>Merge Syllables</button>" + + "<button class='button' id='delete'>Delete</button></p></div>", 'ligatureNc': "<div class='field is-grouped'>" + "<div><p class='control'>" + "<button class='button' id='groupNcs'>Group Neume Components</button></p></div>" + "<div><p class='control'>" + - "<button class='button' id='toggle-ligature'>Toggle Ligature</button></p></div></div>", + "<button class='button' id='toggle-ligature'>Toggle Ligature</button>" + + "<button class='button' id='delete'>Delete</button></p></div></div>", 'ligature': "<div class='field is-grouped'>" + "<div><p class='control'>" + - "<button class='button' id='toggle-ligature'>Toggle Ligature</button></p></div></div>" + "<button class='button' id='toggle-ligature'>Toggle Ligature</button>" + + "<button class='button' id='delete'>Delete</button></p></div></div>", + 'splitSyllable': "<div class='field is-grouped'>" + + "<div><p class='control'>" + + "<button class='button' id='toggle-link'>Toggle Linked Syllables</button>" + + "<button class='button' id='delete'>Delete</button></p></div></div>" };
@@ -278,13 +276,13 @@

Source: SingleEdit/Contents.js


- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:07 GMT-0400 (GMT-04:00)
diff --git a/doc/SingleEdit_EditControls.js.html b/doc/SquareEdit_Controls.js.html similarity index 54% rename from doc/SingleEdit_EditControls.js.html rename to doc/SquareEdit_Controls.js.html index 5692fc1d4..090e41ef6 100644 --- a/doc/SingleEdit_EditControls.js.html +++ b/doc/SquareEdit_Controls.js.html @@ -2,7 +2,7 @@ - JSDoc: Source: SingleEdit/EditControls.js + JSDoc: Source: SquareEdit/Controls.js @@ -17,7 +17,7 @@
-

Source: SingleEdit/EditControls.js

+

Source: SquareEdit/Controls.js

@@ -26,29 +26,22 @@

Source: SingleEdit/EditControls.js

-
/** @module SingleEdit/EditControls */
+            
/** @module SquareEdit/Controls */
 
 import * as Contents from './Contents.js';
 import * as Cursor from '../utils/Cursor.js';
 import Icons from '../img/icons.svg';
-import * as Notification from '../utils/Notification.js';
-import { unselect } from './Select.js';
+import { unselect } from '../utils/SelectTools.js';
 const $ = require('jquery');
 
 /**
  * Set listener on EditMode button.
- * @param {SingleEditMode} editMode - The EditMode object.
+ * @param {EditMode} editMode - The EditMode object.
  */
 export function initEditModeControls (editMode) {
-  /* document.getElementById('dropdown_toggle').innerHTML =
-    '<a class="navbar-item"><button class="button" id="edit_mode">' +
-    'Edit MEI</button></a>'; */
-  $('#edit_mode').on('click', function () {
-    $('#dropdown_toggle').empty();
-    $('#dropdown_toggle').append(Contents.navbarDropdownMenu);
+  document.getElementById('edit_mode').addEventListener('click', function () {
     $('#insert_controls').append(Contents.insertControlsPanel);
     $('#edit_controls').append(Contents.editControlsPanel);
-
     editMode.initEditMode();
   });
 }
@@ -63,6 +56,21 @@ 

Source: SingleEdit/EditControls.js

return tab.id; }); + document.body.addEventListener('keydown', (evt) => { + if (evt.code.match(/^Digit\d$/) && evt.shiftKey) { + try { + let index = Number(evt.code[evt.code.length - 1]) - 1; + let insertOptions = document.getElementsByClassName('insertel'); + let selectedOption = insertOptions[index]; + deactivate('.insertel'); + activate(selectedOption.id, insertHandler); + Cursor.updateCursor(); + } catch (e) { + console.debug(e); + } + } + }); + $.each(tabIds, function (i, tab) { $('#' + tab).on('click', () => { deactivate('.insertTab'); @@ -71,43 +79,10 @@

Source: SingleEdit/EditControls.js

$('#insert_data').empty(); $('#insert_data').append(Contents.insertTabHtml[tab]); bindElements(insertHandler); - }); - }); -} - -/** - * Set listener on switching EditMode button to File dropdown in the navbar. - * @param {string} filename - The name of the MEI file. - * @param {NeonView} neonView - */ -export function initNavbar (neonView) { - // setup navbar listeners - $('#save').on('click', () => { - neonView.save().then(() => { - Notification.queueNotification('Saved'); - }); - }); - $('body').on('keydown', (evt) => { - if (evt.key === 's') { - neonView.save().then(() => { - Notification.queueNotification('Saved'); - }); - } - }); - - $('#revert').on('click', function () { - if (window.confirm('Reverting will cause all changes to be lost. Press OK to continue.')) { - neonView.deleteDb().then(() => { - window.location.reload(); - }); - } - }); - // Download link for MEI - // Is an actual file with a valid URI except in local mode where it must be generated. - $('#getmei').on('click', () => { - neonView.getPageURI().then((uri) => { - $('#getmei').attr('href', uri) - .attr('download', neonView.name); + deactivate('.insertel'); + let firstOption = document.getElementsByClassName('insertel')[0]; + activate(firstOption.id, insertHandler); + Cursor.updateCursor(); }); }); } @@ -136,61 +111,6 @@

Source: SingleEdit/EditControls.js

$('#toggleEdit').attr('xlink:href', Icons + '#dropdown-side'); } }); - - $('#undo').on('click', undoHandler); - $('body').on('keydown', (evt) => { - if (evt.key === 'z' && (evt.ctrlKey || evt.metaKey)) { - undoHandler(); - } - }); - - $('#redo').on('click', redoHandler); - $('body').on('keydown', (evt) => { - if ((evt.key === 'Z' || (evt.key === 'z' && evt.shiftKey)) && (evt.ctrlKey || evt.metaKey)) { - redoHandler(); - } - }); - - $('#delete').on('click', removeHandler); - $('body').on('keydown', (evt) => { - if (evt.key === 'd' || evt.key === 'Backspace') { removeHandler(); } - }); - - function undoHandler () { - if (!neonView.undo(0)) { - console.error('Failed to undo action.'); - } else { - neonView.updateForCurrentPage(); - } - } - - function redoHandler () { - if (!neonView.redo(0)) { - console.error('Failed to redo action'); - } else { - neonView.updateForCurrentPage(); - } - } - - function removeHandler () { - let toRemove = []; - var selected = Array.from(document.getElementsByClassName('selected')); - selected.forEach(elem => { - toRemove.push( - { - 'action': 'remove', - 'param': { - 'elementId': elem.id - } - } - ); - }); - let chainAction = { - 'action': 'chain', - 'param': toRemove - }; - neonView.edit(chainAction, 0).then(() => { neonView.updateForCurrentPage(); }); - } } /** @@ -231,13 +151,6 @@

Source: SingleEdit/EditControls.js

activate(el, insertHandler); Cursor.updateCursor(); }); - document.body.addEventListener('keydown', (evt) => { - if (evt.code === 'Digit' + (i + 1) && evt.shiftKey) { - deactivate('.insertel'); - activate(el, insertHandler); - Cursor.updateCursor(); - } - }); }); } @@ -257,6 +170,7 @@

Source: SingleEdit/EditControls.js

unselect(); $('#moreEdit').empty(); $('#selBySyl').addClass('is-active'); + $('#selByBBox').removeClass('is-active'); $('#selByNeume').removeClass('is-active'); $('#selByNc').removeClass('is-active'); $('#selByStaff').removeClass('is-active'); @@ -275,6 +189,7 @@

Source: SingleEdit/EditControls.js

unselect(); $('#moreEdit').empty(); $('#selByNeume').addClass('is-active'); + $('#selByBBox').removeClass('is-active'); $('#selByNc').removeClass('is-active'); $('#selByStaff').removeClass('is-active'); $('#selBySyl').removeClass('is-active'); @@ -293,6 +208,7 @@

Source: SingleEdit/EditControls.js

unselect(); $('#moreEdit').empty(); $('#selByNc').addClass('is-active'); + $('#selByBBox').removeClass('is-active'); $('#selByNeume').removeClass('is-active'); $('#selByStaff').removeClass('is-active'); $('#selBySyl').removeClass('is-active'); @@ -311,6 +227,7 @@

Source: SingleEdit/EditControls.js

unselect(); $('#moreEdit').empty(); $('#selByStaff').addClass('is-active'); + $('#selByBBox').removeClass('is-active'); $('#selByNc').removeClass('is-active'); $('#selByNeume').removeClass('is-active'); $('#selBySyl').removeClass('is-active'); @@ -327,13 +244,13 @@

Source: SingleEdit/EditControls.js


- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:07 GMT-0400 (GMT-04:00)
diff --git a/doc/SquareEdit_DivaEditMode.js.html b/doc/SquareEdit_DivaEditMode.js.html new file mode 100644 index 000000000..85218928e --- /dev/null +++ b/doc/SquareEdit_DivaEditMode.js.html @@ -0,0 +1,105 @@ + + + + + JSDoc: Source: SquareEdit/DivaEditMode.js + + + + + + + + + + +
+ +

Source: SquareEdit/DivaEditMode.js

+ + + + + + +
+
+
import { bindInsertTabs, initInsertEditControls, initEditModeControls, initSelectionButtons } from './Controls.js';
+import * as Select from '../utils/Select.js';
+import InsertHandler from './InsertHandler.js';
+import * as SelectOptions from './SelectOptions.js';
+import DragHandler from '../utils/DragHandler.js';
+
+class DivaEdit {
+  constructor (neonView) {
+    this.neonView = neonView;
+    initEditModeControls(this);
+  }
+
+  initEditMode () {
+    this.dragHandler = new DragHandler(this.neonView, '.active-page > svg');
+    this.insertHandler = new InsertHandler(this.neonView, '.active-page > svg');
+    bindInsertTabs(this.insertHandler);
+    document.getElementById('primitiveTab').click();
+    Select.setSelectHelperObjects(this.neonView, this.dragHandler);
+    this.setSelectListeners();
+
+    SelectOptions.initNeonView(this.neonView);
+    initInsertEditControls(this.neonView);
+    let editMenu = document.getElementById('editMenu');
+    editMenu.style.backgroundColor = '#ffc7c7';
+    editMenu.style.fontWeight = 'bold';
+
+    Select.setSelectStrokeWidth(1);
+
+    initSelectionButtons();
+
+    this.neonView.view.addUpdateCallback(this.setSelectListeners.bind(this));
+  }
+
+  /**
+   * Get the user mode that Neon is in. Either insert, edit, or viewer.
+   * @returns {string}
+   */
+  getUserMode () {
+    if (this.insertHandler !== undefined) {
+      if (this.insertHandler.isInsertMode()) {
+        return 'insert';
+      }
+      return 'edit';
+    }
+    return 'viewer';
+  }
+
+  setSelectListeners () {
+    Select.clickSelect('.active-page > svg > svg, .active-page > svg > svg use, .active-page > svg > svg rect');
+    Select.dragSelect('.active-page svg');
+  }
+}
+
+export { DivaEdit as default };
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:07 GMT-0400 (GMT-04:00) +
+ + + + + diff --git a/doc/SquareEdit_Grouping.js.html b/doc/SquareEdit_Grouping.js.html new file mode 100644 index 000000000..a76eebd2e --- /dev/null +++ b/doc/SquareEdit_Grouping.js.html @@ -0,0 +1,310 @@ + + + + + JSDoc: Source: SquareEdit/Grouping.js + + + + + + + + + + +
+ +

Source: SquareEdit/Grouping.js

+ + + + + + +
+
+
/** @module SquareEdit/Grouping */
+
+import * as Contents from './Contents.js';
+import * as Warnings from '../Warnings.js';
+import * as Notification from '../utils/Notification.js';
+import { unsetVirgaAction, unsetInclinatumAction } from './SelectOptions.js';
+const $ = require('jquery');
+
+/**
+ * The NeonView parent to access editor actions.
+ * @type {NeonView}
+ */
+var neonView;
+
+/**
+ * Set the neonView member.
+ * @param {NeonView} view
+ */
+export function initNeonView (view) {
+  neonView = view;
+}
+
+/**
+ * Trigger the grouping selection menu.
+ * @param {string} type - The grouping type: nc, neume, syl, ligatureNc, or ligature
+ */
+export function triggerGrouping (type) {
+  $('#moreEdit').removeClass('is-invisible');
+  $('#moreEdit').append(Contents.groupingMenu[type]);
+  initGroupingListeners();
+}
+
+/**
+ * Remove the grouping selection menu.
+ */
+export function endGroupingSelection () {
+  $('#moreEdit').empty();
+  $('#moreEdit').addClass('is-invisible');
+}
+
+/**
+ * The grouping dropdown listener.
+ */
+export function initGroupingListeners () {
+  $('#mergeSyls').on('click', function () {
+    var elementIds = getChildrenIds().filter(e =>
+      document.getElementById(e).classList.contains('neume')
+    );
+    groupingAction('group', 'neume', elementIds);
+  });
+
+  $('#groupNeumes').on('click', function () {
+    var elementIds = getIds();
+    groupingAction('group', 'neume', elementIds);
+  });
+
+  $('#groupNcs').on('click', function () {
+    var elementIds = getIds();
+    groupingAction('group', 'nc', elementIds);
+  });
+
+  $('#ungroupNeumes').on('click', function () {
+    var elementIds = getChildrenIds();
+    groupingAction('ungroup', 'neume', elementIds);
+  });
+
+  $('#ungroupNcs').on('click', function () {
+    var elementIds = getChildrenIds();
+    groupingAction('ungroup', 'nc', elementIds);
+  });
+  $('#toggle-ligature').on('click', async function () {
+    var elementIds = getIds();
+    var isLigature;
+    let ligatureRegex = /#E99[016]/;
+    if (!ligatureRegex.test(document.getElementById(elementIds[0]).children[0].getAttribute('xlink:href'))) { // SMUFL codes for ligature glyphs
+      isLigature = true;
+    } else {
+      isLigature = false;
+      let chainAction = { 'action': 'chain',
+        'param': [
+          unsetInclinatumAction(elementIds[0]), unsetVirgaAction(elementIds[0]),
+          unsetInclinatumAction(elementIds[1]), unsetVirgaAction(elementIds[1])
+        ] };
+      await neonView.edit(chainAction, neonView.view.getCurrentPageURI());
+    }
+
+    let editorAction = {
+      'action': 'toggleLigature',
+      'param': {
+        'elementIds': elementIds,
+        'isLigature': isLigature.toString()
+      }
+    };
+    neonView.edit(editorAction, neonView.view.getCurrentPageURI()).then((result) => {
+      if (result) {
+        Notification.queueNotification('Ligature Toggled');
+      } else {
+        Notification.queueNotification('Ligature Toggle Failed');
+      }
+      endGroupingSelection();
+      neonView.updateForCurrentPage();
+    });
+  });
+  $('#toggle-link').on('click', function (evt) {
+    let elementIds = getIds();
+    let chainAction = {
+      'action': 'chain',
+      'param': []
+    };
+    if (document.getElementById(elementIds[0]).getAttribute('mei:precedes')) {
+      chainAction.param.push({
+        'action': 'set',
+        'param': {
+          'elementId': elementIds[0],
+          'attrType': 'precedes',
+          'attrValue': ''
+        }
+      });
+      chainAction.param.push({
+        'action': 'set',
+        'param': {
+          'elementId': elementIds[1],
+          'attrType': 'follows',
+          'attrValue': ''
+        }
+      });
+    } else if (document.getElementById(elementIds[0]).getAttribute('mei:follows')) {
+      chainAction.param.push({
+        'action': 'set',
+        'param': {
+          'elementId': elementIds[0],
+          'attrType': 'follows',
+          'attrValue': ''
+        }
+      });
+      chainAction.param.push({
+        'action': 'set',
+        'param': {
+          'elementId': elementIds[1],
+          'attrType': 'precedes',
+          'attrValue': ''
+        }
+      });
+    } else {
+      // Associate syllables. Will need to find which is first. Use staves.
+      let syllable0 = document.getElementById(elementIds[0]);
+      let syllable1 = document.getElementById(elementIds[1]);
+      let staff0 = syllable0.closest('.staff');
+      let staff1 = syllable1.closest('.staff');
+      let staffChildren = Array.from(staff0.parentNode.children).filter(elem => elem.classList.contains('staff'));
+
+      let firstSyllable, secondSyllable;
+      // Determine first syllable comes first by staff
+      if (staffChildren.indexOf(staff0) < staffChildren.indexOf(staff1)) {
+        firstSyllable = syllable0;
+        secondSyllable = syllable1;
+      } else {
+        firstSyllable = syllable1;
+        secondSyllable = syllable0;
+      }
+
+      chainAction.param.push({
+        'action': 'set',
+        'param': {
+          'elementId': firstSyllable.id,
+          'attrType': 'precedes',
+          'attrValue': secondSyllable.id
+        }
+      });
+      chainAction.param.push({
+        'action': 'set',
+        'param': {
+          'elementId': secondSyllable.id,
+          'attrType': 'follows',
+          'attrValue': firstSyllable.id
+        }
+      });
+    }
+    neonView.edit(chainAction, neonView.view.getCurrentPageURI()).then((result) => {
+      if (result) {
+        Notification.queueNotification('Toggled Syllable Link');
+      } else {
+        Notification.queueNotification('Failed to Toggle Syllable Link');
+      }
+      endGroupingSelection();
+      neonView.updateForCurrentPage();
+    });
+  });
+}
+
+/**
+ * Form and execute a group/ungroup action.
+ * @param {string} action - The action to execute. Either "group" or "ungroup".
+ * @param {string} groupType - The type of elements to group. Either "neume" or "nc".
+ * @param {string[]} elementIds - The IDs of the elements.
+ */
+function groupingAction (action, groupType, elementIds) {
+  let editorAction = {
+    'action': action,
+    'param': {
+      'groupType': groupType,
+      'elementIds': elementIds
+    }
+  };
+  neonView.edit(editorAction, neonView.view.getCurrentPageURI()).then((result) => {
+    if (result) {
+      if (action === 'group') {
+        Notification.queueNotification('Grouping Success');
+      } else {
+        Notification.queueNotification('Ungrouping Success');
+      }
+    } else {
+      if (action === 'group') {
+        Notification.queueNotification('Grouping Failed');
+      } else {
+        Notification.queueNotification('Ungrouping Failed');
+      }
+    }
+    neonView.updateForCurrentPage();
+
+    // Prompt user to confirm if Neon does not re cognize contour
+    if (groupType === 'nc') {
+      var neumeParent = $('#' + elementIds[0]).parent();
+      var ncs = $(neumeParent).children();
+      var contour = neonView.info.getContour((ncs));
+      if (contour === undefined) {
+        Warnings.groupingNotRecognized();
+      }
+    }
+    endGroupingSelection();
+  });
+}
+
+/**
+ * Get the IDs of selected elements.
+ */
+function getIds () {
+  var ids = [];
+  var elements = Array.from($('.selected'));
+  elements.forEach(el => {
+    ids.push($(el)[0].id);
+  });
+  return ids;
+}
+
+/**
+ * Get the IDs of the selected elements' children.
+ */
+function getChildrenIds () {
+  var childrenIds = [];
+  var elements = Array.from($('.selected'));
+  elements.forEach(el => {
+    var children = Array.from($(el).children());
+    children.forEach(ch => {
+      childrenIds.push($(ch)[0].id);
+    });
+  });
+  return childrenIds;
+}
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:07 GMT-0400 (GMT-04:00) +
+ + + + + diff --git a/doc/SingleEdit_InsertHandler.js.html b/doc/SquareEdit_InsertHandler.js.html similarity index 62% rename from doc/SingleEdit_InsertHandler.js.html rename to doc/SquareEdit_InsertHandler.js.html index 629b85878..168eee410 100644 --- a/doc/SingleEdit_InsertHandler.js.html +++ b/doc/SquareEdit_InsertHandler.js.html @@ -2,7 +2,7 @@ - JSDoc: Source: SingleEdit/InsertHandler.js + JSDoc: Source: SquareEdit/InsertHandler.js @@ -17,7 +17,7 @@
-

Source: SingleEdit/InsertHandler.js

+

Source: SquareEdit/InsertHandler.js

@@ -34,12 +34,14 @@

Source: SingleEdit/InsertHandler.js

* Handle inserting new musical elements and communicate this to Verovio. * @constructor * @param {NeonView} neonView - The NeonView parent. + * @param {string} sel - A CSS selector representing where to put the listeners. */ -function InsertHandler (neonView) { +function InsertHandler (neonView, sel) { var type = ''; var firstClick = true; var coord; var attributes = null; + var selector = sel; /** * Switch to insert mode based on the button pressed. @@ -126,8 +128,8 @@

Source: SingleEdit/InsertHandler.js

} removeInsertClickHandlers(); if (type === 'staff') { - $('body').on('click', '#svg_group', staffHandler); - } else { $('body').on('click', '#svg_group', handler); } + $('body').on('click', selector, staffHandler); + } else { $('body').on('click', selector, handler); } // Disable edit mode listeners $('body').on('keydown', keydownListener); @@ -138,14 +140,11 @@

Source: SingleEdit/InsertHandler.js

// Add 'return to edit mode' button if (!alreadyInInsertMode) { - let editModeContainer = document.createElement('p'); - editModeContainer.classList.add('control'); let editModeButton = document.createElement('button'); editModeButton.id = 'returnToEditMode'; editModeButton.classList.add('button'); editModeButton.innerHTML = 'Return to Edit Mode'; - editModeContainer.appendChild(editModeButton); - document.getElementById('delete').parentNode.parentNode.appendChild(editModeContainer); + document.getElementById('redo').parentNode.appendChild(editModeButton); editModeButton.addEventListener('click', insertDisabled); } $('#editMenu').css('backgroundColor', 'whitesmoke'); @@ -166,7 +165,11 @@

Source: SingleEdit/InsertHandler.js

$('.insertel.is-active').removeClass('is-active'); firstClick = true; Cursor.resetCursor(); - $(document.getElementById('returnToEditMode').parentNode).remove(); + try { + $(document.getElementById('returnToEditMode')).remove(); + } catch (e) { + console.debug(e); + } $('#insertMenu').css('backgroundColor', 'whitesmoke'); $('#insertMenu').css('font-weight', ''); $('#editMenu').css('backgroundColor', '#ffc7c7'); @@ -174,8 +177,9 @@

Source: SingleEdit/InsertHandler.js

} function clickawayHandler (evt) { - if (evt.target.id !== 'svg_group' && $('#svg_group').find(evt.target).length === 0 && evt.target.tagName !== 'path' && - !($(evt.target).hasClass('insertel') || $(evt.target).hasClass('image'))) { + if ($(evt.target).closest('.active-page').length === 0 && + $(evt.target).closest('#insert_controls').length === 0 && + $(evt.target).closest('#svg_group').length === 0) { insertDisabled(); $('body').off('keydown', staffHandler); $('body').off('keydown', handler); @@ -184,7 +188,7 @@

Source: SingleEdit/InsertHandler.js

function resetInsertHandler (evt) { if (evt.key === 'Shift') { - $('body').on('click', '#svg_group', type === 'staff' ? staffHandler : handler); + $('body').on('click', selector, type === 'staff' ? staffHandler : handler); } } @@ -203,12 +207,12 @@

Source: SingleEdit/InsertHandler.js

* @param {object} evt - JQuery event object. */ function handler (evt) { - var container = document.getElementsByClassName('definition-scale')[0]; + var container = document.getElementsByClassName('active-page')[0].getElementsByClassName('definition-scale')[0]; var pt = container.createSVGPoint(); pt.x = evt.clientX; pt.y = evt.clientY; // Transform pt to SVG context - var transformMatrix = container.getScreenCTM(); + var transformMatrix = container.getElementsByClassName('system')[0].getScreenCTM(); var cursorpt = pt.matrixTransform(transformMatrix.inverse()); let editorAction = { @@ -225,7 +229,7 @@

Source: SingleEdit/InsertHandler.js

editorAction['param']['attributes'] = attributes; } - neonView.edit(editorAction, 0).then(() => { + neonView.edit(editorAction, neonView.view.getCurrentPageURI()).then(() => { neonView.updateForCurrentPage(); }); } @@ -235,11 +239,11 @@

Source: SingleEdit/InsertHandler.js

* @param {object} evt - JQuery event object. */ function staffHandler (evt) { - var container = document.getElementsByClassName('definition-scale')[0]; + var container = document.getElementsByClassName('active-page')[0].getElementsByClassName('definition-scale')[0]; var pt = container.createSVGPoint(); pt.x = evt.clientX; pt.y = evt.clientY; - var transformMatrix = container.getScreenCTM(); + var transformMatrix = container.getElementsByClassName('system')[0].getScreenCTM(); var cursorpt = pt.matrixTransform(transformMatrix.inverse()); if (firstClick) { @@ -272,16 +276,16 @@

Source: SingleEdit/InsertHandler.js

} }; - neonView.edit(action, 0).then(() => { + neonView.edit(action, neonView.view.getCurrentPageURI()).then(() => { neonView.updateForCurrentPage(); - insertDisabled(); + firstClick = true; }); } } function removeInsertClickHandlers () { - $('body').off('click', '#svg_group', staffHandler); - $('body').off('click', '#svg_group', handler); + $('body').off('click', selector, staffHandler); + $('body').off('click', selector, handler); } /** @@ -308,13 +312,13 @@

Source: SingleEdit/InsertHandler.js


- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:07 GMT-0400 (GMT-04:00)
diff --git a/doc/SingleEdit_SelectOptions.js.html b/doc/SquareEdit_SelectOptions.js.html similarity index 57% rename from doc/SingleEdit_SelectOptions.js.html rename to doc/SquareEdit_SelectOptions.js.html index 346278e45..71f15e71d 100644 --- a/doc/SingleEdit_SelectOptions.js.html +++ b/doc/SquareEdit_SelectOptions.js.html @@ -2,7 +2,7 @@ - JSDoc: Source: SingleEdit/SelectOptions.js + JSDoc: Source: SquareEdit/SelectOptions.js @@ -17,7 +17,7 @@
-

Source: SingleEdit/SelectOptions.js

+

Source: SquareEdit/SelectOptions.js

@@ -26,12 +26,11 @@

Source: SingleEdit/SelectOptions.js

-
/** @module SingleEdit/SelectOptions */
+            
/** @module SquareEdit/SelectOptions */
 import * as Contents from './Contents.js';
 import * as Grouping from './Grouping.js';
 import * as Notification from '../utils/Notification.js';
-import InfoModule from '../InfoModule.js';
-import SplitHandler from './SplitHandler.js';
+import { SplitHandler } from './StaffTools.js';
 const $ = require('jquery');
 
 /**
@@ -81,7 +80,30 @@ 

Source: SingleEdit/SelectOptions.js

}; } -// TODO: CHANGE NAVABAR-LINK TO PROPER ICON// +/** + * function to handle removing elements + * @param { NeonView } neonView - a neonView object + */ +export function removeHandler () { + let toRemove = []; + var selected = Array.from(document.getElementsByClassName('selected')); + selected.forEach(elem => { + toRemove.push( + { + 'action': 'remove', + 'param': { + 'elementId': elem.id + } + } + ); + }); + let chainAction = { + 'action': 'chain', + 'param': toRemove + }; + neonView.edit(chainAction, neonView.view.getCurrentPageURI()).then(() => { neonView.updateForCurrentPage(); }); +} + /** * Trigger the extra nc action menu. * @param {SVGGraphicsElement} nc - The last selected elements. @@ -94,7 +116,7 @@

Source: SingleEdit/SelectOptions.js

$('#Punctum.dropdown-item').on('click', () => { let unsetInclinatum = unsetInclinatumAction(nc.id); let unsetVirga = unsetVirgaAction(nc.id); - neonView.edit({ 'action': 'chain', 'param': [ unsetInclinatum, unsetVirga ] }, 0).then((result) => { + neonView.edit({ 'action': 'chain', 'param': [ unsetInclinatum, unsetVirga ] }, neonView.view.getCurrentPageURI()).then((result) => { if (result) { Notification.queueNotification('Shape Changed'); } else { @@ -114,7 +136,7 @@

Source: SingleEdit/SelectOptions.js

'attrValue': 'se' } }; - neonView.edit(setInclinatum, 0).then((result) => { + neonView.edit(setInclinatum, neonView.view.getCurrentPageURI()).then((result) => { if (result) { Notification.queueNotification('Shape Changed'); } else { @@ -135,7 +157,7 @@

Source: SingleEdit/SelectOptions.js

'attrValue': 'n' } }; - neonView.edit({ 'action': 'chain', 'param': [ unsetInclinatum, setVirga ] }, 0).then((result) => { + neonView.edit({ 'action': 'chain', 'param': [ unsetInclinatum, setVirga ] }, neonView.view.getCurrentPageURI()).then((result) => { if (result) { Notification.queueNotification('Shape Changed'); } else { @@ -145,6 +167,8 @@

Source: SingleEdit/SelectOptions.js

neonView.updateForCurrentPage(); }); }); + $('#delete').on('click', removeHandler); + $('body').on('keydown', deleteButtonHandler); initOptionsListeners(); } @@ -163,7 +187,7 @@

Source: SingleEdit/SelectOptions.js

} $('.grouping').on('click', (e) => { - var contour = InfoModule.getContourByValue(e.target.id); + var contour = neonView.info.getContourByValue(e.target.id); triggerChangeGroup(contour); }); @@ -175,7 +199,7 @@

Source: SingleEdit/SelectOptions.js

'contour': contour } }; - neonView.edit(changeGroupingAction, 0).then((result) => { + neonView.edit(changeGroupingAction, neonView.view.getCurrentPageURI()).then((result) => { if (result) { Notification.queueNotification('Grouping Changed'); } else { @@ -185,6 +209,9 @@

Source: SingleEdit/SelectOptions.js

neonView.updateForCurrentPage(); }); } + $('#delete').on('click', removeHandler); + $('body').on('keydown', deleteButtonHandler); + initOptionsListeners(); Grouping.initGroupingListeners(); } @@ -199,6 +226,14 @@

Source: SingleEdit/SelectOptions.js

"<div><p class='control'>" + "<button class='button' id='ungroupNeumes'>Ungroup</button></p></div>" ); + $('#moreEdit').append( + "<div><p class='control'>" + + "<button class='button' id='delete'>Delete</button></p></div>" + ); + + $('#delete').on('click', removeHandler); + $('body').on('keydown', deleteButtonHandler); + Grouping.initGroupingListeners(); } @@ -218,7 +253,7 @@

Source: SingleEdit/SelectOptions.js

'shape': 'C' } }; - neonView.edit(setCClef, 0).then((result) => { + neonView.edit(setCClef, neonView.view.getCurrentPageURI()).then((result) => { if (result) { Notification.queueNotification('Shape Changed'); } else { @@ -236,7 +271,7 @@

Source: SingleEdit/SelectOptions.js

'shape': 'F' } }; - neonView.edit(setFClef, 0).then((result) => { + neonView.edit(setFClef, neonView.view.getCurrentPageURI()).then((result) => { if (result) { Notification.queueNotification('Shape Changed'); } else { @@ -246,6 +281,10 @@

Source: SingleEdit/SelectOptions.js

neonView.updateForCurrentPage(); }); }); + + $('#delete').on('click', removeHandler); + $('body').on('keydown', deleteButtonHandler); + initOptionsListeners(); } @@ -269,7 +308,7 @@

Source: SingleEdit/SelectOptions.js

'elementIds': elementIds } }; - neonView.edit(editorAction, 0).then((result) => { + neonView.edit(editorAction, neonView.view.getCurrentPageURI()).then((result) => { if (result) { Notification.queueNotification('Staff Merged'); endOptionsSelection(); @@ -279,6 +318,9 @@

Source: SingleEdit/SelectOptions.js

} }); }); + + $('#delete').on('click', removeHandler); + $('body').on('keydown', deleteButtonHandler); } /** @@ -295,6 +337,21 @@

Source: SingleEdit/SelectOptions.js

split.startSplit(); endOptionsSelection(); }); + + $('#delete').on('click', removeHandler); + $('body').on('keydown', deleteButtonHandler); +} + +/** + * Trigger default selection option. + */ +export function triggerDefaultActions () { + endOptionsSelection(); + $('#moreEdit').removeClass('is-invisible'); + $('#moreEdit').append(Contents.defaultActionContents); + + $('#delete').on('click', removeHandler); + $('body').on('keydown', deleteButtonHandler); } /** @@ -303,6 +360,7 @@

Source: SingleEdit/SelectOptions.js

export function endOptionsSelection () { $('#moreEdit').empty(); $('#moreEdit').addClass('is-invisible'); + $('body').off('keydown', deleteButtonHandler); } /** @@ -313,6 +371,10 @@

Source: SingleEdit/SelectOptions.js

$(this).toggleClass('is-active'); }); } + +function deleteButtonHandler (evt) { + if (evt.key === 'd' || evt.key === 'Backspace') { removeHandler(); evt.preventDefault(); } +}
@@ -323,13 +385,13 @@

Source: SingleEdit/SelectOptions.js


- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:07 GMT-0400 (GMT-04:00)
diff --git a/doc/SquareEdit_SingleEditMode.js.html b/doc/SquareEdit_SingleEditMode.js.html new file mode 100644 index 000000000..844398711 --- /dev/null +++ b/doc/SquareEdit_SingleEditMode.js.html @@ -0,0 +1,114 @@ + + + + + JSDoc: Source: SquareEdit/SingleEditMode.js + + + + + + + + + + +
+ +

Source: SquareEdit/SingleEditMode.js

+ + + + + + +
+
+
import { bindInsertTabs, initInsertEditControls, initEditModeControls, initSelectionButtons } from './Controls.js';
+import * as Select from '../utils/Select.js';
+import InsertHandler from './InsertHandler.js';
+import * as SelectOptions from './SelectOptions.js';
+import DragHandler from '../utils/DragHandler.js';
+
+/**
+ * An Edit Module for a single page of a manuscript.
+ * Works with the SingleView module.
+ */
+class SingleEditMode {
+  /**
+   * Constructor for an EditMode object.
+   * @param {NeonView} neonView - The NeonView parent.
+   */
+  constructor (neonView) {
+    this.neonView = neonView;
+    initEditModeControls(this);
+  }
+
+  /**
+   * Initialize the start of edit mode when first leaving viewer mode.
+   */
+  initEditMode () {
+    this.dragHandler = new DragHandler(this.neonView, '#svg_group');
+    this.insertHandler = new InsertHandler(this.neonView, '#svg_group');
+    bindInsertTabs(this.insertHandler);
+    document.getElementById('primitiveTab').click();
+    Select.setSelectHelperObjects(this.neonView, this.dragHandler);
+    this.setSelectListeners();
+
+    SelectOptions.initNeonView(this.neonView);
+    initInsertEditControls(this.neonView);
+    let editMenu = document.getElementById('editMenu');
+    editMenu.style.backgroundColor = '#ffc7c7';
+    editMenu.style.fontWeight = 'bold';
+
+    initSelectionButtons();
+
+    this.neonView.view.addUpdateCallback(this.setSelectListeners.bind(this));
+  }
+
+  /**
+   * Get the user mode that Neon is in. Either insert, edit, or viewer.
+   * @returns {string}
+   */
+  getUserMode () {
+    if (this.insertHandler !== undefined) {
+      if (this.insertHandler.isInsertMode()) {
+        return 'insert';
+      }
+      return 'edit';
+    }
+    return 'viewer';
+  }
+
+  setSelectListeners () {
+    Select.clickSelect('#mei_output, #mei_output use, #mei_output rect');
+    Select.dragSelect('#svg_group');
+  }
+}
+
+export { SingleEditMode as default };
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:07 GMT-0400 (GMT-04:00) +
+ + + + + diff --git a/doc/SquareEdit_StaffTools.js.html b/doc/SquareEdit_StaffTools.js.html new file mode 100644 index 000000000..76654d8dc --- /dev/null +++ b/doc/SquareEdit_StaffTools.js.html @@ -0,0 +1,143 @@ + + + + + JSDoc: Source: SquareEdit/StaffTools.js + + + + + + + + + + +
+ +

Source: SquareEdit/StaffTools.js

+ + + + + + +
+
+
/** @module SquareEdit/StaffTools */
+
+import * as Notification from '../utils/Notification.js';
+
+const $ = require('jquery');
+
+/**
+ * Handler splitting a staff into two staves through Verovio.
+ * @constructor
+ * @param {NeonView} neonView - The NeonView parent.
+ */
+function SplitHandler (neonView, selector) {
+  function startSplit () {
+    splitDisable();
+
+    $('body').on('click', selector, handler);
+
+    // Handle keypresses
+    $('body').on('keydown', keydownListener);
+    $('body').on('keyup', resetHandler);
+    $('body').on('click', clickawayHandler);
+
+    Notification.queueNotification('Click Where to Split');
+  }
+
+  function keydownListener (evt) {
+    if (evt.key === 'Escape') {
+      splitDisable();
+    } else if (evt.key === 'Shift') {
+      $('body').off('click', selector, handler);
+    }
+  }
+
+  function clickawayHandler (evt) {
+    console.log(evt);
+    if ($(evt.target).closest('.active-page').length === 0) {
+      splitDisable();
+      $('body').off('click', selector, handler);
+    }
+  }
+
+  function resetHandler (evt) {
+    if (evt.key === 'Shift') {
+      $('body').on('click', selector, handler);
+    }
+  }
+
+  function handler (evt) {
+    let id = $('.selected')[0].id;
+
+    var container = document.getElementsByClassName('active-page')[0]
+      .getElementsByClassName('definition-scale')[0];
+    var pt = container.createSVGPoint();
+    pt.x = evt.clientX;
+    pt.y = evt.clientY;
+
+    // Transform to SVG coordinate system.
+    var transformMatrix = container.getElementsByClassName('system')[0]
+      .getScreenCTM().inverse();
+    var cursorPt = pt.matrixTransform(transformMatrix);
+    console.log(cursorPt.x);
+    // Find staff point corresponds to if one exists
+    // TODO
+
+    let editorAction = {
+      'action': 'split',
+      'param': {
+        'elementId': id,
+        'x': parseInt(cursorPt.x)
+      }
+    };
+
+    neonView.edit(editorAction, neonView.view.getCurrentPageURI()).then(async (result) => {
+      if (result) {
+        await neonView.updateForCurrentPagePromise();
+      }
+      splitDisable();
+    });
+  }
+
+  function splitDisable () {
+    $('body').off('keydown', keydownListener);
+    $('body').off('keyup', resetHandler);
+    $('body').off('click', clickawayHandler);
+    $('body').off('click', handler);
+  }
+
+  SplitHandler.prototype.constructor = SplitHandler;
+  SplitHandler.prototype.startSplit = startSplit;
+}
+
+export { SplitHandler };
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:07 GMT-0400 (GMT-04:00) +
+ + + + + diff --git a/doc/TextEditMode.js.html b/doc/TextEditMode.js.html new file mode 100644 index 000000000..77e5a4c7f --- /dev/null +++ b/doc/TextEditMode.js.html @@ -0,0 +1,188 @@ + + + + + JSDoc: Source: TextEditMode.js + + + + + + + + + + +
+ +

Source: TextEditMode.js

+ + + + + + +
+
+
import { unselect } from './utils/SelectTools.js';
+import DragHandler from './utils/DragHandler.js';
+import { setSelectHelperObjects, dragSelect, clickSelect } from './utils/Select.js';
+
+const $ = require('jquery');
+
+/**
+ * A Text editing module that works with the SingleView and DivaView modules
+ */
+export default class TextEditMode {
+  /**
+   * Constructor for a TextEdit
+   * @param {NeonView} neonView
+   */
+  constructor (neonView) {
+    this.neonView = neonView;
+    this.initEditModeControls();
+  }
+
+  /**
+   * set listener on edit mode button
+   */
+  initEditModeControls () {
+    document.getElementById('edit_mode').addEventListener('click', () => {
+      this.initTextEdit();
+      if ($('#displayBBox').is(':checked')) {
+        this.initSelectByBBoxButton();
+      }
+    });
+  }
+
+  /**
+  * set text to edit mode
+  */
+  initTextEdit () {
+    let spans = Array.from($('#syl_text').children('p').children('span'));
+    spans.forEach(span => {
+      $(span).off('click');
+      $(span).on('click', () => {
+        this.updateSylText(span);
+      });
+    });
+  }
+
+  /**
+  * add the selectByRect button
+  * if neume edit mode is there, add it to the bar with the other select by buttons
+  * otherwise add an invisible button
+  * since the only edit mode is selectByRect in that case
+  */
+  initSelectByBBoxButton () {
+    if (this.neonView.NeumeEdit !== undefined) {
+      if ($('#selByBBox').length) {
+        $('#selByBBox').css('display', '');
+        return;
+      }
+      let block = $('#selBySyl').parent('.control').parent('.field');
+      block.append("<p class='control'><button class='button sel-by' id='selByBBox'>BBox</button></p>");
+      let button = $('#selByBBox');
+      button.on('click', () => {
+        if (!$('#selByBBox').hasClass('is-active')) {
+          unselect();
+          $('#moreEdit').empty();
+          $('#selByBBox').addClass('is-active');
+          $('#selByNc').removeClass('is-active');
+          $('#selByNeume').removeClass('is-active');
+          $('#selByStaff').removeClass('is-active');
+          $('#selBySyl').removeClass('is-active');
+        }
+        this.addBBoxListeners();
+      }).bind(this);
+      this.neonView.view.addUpdateCallback(this.addBBoxListeners.bind(this));
+    } else {
+      let block = $('#undo').parent('.control');
+      block.append("<p class='control'><button class='button sel-by' id='selByBBox'>BBox</button></p>");
+      let button = $('#selByBBox');
+      button.addClass('is-active');
+      button.css('display', 'none');
+      this.addBBoxListeners();
+      this.neonView.addUpdateCallback(this.addBBoxListeners.bind(this));
+    }
+  }
+
+  /**
+   * initialize select by bbox mode
+   */
+  addBBoxListeners () {
+    if ($('#selByBBox').hasClass('is-active')) {
+      unselect();
+      if (this.neonView.NeumeEdit === undefined) {
+        // just in case
+        this.dragHandler = new DragHandler(this.neonView, '.sylTextRect-display');
+        setSelectHelperObjects(this.neonView, this.dragHandler);
+        if (this.neonView.view.constructor.name === 'SingleView') {
+          clickSelect('#mei_output, #mei_output rect');
+          dragSelect('#svg_group');
+        } else {
+          clickSelect('.active-page > svg > svg, .active-page > svg > svg rect');
+          dragSelect('.active-page svg');
+        }
+      }
+    }
+  }
+
+  /**
+  * Update the text for a single syl element
+  * @param {HTMLElement} span
+  */
+  updateSylText (span) {
+    let orig = formatRaw($(span).html());
+    let corrected = window.prompt('', orig);
+    if (corrected !== null && corrected !== orig) {
+      let editorAction = {
+        'action': 'setText',
+        'param': {
+          'elementId': $('#' + $(span).attr('class').replace('syl-select', '').trim()).attr('id'),
+          'text': corrected
+        }
+      };
+      this.neonView.edit(editorAction, this.neonView.view.getCurrentPageURI()).then((response) => {
+        if (response) {
+          this.neonView.updateForCurrentPage();
+        }
+      });
+    }
+  }
+}
+
+/**
+ * Format a string for prompting the user.
+ * @param {string} rawString
+ * @returns {string}
+ */
+function formatRaw (rawString) {
+  let removeSymbol = /\u{25CA}/u;
+  return rawString.replace(removeSymbol, '').trim();
+}
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:07 GMT-0400 (GMT-04:00) +
+ + + + + diff --git a/doc/TextView.js.html b/doc/TextView.js.html index c0b8eab8a..ecc0f1b58 100644 --- a/doc/TextView.js.html +++ b/doc/TextView.js.html @@ -27,6 +27,8 @@

Source: TextView.js

import * as Notification from './utils/Notification.js';
+import { unselect } from './utils/SelectTools.js';
+import { updateHighlight } from './DisplayPanel/DisplayControls.js';
 
 /** @module TextView */
 
@@ -40,29 +42,36 @@ 

Source: TextView.js

* A constructor for a TextView. * @param {NeonView} neonView = The NeonView parent. */ - constructor (neonView) { this.neonView = neonView; this.notificationSent = false; // add checkbox to enable/disable the view let block = document.getElementById('extensible-block'); - let label = document.createElement('label'); - let input = document.createElement('input'); - label.classList.add('checkbox'); - label.textContent = 'Display Text: '; - input.classList.add('checkbox'); - input.id = 'displayText'; - input.type = 'checkbox'; - input.checked = false; - label.appendChild(input); - block.prepend(label); - - $('#edit_mode').on('click', () => { - this.setTextEdit(); - }); + let textLabel = document.createElement('label'); + let bboxLabel = document.createElement('label'); + let textButton = document.createElement('input'); + let bboxButton = document.createElement('input'); + textLabel.classList.add('checkbox'); + bboxLabel.classList.add('checkbox'); + textLabel.textContent = 'Display Text: '; + bboxLabel.textContent = 'Display Text BBoxes: '; + textButton.classList.add('checkbox'); + bboxButton.classList.add('checkbox'); + textButton.id = 'displayText'; + textButton.type = 'checkbox'; + bboxButton.id = 'displayBBox'; + bboxButton.type = 'checkbox'; + textButton.checked = false; + bboxButton.checked = false; + textLabel.appendChild(textButton); + bboxLabel.appendChild(bboxButton); + block.prepend(bboxLabel); + block.prepend(textLabel); + this.setTextViewControls(); this.neonView.view.addUpdateCallback(this.updateTextViewVisibility.bind(this)); + this.neonView.view.addUpdateCallback(this.updateBBoxViewVisibility.bind(this)); } /** @@ -70,45 +79,39 @@

Source: TextView.js

*/ setTextViewControls () { this.updateTextViewVisibility(); + this.updateBBoxViewVisibility(); $('#displayText').on('click', () => { this.updateTextViewVisibility(); }); - } - - /** - * set text to edit mode - */ - setTextEdit () { - let spans = Array.from($('#syl_text').children('p').children('span')); - spans.forEach(span => { - $(span).off('click'); - $(span).on('click', () => { - this.updateSylText(span); - }); + $('#displayBBox').on('click', () => { + this.updateBBoxViewVisibility(); }); } /** - * Update the text for a single syl element - * @param {HTMLElement} span - */ - updateSylText (span) { - let orig = formatRaw($(span).html()); - let corrected = window.prompt('', orig); - if (corrected !== null && corrected !== orig) { - let editorAction = { - 'action': 'setText', - 'param': { - 'elementId': $('#' + $(span).attr('class').replace('syl-select', '').trim()).attr('id'), - 'text': corrected - } - }; - this.neonView.edit(editorAction, this.neonView.view.getCurrentPage()).then((response) => { - if (response) { - this.neonView.updateForCurrentPage(); - } - }); + * update visibility of text bounding boxes + */ + updateBBoxViewVisibility () { + if ($('#displayBBox').is(':checked')) { + $('.sylTextRect').addClass('sylTextRect-display'); + $('.sylTextRect').removeClass('sylTextRect'); + $('.syl.selected').find('.sylTextRect-display').css('fill', 'red'); + + if (this.neonView.getUserMode() !== 'viewer' && this.neonView.TextEdit !== undefined) { + this.neonView.TextEdit.initSelectByBBoxButton(); + } + } else { + if ($('#selByBBox').hasClass('is-active')) { + unselect(); + $('#selByBBox').removeClass('is-active'); + $('#selBySyl').addClass('is-active'); + } + $('.sylTextRect-display').addClass('sylTextRect'); + $('.sylTextRect-display').removeClass('sylTextRect-display'); + $('.syl.selected').find('sylTextRect').css('fill', 'none'); + $('#selByBBox').css('display', 'none'); } + updateHighlight(); } /** @@ -124,31 +127,29 @@

Source: TextView.js

let syllable = $('#' + $(span).attr('class')); let syl = syllable.children('.syl'); let text = syl.children('text'); - let int_text = text.children('.text'); - let real_text = text.children('.text').children('.text'); let rect = syl.children('rect'); if (text.attr('class') == null) { text.addClass('text'); } $(span).on('mouseenter', () => { - syllable.addClass('syl-select'); - syllable.attr('fill', '#d00'); - rect.removeClass('sylTextRect'); - rect.addClass('sylTextRect-select'); + syllable.addClass('selected'); + rect.css('fill', '#d00'); // syl.attr('fill', '#ffc7c7'); // this.highlightBoundingBox(span); }); $(span).on('mouseleave', () => { - syllable.removeClass('syl-select'); - syllable.attr('fill', null); - rect.removeClass('sylTextRect-select'); - rect.addClass('sylTextRect'); + syllable.removeClass('selected'); + if (syllable.css('fill') !== 'rgb(0, 0, 0)') { + rect.css('fill', syllable.css('fill')); + } else { + rect.css('fill', 'blue'); + } // syl.attr('fill', null); // this.removeBoundingBox(span); }); }); - if (this.neonView.getUserMode() !== 'viewer') { - this.setTextEdit(); + if (this.neonView.getUserMode() !== 'viewer' && this.neonView.TextEdit !== undefined) { + this.neonView.TextEdit.initTextEdit(); } } else { $('#syl_text').css('display', 'none'); @@ -187,16 +188,6 @@

Source: TextView.js

} } -/** - * Format a string for prompting the user. - * @param {string} rawString - * @returns {string} - */ -function formatRaw (rawString) { - let removeSymbol = /\u{25CA}/u; - return rawString.replace(removeSymbol, '').trim(); -} - export { TextView as default };
@@ -208,13 +199,13 @@

Source: TextView.js


- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:07 GMT-0400 (GMT-04:00)
diff --git a/doc/Validation.js.html b/doc/Validation.js.html index 0f7ae9708..4348fed86 100644 --- a/doc/Validation.js.html +++ b/doc/Validation.js.html @@ -103,6 +103,17 @@

Source: Validation.js

statusField.appendChild(link); } } + +/** + * Update the message on a blank page when there is no MEI. + */ +export function blankPage () { + for (let child of statusField.children) { + statusField.deleteChild(child); + } + statusField.textContent = 'No MEI' + statusField.style = 'color:gray'; +}
@@ -113,13 +124,13 @@

Source: Validation.js


- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:07 GMT-0400 (GMT-04:00)
diff --git a/doc/VerovioWorker.js.html b/doc/VerovioWorker.js.html new file mode 100644 index 000000000..3d34171e0 --- /dev/null +++ b/doc/VerovioWorker.js.html @@ -0,0 +1,102 @@ + + + + + JSDoc: Source: VerovioWorker.js + + + + + + + + + + +
+ +

Source: VerovioWorker.js

+ + + + + + +
+
+
importScripts('./verovio-toolkit.js');
+
+var toolkit = new verovio.toolkit();
+toolkit.setOptions({
+  format: 'mei',
+  noFooter: 1,
+  noHeader: 1,
+  pageMarginLeft: 0,
+  pageMarginTop: 0,
+  font: 'Bravura',
+  useFacsimile: true,
+  createDefaultSyl: true,
+  createDefaultSylBBox: true
+});
+
+onmessage = handleNeonEvent;
+
+/**
+ * Parse and respond to messages sent by NeonCore.
+ * @param {MessageEvent} evt
+ */
+function handleNeonEvent (evt) {
+  let data = evt.data;
+  let result = {
+    id: data.id
+  };
+
+  switch (data.action) {
+    case 'renderData':
+      result.svg = toolkit.renderData(data.mei, {});
+      break;
+    case 'getElementAttr':
+      result.attributes = toolkit.getElementAttr(data.elementId);
+      break;
+    case 'edit':
+      result.result = toolkit.edit(data.editorAction);
+      break;
+    case 'getMEI':
+      result.mei = toolkit.getMEI(0, true);
+      break;
+    case 'editInfo':
+      result.info = toolkit.editInfo();
+      break;
+    case 'renderToSVG':
+      result.svg = toolkit.renderToSVG(1);
+      break;
+    default:
+      break;
+  }
+  postMessage(result);
+}
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:07 GMT-0400 (GMT-04:00) +
+ + + + + diff --git a/doc/VerovioWrapper.js.html b/doc/VerovioWrapper.js.html new file mode 100644 index 000000000..037b20c5b --- /dev/null +++ b/doc/VerovioWrapper.js.html @@ -0,0 +1,78 @@ + + + + + JSDoc: Source: VerovioWrapper.js + + + + + + + + + + +
+ +

Source: VerovioWrapper.js

+ + + + + + +
+
+
import VerovioWorker from './VerovioWorker.js';
+
+/**
+ * A wrapper around the verovio web worker to permit mocking in tests.
+ */
+export default class VerovioWrapper {
+  constructor () {
+    this.verovioWorker = new VerovioWorker();
+  }
+
+  /**
+   * Set an event listener onto the actual web worker.
+   * @param {string} type - The event to listen to.
+   * @param {function} handler - The function to be called when the event occurs.
+   */
+  addEventListener (type, handler) {
+    return this.verovioWorker.addEventListener(type, handler);
+  }
+
+  /**
+   * Send a message to the actual web worker.
+   * @param {object} message - The message to be sent.
+   */
+  postMessage (message) {
+    return this.verovioWorker.postMessage(message);
+  }
+}
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:07 GMT-0400 (GMT-04:00) +
+ + + + + diff --git a/doc/Warnings.js.html b/doc/Warnings.js.html index 441abf0ee..12ff3b8ab 100644 --- a/doc/Warnings.js.html +++ b/doc/Warnings.js.html @@ -48,13 +48,13 @@

Source: Warnings.js


- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:07 GMT-0400 (GMT-04:00)
diff --git a/doc/global.html b/doc/global.html index 669c3af53..37be68bc6 100644 --- a/doc/global.html +++ b/doc/global.html @@ -95,6 +95,1091 @@

+ +

Methods

+ + + + + + + +

addBBoxListeners()

+ + + + + + +
+ initialize select by bbox mode +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

addEventListener(type, handler)

+ + + + + + +
+ Set an event listener onto the actual web worker. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
type + + +string + + + + The event to listen to.
handler + + +function + + + + The function to be called when the event occurs.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

formatRaw(rawString) → {string}

+ + + + + + +
+ Format a string for prompting the user. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
rawString + + +string + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +string + + +
+
+ + + + + + + + + + + + + +

handleNeonEvent(evt)

+ + + + + + +
+ Parse and respond to messages sent by NeonCore. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
evt + + +MessageEvent + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

initEditModeControls()

+ + + + + + +
+ set listener on edit mode button +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

initSelectByBBoxButton()

+ + + + + + +
+ add the selectByRect button +if neume edit mode is there, add it to the bar with the other select by buttons +otherwise add an invisible button +since the only edit mode is selectByRect in that case +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

initTextEdit()

+ + + + + + +
+ set text to edit mode +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

postMessage(message)

+ + + + + + +
+ Send a message to the actual web worker. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
message + + +object + + + + The message to be sent.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

updateSylText(span)

+ + + + + + +
+ Update the text for a single syl element +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
span + + +HTMLElement + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + @@ -254,7 +1339,7 @@
Properties:
Source:
@@ -284,13 +1369,13 @@
Properties:

- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:07 GMT-0400 (GMT-04:00)
diff --git a/doc/index.html b/doc/index.html index fa8bae811..1f545055c 100644 --- a/doc/index.html +++ b/doc/index.html @@ -80,10 +80,11 @@

Setup

Instructions

Neon has two main modes: viewer and editor. To learn how to use both, read the instructions on our wiki.

Test

-

Follow the instructions from above first. The tests for Neon use Selenium and so require a web browser (Firefox) and its driver (geckodriver). -On Mac install these with Homebrew:

-
brew cask install firefox
+

Follow the instructions from above first. The tests for Neon use Selenium and use Firefox, Safari, and Chrome. Their respective webdrivers are required. Safari 12 or greater is required. On Mac, Firefox and Chrome can be installed by:

+
brew cask install firefox
+brew cask install google-chrome
 brew install geckodriver
+brew cask install chromedriver
 

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

@@ -99,13 +100,13 @@

Verovio


- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:07 GMT-0400 (GMT-04:00)
diff --git a/doc/module-DisplayPanel_DisplayControls.html b/doc/module-DisplayPanel_DisplayControls.html new file mode 100644 index 000000000..83692a0dc --- /dev/null +++ b/doc/module-DisplayPanel_DisplayControls.html @@ -0,0 +1,1149 @@ + + + + + JSDoc: Module: DisplayPanel/DisplayControls + + + + + + + + + + +
+ +

Module: DisplayPanel/DisplayControls

+ + + + + + +
+ +
+ + + +
+ +
+
+ + + + + +
+ + + + + + + + + + + + + + + + +

Methods

+ + + + + + + +

(static) initDisplayControls(meiClassName, background)

+ + + + + + +
+ Initialize listeners and controls for display panel. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
meiClassName + + +string + + + + The class used to signifiy the MEI element(s).
background + + +string + + + + The class used to signify the background.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

(static) setHighlightControls()

+ + + + + + +
+ Set listener on staff highlighting checkbox. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

(static) setOpacityFromSlider(meiClassName)

+ + + + + + +
+ Update MEI opacity to value from the slider. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
meiClassName + + +string + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

(static) setZoomControls(zoomHandler)

+ + + + + + +
+ Set zoom control listener for button and slider +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
zoomHandler + + +ZoomHandler + + + + The zoomHandler, if it exists.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

(static) updateHighlight()

+ + + + + + +
+ Reset the highlight for different types based on the 'highlight-selected' class in the DOM. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

(inner) highlightClickaway()

+ + + + + + +
+ Clickaway listener for the highlight dropdown. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

(inner) setBackgroundOpacityControls(background)

+ + + + + + +
+ Set background image opacity button and slider listeners. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
background + + +string + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

(inner) setBurgerControls()

+ + + + + + +
+ Set listener on burger menu for smaller screens. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

(inner) setOpacityControls(meiClassName)

+ + + + + + +
+ Set rendered MEI opacity button and slider listeners. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
meiClassName + + +string + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:08 GMT-0400 (GMT-04:00) +
+ + + + + \ No newline at end of file diff --git a/doc/module-DisplayPanel_DisplayPanel-DisplayPanel.html b/doc/module-DisplayPanel_DisplayPanel-DisplayPanel.html new file mode 100644 index 000000000..4e72030d5 --- /dev/null +++ b/doc/module-DisplayPanel_DisplayPanel-DisplayPanel.html @@ -0,0 +1,513 @@ + + + + + JSDoc: Class: DisplayPanel + + + + + + + + + + +
+ +

Class: DisplayPanel

+ + + + + + +
+ +
+ +

+ DisplayPanel/DisplayPanel~DisplayPanel(view, className, background, zoomHandleropt)

+ +
A class that sets the content of the display panel to the right and +manages controls for viewing.
+ + +
+ +
+
+ + + + +

Constructor

+ + + +

new DisplayPanel(view, className, background, zoomHandleropt)

+ + + + + + +
+ Constructor for DisplayPanel. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeAttributesDescription
view + + +SingleView +| + +DivaView + + + + + + + + + + The View parent.
className + + +string + + + + + + + + + + The class name for the rendered SVG object(s).
background + + +string + + + + + + + + + + The class name associated with the background.
zoomHandler + + +ZoomHandler + + + + + + <optional>
+ + + + + +
The ZoomHandler object, if SingleView.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + +

Methods

+ + + + + + + +

setDisplayListeners()

+ + + + + + +
+ Apply event listeners related to the DisplayPanel. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

updateVisualization()

+ + + + + + +
+ Update SVG based on visualization settings +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:08 GMT-0400 (GMT-04:00) +
+ + + + + \ No newline at end of file diff --git a/doc/module-DisplayPanel_DisplayPanel.html b/doc/module-DisplayPanel_DisplayPanel.html new file mode 100644 index 000000000..d5b1f9028 --- /dev/null +++ b/doc/module-DisplayPanel_DisplayPanel.html @@ -0,0 +1,251 @@ + + + + + JSDoc: Module: DisplayPanel/DisplayPanel + + + + + + + + + + +
+ +

Module: DisplayPanel/DisplayPanel

+ + + + + + +
+ +
+ + + +
+ +
+
+ + + + + +
+ + + + + + +

Classes

+ +
+
DisplayPanel
+
+
+ + + + + + + + + + + +

Methods

+ + + + + + + +

(inner) displayControlsPanel(handleZoom) → {string}

+ + + + + + +
+ Return the HTML for the display panel. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
handleZoom + + +ZoomHandler + + + + Includes zoom controls if defined.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + +
Returns:
+ + + + +
+
+ Type +
+
+ +string + + +
+
+ + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:08 GMT-0400 (GMT-04:00) +
+ + + + + \ No newline at end of file diff --git a/doc/module-InfoModule-InfoModule.html b/doc/module-InfoModule-InfoModule.html index a69e95841..9fe01ebb2 100644 --- a/doc/module-InfoModule-InfoModule.html +++ b/doc/module-InfoModule-InfoModule.html @@ -1376,13 +1376,13 @@
Parameters:

- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:08 GMT-0400 (GMT-04:00)
diff --git a/doc/module-InfoModule.html b/doc/module-InfoModule.html index 5b219d501..588344031 100644 --- a/doc/module-InfoModule.html +++ b/doc/module-InfoModule.html @@ -257,13 +257,13 @@

(inner)
- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:08 GMT-0400 (GMT-04:00)
diff --git a/doc/module-SingleEdit_ResizeStaff.html b/doc/module-SingleEdit_ResizeStaff.html deleted file mode 100644 index 1efc9e4eb..000000000 --- a/doc/module-SingleEdit_ResizeStaff.html +++ /dev/null @@ -1,238 +0,0 @@ - - - - - JSDoc: Module: SingleEdit/ResizeStaff - - - - - - - - - - -
- -

Module: SingleEdit/ResizeStaff

- - - - - - -
- -
- - - - - -
- -
-
- - -
Support for resizing the staff by creating a resizable box around it.
- - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - -
- - - - - - -

Classes

- -
-
Resize
-
-
- - - - - - - - - -

Members

- - - -

(inner, constant) Side

- - - - -
- The sides of the rectangle -
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - -
- -
- - - - -
- - - -
- -
- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) -
- - - - - \ No newline at end of file diff --git a/doc/module-SingleView_Zoom-ZoomHandler.html b/doc/module-SingleView_Zoom-ZoomHandler.html index 8a535809a..a448b7bb8 100644 --- a/doc/module-SingleView_Zoom-ZoomHandler.html +++ b/doc/module-SingleView_Zoom-ZoomHandler.html @@ -885,13 +885,13 @@
Parameters:

- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:08 GMT-0400 (GMT-04:00)
diff --git a/doc/module-SingleView_Zoom.ViewBox.html b/doc/module-SingleView_Zoom.ViewBox.html index e048eac8c..af320d495 100644 --- a/doc/module-SingleView_Zoom.ViewBox.html +++ b/doc/module-SingleView_Zoom.ViewBox.html @@ -875,13 +875,13 @@
Parameters:

- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:08 GMT-0400 (GMT-04:00)
diff --git a/doc/module-SingleView_Zoom.html b/doc/module-SingleView_Zoom.html index 2bf6bb601..cab7161e0 100644 --- a/doc/module-SingleView_Zoom.html +++ b/doc/module-SingleView_Zoom.html @@ -80,13 +80,13 @@

Classes


- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:08 GMT-0400 (GMT-04:00)
diff --git a/doc/module-SingleEdit_Contents.html b/doc/module-SquareEdit_Contents.html similarity index 60% rename from doc/module-SingleEdit_Contents.html rename to doc/module-SquareEdit_Contents.html index eb5bdfbaf..cccecca6c 100644 --- a/doc/module-SingleEdit_Contents.html +++ b/doc/module-SquareEdit_Contents.html @@ -2,7 +2,7 @@ - JSDoc: Module: SingleEdit/Contents + JSDoc: Module: SquareEdit/Contents @@ -17,7 +17,7 @@
-

Module: SingleEdit/Contents

+

Module: SquareEdit/Contents

@@ -112,7 +112,7 @@
Type:
Source:
@@ -130,13 +130,13 @@
Type:
-

(static, constant) editControlsPanel :string

+

(static, constant) defaultActionContents :string

- Contents of edit panel with buttons. + Contents of default action menu.
@@ -184,7 +184,7 @@
Type:
Source:
@@ -202,85 +202,13 @@
Type:
-

(static, constant) groupingMenu :object

- - - - -
- HTML for grouping selection menu. -
- - - -
Type:
-
    -
  • - -object - - -
  • -
- - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - -

(static, constant) insertControlsPanel :string

+

(static, constant) editControlsPanel :string

- Structure of insert panel with basic grouping tabs. + Contents of edit panel with buttons.
@@ -328,7 +256,7 @@
Type:
Source:
@@ -346,13 +274,13 @@
Type:
-

(static, constant) insertTabHtml :object

+

(static, constant) groupingMenu :object

- HTML for each insert tab (neume, grouping, clef, system, and division). + HTML for grouping selection menu.
@@ -400,7 +328,7 @@
Type:
Source:
@@ -418,13 +346,13 @@
Type:
-

(static, constant) navbarDropdownMenu :string

+

(static, constant) insertControlsPanel :string

- Contents of navbar menu after switching to edit mode. + Structure of insert panel with basic grouping tabs.
@@ -472,7 +400,7 @@
Type:
Source:
@@ -490,13 +418,13 @@
Type:
-

(static, constant) navbarFinalize :string

+

(static, constant) insertTabHtml :object

- Finalize option in the navbar for rodan + HTML for each insert tab (neume, grouping, clef, system, and division).
@@ -505,7 +433,7 @@
Type:

- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:08 GMT-0400 (GMT-04:00)
diff --git a/doc/module-SingleEdit_EditControls.html b/doc/module-SquareEdit_Controls.html similarity index 66% rename from doc/module-SingleEdit_EditControls.html rename to doc/module-SquareEdit_Controls.html index f6573755d..e28fee898 100644 --- a/doc/module-SingleEdit_EditControls.html +++ b/doc/module-SquareEdit_Controls.html @@ -2,7 +2,7 @@ - JSDoc: Module: SingleEdit/EditControls + JSDoc: Module: SquareEdit/Controls @@ -17,7 +17,7 @@
-

Module: SingleEdit/EditControls

+

Module: SquareEdit/Controls

@@ -165,7 +165,7 @@
Parameters:
Source:
@@ -251,7 +251,7 @@
Parameters:
-SingleEditMode +EditMode @@ -302,7 +302,7 @@
Parameters:
Source:
@@ -439,167 +439,7 @@
Parameters:
Source:
- - - - - - - -

- - - - - - - - - - - - - - - - - - - - - - - - - - -

(static) initNavbar(filename, neonView)

- - - - - - -
- Set listener on switching EditMode button to File dropdown in the navbar. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
filename - - -string - - - - The name of the MEI file.
neonView - - -NeonView - - - -
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
@@ -687,7 +527,7 @@

(static
Source:
@@ -847,7 +687,7 @@
Parameters:
Source:
@@ -984,7 +824,7 @@
Parameters:
Source:
@@ -1121,7 +961,7 @@
Parameters:
Source:
@@ -1167,13 +1007,13 @@
Parameters:

- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:08 GMT-0400 (GMT-04:00)
diff --git a/doc/module-SingleEdit_Grouping.html b/doc/module-SquareEdit_Grouping.html similarity index 66% rename from doc/module-SingleEdit_Grouping.html rename to doc/module-SquareEdit_Grouping.html index f10e7ae44..0edbb112b 100644 --- a/doc/module-SingleEdit_Grouping.html +++ b/doc/module-SquareEdit_Grouping.html @@ -2,7 +2,7 @@ - JSDoc: Module: SingleEdit/Grouping + JSDoc: Module: SquareEdit/Grouping @@ -17,7 +17,7 @@
-

Module: SingleEdit/Grouping

+

Module: SquareEdit/Grouping

@@ -112,7 +112,7 @@
Type:
Source:
@@ -192,7 +192,7 @@

(static
Source:
@@ -280,7 +280,7 @@

(stati
Source:
@@ -417,7 +417,7 @@
Parameters:
Source:
@@ -554,7 +554,7 @@
Parameters:
Source:
@@ -642,7 +642,7 @@

(inner) Source:
@@ -730,7 +730,7 @@

(inner) getIds
Source:
@@ -913,7 +913,7 @@

Parameters:
Source:
@@ -959,13 +959,13 @@
Parameters:


- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:08 GMT-0400 (GMT-04:00)
diff --git a/doc/module-SingleEdit_SelectOptions.html b/doc/module-SquareEdit_SelectOptions.html similarity index 62% rename from doc/module-SingleEdit_SelectOptions.html rename to doc/module-SquareEdit_SelectOptions.html index 84353119d..dd72bd687 100644 --- a/doc/module-SingleEdit_SelectOptions.html +++ b/doc/module-SquareEdit_SelectOptions.html @@ -2,7 +2,7 @@ - JSDoc: Module: SingleEdit/SelectOptions + JSDoc: Module: SquareEdit/SelectOptions @@ -17,7 +17,7 @@
-

Module: SingleEdit/SelectOptions

+

Module: SquareEdit/SelectOptions

@@ -112,7 +112,7 @@
Type:
Source:
@@ -192,7 +192,7 @@

(static)
Source:
@@ -329,7 +329,144 @@
Parameters:
Source:
+ + + + + + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + +

(static) removeHandler(neonView)

+ + + + + + +
+ function to handle removing elements +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
neonView + + +NeonView + + + + a neonView object
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
@@ -466,7 +603,95 @@
Parameters:
Source:
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

(static) triggerDefaultActions()

+ + + + + + +
+ Trigger default selection option. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
@@ -603,7 +828,7 @@
Parameters:
Source:
@@ -691,7 +916,7 @@

(static)
Source:
@@ -779,7 +1004,7 @@

(static)
Source:
@@ -867,7 +1092,7 @@

(static)
Source:
@@ -955,7 +1180,7 @@

(static) <
Source:
@@ -1092,7 +1317,7 @@
Parameters:
Source:
@@ -1247,7 +1472,7 @@
Parameters:
Source:
@@ -1353,7 +1578,7 @@

(inner)
Source:
@@ -1399,13 +1624,13 @@

(inner)
- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:08 GMT-0400 (GMT-04:00)
diff --git a/doc/module-SquareEdit_StaffTools-SplitHandler.html b/doc/module-SquareEdit_StaffTools-SplitHandler.html new file mode 100644 index 000000000..641aee946 --- /dev/null +++ b/doc/module-SquareEdit_StaffTools-SplitHandler.html @@ -0,0 +1,220 @@ + + + + + JSDoc: Class: SplitHandler + + + + + + + + + + +
+ +

Class: SplitHandler

+ + + + + + +
+ +
+ +

+ SquareEdit/StaffTools~SplitHandler(neonView)

+ + +
+ +
+
+ + + + + + +

new SplitHandler(neonView)

+ + + + + + +
+ Handler splitting a staff into two staves through Verovio. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
neonView + + +NeonView + + + + The NeonView parent.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:08 GMT-0400 (GMT-04:00) +
+ + + + + \ No newline at end of file diff --git a/doc/module-SquareEdit_StaffTools.html b/doc/module-SquareEdit_StaffTools.html new file mode 100644 index 000000000..96aa5ad8e --- /dev/null +++ b/doc/module-SquareEdit_StaffTools.html @@ -0,0 +1,92 @@ + + + + + JSDoc: Module: SquareEdit/StaffTools + + + + + + + + + + +
+ +

Module: SquareEdit/StaffTools

+ + + + + + +
+ +
+ + + +
+ +
+
+ + + + + +
+ + + + + + +

Classes

+ +
+
SplitHandler
+
+
+ + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:08 GMT-0400 (GMT-04:00) +
+ + + + + \ No newline at end of file diff --git a/doc/module-TextView-TextView.html b/doc/module-TextView-TextView.html index 2ac6f0872..f4dd98af6 100644 --- a/doc/module-TextView-TextView.html +++ b/doc/module-TextView-TextView.html @@ -143,7 +143,7 @@
Parameters:
Source:
@@ -251,7 +251,7 @@

getSylText<
Source:
@@ -299,94 +299,6 @@

Returns:
- - - - - - -

setTextEdit()

- - - - - - -
- set text to edit mode -
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - - - - - - @@ -445,7 +357,7 @@

se
Source:
@@ -481,7 +393,7 @@

se -

updateSylText(span)

+

updateBBoxViewVisibility()

@@ -489,7 +401,7 @@

updateSy
- Update the text for a single syl element + update visibility of text bounding boxes
@@ -500,55 +412,6 @@

updateSy -

Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
span - - -HTMLElement - - - -
- - @@ -582,7 +445,7 @@
Parameters:
Source:
@@ -671,7 +534,7 @@

Source:
@@ -717,13 +580,13 @@


- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:08 GMT-0400 (GMT-04:00)
diff --git a/doc/module-TextView.html b/doc/module-TextView.html index e38c34965..011b18e0f 100644 --- a/doc/module-TextView.html +++ b/doc/module-TextView.html @@ -63,165 +63,6 @@

Classes

-

Methods

- - - - - - - -

(inner) formatRaw(rawString) → {string}

- - - - - - -
- Format a string for prompting the user. -
- - - - - - - - - -
Parameters:
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
NameTypeDescription
rawString - - -string - - - -
- - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - - -
- - - - - - - - - - - - - - - -
Returns:
- - - - -
-
- Type -
-
- -string - - -
-
- - - - - - - - - @@ -236,13 +77,13 @@
Returns:

- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:08 GMT-0400 (GMT-04:00)
diff --git a/doc/module-Validation.html b/doc/module-Validation.html index b726cb7c4..95ef48f7f 100644 --- a/doc/module-Validation.html +++ b/doc/module-Validation.html @@ -58,6 +58,94 @@

Module: Validation

Methods

+ + + + + + +

(static) blankPage()

+ + + + + + +
+ Update the message on a blank page when there is no MEI. +
+ + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + @@ -486,13 +574,13 @@
Properties

- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:08 GMT-0400 (GMT-04:00)
diff --git a/doc/module-Warnings.html b/doc/module-Warnings.html index 8663a7bf2..1185a7437 100644 --- a/doc/module-Warnings.html +++ b/doc/module-Warnings.html @@ -162,13 +162,13 @@

(stati
- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:08 GMT-0400 (GMT-04:00)
diff --git a/doc/module-utils_Color.html b/doc/module-utils_Color.html index ce94d32f4..6d9f222f5 100644 --- a/doc/module-utils_Color.html +++ b/doc/module-utils_Color.html @@ -114,7 +114,7 @@
Type:
Source:
@@ -266,7 +266,7 @@
Parameters:
Source:
@@ -491,7 +491,7 @@

(static) <
Source:
@@ -631,7 +631,7 @@
Parameters:
Source:
@@ -719,7 +719,7 @@

(stat
Source:
@@ -807,7 +807,7 @@

(static)
Source:
@@ -853,13 +853,13 @@

(static)
- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:08 GMT-0400 (GMT-04:00)
diff --git a/doc/module-utils_Cursor.html b/doc/module-utils_Cursor.html index e9edfe819..941f55e83 100644 --- a/doc/module-utils_Cursor.html +++ b/doc/module-utils_Cursor.html @@ -116,7 +116,7 @@

(static)
Source:
@@ -341,7 +341,7 @@

Parameters:
Source:
@@ -387,13 +387,13 @@
Parameters:

- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:08 GMT-0400 (GMT-04:00)
diff --git a/doc/module-utils_EditContents.html b/doc/module-utils_EditContents.html new file mode 100644 index 000000000..72f92239a --- /dev/null +++ b/doc/module-utils_EditContents.html @@ -0,0 +1,305 @@ + + + + + JSDoc: Module: utils/EditContents + + + + + + + + + + +
+ +

Module: utils/EditContents

+ + + + + + +
+ +
+ + + +
+ +
+
+ + + + + +
+ + + + + + + + + + + + + + +

Members

+ + + +

(static, constant) navbarDropdownMenu :string

+ + + + +
+ Contents of navbar menu after switching to edit mode. +
+ + + +
Type:
+
    +
  • + +string + + +
  • +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static, constant) navbarFinalize :string

+ + + + +
+ Finalize option in the navbar for rodan +
+ + + +
Type:
+
    +
  • + +string + + +
  • +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + +

(static, constant) undoRedoPanel :string

+ + + + +
+ Contents of the undo/redo panel with buttons +
+ + + +
Type:
+
    +
  • + +string + + +
  • +
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:08 GMT-0400 (GMT-04:00) +
+ + + + + \ No newline at end of file diff --git a/doc/module-utils_EditControls.html b/doc/module-utils_EditControls.html new file mode 100644 index 000000000..c2446c29c --- /dev/null +++ b/doc/module-utils_EditControls.html @@ -0,0 +1,638 @@ + + + + + JSDoc: Module: utils/EditControls + + + + + + + + + + +
+ +

Module: utils/EditControls

+ + + + + + +
+ +
+ + + +
+ +
+
+ + + + + +
+ + + + + + + + + + + + + + + + +

Methods

+ + + + + + + +

(static) initNavbar(neonView)

+ + + + + + +
+ Set listener on switching EditMode button to File dropdown in the navbar. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
neonView + + +NeonView + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

(static) initUndoRedoPanel(neonView)

+ + + + + + +
+ Initialize the undo/redo panel +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
neonView + + +NeonView + + + + the NeonView parent
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

(static) prepareEditMode(neonView)

+ + + + + + +
+ prepare the edit mode button +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
neonView + + +NeonView + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

(static) startEditMode(neonView)

+ + + + + + +
+ start the basic edit mode features +is called when the edit mode button is clicked +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
neonView + + +NeonView + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:08 GMT-0400 (GMT-04:00) +
+ + + + + \ No newline at end of file diff --git a/doc/module-utils_NeonManifest.html b/doc/module-utils_NeonManifest.html new file mode 100644 index 000000000..6ed61de56 --- /dev/null +++ b/doc/module-utils_NeonManifest.html @@ -0,0 +1,226 @@ + + + + + JSDoc: Module: utils/NeonManifest + + + + + + + + + + +
+ +

Module: utils/NeonManifest

+ + + + + + +
+ +
+ + + +
+ +
+
+ + + + + +
+ + + + + + + + + + + + + + + + +

Methods

+ + + + + + + +

(static) parseManifest(manifestString)

+ + + + + + +
+ Check if the provided Neon manifest is parseable. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
manifestString + + +string + + + + The Neon manifest as a string.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:08 GMT-0400 (GMT-04:00) +
+ + + + + \ No newline at end of file diff --git a/doc/module-Notification-Notification.html b/doc/module-utils_Notification-Notification.html similarity index 51% rename from doc/module-Notification-Notification.html rename to doc/module-utils_Notification-Notification.html index 7d1470c08..85ea48707 100644 --- a/doc/module-Notification-Notification.html +++ b/doc/module-utils_Notification-Notification.html @@ -29,7 +29,7 @@

Class: Notification

- Notification~Notification(message)

+ utils/Notification~Notification(message)

A class to manage Neon notifications.
@@ -319,13 +319,13 @@
Returns:

- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:08 GMT-0400 (GMT-04:00)
diff --git a/doc/module-Notification.html b/doc/module-utils_Notification.html similarity index 68% rename from doc/module-Notification.html rename to doc/module-utils_Notification.html index 466301670..0062c37f6 100644 --- a/doc/module-Notification.html +++ b/doc/module-utils_Notification.html @@ -2,7 +2,7 @@ - JSDoc: Module: Notification + JSDoc: Module: utils/Notification @@ -17,7 +17,7 @@
-

Module: Notification

+

Module: utils/Notification

@@ -49,7 +49,7 @@

Module: Notification

Classes

-
Notification
+
Notification
@@ -65,7 +65,7 @@

Members

-

(inner) currentModeMessage :module:Notification~Notification

+

(inner) currentModeMessage :module:Notification~Notification

@@ -76,7 +76,7 @@
Type:
@@ -86,7 +86,7 @@
Parameters:
- staffId + elementId @@ -102,7 +102,7 @@
Parameters:
- The ID of the staff to resize. + The ID of the element to resize. @@ -189,7 +189,7 @@
Parameters:
Source:
@@ -245,7 +245,7 @@

(inner) lrx - The lower-right x-coordinate of the staff. + The lower-right x-coordinate of the element. @@ -293,7 +293,7 @@
Type:
Source:
@@ -317,7 +317,7 @@

(inner) lry - The lower-right y-coordinate of the staff. + The lower-right y-coordinate of the element. @@ -365,7 +365,7 @@
Type:
Source:
@@ -389,7 +389,7 @@

(inner) ulx - The upper-left x-coordinate of the staff. + The upper-left x-coordinate of the element. @@ -437,7 +437,7 @@
Type:
Source:
@@ -461,7 +461,7 @@

(inner) uly - The upper-left y-coordinate of the staff. + The upper-left y-coordinate of the element. @@ -509,7 +509,7 @@
Type:
Source:
@@ -545,7 +545,7 @@

(inner) - Draw the initial rectangle around the staff + Draw the initial rectangle around the element and add the listeners to support dragging to resize. @@ -590,7 +590,7 @@

(inner) Source:
@@ -678,7 +678,7 @@

(inner) redraw
Source:
@@ -724,13 +724,13 @@

(inner) redraw
- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:08 GMT-0400 (GMT-04:00)
diff --git a/doc/module-utils_Resize.html b/doc/module-utils_Resize.html new file mode 100644 index 000000000..6c47f481a --- /dev/null +++ b/doc/module-utils_Resize.html @@ -0,0 +1,158 @@ + + + + + JSDoc: Module: utils/Resize + + + + + + + + + + +
+ +

Module: utils/Resize

+ + + + + + +
+ +
+ + + +
+ +
+
+ + + + + +
+ + + + + + +

Classes

+ +
+
Resize
+
+
+ + + + + + + + + +

Members

+ + + +

(inner, constant) Side

+ + + + +
+ The sides of the rectangle +
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:08 GMT-0400 (GMT-04:00) +
+ + + + + \ No newline at end of file diff --git a/doc/module-utils_Select.html b/doc/module-utils_Select.html new file mode 100644 index 000000000..6bcb7ebec --- /dev/null +++ b/doc/module-utils_Select.html @@ -0,0 +1,660 @@ + + + + + JSDoc: Module: utils/Select + + + + + + + + + + +
+ +

Module: utils/Select

+ + + + + + +
+ +
+ + + +
+ +
+
+ + + + + +
+ + + + + + + + + + + + + + + + +

Methods

+ + + + + + + +

(static) clickSelect(selector)

+ + + + + + +
+ Apply listeners for click selection. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
selector + + +string + + + + The CSS selector used to choose where listeners are applied.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

(static) dragSelect(selector)

+ + + + + + +
+ Apply listeners for drag selection. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
selector + + +string + + + + The CSS selector used to choose where listeners are applied.
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

(static) setSelectHelperObjects(nv, dh)

+ + + + + + +
+ Set the objects for this module. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
nv + + +NeonView + + + + The NeonView object
dh + + +DragHandler + + + + The drag handler object
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +

(inner) clickHandler(evt)

+ + + + + + +
+ Handle click events related to element selection. +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
evt + + +object + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:08 GMT-0400 (GMT-04:00) +
+ + + + + \ No newline at end of file diff --git a/doc/module-SingleEdit_Select.html b/doc/module-utils_SelectTools.html similarity index 61% rename from doc/module-SingleEdit_Select.html rename to doc/module-utils_SelectTools.html index 2e805ac84..140363c70 100644 --- a/doc/module-SingleEdit_Select.html +++ b/doc/module-utils_SelectTools.html @@ -2,7 +2,7 @@ - JSDoc: Module: SingleEdit/Select + JSDoc: Module: utils/SelectTools @@ -17,7 +17,7 @@
-

Module: SingleEdit/Select

+

Module: utils/SelectTools

@@ -64,7 +64,7 @@

Methods

-

(static) clickSelect()

+

(static) getSelectionType() → {string|null}

@@ -72,7 +72,7 @@

(static)
- Apply listeners for click selection. + Get the selection mode chosen by the user.
@@ -116,7 +116,7 @@

(static)
Source:
@@ -141,94 +141,27 @@

(static) - - - - +

Returns:
- - - - - -

(static) dragSelect()

- - - - - - -
- Apply listeners for drag selection. -
- - - - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- +
+
+ Type +
+
+ +string +| - +null - - +
- - - - - - - - - - - - - - + @@ -240,7 +173,7 @@

(static) d -

(static) selectStaff(el, dragHandler)

+

(static) getStaffBBox(staff) → {object}

@@ -248,7 +181,7 @@

(static)
- Select a staff element. + Get the bounding box of a staff based on its staff lines.
@@ -284,7 +217,7 @@

Parameters:
- el + staff @@ -300,30 +233,7 @@
Parameters:
- The staff element in the DOM. - - - - - - - dragHandler - - - - - -DragHandler - - - - - - - - - - The drag handler in use. + @@ -364,7 +274,7 @@
Parameters:
Source:
@@ -389,6 +299,24 @@
Parameters:
+
Returns:
+ + + + +
+
+ Type +
+
+ +object + + +
+
+ + @@ -400,7 +328,7 @@
Parameters:
-

(static) setSelectHelperObjects(dh, nv)

+

(static) isLigature(nc) → {boolean}

@@ -408,7 +336,7 @@

(stat
- Set the objects for this module. + Check if neume component is part of a ligature
@@ -444,36 +372,13 @@
Parameters:
- dh - - - - - -DragHandler - - - - - - - - - - The drag handler object - - - - - - - nv + nc -NeonView +SVGGraphicsElement @@ -483,7 +388,7 @@
Parameters:
- The NeonView object + The neume component to check. @@ -524,7 +429,7 @@
Parameters:
Source:
@@ -549,95 +454,24 @@
Parameters:
- - - - +
Returns:
- - - - - -

(static) unselect()

- - - - - - -
- Unselect all selected elements and run undo any extra -actions. -
- - - - - - - - - - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - +
+
+ Type +
+
+ +boolean - - +
- - - - - - - - - - - - - - + @@ -649,7 +483,7 @@

(static) uns -

(inner) clickHandler(evt)

+

(static) select(el, dragHandleropt)

@@ -657,7 +491,7 @@

(inner)
- Handle click events related to element selection. + Generic select function.
@@ -681,6 +515,8 @@

Parameters:
Type + Attributes + @@ -693,19 +529,60 @@
Parameters:
- evt + el -object +SVGGraphicsElement + + + + + + + + + + + + + + + + + + + + + + + + + dragHandler + + + + + +DragHandler + + + <optional>
+ + + + + + + @@ -750,7 +627,7 @@
Parameters:
Source:
@@ -786,7 +663,7 @@
Parameters:
-

(inner) getSelectionType() → {string|null}

+

(static) selectAll(elements, neonView, info, dragHandler)

@@ -794,7 +671,7 @@

(inner) - Get the selection mode chosen by the user. + Handle selecting an array of elements based on the selection type.

@@ -805,147 +682,107 @@

(inner) - - - - - - - - - - - - - - - - - - - - - - - - - - -
Source:
-
- - - - - - +
Parameters:
-

- - - - - - - - - - - - - - - -
Returns:
+ + + + + + -
-
- Type -
-
-string -| - -null + -
-
+ + + + + + + + + - - + - -

(inner) getStaffBBox(staff) → {object}

- + + + + + + - -
- Get the bounding box of a staff based on its staff lines. -
- + + + + + + + + + + -
Parameters:
- +
NameTypeDescription
elements + + +Array.<SVGGraphicsElement> + + The elements to select. Either or .
neonView + + +NeonView + +
info + + +InfoModule - - - - - - - + + - + - + - - - + + - - + - + + + + + + + + + + + + + + + + + + @@ -1151,7 +993,7 @@
Parameters:
Source:
@@ -1176,24 +1018,6 @@
Parameters:
-
Returns:
- - - - -
-
- Type -
-
- -boolean - - -
-
- - @@ -1205,7 +1029,7 @@
Returns:
-

(inner) select(el)

+

(static) selectNcs(el, dragHandler, neonView)

@@ -1213,7 +1037,7 @@

(inner) select
- Generic select function. + Select an nc.
@@ -1265,7 +1089,53 @@

Parameters:
- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1306,7 +1176,7 @@
Parameters:
Source:
@@ -1342,7 +1212,7 @@
Parameters:
-

(async, inner) selectAll(elements)

+

(static) selectNn(notNeumes)

@@ -1350,7 +1220,7 @@

(async, inner) - Handle selecting an array of elements based on the selection type. + Select not neume elements. @@ -1386,7 +1256,7 @@
Parameters:

- + + @@ -1443,7 +1313,7 @@
Parameters:
Source:
@@ -1479,7 +1349,7 @@
Parameters:
-

(async, inner) selectNcs(el, dragHandler)

+

(static) selectStaff(el, dragHandler)

@@ -1487,7 +1357,7 @@

(async, inner) - Select an nc. + Select a staff element. @@ -1529,7 +1399,7 @@
Parameters:

+ @@ -1562,7 +1432,7 @@
Parameters:
- + @@ -1603,7 +1473,7 @@
Parameters:
Source:
@@ -1639,7 +1509,7 @@
Parameters:
-

(inner) selectNn(notNeumes)

+

(static) sharedSecondLevelParent(elements) → {boolean}

@@ -1647,7 +1517,7 @@

(inner) sele
- Select not neume elements. + Check if the elements have the same parent up two levels.
@@ -1683,13 +1553,13 @@

Parameters:
- + + @@ -1740,7 +1610,7 @@
Parameters:
Source:
@@ -1765,83 +1635,57 @@
Parameters:
+
Returns:
+ +
+ - If the elements share the same second level parent. +
- +
+
+ Type +
+
- +boolean - - -

(inner) sharedSecondLevelParent(elements) → {boolean}

- +
+
-
- Check if the elements have the same parent up two levels. -
- - - - - - - - -
Parameters:
- -
NameTypeDescription
staffdragHandler -SVGGElement +DragHandler @@ -996,7 +833,7 @@
Parameters:
Source:
@@ -1021,24 +858,6 @@
Parameters:
-
Returns:
- - - - -
-
- Type -
-
- -object - - -
-
- - @@ -1050,7 +869,7 @@
Returns:
-

(async, inner) isLigature(nc) → {boolean}

+

(static) selectBBox(el, dragHandler)

@@ -1058,7 +877,7 @@

(async, inner) - Check if neume component is part of a ligature + select a boundingbox element @@ -1094,13 +913,13 @@
Parameters:

ncel -SVGGraphicsElement +SVGGElement @@ -1110,7 +929,30 @@
Parameters:
-
The neume component to check.the bbox (sylTextRect) element in the DOM
dragHandler + + +DragHandler + + + + the drag handler in use
The nc element to select.
dragHandler + + +DragHandler + + + + An instantiated DragHandler.
neonView + + +NeonView + + + + The NeonView parent
elementsnotNeumes @@ -1402,7 +1272,7 @@
Parameters:
-
The elements to select. Either or .An array of not neumes elements.
-SVGGraphicsElement +SVGGElement @@ -1539,7 +1409,7 @@
Parameters:
-
The nc element to select.The staff element in the DOM.
An instantiated DragHandler.The drag handler in use.
notNeumeselements -Array.<SVGGraphicsElement> +Array.<Element> @@ -1699,7 +1569,7 @@
Parameters:
-
An array of not neumes elements.The array of elements.
- - - - + - + - + +

(static) unselect()

+ - + - - - - - - - - - +
+ Unselect all selected elements and run undo any extra +actions. +
- - - - - - - -
NameTypeDescription
elements - - -Array.<Element> - - The array of elements.
@@ -1877,7 +1721,7 @@
Parameters:
Source:
@@ -1902,28 +1746,6 @@
Parameters:
-
Returns:
- - -
- - If the elements share the same second level parent. -
- - - -
-
- Type -
-
- -boolean - - -
-
- - @@ -1945,13 +1767,13 @@
Returns:

- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:08 GMT-0400 (GMT-04:00)
diff --git a/doc/module.exports.html b/doc/module.exports.html new file mode 100644 index 000000000..0cf3a547c --- /dev/null +++ b/doc/module.exports.html @@ -0,0 +1,354 @@ + + + + + JSDoc: Class: exports + + + + + + + + + + +
+ +

Class: exports

+ + + + + + +
+ +
+ +

exports()

+ +
A wrapper around the verovio web worker to permit mocking in tests.
+ + +
+ +
+
+ + + + +

Constructor

+ + + +

new exports()

+ + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + + + + +
+ +
+ +

exports(neonView)

+ +
A Text editing module that works with the SingleView and DivaView modules
+ + +
+ +
+
+ + + + +

Constructor

+ + + +

new exports(neonView)

+ + + + + + +
+ Constructor for a TextEdit +
+ + + + + + + + + +
Parameters:
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
neonView + + +NeonView + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
Source:
+
+ + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:07 GMT-0400 (GMT-04:00) +
+ + + + + \ No newline at end of file diff --git a/doc/utils_Color.js.html b/doc/utils_Color.js.html index 48090abc1..6aaefc194 100644 --- a/doc/utils_Color.js.html +++ b/doc/utils_Color.js.html @@ -46,7 +46,12 @@

Source: utils/Color.js

let groupColor = ColorPalette[i % ColorPalette.length]; if (!$(groups[i]).parents('.selected').length && !$(groups[i]).hasClass('selected')) { groups[i].setAttribute('fill', groupColor); + let rects = Array.from($(groups[i]).find('.sylTextRect-display')); + rects.forEach(function (rect) { + $(rect).css('fill', groupColor); + }); $(groups[i]).addClass('highlighted'); + $(groups[i]).find('.sylTextRect-display').addClass('highlighted'); } else { if (!$(groups[i]).hasClass('selected')) { groups[i].setAttribute('fill', null); @@ -66,7 +71,20 @@

Source: utils/Color.js

let highlighted = Array.from($('.highlighted').filter((index, elem) => { return !$(elem.parentElement).hasClass('selected'); })); highlighted.forEach(elem => { elem.setAttribute('fill', null); + let rects = Array.from($(elem).find('.sylTextRect-display')); + if (!rects.length) { + if (Array.from($(elem).parents('syllable')).length) { + rects = Array.from($(elem).parents('syllable').find('.sylTextRect-display')); + } + } + rects.forEach(function (rect) { + if (!($(rect).closest('.syllable').hasClass('selected'))) { + $(rect).css('fill', 'blue'); + $(rect).removeClass('highlighted'); + } + }); $(elem).removeClass('highlighted'); + $(elem).find('sylTextRect-display').removeClass('highlighted'); }); } @@ -100,6 +118,12 @@

Source: utils/Color.js

child.setAttribute('stroke', color); } else { child.setAttribute('fill', color); + let rects = Array.from($(child).find('.sylTextRect-display')); + if (!rects.length) { rects = Array.from($(child).parents('syllable').find('.sylTextRect-display')); } + rects.forEach(function (rect) { + $(rect).css('fill', color); + $(rect).addClass('highlighted'); + }); } $(child).addClass('highlighted'); }); @@ -116,6 +140,12 @@

Source: utils/Color.js

elem.setAttribute('stroke', '#000000'); } else { elem.removeAttribute('fill'); + let rects = Array.from($(elem).find('.sylTextRect-display')); + if (!rects.length) { rects = Array.from($(elem).parents('syllable').find('.sylTextRect-display')); } + rects.forEach(function (rect) { + $(rect).css('fill', 'blue'); + $(rect).removeClass('highlighted'); + }); } }); $(staff).filter(':not(.selected)').children('.highlighted').removeClass('highlighted'); @@ -147,13 +177,13 @@

Source: utils/Color.js


- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:07 GMT-0400 (GMT-04:00)
diff --git a/doc/utils_Cursor.js.html b/doc/utils_Cursor.js.html index 680bc967d..a6823887d 100644 --- a/doc/utils_Cursor.js.html +++ b/doc/utils_Cursor.js.html @@ -36,27 +36,6 @@

Source: utils/Cursor.js

export function updateCursor () { $('#bgimg').css('cursor', 'crosshair'); $('#mei_output').css('cursor', 'crosshair'); - - /// //TODO: Find a way to scale cursor image to the same sice as current svg mei output - // var nc = d3.selectAll(".nc").node().getBBox(); - // var ncHeight = nc.height; - // var ncWidth = nc.width; - - // var curViewBox = d3.select("#svg_group").attr("viewBox"); - // var curVbHeight = parseInt(curViewBox.split(" ")[3]); - // var curVbWidth = parseInt(curViewBox.split(" ")[2]); - - // var imgHeight = this.origHeight/curVbHeight; - // var imgScale = this.origWidth/curVbWidth; - - // var punctum = 'data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="30" height="30" viewBox="0 0 300 300"><path d="M31,208.5c16.667-7,37-10.5,61-10.5c22.666,0,42.334,3.5,59,10.5s27.334,10.5,32,10.5V38 C171,12,140.666-1,92-1C42-1,11.333,12,0,38v181C4,219,14.333,215.5,31,208.5" transform="scale(' + imgScale + ')"/></svg>' - - // // var url = "url('" + punctum + "'), auto"; - - // console.log(url); - - // $("#bgimg").css('cursor', url); - // $("#mei_output").css('cursor', url); } /** @@ -86,13 +65,13 @@

Source: utils/Cursor.js


- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:07 GMT-0400 (GMT-04:00)
diff --git a/doc/utils_DragHandler.js.html b/doc/utils_DragHandler.js.html new file mode 100644 index 000000000..d3803be20 --- /dev/null +++ b/doc/utils_DragHandler.js.html @@ -0,0 +1,177 @@ + + + + + JSDoc: Source: utils/DragHandler.js + + + + + + + + + + +
+ +

Source: utils/DragHandler.js

+ + + + + + +
+
+
const d3 = require('d3');
+const $ = require('jquery');
+
+/**
+ * Handle the dragging of musical elements and communicate actions.
+ * @constructor
+ * @param {NeonView} neonView - The NeonView parent object.
+ */
+function DragHandler (neonView, selector) {
+  var dragStartCoords;
+  var dragEndCoords;
+  var resetToAction;
+
+  /**
+   * Initialize the dragging action and handler for selected elements.
+   */
+  function dragInit () {
+    // Adding listeners
+    var dragBehaviour = d3.drag()
+      .on('start', dragStarted)
+      .on('drag', dragging)
+      .on('end', dragEnded);
+
+    var activeNc = d3.selectAll('.selected');
+    var selection = Array.from(activeNc._groups[0]);
+
+    dragStartCoords = new Array(activeNc.size());
+    dragEndCoords = new Array(activeNc.size());
+
+    activeNc.call(dragBehaviour);
+
+    var editorAction;
+
+    // Drag effects
+    function dragStarted () {
+      dragStartCoords = d3.mouse(this);
+      if (this.classList.contains('staff')) {
+        d3.select(selector).call(dragBehaviour);
+      }
+    }
+
+    function dragging () {
+      var relativeY = d3.event.y - dragStartCoords[1];
+      var relativeX = d3.event.x - dragStartCoords[0];
+      selection.forEach((el) => {
+        d3.select(el).attr('transform', function () {
+          return 'translate(' + [relativeX, relativeY] + ')';
+        });
+      });
+      /*
+       * if we're dragging a syllable (or neume etc) then there won't be a syl selected
+       * then we don't want the bounding box (if it is displayed) to move when dragging the neumes
+       * it will be a child of the element in selection, so it will get moved in the above loop
+       * so we cancel that movement out here
+       */
+      if (selection.filter(element => element.classList.contains('syl')).length === 0) {
+        d3.selectAll('.syllable.selected').selectAll('.sylTextRect-display').attr('transform', function () {
+          return 'translate(' + [-1 * relativeX, -1 * relativeY] + ')';
+        });
+      }
+    }
+
+    function dragEnded () {
+      dragEndCoords = [d3.event.x, d3.event.y];
+      let paramArray = [];
+      selection.forEach((el) => {
+        let id = (el.tagName === 'rect') ? el.closest('.syl').id : el.id;
+        let singleAction = { action: 'drag',
+          param: { elementId: id,
+            x: parseInt(dragEndCoords[0] - dragStartCoords[0]),
+            y: parseInt(dragEndCoords[1] - dragStartCoords[1]) * -1 }
+        };
+        paramArray.push(singleAction);
+      });
+      editorAction = {
+        'action': 'chain',
+        'param': paramArray
+      };
+
+      var xDiff = Math.abs(dragStartCoords[0] - dragEndCoords[0]);
+      var yDiff = Math.abs(dragStartCoords[1] - dragEndCoords[1]);
+
+      if (xDiff > 5 || yDiff > 5) {
+        neonView.edit(editorAction, neonView.view.getCurrentPageURI()).then(() => {
+          neonView.updateForCurrentPage();
+          endOptionsSelection();
+          reset();
+          dragInit();
+        });
+      } else {
+        reset();
+        dragInit();
+      }
+    }
+  }
+
+  /**
+   * A d3 action that should be put on the selector once the drag action finishes.
+   * @param {oject} reset
+   */
+  function resetTo (reset) {
+    resetToAction = reset;
+  }
+
+  /**
+   * Reset the actino on the selector to the one set by resetTo.
+   */
+  function reset () {
+    if (resetToAction !== undefined) {
+      d3.select(selector).call(resetToAction);
+    }
+  }
+
+  /**
+   * Remove the additonal editor options that exist.
+   */
+  function endOptionsSelection () {
+    $('#moreEdit').empty();
+    $('#moreEdit').addClass('is-invisible');
+  }
+
+  DragHandler.prototype.dragInit = dragInit;
+  DragHandler.prototype.resetTo = resetTo;
+}
+
+export { DragHandler as default };
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:07 GMT-0400 (GMT-04:00) +
+ + + + + diff --git a/doc/utils_EditContents.js.html b/doc/utils_EditContents.js.html new file mode 100644 index 000000000..2615d219e --- /dev/null +++ b/doc/utils_EditContents.js.html @@ -0,0 +1,81 @@ + + + + + JSDoc: Source: utils/EditContents.js + + + + + + + + + + +
+ +

Source: utils/EditContents.js

+ + + + + + +
+
+
/** @module utils/EditContents */
+
+/**
+ * Contents of navbar menu after switching to edit mode.
+ * @type {string}
+ */
+export const navbarDropdownMenu =
+    "<div class='navbar-item has-dropdown is-hoverable'><a class='navbar-link'>File</a>" +
+    "<div id='navbar-dropdown-options' class='navbar-dropdown'>" +
+    "<a id='save' class='navbar-item'>Save</a>" +
+    "<a id='export' class='navbar-item'>Save and Export to File</a>" +
+    "<a id='getmei' class='navbar-item' href='' download=''> Download MEI </a>" +
+    "<a id='revert' class='navbar-item'> Revert </a>";
+
+/**
+ * Finalize option in the navbar for rodan
+ * @type {string}
+ */
+export const navbarFinalize =
+    "<a id='finalize' class='navbar-item'> Finalize MEI </a>";
+
+/**
+ * Contents of the undo/redo panel with buttons
+ * @type {string}
+ */
+export const undoRedoPanel =
+    "<div class='field has-addons buttons' style='overflow-x: auto;'>" +
+    "<p class='control'>" +
+    "<button class='button' id='undo'>Undo</button>" +
+    "<button class='button' id='redo'>Redo</button></p></a></div>";
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:07 GMT-0400 (GMT-04:00) +
+ + + + + diff --git a/doc/utils_EditControls.js.html b/doc/utils_EditControls.js.html new file mode 100644 index 000000000..b21ac36d4 --- /dev/null +++ b/doc/utils_EditControls.js.html @@ -0,0 +1,186 @@ + + + + + JSDoc: Source: utils/EditControls.js + + + + + + + + + + +
+ +

Source: utils/EditControls.js

+ + + + + + +
+
+
/** @module utils/EditControls */
+
+import * as Notification from './Notification.js';
+import { navbarDropdownMenu, undoRedoPanel } from './EditContents';
+
+const $ = require('jquery');
+
+/**
+ * prepare the edit mode button
+ * @param {NeonView} neonView
+ */
+export function prepareEditMode (neonView) {
+  let parent = document.getElementById('dropdown_toggle');
+  let editItem = document.createElement('a');
+  editItem.classList.add('navbar-item');
+  let editButton = document.createElement('button');
+  editButton.classList.add('button');
+  editButton.id = 'edit_mode';
+  editButton.textContent = 'Edit MEI';
+  editItem.appendChild(editButton);
+  parent.appendChild(editItem);
+
+  editButton.addEventListener('click', () => {
+    startEditMode(neonView);
+  });
+}
+
+/**
+ * start the basic edit mode features
+ * is called when the edit mode button is clicked
+ * @param {NeonView} neonView
+ */
+export function startEditMode (neonView) {
+  $('#dropdown_toggle').empty();
+  $('#dropdown_toggle').append(navbarDropdownMenu);
+  $('#undoRedo_controls').append(undoRedoPanel);
+  initNavbar(neonView);
+  initUndoRedoPanel(neonView);
+}
+
+/**
+ * Set listener on switching EditMode button to File dropdown in the navbar.
+ * @param {NeonView} neonView
+ */
+export function initNavbar (neonView) {
+  // setup navbar listeners
+  $('#save').on('click', () => {
+    neonView.save().then(() => {
+      Notification.queueNotification('Saved');
+    });
+  });
+  $('body').on('keydown', (evt) => {
+    if (evt.key === 's') {
+      neonView.save().then(() => {
+        Notification.queueNotification('Saved');
+      });
+    }
+  });
+
+  $('#export').on('click', (evt) => {
+    neonView.export().then(manifest => {
+      let link = document.createElement('a');
+      link.href = manifest;
+      link.download = neonView.name + '.jsonld';
+      $('body').append(link);
+      link.click();
+      link.remove();
+      Notification.queueNotification('Saved');
+    });
+  });
+
+  $('#revert').on('click', function () {
+    if (window.confirm('Reverting will cause all changes to be lost. Press OK to continue.')) {
+      neonView.deleteDb().then(() => {
+        window.location.reload();
+      });
+    }
+  });
+  // Download link for MEI
+  // Is an actual file with a valid URI except in local mode where it must be generated.
+  $('#getmei').on('click', () => {
+    let uri = neonView.view.getCurrentPageURI();
+    neonView.getPageMEI(uri).then(mei => {
+      let data = 'data:application/mei+xml;base64,' + window.btoa(mei);
+      $('#getmei').attr('href', data)
+        .attr('download', neonView.view.getPageName() + '.mei');
+    });
+  });
+}
+
+/**
+ * Initialize the undo/redo panel
+ * @param {NeonView} neonView - the NeonView parent
+ */
+export function initUndoRedoPanel (neonView) {
+  $('#undo').on('click', undoHandler);
+  $('body').on('keydown', (evt) => {
+    if (evt.key === 'z' && (evt.ctrlKey || evt.metaKey)) {
+      undoHandler();
+    }
+  });
+
+  $('#redo').on('click', redoHandler);
+  $('body').on('keydown', (evt) => {
+    if ((evt.key === 'Z' || (evt.key === 'z' && evt.shiftKey)) && (evt.ctrlKey || evt.metaKey)) {
+      redoHandler();
+    }
+  });
+
+  /**
+   * Tries to undo an action and update the page if it succeeds.
+   */
+  function undoHandler () {
+    neonView.undo(neonView.view.getCurrentPageURI()).then(result => {
+      if (result) {
+        neonView.updateForCurrentPage();
+      } else {
+        console.error('Failed to undo action');
+      }
+    });
+  }
+
+  /**
+   * Tries to redo an action and update the page if it succeeds.
+   */
+  function redoHandler () {
+    neonView.redo(neonView.view.getCurrentPageURI()).then(result => {
+      if (result) {
+        neonView.updateForCurrentPage();
+      } else {
+        console.error('Failed to redo action');
+      }
+    });
+  }
+}
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:07 GMT-0400 (GMT-04:00) +
+ + + + + diff --git a/doc/utils_NeonManifest.js.html b/doc/utils_NeonManifest.js.html new file mode 100644 index 000000000..a6fa4b440 --- /dev/null +++ b/doc/utils_NeonManifest.js.html @@ -0,0 +1,77 @@ + + + + + JSDoc: Source: utils/NeonManifest.js + + + + + + + + + + +
+ +

Source: utils/NeonManifest.js

+ + + + + + +
+
+
/** @module utils/NeonManifest */
+
+import NeonSchema from './manifest/NeonSchema.json';
+import NeonContext from './manifest/context.json';
+
+const validate = require('jsonschema').validate;
+
+/**
+ * Check if the provided Neon manifest is parseable.
+ * @param {string} manifestString - The Neon manifest as a string.
+ */
+export function parseManifest (manifestString) {
+  let results = validate(manifestString, NeonSchema);
+  let instance = results.instance;
+  if (results.errors.length > 0) {
+    console.error(results.errors);
+    return false;
+  }
+  if (JSON.stringify(instance['@context']) !== JSON.stringify(NeonContext)) {
+    console.error('Context mismatch');
+    console.error(instance['@context']);
+    console.error(NeonContext);
+    return false;
+  }
+  return true;
+}
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:07 GMT-0400 (GMT-04:00) +
+ + + + + diff --git a/doc/utils_Notification.js.html b/doc/utils_Notification.js.html index c8138c35f..adedaa149 100644 --- a/doc/utils_Notification.js.html +++ b/doc/utils_Notification.js.html @@ -26,7 +26,7 @@

Source: utils/Notification.js

-
/** @module Notification */
+            
/** @module utils/Notification */
 
 const uuid = require('uuid/v4');
 const $ = require('jquery');
@@ -115,9 +115,9 @@ 

Source: utils/Notification.js

*/ class Notification { /** - * Create a new notification - * @param {string} message - */ + * Create a new notification + * @param {string} message + */ constructor (message) { this.message = message; this.displayed = false; @@ -135,9 +135,9 @@

Source: utils/Notification.js

} /** - * Get the UUID for this notification - * @returns {string} - */ + * Get the UUID for this notification + * @returns {string} + */ getId () { return this.id; } @@ -152,13 +152,13 @@

Source: utils/Notification.js


- Documentation generated by JSDoc 3.6.2 on Tue Jun 18 2019 11:17:42 GMT-0400 (GMT-04:00) + Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:07 GMT-0400 (GMT-04:00)
diff --git a/doc/utils_Resize.js.html b/doc/utils_Resize.js.html new file mode 100644 index 000000000..b036b0e3f --- /dev/null +++ b/doc/utils_Resize.js.html @@ -0,0 +1,238 @@ + + + + + JSDoc: Source: utils/Resize.js + + + + + + + + + + +
+ +

Source: utils/Resize.js

+ + + + + + +
+
+
/** @module utils/Resize */
+
+/* for resizing objects.
+ * current use cases: bounding boxes and staves
+ */
+
+import { getStaffBBox, selectBBox, selectStaff } from './SelectTools.js';
+
+const d3 = require('d3');
+
+/**
+ * The sides of the rectangle
+ */
+const Side = {
+  Top: 0,
+  Bottom: 1,
+  Left: 2,
+  Right: 3
+};
+
+/**
+ * Handle the resizing of the selected object.
+ * @constructor
+ * @param {string} elementId - The ID of the element to resize.
+ * @param {NeonView} neonView - The NeonView parent for editing and refreshing.
+ * @param {DragHandler} dragHandler - A drag handler object.
+ */
+function Resize (elementId, neonView, dragHandler) {
+  var element = document.getElementById(elementId);
+  /**
+   * The upper-left x-coordinate of the element.
+   * @type {number}
+   */
+  var ulx;
+  /**
+   * The upper-left y-coordinate of the element.
+   * @type {number}
+   */
+  var uly;
+  /**
+   * The lower-right x-coordinate of the element.
+   * @type {number}
+   */
+  var lrx;
+  /**
+   * The lower-right y-coordinate of the element.
+   * @type {number}
+   */
+  var lry;
+
+  /**
+   * Draw the initial rectangle around the element
+   * and add the listeners to support dragging to resize.
+   */
+  function drawInitialRect () {
+    if (element === null) return;
+
+    // if it's a boundingbox just get the coordinates
+    if (element.classList.contains('syl')) {
+      let rect = element.querySelector('.sylTextRect-display');
+      if (rect === null) {
+        console.warn("Tried to draw resize rect for a sylTextRect that doesn't exist (or isn't displaying)");
+        return;
+      }
+      ulx = Number(rect.getAttribute('x'));
+      uly = Number(rect.getAttribute('y'));
+      lrx = +ulx + +rect.getAttribute('width');
+      lry = +uly + +rect.getAttribute('height');
+    }
+
+    // if it's a staff use the paths to get it's boundingbox
+    if (element.classList.contains('staff')) {
+      var bbox = getStaffBBox(element);
+      ulx = bbox.ulx;
+      uly = bbox.uly;
+      lrx = bbox.lrx;
+      lry = bbox.lry;
+    }
+
+    d3.select('#' + element.id).append('rect')
+      .attr('x', ulx)
+      .attr('y', uly)
+      .attr('width', lrx - ulx)
+      .attr('height', lry - uly)
+      .attr('id', 'resizeRect')
+      .attr('stroke', 'black')
+      .attr('stroke-width', 15)
+      .attr('fill', 'none')
+      .style('cursor', 'move');
+
+    d3.select('#resizeRect').call(
+      d3.drag()
+        .on('start', resizeStart)
+        .on('drag', resizeDrag)
+        .on('end', resizeEnd.bind(this))
+    );
+
+    var side;
+    var initialPoint;
+
+    function resizeStart () {
+      initialPoint = d3.mouse(this);
+      {
+        let dist = Math.abs(initialPoint[0] - ulx);
+        side = Side.Left;
+        if (dist > Math.abs(initialPoint[0] - lrx)) {
+          dist = Math.abs(initialPoint[0] - lrx);
+          side = Side.Right;
+        }
+        if (dist > Math.abs(initialPoint[1] - uly)) {
+          dist = Math.abs(initialPoint[1] - uly);
+          side = Side.Top;
+        }
+        if (dist > Math.abs(initialPoint[1] - lry)) {
+          dist = Math.abs(initialPoint[1] - lry);
+          side = Side.Bottom;
+        }
+      }
+    }
+
+    function resizeDrag () {
+      let currentPoint = d3.mouse(this);
+      switch (side) {
+        case Side.Left:
+          ulx = currentPoint[0];
+          break;
+        case Side.Right:
+          lrx = currentPoint[0];
+          break;
+        case Side.Top:
+          uly = currentPoint[1];
+          break;
+        case Side.Bottom:
+          lry = currentPoint[1];
+          break;
+        default:
+          console.error("Something that wasn't a side of the rectangle was dragged. This shouldn't happen.");
+      }
+      redraw();
+    }
+
+    function resizeEnd () {
+      let editorAction = {
+        'action': 'resize',
+        'param': {
+          'elementId': element.id,
+          'ulx': ulx,
+          'uly': uly,
+          'lrx': lrx,
+          'lry': lry
+        }
+      };
+      neonView.edit(editorAction, neonView.view.getCurrentPageURI()).then(async (result) => {
+        if (result) {
+          await neonView.updateForCurrentPagePromise();
+        }
+        element = document.getElementById(elementId);
+        ulx = undefined;
+        uly = undefined;
+        lrx = undefined;
+        lry = undefined;
+        if (element.classList.contains('syl')) {
+          selectBBox(element.querySelector('.sylTextRect-display'), dragHandler, this);
+        } else {
+          selectStaff(element, dragHandler);
+        }
+        drawInitialRect();
+      });
+    }
+  }
+
+  /**
+   * Redraw the rectangle with the new bounds
+   */
+  function redraw () {
+    d3.select('#resizeRect')
+      .attr('x', ulx)
+      .attr('y', uly)
+      .attr('width', lrx - ulx)
+      .attr('height', lry - uly);
+  }
+
+  Resize.prototype.constructor = Resize;
+  Resize.prototype.drawInitialRect = drawInitialRect;
+}
+
+export { Resize };
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:07 GMT-0400 (GMT-04:00) +
+ + + + + diff --git a/doc/utils_Select.js.html b/doc/utils_Select.js.html new file mode 100644 index 000000000..785a123e3 --- /dev/null +++ b/doc/utils_Select.js.html @@ -0,0 +1,363 @@ + + + + + JSDoc: Source: utils/Select.js + + + + + + + + + + +
+ +

Source: utils/Select.js

+ + + + + + +
+
+
/** @module utils/Select */
+
+import {
+  unselect, getStaffBBox, selectStaff, selectAll, getSelectionType
+} from './SelectTools.js';
+import { Resize } from './Resize.js';
+
+const d3 = require('d3');
+const $ = require('jquery');
+
+var dragHandler, neonView, info, zoomHandler;
+var strokeWidth = 7;
+
+export function setSelectStrokeWidth (width) {
+  strokeWidth = width;
+}
+
+/**
+ * Set the objects for this module.
+ * @param {NeonView} nv - The NeonView object
+ * @param {DragHandler} dh - The drag handler object
+ */
+export function setSelectHelperObjects (nv, dh) {
+  dragHandler = dh;
+  neonView = nv;
+  info = neonView.info;
+  zoomHandler = neonView.view.zoomHandler;
+}
+
+/**
+ * Apply listeners for click selection.
+ * @param {string} selector - The CSS selector used to choose where listeners are applied.
+ */
+export function clickSelect (selector) {
+  $(selector).off('mousedown', clickHandler);
+  $(selector).on('mousedown', clickHandler);
+
+  // Click away listeners
+  $('body').on('keydown', (evt) => {
+    if (evt.key === 'Escape') {
+      if ($('.selected').length > 0) {
+        info.infoListeners();
+      }
+      unselect();
+    }
+  });
+
+  $('#container').on('contextmenu', (evt) => { evt.preventDefault(); });
+
+  $('use').on('click', (e) => { e.stopPropagation(); });
+  $('rect').on('click', (e) => { e.stopPropagation(); });
+  $('#moreEdit').on('click', (e) => { e.stopPropagation(); });
+}
+
+/**
+ * Handle click events related to element selection.
+ * @param {object} evt
+ */
+function clickHandler (evt) {
+  if (!neonView) return;
+  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' && getSelectionType() !== 'selByBBox') {
+    if ($(this).parents('.selected').length === 0) {
+      let selection = [this];
+      if (window.navigator.userAgent.match(/Mac/) ? evt.metaKey : evt.ctrlKey) {
+        selection = selection.concat(Array.from(document.getElementsByClassName('selected')));
+      }
+      selectAll(selection, neonView, info, dragHandler);
+      if (dragHandler) {
+        dragHandler.dragInit();
+      }
+    }
+  } else if (evt.target.tagName === 'rect' && getSelectionType() === 'selByBBox') {
+    if ($(this).parents('.selected').length === 0) {
+      let selection = [evt.target];
+      if (window.navigator.userAgent.match(/Mac/) ? evt.metaKey : evt.ctrlKey) {
+        selection = selection.concat(Array.from(document.getElementsByClassName('selected')));
+      }
+      selectAll(selection, neonView, info, dragHandler);
+      if (dragHandler) {
+        dragHandler.dragInit();
+      }
+    }
+  } 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('active-page')[0].getElementsByClassName('definition-scale')[0];
+    let pt = container.createSVGPoint();
+    pt.x = evt.clientX;
+    pt.y = evt.clientY;
+    let transformMatrix = container.getElementsByClassName('system')[0].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, dragHandler);
+      let resize = new Resize(staff.id, neonView, dragHandler);
+      resize.drawInitialRect();
+      if (dragHandler) {
+        dragHandler.dragInit();
+      }
+    }
+    // Trigger mousedown event on the staff
+    staff.dispatchEvent(new window.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
+    }));
+  }
+}
+
+/**
+ * Apply listeners for drag selection.
+ * @param {string} selector - The CSS selector used to choose where listeners are applied.
+ */
+export function dragSelect (selector) {
+  var initialX = 0;
+  var initialY = 0;
+  var panning = false;
+  var dragSelecting = false;
+  // var canvas = d3.select('#svg_group');
+  d3.selectAll(selector.replace('.active-page', '').trim())
+    .on('.drag', null);
+  var canvas = d3.select(selector);
+  var dragSelectAction = d3.drag()
+    .on('start', selStart)
+    .on('drag', selecting)
+    .on('end', selEnd);
+  canvas.call(dragSelectAction);
+  if (dragHandler) {
+    dragHandler.resetTo(dragSelectAction);
+  }
+
+  function selStart () {
+    if (!neonView) return;
+    let userMode = neonView.getUserMode();
+    if (d3.event.sourceEvent.target.nodeName !== 'use' && userMode !== 'insert' && d3.event.sourceEvent.target.nodeName !== 'rect') {
+      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;
+          let initialP = d3.mouse(this);
+          initialX = initialP[0];
+          initialY = initialP[1];
+          initRect(initialX, initialY);
+        }
+      } else {
+        panning = true;
+        if (zoomHandler !== undefined) {
+          zoomHandler.startDrag();
+        }
+      }
+    } else if (d3.event.sourceEvent.shiftKey) {
+      panning = true;
+      if (zoomHandler !== undefined) {
+        zoomHandler.startDrag();
+      }
+    }
+  }
+
+  /**
+   * Check if a point is in the bounds of a staff element.
+   * @param {SVGPoint} point
+   * @returns {boolean}
+   */
+  function pointNotInStaff (point) {
+    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);
+    });
+    return (filtered.length === 0);
+  }
+
+  function selecting () {
+    if (!panning && dragSelecting) {
+      var currentPt = d3.mouse(this);
+      var curX = currentPt[0];
+      var curY = currentPt[1];
+
+      var newX = curX < initialX ? curX : initialX;
+      var newY = curY < initialY ? curY : initialY;
+      var width = curX < initialX ? initialX - curX : curX - initialX;
+      var height = curY < initialY ? initialY - curY : curY - initialY;
+
+      updateRect(newX, newY, width, height);
+    } else if (panning) {
+      if (zoomHandler !== undefined) {
+        zoomHandler.dragging();
+      }
+    }
+  }
+
+  function selEnd () {
+    if (!panning && dragSelecting) {
+      var rx = parseInt($('#selectRect').attr('x'));
+      var ry = parseInt($('#selectRect').attr('y'));
+      var lx = parseInt($('#selectRect').attr('x')) + parseInt($('#selectRect').attr('width'));
+      var ly = parseInt($('#selectRect').attr('y')) + parseInt($('#selectRect').attr('height'));
+      // Transform to the correct coordinate system
+      let ul = canvas.node().createSVGPoint();
+      ul.x = rx;
+      ul.y = ry;
+      let lr = canvas.node().createSVGPoint();
+      lr.x = lx;
+      lr.y = ly;
+      let transform = canvas.node().getScreenCTM().inverse().multiply(canvas.select('.system').node().getScreenCTM()).inverse();
+      ul = ul.matrixTransform(transform);
+      lr = lr.matrixTransform(transform);
+
+      var nc;
+      if ($('#selByStaff').hasClass('is-active')) {
+        nc = d3.selectAll(selector + ' use, ' + selector + ' .staff')._groups[0];
+      } else if ($('#selByBBox').hasClass('is-active')) {
+        nc = d3.selectAll(selector + ' .sylTextRect-display')._groups[0];
+      } else {
+        nc = d3.selectAll(selector + ' use')._groups[0];
+      }
+      var els = Array.from(nc);
+
+      var elements = els.filter(function (d) {
+        var ulx, uly, lrx, lry;
+        if ($('#selByBBox').hasClass('is-active')) {
+          ulx = Number($(d).attr('x'));
+          uly = Number($(d).attr('y'));
+          lrx = +ulx + +(d.getAttribute('width').slice(0, -2));
+          lry = +uly + +(d.getAttribute('height').slice(0, -2));
+          return !(((ul.x < ulx && lr.x < ulx) || (ul.x > lrx && lr.x > lrx)) || ((ul.y < uly && lr.y < uly) || (ul.y > lry && lr.y > lry)));
+        } else if (d.tagName === 'use') {
+          let box = d.parentNode.getBBox();
+          ulx = box.x;
+          uly = box.y;
+          lrx = box.x + box.width;
+          lry = box.y + box.height;
+          return !(((ul.x < ulx && lr.x < ulx) || (ul.x > lrx && lr.x > lrx)) || ((ul.y < uly && lr.y < uly) || (ul.y > lry && lr.y > lry)));
+        } else {
+          let box = getStaffBBox(d);
+          return !(((ul.x < box.ulx && lr.x < box.ulx) || (ul.x > box.lrx && lr.x > box.lrx)) || ((ul.y < box.uly && lr.y < box.uly) || (ul.y > box.lry && lr.y > box.lry)));
+        }
+      });
+      selectAll(elements, neonView, info, dragHandler);
+
+      if (dragHandler) {
+        dragHandler.dragInit();
+      }
+      d3.selectAll('#selectRect').remove();
+      dragSelecting = false;
+    }
+    panning = false;
+  }
+
+  /**
+     * Create an initial dragging rectangle.
+     * @param {number} ulx - The upper left x-position of the new rectangle.
+     * @param {number} uly - The upper left y-position of the new rectangle.
+     */
+  function initRect (ulx, uly) {
+    canvas.append('rect')
+      .attr('x', ulx)
+      .attr('y', uly)
+      .attr('width', 0)
+      .attr('height', 0)
+      .attr('id', 'selectRect')
+      .attr('stroke', 'black')
+      .attr('stroke-width', strokeWidth)
+      .attr('fill', 'none');
+  }
+
+  /**
+     * Update the dragging rectangle.
+     * @param {number} newX - The new ulx.
+     * @param {number} newY - The new uly.
+     * @param {number} currentWidth - The width of the rectangle in pixels.
+     * @param {number} currentHeight - The height of the rectangle in pixels.
+     */
+  function updateRect (newX, newY, currentWidth, currentHeight) {
+    d3.select('#selectRect')
+      .attr('x', newX)
+      .attr('y', newY)
+      .attr('width', currentWidth)
+      .attr('height', currentHeight);
+  }
+}
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:07 GMT-0400 (GMT-04:00) +
+ + + + + diff --git a/doc/utils_SelectTools.js.html b/doc/utils_SelectTools.js.html new file mode 100644 index 000000000..cd0b3fbed --- /dev/null +++ b/doc/utils_SelectTools.js.html @@ -0,0 +1,499 @@ + + + + + JSDoc: Source: utils/SelectTools.js + + + + + + + + + + +
+ +

Source: utils/SelectTools.js

+ + + + + + +
+
+
/** @module utils/SelectTools */
+
+import * as Color from './Color.js';
+import { updateHighlight } from '../DisplayPanel/DisplayControls.js';
+import * as Grouping from '../SquareEdit/Grouping.js';
+import { Resize } from './Resize.js';
+import * as SelectOptions from '../SquareEdit/SelectOptions.js';
+
+const d3 = require('d3');
+const $ = require('jquery');
+
+/**
+ * Get the selection mode chosen by the user.
+ * @returns {string|null}
+ */
+export function getSelectionType () {
+  let element = document.getElementsByClassName('sel-by is-active');
+  if (element.length !== 0) {
+    return element[0].id;
+  } else {
+    return null;
+  }
+}
+
+/**
+ * Unselect all selected elements and run undo any extra
+ * actions.
+ */
+export function unselect () {
+  var selected = $('.selected');
+  for (var i = 0; i < selected.length; i++) {
+    if ($(selected[i]).hasClass('staff')) {
+      $(selected[i]).removeClass('selected');
+      selected[i].removeAttribute('style');
+      Color.unhighlight(selected[i]);
+    } else {
+      $(selected[i]).removeClass('selected');
+      selected[i].removeAttribute('style');
+      $(selected[i]).attr('fill', 'null');
+      $(selected[i]).removeClass('selected');
+    }
+  }
+  let sylRects = $('.sylTextRect-display');
+  sylRects.css('fill', 'blue');
+
+  $('.syllable-highlighted').css('fill', '');
+  $('.syllable-highlighted').addClass('syllable');
+  $('.syllable-highlighted').removeClass('syllable-highlighted');
+
+  d3.selectAll('#resizeRect').remove();
+
+  if (!$('#selByStaff').hasClass('is-active')) {
+    Grouping.endGroupingSelection();
+  } else {
+    SelectOptions.endOptionsSelection();
+  }
+  updateHighlight();
+}
+
+/**
+ * Generic select function.
+ * @param {SVGGraphicsElement} el
+ * @param {DragHandler} [dragHandler]
+ */
+export function select (el, dragHandler) {
+  if (!$(el).hasClass('selected') && !($(el).hasClass('sylTextRect')) && !($(el).hasClass('sylTextRect-display'))) {
+    $(el).addClass('selected');
+    $(el).css('fill', '#d00');
+    if ($(el).find('.sylTextRect-display').length) {
+      $(el).find('.sylTextRect-display').css('fill', 'red');
+    }
+  }
+  updateHighlight();
+}
+
+/**
+ * Select an nc.
+ * @param {SVGGraphicsElement} el - The nc element to select.
+ * @param {DragHandler} dragHandler - An instantiated DragHandler.
+ * @param {NeonView} neonView - The NeonView parent
+ */
+export async function selectNcs (el, neonView, dragHandler) {
+  if (!$(el).parent().hasClass('selected')) {
+    var parent = el.parentNode;
+    unselect();
+    select(parent);
+    if (await isLigature(parent, neonView)) {
+      var prevNc = $(parent).prev()[0];
+      if (await isLigature(prevNc, neonView)) {
+        select(prevNc);
+      } else {
+        var nextNc = $(parent).next()[0];
+        if (await isLigature(nextNc, neonView)) {
+          select(nextNc);
+        } else {
+          console.warn('Error: Neither prev or next nc are ligatures');
+        }
+      }
+      Grouping.triggerGrouping('ligature');
+    } else if ($(parent).hasClass('nc')) {
+      SelectOptions.triggerNcActions(parent);
+    } else {
+      console.warn('No action triggered!');
+    }
+    dragHandler.dragInit();
+  }
+}
+
+/**
+ * Check if neume component is part of a ligature
+ * @param {SVGGraphicsElement} nc - The neume component to check.
+ * @returns {boolean}
+ */
+export async function isLigature (nc, neonView) {
+  var attributes = await neonView.getElementAttr(nc.id, neonView.view.getCurrentPageURI());
+  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.
+ */
+export function sharedSecondLevelParent (elements) {
+  let tempElements = Array.from(elements);
+  let firstElement = tempElements.pop();
+  let secondParent = firstElement.parentElement.parentElement;
+  for (let element of tempElements) {
+    let secPar = element.parentElement.parentElement;
+    if (secPar.id !== secondParent.id) {
+      return false;
+    }
+  }
+  return true;
+}
+
+/**
+ * Get the bounding box of a staff based on its staff lines.
+ * @param {SVGGElement} staff
+ * @returns {object}
+ */
+export 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 a boundingbox element
+ * @param {SVGGElement} el - the bbox (sylTextRect) element in the DOM
+ * @param {DragHandler} dragHandler - the drag handler in use
+ */
+export function selectBBox (el, dragHandler, resizeHandler) {
+  let bbox = $(el);
+  let syl = bbox.closest('.syl');
+  if (!syl.hasClass('selected')) {
+    syl.addClass('selected');
+    bbox.css('fill', '#d00');
+    $(el).parents('.syllable').css('fill', 'red');
+    $(el).parents('.syllable').addClass('syllable-highlighted');
+    if (resizeHandler !== undefined) {
+      resizeHandler.drawInitialRect();
+    }
+    if (dragHandler !== undefined) {
+      dragHandler.dragInit();
+    }
+  }
+}
+
+/**
+ * Select not neume elements.
+ * @param {SVGGraphicsElement[]} notNeumes - An array of not neumes elements.
+ */
+export function selectNn (notNeumes) {
+  if (notNeumes.length > 0) {
+    notNeumes.forEach(nn => { select(nn); });
+    return false;
+  } else {
+    return true;
+  }
+}
+
+/**
+ * Select a staff element.
+ * @param {SVGGElement} el - The staff element in the DOM.
+ * @param {DragHandler} dragHandler - The drag handler in use.
+ */
+export function selectStaff (el, dragHandler) {
+  let staff = $(el);
+  if (!staff.hasClass('selected')) {
+    staff.addClass('selected');
+    updateHighlight();
+    Color.highlight(el, '#d00');
+    dragHandler.dragInit();
+  }
+}
+
+/**
+ * Handle selecting an array of elements based on the selection type.
+ * @param {SVGGraphicsElement[]} elements - The elements to select. Either <g> or <use>.
+ * @param {NeonView} neonView
+ * @param {InfoModule} info
+ * @param {DragHandler} dragHandler
+ */
+export async function selectAll (elements, neonView, info, dragHandler) {
+  let selectionType = getSelectionType();
+  unselect();
+  if (elements.length === 0) {
+    return;
+  }
+
+  let selectionClass;
+  let containsClefOrCustos = false;
+
+  switch (selectionType) {
+    case 'selBySyl':
+      selectionClass = '.syllable';
+      break;
+    case 'selByNeume':
+      selectionClass = '.neume';
+      break;
+    case 'selByNc':
+      selectionClass = '.nc';
+      break;
+    case 'selByStaff':
+      selectionClass = '.staff';
+      break;
+    case 'selByBBox':
+      selectionClass = '.sylTextRect-display';
+      break;
+    default:
+      console.error('Unknown selection type ' + selectionType);
+      return;
+  }
+
+  // Get the groupings specified by selectionClass
+  // that contain the provided elements to select.
+  let groupsToSelect = new Set();
+  for (let element of elements) {
+    let grouping = element.closest(selectionClass);
+    if (grouping === null) {
+      // Check if we click-selected a clef or a custos
+      grouping = element.closest('.clef, .custos');
+      if (grouping === null) {
+        console.warn('Element ' + element.id + ' is not part of specified group and is not a clef or custos.');
+        continue;
+      }
+      containsClefOrCustos |= true;
+    }
+    groupsToSelect.add(grouping);
+
+    // Check for precedes/follows
+    let follows = grouping.getAttribute('mei:follows');
+    if (follows) {
+      groupsToSelect.add(document.getElementById(follows));
+    }
+    let precedes = grouping.getAttribute('mei:precedes');
+    if (precedes) {
+      groupsToSelect.add(document.getElementById(precedes));
+    }
+  }
+
+  // Select the elements
+  groupsToSelect.forEach(group => { select(group, dragHandler); });
+
+  /* Determine the context menu to display (if any) */
+
+  let groups = Array.from(groupsToSelect.values());
+
+  // Handle occurance of clef or custos
+  if (containsClefOrCustos) {
+    // A context menu will only be displayed if there is a single clef
+    if (groupsToSelect.size === 1 && groups[0].classList.contains('clef')) {
+      SelectOptions.triggerClefActions(groups[0]);
+    } else {
+      SelectOptions.triggerDefaultActions();
+    }
+    return;
+  }
+
+  switch (selectionType) {
+    case 'selByStaff':
+      switch (groups.length) {
+        case 1:
+          SelectOptions.triggerSplitActions();
+          let resize = new Resize(groups[0].id, neonView, dragHandler);
+          resize.drawInitialRect();
+          break;
+        case 2:
+          let bb1 = getStaffBBox(groups[0]);
+          let bb2 = getStaffBBox(groups[1]);
+          let avgStaffHeight = (bb1.lry - bb1.uly + bb2.lry - bb2.uly) / 2;
+          if (Math.abs(bb1.uly - bb2.uly) < avgStaffHeight) {
+            SelectOptions.triggerStaffActions();
+          } else {
+            SelectOptions.triggerDefaultActions();
+          }
+          break;
+        default:
+          SelectOptions.triggerDefaultActions();
+      }
+      break;
+
+    case 'selBySyl':
+      switch (groups.length) {
+        case 1:
+          // TODO change context if it is only a neume/nc.
+          SelectOptions.triggerSylActions();
+          break;
+        case 2:
+          // Check if this is a linked syllable split by a staff break
+          if ((groups[0].getAttribute('mei:follows') === groups[1].id) || (groups[0].getAttribute('mei:precedes') === groups[1].id)) {
+            Grouping.triggerGrouping('splitSyllable');
+          } else if (sharedSecondLevelParent(groups)) {
+            Grouping.triggerGrouping('syl');
+          } else {
+            // Check if this *could* be a selection with a single logical syllable split by a staff break.
+            let staff0 = groups[0].closest('.staff');
+            let staff1 = groups[1].closest('.staff');
+            let staffChildren = Array.from(staff0.parentNode.children);
+            // Check if these are adjacent staves (logically)
+            if (Math.abs(staffChildren.indexOf(staff0) - staffChildren.indexOf(staff1)) === 1) {
+              // Check if one syllable is the last in the first staff and the other is the first in the second.
+              // Determine which staff is first.
+              let firstStaff = (staffChildren.indexOf(staff0) < staffChildren.indexOf(staff1)) ? staff0 : staff1;
+              let secondStaff = (firstStaff.id === staff0.id) ? staff1 : staff0;
+              let firstLayer = firstStaff.querySelector('.layer');
+              let secondLayer = secondStaff.querySelector('.layer');
+
+              // Check that the first staff has either syllable as the last syllable
+              let firstSyllableChildren = Array.from(firstLayer.children).filter(elem => elem.classList.contains('syllable'));
+              let secondSyllableChildren = Array.from(secondLayer.children).filter(elem => elem.classList.contains('syllable'));
+              let lastSyllable = firstSyllableChildren[firstSyllableChildren.length - 1];
+              let firstSyllable = secondSyllableChildren[0];
+              if (lastSyllable.id === groups[0].id && firstSyllable.id === groups[1].id) {
+                Grouping.triggerGrouping('splitSyllable');
+                break;
+              } else if (lastSyllable.id === groups[1].id && firstSyllable.id === groups[0].id) {
+                Grouping.triggerGrouping('splitSyllable');
+                break;
+              }
+            }
+            SelectOptions.triggerDefaultActions();
+          }
+          break;
+        default:
+          if (sharedSecondLevelParent(groups)) {
+            Grouping.triggerGrouping('syl');
+          } else {
+            SelectOptions.triggerDefaultActions();
+          }
+      }
+      break;
+
+    case 'selByNeume':
+      switch (groups.length) {
+        case 1:
+          // TODO change context if it is only a nc.
+          SelectOptions.triggerNeumeActions();
+          break;
+        default:
+          if (sharedSecondLevelParent(groups)) {
+            Grouping.triggerGrouping('neume');
+          } else {
+            SelectOptions.triggerDefaultActions();
+          }
+      }
+      break;
+
+    case 'selByNc':
+      switch (groups.length) {
+        case 1:
+          SelectOptions.triggerNcActions(groups[0]);
+          break;
+        case 2:
+          if (sharedSecondLevelParent(groups)) {
+            // Check if this selection is a ligature or can be a ligature
+            // Check if these neume components are part of the same neume
+            if (groups[0].parentNode === groups[1].parentNode) {
+              let children = Array.from(groups[0].parentNode.children);
+              // Check that neume components are adjacent
+              if (Math.abs(children.indexOf(groups[0]) - children.indexOf(groups[1])) === 1) {
+                // Check that second neume component is lower than first.
+                // Note that the order in the list may not be the same as the
+                // order by x-position.
+                let orderFirstX = groups[0].children[0].x.baseVal.value;
+                let orderSecondX = groups[1].children[0].x.baseVal.value;
+                let posFirstY, posSecondY;
+
+                if (orderFirstX < orderSecondX) {
+                  posFirstY = groups[0].children[0].y.baseVal.value;
+                  posSecondY = groups[1].children[0].y.baseVal.value;
+                } else {
+                  posFirstY = groups[1].children[0].y.baseVal.value;
+                  posSecondY = groups[0].children[0].y.baseVal.value;
+                }
+
+                // Also ensure both components are marked or not marked as ligatures.
+                let isFirstLigature = await isLigature(groups[0], neonView);
+                let isSecondLigature = await isLigature(groups[1], neonView);
+                if ((posSecondY > posFirstY) && !(isFirstLigature ^ isSecondLigature)) {
+                  Grouping.triggerGrouping('ligature');
+                  break;
+                }
+              }
+            }
+            Grouping.triggerGrouping('nc');
+          } else {
+            SelectOptions.triggerDefaultActions();
+          }
+          break;
+        default:
+          if (sharedSecondLevelParent(groups)) {
+            Grouping.triggerGrouping('nc');
+          } else {
+            SelectOptions.triggerDefaultActions();
+          }
+      }
+      break;
+    case 'selByBBox':
+      switch (groups.length) {
+        case 1:
+          let resize = new Resize(groups[0].closest('.syl').id, neonView, dragHandler);
+          selectBBox(groups[0], dragHandler, resize);
+          break;
+        default:
+          groups.forEach(g => selectBBox(g, dragHandler, undefined));
+          break;
+      }
+      break;
+    default:
+      console.error('Unknown selection type. This should not have occurred.');
+  }
+}
+
+
+
+ + + + +
+ + + +
+ +
+ Documentation generated by JSDoc 3.6.2 on Mon Jul 15 2019 09:24:07 GMT-0400 (GMT-04:00) +
+ + + + + diff --git a/package.json b/package.json index d9c1db6d1..57b0fbc71 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "Neon", - "version": "3.1.1", + "version": "4.0.0", "description": "A web-based editor for correcting MEI-Neume files", "main": "server.js", "license": "MIT", @@ -34,15 +34,16 @@ "start": "nodemon server.js", "build": "./setup-verovio && webpack --config webpack.config.js", "test": "jest --silent", - "doc": "jsdoc -d ./doc/ ./src/ ./src/SingleEdit/ ./src/SingleView/ ./src/utils/ -R ./README.md", - "pages": "./setup-verovio && webpack --config webpack.pages-config.js && cp public/verovio-toolkit.js dist/verovio-toolkit.js" + "doc": "jsdoc -d ./doc/ ./src/ ./src/DisplayPanel/ ./src/SingleView/ ./src/SquareEdit/ ./src/utils/ -R ./README.md", + "pages": "./setup-verovio && webpack --config webpack.pages-config.js && cp -R public/verovio-toolkit.js public/diva-v6.0.1 pages/manifests dist/ && cp pages/static/* dist/" }, "jest": { "testPathIgnorePatterns": [ "/node_modules/" ], "moduleNameMapper": { - "Validation": "/test/SubstituteValidation.js" + "Validation": "/test/SubstituteValidation.js", + "VerovioWrapper": "/test/VerovioWrapper.js" } }, "babel": { @@ -64,10 +65,13 @@ "bulma-slider": "^2.0.0", "css-loader": "^0.28.11", "d3": "^5.5.0", + "elementtree": "^0.1.7", "file-loader": "^1.1.11", + "html-loader": "^0.5.5", "jest": "^23.1.0", "jquery": "^3.4.1", "jsdoc": "^3.5.5", + "jsonschema": "^1.2.4", "pouchdb": "^7.0.0", "raw-loader": "^2.0.0", "selenium-webdriver": "^4.0.0-alpha.1", diff --git a/pages/.gitignore b/pages/.gitignore index 2f95795e2..edacacd47 100644 --- a/pages/.gitignore +++ b/pages/.gitignore @@ -1,3 +1,4 @@ *.mei *.png index.js +manifests/ diff --git a/pages/editor.js b/pages/editor.js index 5ada602d1..a9a5cafb1 100644 --- a/pages/editor.js +++ b/pages/editor.js @@ -1,122 +1,88 @@ import NeonView from '../src/NeonView.js'; import DisplayPanel from '../src/DisplayPanel/DisplayPanel.js'; -import DivaView from '../src/DivaView/DivaView.js'; +import DivaView from '../src/DivaView.js'; import SingleView from '../src/SingleView/SingleView.js'; -import SingleEditMode from '../src/SingleEdit/SingleEditMode.js'; +import DivaEdit from '../src/SquareEdit/DivaEditMode.js'; +import SingleEditMode from '../src/SquareEdit/SingleEditMode.js'; import InfoModule from '../src/InfoModule.js'; import TextView from '../src/TextView.js'; +import TextEditMode from '../src/TextEditMode.js'; import PouchDb from 'pouchdb'; -const $ = require('jquery'); -let mei = getGetParam('page'); -let mode = getGetParam('mode'); -let map = new Map(); +let name = getGetParam('manifest'); +let manifestLocation = 'manifests/' + name + '.jsonld'; +let storage = getGetParam('storage'); -// Since in local mode there are no GET parameters, mei will be null -if (mode === 'demo-page') { - console.log('Demo page'); - $.get('./mei/' + mei + '.mei', (data) => { - map.set(0, data); +if (name) { + window.fetch(manifestLocation).then(response => { + if (response.ok) { + return response.text(); + } else { + throw new Error(response.statusText); + } + }).then(async text => { + let manifest = JSON.parse(text); let params = { - mode: 'single', - options: { - image: './img/' + mei + '.png', - meiMap: map, - name: mei - }, - View: SingleView, + manifest: manifest, Display: DisplayPanel, Info: InfoModule, - Edit: SingleEditMode, - TextView: TextView + TextView: TextView, + TextEdit: TextEditMode }; + // Determine if it is a single page or multiple by media type + let mediaType = await new Promise((resolve, reject) => { + window.fetch(manifest.image).then(response => { + resolve(response.headers.get('Content-Type')); + }).catch(err => { + reject(err); + }); + }); + if (mediaType.match(/image\/*/)) { + params.View = SingleView; + params.NeumeEdit = SingleEditMode; + } else { + params.View = DivaView; + params.NeumeEdit = DivaEdit; + } var view = new NeonView(params); view.start(); }); -} else if (mode === 'demo-iiif') { - console.log('IIIF'); - let params = { - mode: 'iiif', - options: { - manifest: 'https://images.simssa.ca/iiif/manuscripts/cdn-hsmu-m2149l4/manifest.json' - }, - View: DivaView, - Display: DisplayPanel, - Info: InfoModule, - TextView: TextView - }; - if (mei === 'Salzinnes') { - console.log('Salzinnes'); - $.get('https://images.simssa.ca/iiif/manuscripts/cdn-hsmu-m2149l4/manifest.json').then((manifest) => { - params.options.name = manifest.label; - console.log(manifest); - return $.get('./mei/CF-017.mei'); - }).then((data) => { - console.log(18); - map.set(18, data); - return $.get('./mei/CF-018.mei'); - }).then((data) => { - console.log(20); - map.set(20, data); - return $.get('./mei/CF-019.mei'); - }).then((data) => { - map.set(22, data); - console.log(22); - params.options.meiMap = map; - var view = new NeonView(params); - view.start(); +} else { + let db = new PouchDb('Neon-User-Storage'); + db.getAttachment(storage, 'manifest').then(blob => { + return new window.Response(blob).json(); + }).then(async manifest => { + console.log(manifest); + let params = { + manifest: manifest, + Display: DisplayPanel, + Info: InfoModule, + TextView: TextView, + TextEdit: TextEditMode + }; + + let mediaType = await new Promise((resolve, reject) => { + window.fetch(manifest.image).then(response => { + resolve(response.headers.get('Content-Type')); + }).catch(err => { + reject(err); + }); }); - } -} else if (mode === 'user-page') { - let db = new PouchDb('neon-temporary'); - let params = { - mode: 'single', - options: { - name: 'User MEI' - }, - View: SingleView, - Edit: SingleEditMode, - Display: DisplayPanel, - Info: InfoModule, - TextView: TextView - }; - db.get('mei').then((doc) => { - map.set(0, doc.data); - params.options.meiMap = map; - return db.get('img'); - }).then((doc) => { - params.options.image = doc.data; + if (mediaType.match(/image\/*/)) { + params.View = SingleView; + params.NeumeEdit = SingleEditMode; + } else { + params.View = DivaView; + params.NeumeEdit = DivaEdit; + } + var view = new NeonView(params); view.start(); - }).catch((err) => { - console.error(err); }); -} else { - console.log('None of the above'); } -/* -var view; -if (mei === null) { - view = new NeonView({ - meifile: '', - bgimg: '', - mode: 'local', - raw: 'true' - }); -} else { - view = new NeonView({ - meifile: './mei/' + mei + '.mei', - bgimg: './img/' + mei + '.png', - mode: 'pages' - }); -} - -view.start(); -*/ - function getGetParam (paramName) { let result = null; diff --git a/pages/generate-index b/pages/generate-index index a0864835e..6b3645613 100755 --- a/pages/generate-index +++ b/pages/generate-index @@ -1,19 +1,24 @@ #!/bin/bash +context="[\"http://www.w3.org/ns/anno.jsonld\",{\"schema\":\"http://schema.org/\",\"title\":\"schema:name\",\"timestamp\":\"schema:dateModified\",\"image\":{\"@id\":\"schema:image\",\"@type\":\"@id\"},\"mei_annotations\":{\"@id\":\"Annotation\",\"@type\":\"@id\",\"@container\":\"@list\"}}]" imports="" json="" doc="/**\n * Pages that can be loaded.\n * @type {{name: string, mei: string, img: string}[]}\n */" +mkdir -p manifests for file in *.mei; do filename=`basename "$file" .mei` base=`echo $filename | tr - _` mei="${base}_MEI" background="${base}_PNG" + background_file="${filename}.png" imports="${imports}import $mei from \"./$file\";\nimport $background from \"./${filename}.png\";\n" - json="${json} \"$filename\",\n" + json="${json}\n \"$filename\"," + id="https://ddmal.github.io/Neon/manifests/${filename}.jsonld" + echo "{\"@context\":$context,\"@id\":\"$id\",\"title\":\"$base\",\"image\":\"img/$background_file\",\"timestamp\":\"`date +%Y-%m-%dT%H:%M:%S%z`\",\"mei_annotations\":[{\"id\":\"`uuidgen`\",\"type\":\"Annotation\",\"body\":\"mei/$file\",\"target\":\"img/$background_file\"}]}" > manifests/$filename.jsonld done printf "${imports}\n" > index.js printf "${doc}\n" >> index.js -printf "const selectionOptions = [\n" >> index.js +printf "const selectionOptions = [" >> index.js printf "${json}" >> index.js printf "];\n" >> index.js cat index-end.js >> index.js diff --git a/pages/static/editor.html b/pages/static/editor.html new file mode 100644 index 000000000..8c2ffb4ec --- /dev/null +++ b/pages/static/editor.html @@ -0,0 +1,29 @@ + + + + Neon + + + + + + + + + + + + + diff --git a/pages/static/index.html b/pages/static/index.html new file mode 100644 index 000000000..a76044d19 --- /dev/null +++ b/pages/static/index.html @@ -0,0 +1,84 @@ + + + + Neon + + + + +
+
+ +
+ +
+
+
+ +
+
+ +
+
+ +
+ +
+
+
+ +
+
+ +
+
+ +
+ +
+
+
+ + +
+
+ +
+
+ +
+ +
+
+
+ +
+
+ + + + + + + diff --git a/pages/static/storage.js b/pages/static/storage.js new file mode 100644 index 000000000..b3f67e4fa --- /dev/null +++ b/pages/static/storage.js @@ -0,0 +1,89 @@ +var db = new PouchDB('Neon-User-Storage'); + +getAllDocuments().then(response => { + let storage = document.getElementById('storage-selector'); + for (let doc of response.rows) { + let option = document.createElement('option'); + option.setAttribute('value', doc.id); + option.textContent = doc.id; + storage.appendChild(option); + } +}).catch(err => { + console.error(err); +}); + +document.getElementById('add-storage-form').onsubmit = (evt) => { + evt.preventDefault(); + if (evt.target.checkValidity()) { + let file = document.getElementById('upload-manifest').files[0]; + console.log(file); + addEntry(file.name, file).then(response => { + window.location.reload(); + }).catch(err => { + console.error(err); + }); + } +}; + +document.getElementById('remove-button').onclick = (evt) => { + let form = document.getElementById('user-form'); + if (form.checkValidity()) { + let selectedIndex = document.getElementById('storage-selector').selectedIndex; + if (selectedIndex >= 0) { + let option = document.getElementById('storage-selector')[selectedIndex]; + deleteEntry(option.value).then(response => { + window.location.reload(); + }).catch(err => { + console.error(err); + }); + } + } +}; + +function getAllDocuments () { + return new Promise((resolve, reject) => { + db.allDocs().then(result => { resolve(result); }) + .catch(err => { reject(err); }); + }); +} + +/** + * @param {string} title + * @param {Blob} content + * @returns {Promise} + */ +function addEntry (title, content) { + return new Promise((resolve, reject) => { + db.put({ + _id: title, + _attachments: { + manifest: { + content_type: 'application/ld+json', + data: content + } + } + }).then(response => { + resolve(true); + }).catch(err => { + reject(err); + }); + }); +} + +/** + * @param {string} id + * @returns {Promise} + */ +function deleteEntry (id) { + return new Promise((resolve, reject) => { + db.get(id).then(doc => { + db.remove(doc).then(response => { + resolve(true); + }).catch(err => { + reject(err); + }); + }).catch(err => { + reject(err); + }); + }); +} diff --git a/public/uploads/img/.gitignore b/public/uploads/img/.gitignore new file mode 100644 index 000000000..72e8ffc0d --- /dev/null +++ b/public/uploads/img/.gitignore @@ -0,0 +1 @@ +* diff --git a/public/uploads/manifests/.gitignore b/public/uploads/manifests/.gitignore new file mode 100644 index 000000000..72e8ffc0d --- /dev/null +++ b/public/uploads/manifests/.gitignore @@ -0,0 +1 @@ +* diff --git a/public/uploads/png/.gitignore b/public/uploads/png/.gitignore deleted file mode 100644 index 73c050709..000000000 --- a/public/uploads/png/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Mainly exists so git will track this folder -# Excluding png so we don't accidentally push these resources -*.png diff --git a/server.js b/server.js index 82e724d16..85cdcdc52 100644 --- a/server.js +++ b/server.js @@ -6,11 +6,26 @@ var bodyParser = require('body-parser'); global.__base = __dirname + '/'; +//=============================== +// MEI Middleware +//=============================== +var handleMEI = function (req, res, next) { + if (req.is('application/xml') || req.is('application/mei+xml')) { + req.setEncoding('utf8'); + req.body.mei = ''; + req.on('data', (data) => { req.body.mei += data; }); + req.on('end', () => { next(); }); + } else { + next(); + } +}; + //=========== // Bodyparser //=========== app.use(bodyParser.json({ limit: '50mb' })); app.use(bodyParser.urlencoded({ limit: '50mb', extended: false })); +app.use(handleMEI); //===================== // Route import & setup diff --git a/server/routes/index.js b/server/routes/index.js index 245442f91..1b697a4ba 100644 --- a/server/routes/index.js +++ b/server/routes/index.js @@ -4,17 +4,26 @@ var fs = require('fs-extra'); var multer = require('multer'); const request = require('request'); const path = require('path'); +const uuidv4 = require('uuid/v4'); var router = express.Router(); const __base = ''; +const manifestUpload = path.join(__base, 'public', 'uploads', 'manifests'); const meiUpload = path.join(__base, 'public', 'uploads', 'mei'); -const pngUpload = path.join(__base, 'public', 'uploads', 'png'); +const imgUpload = path.join(__base, 'public', 'uploads', 'img'); const iiifUpload = path.join(__base, 'public', 'uploads', 'iiif'); +const iiifPublicPath = path.join('/', 'uploads', 'iiif'); +const neonContext = JSON.parse(fs.readFileSync(path.join(__base, 'src', 'utils', 'manifest', 'context.json')).toString()); const allowedPattern = /^[-_\.,\d\w ]+$/; const consequtivePeriods = /\.{2,}/; +var upload = multer({ + storage: multer.memoryStorage(), + limits: { filesize: 100000 } +}); + function isUserInputValid (input) { return (input.match(allowedPattern) && !input.match(consequtivePeriods)); } @@ -28,7 +37,7 @@ router.route('/') .get(function (req, res) { var meiFiles = []; var iiifFiles = []; - fs.readdir(meiUpload, function (err, files) { + fs.readdir(manifestUpload, function (err, files) { if (err) { console.error(err); res.sendStatus(500); @@ -67,58 +76,80 @@ router.route('/') }); }); -var upload = multer({ - storage: multer.memoryStorage(), - limits: { filesize: 100000 } -}); - -router.route('/upload_file') - .post(upload.array('resource', 2), function (req, res) { - if (req.files[1].mimetype !== 'image/png') { - res.sendStatus(400); - } - let files = [req.files[0].originalname, req.files[1].originalname]; - let meiSplit = files[0].split(/\.mei/, 2); - let filename = meiSplit[0]; - let newImageName = filename + '.png'; - if (!isUserInputValid(files[0]) || !isUserInputValid(newImageName)) { - res.sendStatus(403); - } - fs.writeFile(path.join(meiUpload, files[0]), req.files[0].buffer, (err) => { - if (err) { - console.error(err); - throw err; - } - fs.writeFile(path.join(pngUpload, newImageName), req.files[1].buffer, (err) => { - if (err) { - console.error(err); - throw err; +router.route('/upload_file').post(upload.array('resource', 2), function (req, res) { + // Check media type + if (!req.files[1].mimetype.match(/^image\/*/)) { + res.sendStatus(415); // Unsupported Media Type + } else { + let meiFileName = req.files[0].originalname; + let basename = meiFileName.split(/\.mei$/, 2)[0]; + let imageExtension = /^.*(\.[a-zA-Z0-9]+)$/.exec(req.files[1].originalname)[1]; + let imageName = basename + (imageExtension !== null ? imageExtension : ''); + let manifestName = basename + '.jsonld'; + // Validate the file names + if (!isUserInputValid(meiFileName) || !isUserInputValid(imageName) || !isUserInputValid(manifestName)) { + res.sendStatus(403); // Forbidden + } else { + let manifest = { + '@context': neonContext, + '@id': '/uploads/manifests/' + manifestName, + title: basename, + timestamp: (new Date()).toISOString(), + image: '/uploads/img/' + imageName, + mei_annotations: [ + { + id: 'urn:uuid:' + uuidv4(), + type: 'Annotation', + body: '/uploads/mei/' + meiFileName, + target: '/uploads/img/' + imageName + } + ] + }; + // Ensure files do not already exist + if (fs.existsSync(path.join(meiUpload, meiFileName)) || fs.existsSync(path.join(imgUpload, imageName)) || fs.existsSync(path.join(manifestUpload, manifestName))) { + res.sendStatus(409); // Conflict + } else { + // Write files + try { + fs.writeFileSync(path.join(meiUpload, meiFileName), req.files[0].buffer); + fs.writeFileSync(path.join(imgUpload, imageName), req.files[1].buffer); + fs.writeFileSync(path.join(manifestUpload, manifestName), JSON.stringify(manifest, null, 4)); + } catch (e) { + console.error(e); + res.sendStatus(500); + return; } res.redirect('/'); - }); - }); - }); + } + } + } +}); // Delete file TODO: Optimize function with regex router.route('/delete/:filename') .get(function (req, res) { if (!isUserInputValid(req.params.filename)) { - res.sendStatus(403); + return res.sendStatus(403); } - var meifile = req.params.filename; - var pngfile = meifile.split('.')[0] + '.png'; - // delete file from all folders - fs.unlink(path.join(meiUpload), function (err) { + fs.readFile(path.join(manifestUpload, req.params.filename), (err, data) => { if (err) { - return console.log('failed to delete mei file'); - } - }); - fs.unlink(path.join(pngUpload, pngfile), function (err) { - if (err) { - return console.log('failed to delete png file'); + console.error(err); + res.sendStatus(404); + } else { + let manifest = JSON.parse(data); + let imagePath = manifest.image.split('/'); + let meiPath = manifest.mei_annotations[0].body.split('/'); + try { + fs.unlinkSync(path.join(manifestUpload, req.params.filename)); + fs.unlinkSync(path.join('public', ...imagePath)); + fs.unlinkSync(path.join('public', ...meiPath)); + } catch (e) { + console.error(e); + return res.sendStatus(500); + } + res.redirect('/'); } }); - res.redirect('/'); }); // Delete IIIF files @@ -136,55 +167,44 @@ router.route('/delete/:label/:rev').get((req, res) => { }); // redirect to editor -router.route('/edit/:filename') - .get(function (req, res) { - if (!isUserInputValid(req.params.filename)) { - res.sendStatus(403); - } - var mei = req.params.filename; - var bgimg = mei.split('.', 2)[0] + '.png'; - var autosave = false; - // Check that the MEI exists - fs.stat(path.join(meiUpload, mei), (err, stats) => { - if (err) { - console.error("File of name '" + mei + "' does not exist."); - res.status(404).render('error', { statusCode: '404 - File Not Found', message: 'The file ' + mei + ' could not be found on the server!' }); - return; +router.route('/edit/:filename').get(function (req, res) { + if (!isUserInputValid(req.params.filename)) { + return res.sendStatus(403); + } + + // Check that the manifest exists + fs.stat(path.join(manifestUpload, req.params.filename), (err, stats) => { + if (err) { + if (err.code !== 'ENOENT') { + console.error(err); } - res.render('editor', { 'meifile': '/uploads/mei/' + mei, 'bgimg': '/uploads/png/' + bgimg, 'autosave': autosave }); - }); + res.sendStatus(404); // Not Found + } else { + // Read manifest + fs.readFile(path.join(manifestUpload, req.params.filename), (err, data) => { + if (err) { + console.error(err); + res.sendStatus(500); // Internal Server Error + } else { + res.render('editor', { 'manifest': encodeURIComponent(data) }); + } + }); + } }); +}); // redirect to salzinnes editor -router.route('/edit-iiif/:label/:rev').get((req, res) => { +router.route('/edit/:label/:rev').get((req, res) => { if (!isUserInputValid(req.params.label) || !isUserInputValid(req.params.rev)) { - res.sendStatus(403); + return res.sendStatus(403); } let pathName = path.join(req.params.label, req.params.rev); - fs.readFile(path.join(iiifUpload, pathName, 'metadata.json'), (err, data) => { + fs.readFile(path.join(iiifUpload, pathName, 'manifest.jsonld'), (err, data) => { if (err) { console.error(err); res.status(500).render('error', { statusCode: '500 - Internal Server Error', message: 'Could not find the manifest for IIIF entry ' + pathName }); } else { - let metadata; - try { - metadata = JSON.parse(data.toString()); - } catch (e) { - console.error(e); - res.status(500).render('error', { statusCode: '500 - Internal Server Error', message: 'Could not parse entry metadata' }); - } - let map = new Map(); - for (let page of metadata.pages) { - let data; - try { - data = fs.readFileSync(path.join(iiifUpload, pathName, page.file)); - } catch (e) { - console.error(e); - continue; - } - map.set(page.index, data.toString()); - } - res.render('editor', { 'manifest': metadata.manifest, 'meiMap': encodeURIComponent(JSON.stringify([...map])) }); + res.render('editor', { 'manifest': encodeURIComponent(data) }); } }); }); @@ -235,13 +255,21 @@ router.route('/add-iiif').get(function (req, res) { } // Create appropriate directory - fs.mkdir(path.join(iiifUpload, label, req.body.revision), (err) => { + fs.mkdir(path.join(iiifUpload, label, req.body.revision), { recursive: true }, (err) => { if (err) { console.error(err); res.sendStatus(500); } - fs.writeFile(path.join(iiifUpload, label, req.body.revision, 'metadata.json'), - JSON.stringify({ manifest: req.body.manifest, pages: [] }), + let manifest = { + '@context': neonContext, + '@id': '/uploads/iiif/' + label + '/' + req.body.revision + '/manifest.jsonld', + 'title': label, + 'timestamp': (new Date()).toISOString(), + 'image': req.body.manifest, + 'mei_annotations': [] + }; + fs.writeFile(path.join(iiifUpload, label, req.body.revision, 'manifest.jsonld'), + JSON.stringify(manifest, null, 4), (err) => { if (err) { console.error(err); @@ -259,32 +287,34 @@ router.route('/add-mei-iiif/:label/:rev').post(upload.array('mei'), function (re if (!isUserInputValid(req.params.label) || !isUserInputValid(req.params.rev)) { res.sendStatus(403); } - // Get metadata - let metadata; + let manifest; try { - metadata = JSON.parse(fs.readFileSync(path.join(iiifUpload, req.params.label, req.params.rev, 'metadata.json'))); + manifest = JSON.parse(fs.readFileSync(path.join(iiifUpload, req.params.label, req.params.rev, 'manifest.jsonld'))); } catch (e) { console.error(e); res.sendStatus(500); } - // Get manifest - request(metadata.manifest, (error, response, body) => { + request(manifest.image, (error, response, body) => { if (error) { res.send(error); } else if (!response.statusCode === 200) { res.status(response.statusCode).send(response.statusMessage); } else { - let manifest; + let iiif; try { manifest = JSON.parse(body); } catch (e) { res.status(500).send('Could not parse the JSON object'); } + let labels = []; + let ids = []; + for (let sequence of manifest['sequences']) { for (let canvas of sequence['canvases']) { labels.push(canvas['label']); + ids.push(canvas['@id']); } } @@ -311,7 +341,8 @@ router.route('/add-mei-iiif/:label/:rev').post(upload.array('mei'), function (re label: req.params.label, rev: req.params.rev, files: filenames, - labels: labels + labels: labels, + ids: ids } ); } @@ -322,27 +353,100 @@ router.route('/associate-mei-iiif/:label/:rev').post(function (req, res) { if (!isUserInputValid(req.params.label) || !isUserInputValid(req.params.rev)) { res.sendStatus(403); } - // Load metadata file - let metadata; + // Load manifest file + let manifest; try { - metadata = JSON.parse(fs.readFileSync(path.join(iiifUpload, req.params.label, req.params.rev, 'metadata.json'))); + manifest = JSON.parse(fs.readFileSync(path.join(iiifUpload, req.params.label, req.params.rev, 'manifest.jsonld'))); } catch (e) { console.error(e); - res.sendStatus(500); + return res.sendStatus(500); + } + + manifest.mei_annotations = []; + if (typeof req.body.select !== 'string') { + for (let entryText of req.body.select) { + let entry = JSON.parse(entryText); + manifest.mei_annotations.push({ + 'id': 'urn:uuid:' + uuidv4(), + 'type': 'Annotation', + 'body': '/uploads/iiif/' + req.params.label + '/' + req.params.rev + '/' + entry.file, + 'target': entry.id + }); + } + } else { + let entry = JSON.parse(req.body.select); + manifest.mei_annotations.push({ + 'id': 'urn:uuid:' + uuidv4(), + 'type': 'Annotation', + 'body': '/uploads/iiif/' + req.params.label + '/' + req.params.rev + '/' + entry.file, + 'target': entry.id + }); + } + + fs.writeFile(path.join(iiifUpload, req.params.label, req.params.rev, 'manifest.jsonld'), + JSON.stringify(manifest, null, 4), (err) => { + if (err) { + console.error(err); + res.sendStatus(500); + } else { + res.redirect('/'); + } + }); +}); + +router.route('/uploads/mei/:file').put(function (req, res) { + if (!isUserInputValid(req.params.file)) { + res.sendStatus(403); + return; + } + if (typeof req.body.mei === 'undefined') { + res.sendStatus(400); + return; } - // Update metadata - metadata.pages = []; - for (let entry of req.body.select) { - metadata.pages.push(JSON.parse(entry)); + // Check if file file exists. If it does, write. Otherwise return 404 + let filePath = path.join(meiUpload, req.params.file); + fs.access(filePath, fs.constants.F_OK, (err) => { + if (err) { + res.sendStatus(404); + } else { + fs.writeFile(filePath, req.body.mei, (err) => { + if (err) { + console.error(err); + res.sendStatus(500); + } else { + res.sendStatus(200); + } + }); + } + }); +}); + +router.route('/uploads/iiif/:label/:rev/:file').put(function (req, res) { + if (!isUserInputValid(req.params.label) || !isUserInputValid(req.params.rev) || !isUserInputValid(req.params.file)) { + res.sendStatus(403); + return; + } + if (typeof req.body.mei === 'undefined') { + res.sendStatus(400); + return; } - fs.writeFile(path.join(iiifUpload, req.params.label, req.params.rev, 'metadata.json'), JSON.stringify(metadata), (err) => { + // Check if file exists. If it does, write. Otherwise 404. + let filePath = path.join(iiifUpload, req.params.label, req.params.rev, req.params.file); + fs.access(filePath, fs.constants.F_OK, (err) => { if (err) { - console.error(err); - res.sendStatus(500); + res.sendStatus(404); + } else { + fs.writeFile(filePath, req.body.mei, (err) => { + if (err) { + console.error(err); + res.sendStatus(500); + } else { + res.sendStatus(200); + } + }); } - res.redirect('/'); }); }); diff --git a/src/DisplayPanel/DisplayControls.js b/src/DisplayPanel/DisplayControls.js index d8435ab42..e70d60064 100644 --- a/src/DisplayPanel/DisplayControls.js +++ b/src/DisplayPanel/DisplayControls.js @@ -5,6 +5,8 @@ import Icons from '../img/icons.svg'; const $ = require('jquery'); +var lastGlyphOpacity, lastImageOpacity; + /** * Initialize listeners and controls for display panel. * @param {string} meiClassName - The class used to signifiy the MEI element(s). @@ -72,20 +74,22 @@ export function setZoomControls (zoomHandler) { * @param {string} meiClassName */ function setOpacityControls (meiClassName) { + lastGlyphOpacity = 100; $('#opacitySlider').val(100); $('#reset-opacity').click(function () { // Definition scale is the root element of what is generated by verovio - Array.from(document.getElementsByClassName(meiClassName)).forEach(elem => { - elem.style.opacity = 1; - }); - $('.definition-scale').css('opacity', 1); + let lowerOpacity = lastGlyphOpacity < 95 ? lastGlyphOpacity / 100.0 : 0; + let newOpacity = $('#opacitySlider').val() === '100' ? lowerOpacity : 1; + $('.' + meiClassName).css('opacity', newOpacity); - $('#opacitySlider').val(100); - $('#opacityOutput').val(100); + lastGlyphOpacity = Number($('#opacitySlider').val()); + $('#opacitySlider').val(newOpacity * 100); + $('#opacityOutput').val(newOpacity * 100); }); $(document).on('input change', '#opacitySlider', () => { $('#opacityOutput').val($('#opacitySlider').val()); + lastGlyphOpacity = Number($('#opacitySlider').val()); $('.' + meiClassName).css('opacity', $('#opacityOutput').val() / 100.0); }); } @@ -104,16 +108,21 @@ export function setOpacityFromSlider (meiClassName) { * @param {string} background */ function setBackgroundOpacityControls (background) { + lastImageOpacity = 100; $('#bgOpacitySlider').val(100); $('#reset-bg-opacity').click(function () { - document.getElementsByClassName(background)[0].style.opacity = 1; + let lowerOpacity = lastImageOpacity < 95 ? lastImageOpacity / 100.0 : 0; + let newOpacity = $('#bgOpacitySlider').val() === '100' ? lowerOpacity : 1; + document.getElementsByClassName(background)[0].style.opacity = newOpacity; - $('#bgOpacitySlider').val(100); - $('#bgOpacityOutput').val(100); + lastImageOpacity = Number($('#bgOpacitySlider').val()); + $('#bgOpacitySlider').val(newOpacity * 100); + $('#bgOpacityOutput').val(newOpacity * 100); }); $(document).on('input change', '#bgOpacitySlider', function () { $('#bgOpacityOutput').val(parseInt($('#bgOpacitySlider').val())); + lastImageOpacity = Number($('#bgOpacitySlider').val()); document.getElementsByClassName(background)[0].style.opacity = $('#bgOpacityOutput').val() / 100.0; }); } @@ -148,6 +157,13 @@ export function setHighlightControls () { $('#highlight-type').html(' - Neume'); Color.setGroupingHighlight('neume'); }); + $('#highlight-selection').on('click', () => { + $('#highlight-dropdown').removeClass('is-active'); + $('.highlight-selected').removeClass('highlight-selected'); + $('#highlight-selection').addClass('highlight-selected'); + $('#highlight-type').html(' - Selection'); + Color.setGroupingHighlight('selection'); + }); $('#highlight-none').on('click', () => { $('#highlight-dropdown').removeClass('is-active'); $('.highlight-selected').removeClass('highlight-selected'); @@ -175,6 +191,9 @@ export function updateHighlight () { case 'highlight-neume': Color.setGroupingHighlight('neume'); break; + case 'highlight-selection': + Color.setGroupingHighlight('selection'); + break; default: Color.unsetGroupingHighlight(); } diff --git a/src/DisplayPanel/DisplayPanel.js b/src/DisplayPanel/DisplayPanel.js index 312cbc8f5..df168ffb9 100644 --- a/src/DisplayPanel/DisplayPanel.js +++ b/src/DisplayPanel/DisplayPanel.js @@ -23,7 +23,7 @@ class DisplayPanel { let displayPanel = document.getElementById('display_controls'); displayPanel.innerHTML = displayControlsPanel(this.zoomHandler); - this.view.addUpdateCallback(this.updateGlyphOpacity.bind(this)); + this.view.addUpdateCallback(this.updateVisualization.bind(this)); } /** @@ -38,10 +38,11 @@ class DisplayPanel { } /** - * Update the opacity of rendered SVGs. + * Update SVG based on visualization settings */ - updateGlyphOpacity () { + updateVisualization () { DisplayControls.setOpacityFromSlider(this.className); + DisplayControls.updateHighlight(); } } diff --git a/src/DivaView/DivaView.js b/src/DivaView.js similarity index 51% rename from src/DivaView/DivaView.js rename to src/DivaView.js index 81732b3d9..2f64fecd4 100644 --- a/src/DivaView/DivaView.js +++ b/src/DivaView.js @@ -1,4 +1,14 @@ +/** + * View module that uses the diva.js viewer to render the pages of a IIIF manifests + * and then display the rendered MEI files over the proper pages. + */ class DivaView { + /** + * Constructor for DivaView. + * @param {NeonView} neonView - NeonView parent + * @param {function} Display - A constructor for a DisplayPanel + * @param {string} manifest - Link to the IIIF manifest. + */ constructor (neonView, Display, manifest) { this.neonView = neonView; this.updateCallbacks = []; @@ -7,13 +17,19 @@ class DivaView { objectData: manifest }); document.getElementById('container').style.minHeight = '100%'; + this.indexMap = new Map(); this.diva.disableDragScrollable(); this.displayPanel = new Display(this, 'neon-container', 'diva-viewer-canvas'); + this.loadDelay = 500; // in milliseconds this.initDivaEvents(); this.setViewEventHandlers(); } + /** + * Set the listeners for certain events internal to diva.js + */ initDivaEvents () { + Diva.Events.subscribe('ManifestDidLoad', this.parseManifest.bind(this), this.diva.settings.ID); Diva.Events.subscribe('ObjectDidLoad', this.didLoad.bind(this), this.diva.settings.ID); Diva.Events.subscribe('VisiblePageDidChange', this.changePage.bind(this), this.diva.settings.ID); Diva.Events.subscribe('ZoomLevelDidChange', this.adjustZoom.bind(this), this.diva.settings.ID); @@ -22,8 +38,10 @@ class DivaView { /** * Called when the visible page changes in the diva.js viewer. * @param {number | number[]} pageIndexes - The zero-index or -indexes of the page(s) visible. + * @param {boolean} [delay=true] - whether to delay the loading of the page. defaults to true + * delay the loading of the page when scrolling so that neon doesn't lag while scrolling */ - async changePage (pageIndexes) { + async changePage (pageIndexes, delay = true) { if (typeof pageIndexes !== 'object') { pageIndexes = [pageIndexes]; } @@ -31,21 +49,27 @@ class DivaView { elem.classList.remove('active-page'); }); for (let page of pageIndexes) { - try { - let svg = await this.neonView.getPageSVG(page); - this.updateSVG(svg, page); - } catch (err) { - if (err.name !== 'not_found' && err.name !== 'missing_mei') { - console.error(err); - } - } + window.setTimeout(checkAndLoad.bind(this), (delay ? this.loadDelay : 0), page); } - let containerId = 'neon-container-' + this.getCurrentPage(); - let container = document.getElementById(containerId); - if (container !== null) { - container.classList.add('active-page'); + + function checkAndLoad (page) { + if (page === this.getCurrentPage()) { + let pageURI = this.indexMap.get(page); + this.neonView.getPageSVG(pageURI).then(svg => { + this.updateSVG(svg, page); + let containerId = 'neon-container-' + page; + let container = document.getElementById(containerId); + if (container !== null) { + container.classList.add('active-page'); + } + this.updateCallbacks.forEach(callback => callback()); + }).catch(err => { + if (err.name !== 'not_found' && err.name !== 'missing_mei') { + console.error(err); + } + }); + } } - this.updateCallbacks.forEach(callback => callback()); } /** @@ -56,6 +80,14 @@ class DivaView { return this.diva.getActivePageIndex(); } + /** + * Get the active page URI in the diva.js viewer. + * @returns {string} + */ + getCurrentPageURI () { + return this.indexMap.get(this.getCurrentPage()); + } + /** * Adjust the rendered SVG(s) to be the correct size after zooming. * @param {number} zoomLevel - The new diva.js zoom level. @@ -66,9 +98,14 @@ class DivaView { .forEach(elem => { elem.style.display = 'none'; }); setTimeout(resolve, this.diva.settings.zoomDuration + 100); })).then(() => { - this.changePage(this.diva.getActivePageIndex()); + this.changePage(this.diva.getActivePageIndex(), true); Array.from(document.getElementsByClassName('neon-container')) - .forEach(elem => { elem.style.display = ''; }); + .forEach(elem => { + let svg = elem.firstChild; + let pageNo = parseInt(elem.id.match(/\d+/)[0]); + this.updateSVG(svg, pageNo); + elem.style.display = ''; + }); }); } @@ -119,10 +156,18 @@ class DivaView { console.log(this.diva); } + /** + * Add a callback function that will be run whenever an SVG is updated. + * @param {function} cb - The callback function. + */ addUpdateCallback (cb) { this.updateCallbacks.push(cb); } + /** + * Remove a callback function previously added to the list of functions to call. + * @param {function} cb - The callback function to remove. + */ removeUpdateCallback (cb) { let index = this.updateCallbacks.findItem((elem) => { return elem === cb; @@ -132,26 +177,55 @@ class DivaView { } } + /** + * Set listeners on the body element for global events. + */ setViewEventHandlers () { - document.body.addEventListener('keydown', (event) => { - switch (event.key) { - case 'Shift': - this.diva.enableDragScrollable(); - break; - default: + document.body.addEventListener('keydown', (evt) => { + switch (evt.key) { + case 'h': + for (let container of document.getElementsByClassName('neon-container')) { + container.style.visibility = 'hidden'; + } break; + default: break; } }); - document.body.addEventListener('keyup', (event) => { - switch (event.key) { - case 'Shift': - this.diva.disableDragScrollable(); - break; - default: + + document.body.addEventListener('keyup', (evt) => { + switch (evt.key) { + case 'h': + for (let container of document.getElementsByClassName('neon-container')) { + container.style.visibility = ''; + } break; + default: break; } }); } + + /** + * Use the IIIF manifest to create a map between IIIF canvases and page indexes. + * @param {object} manifest - The IIIF manifest + */ + parseManifest (manifest) { + this.indexMap.clear(); + for (let sequence of manifest.sequences) { + for (let canvas of sequence.canvases) { + this.indexMap.set(sequence.canvases.indexOf(canvas), canvas['@id']); + } + } + } + + /** + * Get the name of the active page/canvas combined with the manuscript name. + * @returns {string} + */ + getPageName () { + let manuscriptName = this.diva.settings.manifest.itemTitle; + let pageName = this.diva.settings.manifest.pages[this.getCurrentPage()].l; + return manuscriptName + ' \u2014 ' + pageName; + } } export { DivaView as default }; diff --git a/src/InfoModule.js b/src/InfoModule.js index 1ec54bd4e..ac5237db7 100644 --- a/src/InfoModule.js +++ b/src/InfoModule.js @@ -21,12 +21,13 @@ class InfoModule { input.classList.add('checkbox'); input.id = 'displayInfo'; input.type = 'checkbox'; - input.checked = true; + input.checked = false; label.appendChild(input); block.prepend(label); this.neonView.view.addUpdateCallback(this.resetInfoListeners.bind(this)); setInfoControls(); + this.resetInfoListeners(); } /** @@ -78,7 +79,7 @@ class InfoModule { var ncs = element.children('.nc'); var contour = await this.getContour(ncs); if (contour === 'Clivis') { - var attr = await this.neonView.getElementAttr($(ncs[0])[0].id, this.neonView.view.getCurrentPage()); + var attr = await this.neonView.getElementAttr($(ncs[0])[0].id, this.neonView.view.getCurrentPageURI()); if (attr.ligated) { contour = 'Ligature'; } @@ -90,11 +91,11 @@ class InfoModule { 'Pitch(es): ' + pitches; break; case 'custos': - attributes = await this.neonView.getElementAttr(id, this.neonView.view.getCurrentPage()); + attributes = await this.neonView.getElementAttr(id, this.neonView.view.getCurrentPageURI()); body += 'Pitch: ' + (attributes.pname).toUpperCase() + attributes.oct; break; case 'clef': - attributes = await this.neonView.getElementAttr(id, this.neonView.view.getCurrentPage()); + attributes = await this.neonView.getElementAttr(id, this.neonView.view.getCurrentPageURI()); body += 'Shape: ' + attributes.shape + '
' + 'Line: ' + attributes.line; break; @@ -112,27 +113,27 @@ class InfoModule { } /** - * Get the individual pitches of a neume. - * @param {array.} ncs - neume components in the neume. - */ + * Get the individual pitches of a neume. + * @param {array.} ncs - neume components in the neume. + */ async getPitches (ncs) { var pitches = ''; for (let nc of ncs) { - var attributes = await this.neonView.getElementAttr(nc.id, this.neonView.view.getCurrentPage()); + var attributes = await this.neonView.getElementAttr(nc.id, this.neonView.view.getCurrentPageURI()); pitches += attributes.pname + attributes.oct + ' '; } return pitches; } /** - * Get the contour of a neume. - * @param {array.} ncs - neume components in the neume. - */ + * Get the contour of a neume. + * @param {array.} ncs - neume components in the neume. + */ async getContour (ncs) { var contour = ''; var previous = null; for (let nc of ncs) { - var attributes = await this.neonView.getElementAttr(nc.id, this.neonView.view.getCurrentPage()); + var attributes = await this.neonView.getElementAttr(nc.id, this.neonView.view.getCurrentPageURI()); if (previous !== null) { if (previous.oct > attributes.oct) { contour += 'd'; @@ -157,27 +158,24 @@ class InfoModule { } /** - * Show and update the info box. - * @param {string} title - The info box title. - * @param {string} body - The info box contents. - */ + * Show and update the info box. + * @param {string} title - The info box title. + * @param {string} body - The info box contents. + */ updateInfoModule (title, body) { + $('.message-header').children('p').html(title); + $('.message-body').html(body); + if ($('#displayInfo').is(':checked')) { $('.message').css('display', ''); - $('.message-header').children('p').html(title); - $('.message-body').html(body); } - // Setting up listener for dismissing message - $('#notification-delete').on('click', function () { - $('.message').css('display', 'none'); - }); } /** - * Convert a pitch name (a-g) to a number (where c is 1, d is 2 and b is 7). - * @param {string} pname - The pitch name. - * @returns {number} - */ + * Convert a pitch name (a-g) to a number (where c is 1, d is 2 and b is 7). + * @param {string} pname - The pitch name. + * @returns {number} + */ pitchNameToNum (pname) { switch (pname) { case 'c': @@ -200,10 +198,10 @@ class InfoModule { } /** - * Find the contour of an neume grouping based on the grouping name. - * @param {string} value - the value name. - * @returns {string} - */ + * Find the contour of an neume grouping based on the grouping name. + * @param {string} value - the value name. + * @returns {string} + */ getContourByValue (value) { for (let [cont, v] of InfoModule.neumeGroups.entries()) { if (v === value) { @@ -226,20 +224,25 @@ InfoModule.neumeGroups = new Map( * Set listener on info visibility checkbox. */ function setInfoControls () { + startInfoVisibility(); updateInfoVisibility(); $('#displayInfo').click(updateInfoVisibility); } +function startInfoVisibility () { + $('#neume_info').append("

" + + "
"); + $('#neume_info').addClass('is-invisible'); +} + /** * Update the visibility of infoBox */ function updateInfoVisibility () { if ($('#displayInfo').is(':checked')) { - $('#neume_info').append("