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

Feat: allow user to create new MEI files #1153

Merged
merged 8 commits into from
Dec 12, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
52 changes: 26 additions & 26 deletions assets/js/verovio-toolkit.js

Large diffs are not rendered by default.

37 changes: 37 additions & 0 deletions deployment/server/samples/mei/mei_template.mei
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml-model href="https://music-encoding.org/schema/dev/mei-all.rng" type="application/xml" schematypens="http://relaxng.org/ns/structure/1.0"?>
<?xml-model href="https://music-encoding.org/schema/dev/mei-all.rng" type="application/xml" schematypens="http://purl.oclc.org/dsdl/schematron"?>
<mei
xmlns="http://www.music-encoding.org/ns/mei" meiversion="5.0.0-dev">
<meiHead>
<fileDesc>
<titleStmt>
<title>MEI Encoding Output (1.0.0)</title>
</titleStmt>
<pubStmt/>
</fileDesc>
</meiHead>
<music>
<facsimile>
<surface lrx="4872" lry="6496">
<zone ulx="601" uly="506" lrx="1666" lry="732"/>
</surface>
</facsimile>
<body>
<mdiv>
<score>
<scoreDef>
<staffGrp>
<staffDef n="1" notationtype="neume" lines="4" clef.shape="C" clef.line="3"/>
</staffGrp>
</scoreDef>
<section>
<staff n="1">
<layer n="1"/>
</staff>
</section>
</score>
</mdiv>
</body>
</music>
</mei>
11 changes: 10 additions & 1 deletion src/Dashboard/DashboardContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,16 @@ export const uploadAreaHTML =
<div>Name</div>
<div class="sort_name arrow_btn">&#x22C0;</div>
</div>
<div class="file_list" id="mei_list"></div>
<div class="file_list" id="mei_list">
<div class="unpaired_item_container" style="width: 100%; background: #DAEAF1; border-radius: 5px;">
<label class="unpaired_item_label">
<input type="radio" class="unpaired_item_radio" name="mei_radio_group" value="create_mei">
<span style="margin-top: auto;margin-left: 3px;">
Create new MEI file
</span>
</label>
</div>
</div>
</div>
<div id="image_container">
Expand Down
2 changes: 1 addition & 1 deletion src/Dashboard/FileSystem/FileSystemManager.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { IFolder, FileSystemTools } from '.';
import { fetchUploads, db } from '../Storage';
import { fetchUploads } from '../Storage';
import { samples } from '../samples_filenames';

interface FileSystemProps {
Expand Down
2 changes: 1 addition & 1 deletion src/Dashboard/UploadArea.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ModalWindow, ModalWindowView } from '../utils/ModalWindow';
import { addNewFiles, handleUploadAllDocuments, handleMakePair, sortFileByName } from './UploadTools';
import { addNewFiles, handleUploadAllDocuments, handleMakePair, sortFileByName } from './UploadTools';
import { updateDashboard } from './Dashboard';
import { IFolder } from './FileSystem';

Expand Down
83 changes: 81 additions & 2 deletions src/Dashboard/UploadFileManager.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { uuidv4 } from '../utils/random';
import * as vkbeautify from 'vkbeautify';

/**
* Manager for file uploading and pairing process
*/
Expand All @@ -7,6 +10,7 @@ class UploadFileManager {
private allFiles = new Map<string, {file: File, count: number}>();
private folios = new Array<folio>(); // filename, mei_filename, image_filename
// private manuscripts = new Array<string>();
private meiTemplate: string;

// eslint-disable-next-line @typescript-eslint/no-empty-function
private constructor() {}
Expand All @@ -30,6 +34,74 @@ class UploadFileManager {
}
}

public async setMeiTemplate(templatePath: string): Promise<void> {
try {
const response = await fetch(templatePath);
if (!response.ok) {
throw new Error(`Failed to load MEI template from ${templatePath}`);
}

this.meiTemplate = await response.text();
} catch (error) {
console.error(error);
}
}

