Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 Fix divlines jumping to wrong staff lines. #882

Merged
merged 3 commits into from
Jun 20, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 29 additions & 29 deletions assets/js/verovio-toolkit.js

Large diffs are not rendered by default.

13 changes: 12 additions & 1 deletion src/Types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

export type Attributes = { pname?: string; oct?: number; shape?: string; line?: number; ligated?: boolean; curve?: string; tilt?: string; form?: string };

/** Drag editing action sent to verovio as described [here](https://github.com/DDMAL/Neon/wiki/Toolkit-Actions). */
/**
* Drag editing action sent to verovio as described [here](https://github.com/DDMAL/Neon/wiki/Toolkit-Actions).
*/
export type DragAction = {
action: 'drag',
param: {
Expand Down Expand Up @@ -161,6 +163,14 @@ export type ChangeStaffAction = {
}
};

export type ChangeStaffToAction = {
action: 'changeStaffTo',
param: {
elementId: string,
staffId: string,
}
};

export type ChangeGroupAction = {
action: 'changeGroup',
param: {
Expand Down Expand Up @@ -189,6 +199,7 @@ export type EditorAction =
| ToggleLigatureAction
| ChangeSkewAction
| ChangeStaffAction
| ChangeStaffToAction
| ChangeGroupAction
| ChainAction;

Expand Down
155 changes: 108 additions & 47 deletions src/utils/DragHandler.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,47 @@
import NeonView from '../NeonView';
import { DragAction, EditorAction } from '../Types';
import { ChangeStaffToAction, DragAction, EditorAction } from '../Types';
import * as d3 from 'd3';
import { getStaffBBox } from './SelectTools';

/**
* Get SVG relative coordinates given clientX and clientY
* Source: https://stackoverflow.com/questions/29261304
*/
function getSVGRelCoords (clientX: number, clientY: number): [number, number] {
const pt = new DOMPoint(clientX, clientY);
const svg = document.querySelector<SVGSVGElement>('#svg_group');
const { x, y } = pt.matrixTransform(svg.getScreenCTM().inverse());

return [x, y];
}

/**
* Get ID of staff by client's x-y coordinates.
* This function considers the *visual* bounding box of the staff
* based on its staff lines, instead of the SVG element itself.
*/
function getStaff (clientX: number, clientY: number): string {
const staves = Array.from(document.querySelectorAll<SVGGElement>('.staff'));
const staffBBoxes = staves.map(staff => getStaffBBox(staff));

// find the staff that the cursor is inside
const [x, y] = getSVGRelCoords(clientX, clientY);
const staff = staffBBoxes.find(
(bbox) => x <= bbox.lrx && x >= bbox.ulx && y <= bbox.lry && y >= bbox.uly
);

// if the cursor is not inside any staff, then explicitly return null
return staff ? staff.id : null;
}

class DragHandler {
private dragStartCoords: Array<number>;
private resetToAction: (selection: d3.Selection<d3.BaseType, {}, HTMLElement, any>, args: any[]) => void;
readonly neonView: NeonView;
private selector: string;
private selection: Element[];

private dragStartCoords: [number, number] = [-1, -1];
private resetToAction: (selection: d3.Selection<d3.BaseType, {}, HTMLElement, any>, args: any[]) => void;

private dx: number;
private dy: number;

Expand All @@ -23,21 +57,6 @@ class DragHandler {
* Initialize the dragging action and handler for selected elements.
*/
dragInit (): void {
// Adding listeners
const dragBehaviour = d3.drag()
// eslint-disable-next-line @typescript-eslint/no-use-before-define
.on('start', dragStarted.bind(this))
.on('drag', this.dragging.bind(this))
.on('end', this.dragEnded.bind(this));

const activeNc = d3.selectAll('.selected');
const selection = Array.from(document.getElementsByClassName('selected'));
this.selection = selection.concat(Array.from(document.getElementsByClassName('resizePoint')));

this.dragStartCoords = new Array(activeNc.size());

activeNc.call(dragBehaviour);

// Drag effects
function dragStarted (): void {
this.dragStartCoords = [d3.event.x, d3.event.y];
Expand All @@ -48,16 +67,26 @@ class DragHandler {
d3.select(this.selector).call(dragBehaviour);
}
}

// Adding listeners
const dragBehaviour = d3.drag()
.on('start', dragStarted.bind(this))
.on('drag', this.dragging.bind(this))
.on('end', this.dragEnded.bind(this));

const activeNc = d3.selectAll('.selected');
activeNc.call(dragBehaviour);

const selection = Array.from(document.getElementsByClassName('selected'));
this.selection = selection.concat(Array.from(document.getElementsByClassName('resizePoint')));
}

dragging (): void {
const relativeY = d3.event.y - this.dragStartCoords[1];
const relativeX = d3.event.x - this.dragStartCoords[0];
this.dx = d3.event.x - this.dragStartCoords[0];
this.dy = d3.event.y - this.dragStartCoords[1];
this.selection.forEach((el) => {
d3.select(el).attr('transform', function () {
return 'translate(' + [relativeX, relativeY] + ')';
d3.select(el).attr('transform', () => {
return 'translate(' + [this.dx, this.dy] + ')';
});
});
/*
Expand All @@ -66,25 +95,54 @@ class DragHandler {
* 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 (this.selection.filter((element: HTMLElement) => element.classList.contains('syl')).length === 0) {
d3.selectAll('.syllable.selected').selectAll('.sylTextRect-display').attr('transform', function () {
return 'translate(' + [-1 * relativeX, -1 * relativeY] + ')';
});
const syls = this.selection.filter((el) => el.classList.contains('syl'));
if (syls.length === 0) {
d3.selectAll('.syllable.selected')
.selectAll('.sylTextRect-display')
.attr(
'transform',
() => 'translate(' + [-1 * this.dx, -1 * this.dy] + ')'
);
}
}


dragEnded (): void {
const paramArray = [];
this.selection.filter((el: SVGElement) => !el.classList.contains('resizePoint')).forEach((el: SVGElement) => {
const id = (el.tagName === 'rect') ? el.closest('.syl').id : el.id;
const singleAction: DragAction = {
action: 'drag',
param: { elementId: id,
x: this.dx,
y: (this.dy) * -1 }
};
paramArray.push(singleAction);
});
const paramArray: EditorAction[] = [];
this.selection
.filter((el) => !el.classList.contains('resizePoint'))
.forEach((el) => {
const id = el.tagName === 'rect' ? el.closest('.syl').id : el.id;

const dragAction: DragAction = {
action: 'drag',
param: {
elementId: id,
x: this.dx,
y: -this.dy,
}
};

paramArray.push(dragAction);

if (el.classList.contains('divLine') || el.classList.contains('accid') || el.classList.contains('custos')) {
const { clientX, clientY } = d3.event.sourceEvent;
const newStaff = getStaff(clientX, clientY);

const staffAction: ChangeStaffToAction = {
action: 'changeStaffTo',
param: {
elementId: id,
// if divline is moved to the background (and not a staff),
// set the staffId to the original staff
staffId: newStaff ? newStaff : el.closest('.staff').id,
}
};

paramArray.push(staffAction);
}
});

const editorAction: EditorAction = {
action: 'chain',
param: paramArray
Expand All @@ -94,12 +152,14 @@ class DragHandler {
const yDiff = Math.abs(this.dy);

if (xDiff > 5 || yDiff > 5) {
this.neonView.edit(editorAction, this.neonView.view.getCurrentPageURI()).then(() => {
this.neonView.updateForCurrentPage();
this.endOptionsSelection();
this.reset();
this.dragInit();
});
this.neonView
.edit(editorAction, this.neonView.view.getCurrentPageURI())
.then(() => {
this.neonView.updateForCurrentPage();
this.endOptionsSelection();
this.reset();
this.dragInit();
});
} else {
this.reset();
this.dragInit();
Expand All @@ -119,10 +179,11 @@ class DragHandler {
}

endOptionsSelection (): void {
try {
document.getElementById('moreEdit').innerHTML = '';
document.getElementById('moreEdit').parentElement.classList.add('hidden');
} catch (e) {}
const moreEdit = document.getElementById('moreEdit');
if (moreEdit) {
moreEdit.innerHTML = '';
moreEdit.parentElement.classList.add('hidden');
}
}
}

Expand Down
26 changes: 23 additions & 3 deletions src/utils/SelectTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,11 +211,23 @@ export function sharedSecondLevelParent (elements: SVGElement[]): boolean {
return true;
}

/**
* Bounding box object interface for getStaffBBox()
*/
export interface StaffBBox {
id: string;
ulx: number;
uly: number;
lrx: number;
lry: number;
rotate: number;
}

/**
* Get the bounding box of a staff based on its staff lines.
* Rotate is included in radians.
*/
export function getStaffBBox (staff: SVGGElement): {ulx: number; uly: number; lrx: number; lry: number; rotate: number} {
export function getStaffBBox (staff: SVGGElement): StaffBBox {
let ulx, uly, lrx, lry, rotate;
staff.querySelectorAll('path').forEach(path => {
const coordinates: number[] = path.getAttribute('d')
Expand All @@ -239,7 +251,15 @@ export function getStaffBBox (staff: SVGGElement): {ulx: number; uly: number; lr
lrx = coordinates[2];
}
});
return { ulx: ulx, uly: uly, lrx: lrx, lry: lry, rotate: rotate };

return {
id: staff.id,
ulx: ulx,
uly: uly,
lrx: lrx,
lry: lry,
rotate: rotate,
};
}

/**
Expand Down Expand Up @@ -560,4 +580,4 @@ export async function selectAll (elements: Array<SVGGraphicsElement>, neonView:
} catch (e) {console.debug(e);}
}

}
}
58 changes: 29 additions & 29 deletions verovio-util/verovio-dev/index.js

Large diffs are not rendered by default.

58 changes: 29 additions & 29 deletions verovio-util/verovio.js

Large diffs are not rendered by default.