public createMeiFile(filename: string): File {
try {
if (!this.meiTemplate) {
throw new Error('Cannot find MEI template');
}

const parser = new DOMParser();
const serializer = new XMLSerializer();
const meiDoc = parser.parseFromString(this.meiTemplate, 'text/xml');
const mei = meiDoc.documentElement;

const meiHead = mei.querySelector('meiHead');
meiHead.setAttribute('xml:id', 'm-' + uuidv4());
const fileDesc = mei.querySelector('fileDesc');
fileDesc.setAttribute('xml:id', 'm-' + uuidv4());
const titleStmt = mei.querySelector('titleStmt');
titleStmt.setAttribute('xml:id', 'm-' + uuidv4());
const title = mei.querySelector('title');
title.setAttribute('xml:id', 'm-' + uuidv4());
const pubStmt = mei.querySelector('pubStmt');
pubStmt.setAttribute('xml:id', 'm-' + uuidv4());
const facsimile = mei.querySelector('facsimile');
facsimile.setAttribute('xml:id', 'm-' + uuidv4());
const surface = mei.querySelector('surface');
surface.setAttribute('xml:id', 'm-' + uuidv4());

const mdiv = mei.querySelector('mdiv');
mdiv.setAttribute('xml:id', 'm-' + uuidv4());
const score = mei.querySelector('score');
score.setAttribute('xml:id', 'm-' + uuidv4());
const scoreDef = mei.querySelector('scoreDef');
scoreDef.setAttribute('xml:id', 'm-' + uuidv4());
const staffGrp = mei.querySelector('staffGrp');
staffGrp.setAttribute('xml:id', 'm-' + uuidv4());
const staffDef = mei.querySelector('staffDef');
staffDef.setAttribute('xml:id', 'm-' + uuidv4());
const section = mei.querySelector('section');
section.setAttribute('xml:id', 'm-' + uuidv4());
const staff = mei.querySelector('staff');
staff.setAttribute('xml:id', 'm-' + uuidv4());
const layer = mei.querySelector('layer');
layer.setAttribute('xml:id', 'm-' + uuidv4());
const zone = mei.querySelector('zone');
zone.setAttribute('xml:id', 'm-' + uuidv4());
staff.setAttribute('facs', '#'+zone.getAttribute('xml:id'));

const meiFileContent = vkbeautify.xml(serializer.serializeToString(meiDoc));
const meiBlob = new Blob([meiFileContent], { type: 'text/xml' });

return new File([meiBlob], filename, { type: 'text/xml' });
} catch (error) {
console.error(error);
}
}

public getFile(key: string): File {
if (this.allFiles.has(key)) {
return this.allFiles.get(key).file;
Expand All @@ -55,11 +127,12 @@ class UploadFileManager {
else return 0;
}

public addFolio(name: string, mei: string, image: string): void {
public addFolio(name: string, mei: string, image: string, isCreated: boolean): void {
const newFolio: folio = {
filename: name,
mei_filename: mei,
image_filename: image
image_filename: image,
isCreated: isCreated,
};
this.folios.push(newFolio);
}
Expand All @@ -68,6 +141,11 @@ class UploadFileManager {
// this.manuscripts.push(filename);
// }

public isCreatedFolio(filename: string): boolean {
const folio = this.folios.find((folio) => folio.filename === filename);
return folio ? folio.isCreated : false;
}

public removeFolio(filename: string): void {
const idx = this.folios.findIndex( folio => folio.filename === filename);
this.folios.splice(idx, 1);
Expand Down Expand Up @@ -117,4 +195,5 @@ type folio = {
filename: string,
mei_filename: string,
image_filename: string,
isCreated: boolean,
};
35 changes: 29 additions & 6 deletions src/Dashboard/UploadTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import { IFolder, FileSystemTools } from './FileSystem';

const fm = UploadFileManager.getInstance();

// Set MEI template
const templatePath = 'samples/mei/mei_template.mei';
fm.setMeiTemplate(templatePath);

// Adds new files to upload pairing container and filemanager, returns list of rejected files
export function addNewFiles( files: File[] ): File[] {
const mei_container: HTMLDivElement = document.querySelector('#mei_list');
Expand Down Expand Up @@ -59,7 +63,7 @@ function createUnpairedItem(filename: string, group: string): HTMLDivElement {

const text = document.createElement('span');
text.innerText = formatFilename(filename, 50);
text.setAttribute('style', 'margin-top: auto');
text.setAttribute('style', 'margin-top: auto; margin-left: 3px;');

const delBtn = document.createElement('img');
delBtn.className = 'unpaired_del_btn';
Expand All @@ -86,16 +90,32 @@ export function handleMakePair(): void {
const selectedImageElement: HTMLInputElement = document.querySelector('input[name="image_radio_group"]:checked');
if (selectedMeiElement === null || selectedImageElement === null) return;

const mei_filename = selectedMeiElement.value;
let mei_filename = selectedMeiElement.value;
const image_filename = selectedImageElement.value;
let isCreated = false;

if (selectedMeiElement.value === 'create_mei'){
isCreated = true;
// Change extension
const fn = image_filename.split('.');
if (fn.length > 1) {
fn.pop();
}
fn.push('mei');

mei_filename = fn.join('.');
// Create new MEI file
const newMeiFile = fm.createMeiFile(mei_filename);
fm.addFile(newMeiFile);
}
const filename = mei_filename.substring(0, mei_filename.length-4);
// make and append UI element
const paired_el = createPairedFolio(filename, mei_filename, image_filename);
paired_container.appendChild(paired_el);
// reflect in file manager
fm.addFolio(filename, mei_filename, image_filename);
fm.addFolio(filename, mei_filename, image_filename, isCreated);
// remove from unpaired mei and image lists
selectedMeiElement.parentElement.parentElement.remove();
if (!isCreated) selectedMeiElement.parentElement.parentElement.remove();
selectedImageElement.parentElement.parentElement.remove();
}

Expand All @@ -117,10 +137,13 @@ function createPairedFolio(filename: string, mei_filename: string, image_filenam
// remove folio from UI
folio.remove();
// remove folio from file manager
const isCreated = fm.isCreatedFolio(filename);
fm.removeFolio(filename);
// add items back to unpaired containers
const meiItem = createUnpairedItem(mei_filename, 'mei');
mei_container.appendChild(meiItem);
if (!isCreated) {
const meiItem = createUnpairedItem(mei_filename, 'mei');
mei_container.appendChild(meiItem);
}
const imageItem = createUnpairedItem(image_filename, 'image');
image_container.appendChild(imageItem);
}
Expand Down
29 changes: 16 additions & 13 deletions src/utils/ModalWindow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,11 +161,14 @@ export class ModalWindow implements ModalWindowInterface {
* Set content of modal window
*/
private setModalWindowContent(content?: string): void {
const container = document.getElementById('neon-modal-window-content-container');
const title = document.getElementById('neon-modal-window-header-title');

switch (this.modalWindowView) {
case ModalWindowView.EDIT_TEXT:
document.getElementById('neon-modal-window-content-container').innerHTML = editTextModal;
container.innerHTML = editTextModal;
// set modal window title
document.getElementById('neon-modal-window-header-title').innerText = 'EDIT SYLLABLE TEXT';
title.innerText = 'EDIT SYLLABLE TEXT';

// span and current text of selected-to-edit syllable and filter out unwanted chars
const span = <HTMLSpanElement> document.getElementById('syl_text').querySelectorAll('span.selected-to-edit')[0];
Expand All @@ -177,38 +180,38 @@ export class ModalWindow implements ModalWindowInterface {
break;

case ModalWindowView.HOTKEYS:
document.getElementById('neon-modal-window-content-container').innerHTML = hotkeysModal;
document.getElementById('neon-modal-window-header-title').innerText = 'HOTKEYS';
container.innerHTML = hotkeysModal;
title.innerText = 'HOTKEYS';
break;

case ModalWindowView.VALIDATION_STATUS:
document.getElementById('neon-modal-window-content-container').innerHTML =
container.innerHTML =
`<div style="margin-bottom: 30px;white-space: pre-line;">${content}</div>
<div class="neon-modal-window-btn">
<a href="data:text/plain;charset=utf-8,${encodeURIComponent(content)}" download="validation.log">
Export
</a>
</div>`;
document.getElementById('neon-modal-window-header-title').innerText = 'ERROR LOG';
title.innerText = 'ERROR LOG';
break;

case ModalWindowView.DOCUMENT_UPLOAD:
document.getElementById('neon-modal-window-header-title').innerText = 'DOCUMENT UPLOAD';
document.getElementById('neon-modal-window-content-container').innerHTML = uploadAreaHTML;
title.innerText = 'DOCUMENT UPLOAD';
container.innerHTML = uploadAreaHTML;
break;

case ModalWindowView.MOVE_TO:
document.getElementById('neon-modal-window-header-title').innerText = 'MOVE TO';
title.innerText = 'MOVE TO';
break;

case ModalWindowView.NEW_FOLDER:
document.getElementById('neon-modal-window-header-title').innerText = 'NEW FOLDER';
document.getElementById('neon-modal-window-content-container').innerHTML = newFolderHTML;
title.innerText = 'NEW FOLDER';
container.innerHTML = newFolderHTML;
break;

case ModalWindowView.RENAME:
document.getElementById('neon-modal-window-header-title').innerText = 'RENAME';
document.getElementById('neon-modal-window-content-container').innerHTML = renameHTML;
title.innerText = 'RENAME';
container.innerHTML = renameHTML;
break;

default:
Expand Down
52 changes: 26 additions & 26 deletions verovio-util/verovio-dev/index.js

Large diffs are not rendered by default.

52 changes: 26 additions & 26 deletions verovio-util/verovio.js

Large diffs are not rendered by default.

Loading