Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
55a5367
fix: pin diva.js to stable commit to fix fresh installs
zih-syuan Jun 12, 2026
807715c
chore: add Hufnagel font zip at default location
zih-syuan Jun 12, 2026
f01c66f
chore: sync Hufnagel zip with neume glyphs from font branch
zih-syuan Jun 29, 2026
8cfa1a8
feat: add Notation Type dropdown UI scaffold to Display panel
zih-syuan Jun 29, 2026
c158db3
feat: wire Notation Type dropdown to live font switching
zih-syuan Jun 29, 2026
5fc7449
style(display-panel): tweak notation dropdown tooltip text
zih-syuan Jun 30, 2026
5602d3a
feat: render insert glyph icons dynamically
zih-syuan Jun 30, 2026
7e8da31
fix: align dynamic insert glyph icons
zih-syuan Jun 30, 2026
b373d5f
feat(insert): add Hufnagel primitives and fClef variant with Bravura …
zih-syuan Jun 30, 2026
d114fa1
feat(insert): add Hufnagel grouping tab with font-specific preview icons
zih-syuan Jun 30, 2026
4190021
fix(insert): refresh grouping tab icons on notation type change
zih-syuan Jun 30, 2026
696c13e
fix(insert): use Hufnagel-rendered icon for distropha primitive
zih-syuan Jun 30, 2026
12ff298
fix(insert): prioritize hufnagel PNG override over Bravura glyph fall…
zih-syuan Jun 30, 2026
82a1841
fix(verovio): set fontFallback to Bravura for Hufnagel mode
zih-syuan Jun 30, 2026
2545e1a
fix(display): update Hufnagel tooltip to reflect Bravura fallback
zih-syuan Jun 30, 2026
723b52c
fix(mei): keep Verovio-safe MEI in sync with canonical Hufnagel encoding
zih-syuan Jul 2, 2026
a1ce921
fix(insert): remove hufnagel liquescent down and duplicate F clef
zih-syuan Jul 2, 2026
063e038
fix(insert): adjust hufnagel primitive icon vertical alignment
zih-syuan Jul 2, 2026
7a95714
feat(upload): write notationtype on upload, fix Hufnagel con/ligated …
zih-syuan Jul 2, 2026
4e52519
chore: sync Hufnagel zip with latest font branch update
clarelinzx Jul 2, 2026
3028e60
Update hufnagel font (custos)
clarelinzx Jul 7, 2026
e7887ad
feat(verovio): port Hufnagel font-switching to production worker
zih-syuan Jul 7, 2026
1e40fb5
refactor(mei): simplify notation type handling
clarelinzx Jul 21, 2026
44e3334
refactor(display): decouple notation controls from NeonView
clarelinzx Jul 21, 2026
4717177
refactor(font): simplify custom font loading
clarelinzx Jul 21, 2026
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
Binary file added assets/Bravura.zip
Binary file not shown.
Binary file added assets/Hufnagel.zip
Binary file not shown.
Binary file added assets/fonts/Bravura.woff2
Binary file not shown.
1,311 changes: 1,311 additions & 0 deletions assets/fonts/Bravura.xml

Large diffs are not rendered by default.

Binary file added assets/fonts/Hufnagel.woff2
Binary file not shown.
21 changes: 21 additions & 0 deletions assets/fonts/Hufnagel.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version='1.0' encoding='UTF-8'?>
<bounding-boxes font-family="Hufnagel" units-per-em="1000">
<g c="E990" x="1.0" y="-84.0" w="159.0" h="168.0" h-a-x="160" n="chantPunctum" />
<g c="EA06" x="1.0" y="-84.0" w="265.0" h="169.0" h-a-x="160" n="chantCustosStemUpPosMiddle" />
<g c="E996" x="1.0" y="-315.0" w="159.0" h="399.0" h-a-x="160" n="chantPunctumVirga" />
<g c="E997" x="-0.0" y="-84.7" w="158.99" h="398.54" h-a-x="160" n="chantPunctumVirgaReversed" />
<g c="E906" x="-0.1" y="-211.0" w="252.1" h="347.89" h-a-x="160" n="chantCclef" />
<g c="E902" x="0.29" y="-181.65" w="170.71" h="327.65" h-a-x="160" n="chantFclef" />
<g c="E9B4" x="0.0" y="0.0" w="0.0" h="0.0" h-a-x="0" n="chantEntryLineAsc2nd" />
<g c="E9B5" x="0.0" y="0.0" w="0.0" h="0.0" h-a-x="0" n="chantEntryLineAsc3rd" />
<g c="E9B6" x="0.0" y="0.0" w="0.0" h="0.0" h-a-x="0" n="chantEntryLineAsc4th" />
<g c="E9B7" x="0.0" y="0.0" w="0.0" h="0.0" h-a-x="0" n="chantEntryLineAsc5th" />
<g c="E9B8" x="0.0" y="0.0" w="0.0" h="0.0" h-a-x="440" n="chantEntryLineAsc6th" />
<g c="E9B9" x="-0.0" y="-247.0" w="260.0" h="301.03" h-a-x="460" n="chantLigaturaDesc2nd" />
<g c="E9BA" x="-0.0" y="-377.0" w="260.0" h="431.03" h-a-x="460" n="chantLigaturaDesc3rd" />
<g c="E9BB" x="-0.0" y="-496.0" w="260.0" h="550.03" h-a-x="463" n="chantLigaturaDesc4th" />
<g c="E9BE" x="0.0" y="0.0" w="0.0" h="0.0" h-a-x="0" n="chantConnectingLineAsc3rd" />
<g c="E994" x="-15.0" y="-173.15" w="259.29" h="345.92" h-a-x="140" n="chantAuctumAsc" />
<g c="E995" x="-67.0" y="-169.0" w="205.0" h="432.0" h-a-x="138" n="chantAuctumDesc" />
<g c="E9BC" x="-0.0" y="-621.0" w="260.0" h="675.03" h-a-x="481" n="chantLigaturaDesc5th" />
</bounding-boxes>
Binary file added assets/img/hufnagel/climacus.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/img/hufnagel/clivis.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/img/hufnagel/distropha.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/img/hufnagel/pes.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/img/hufnagel/porrectus.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/img/hufnagel/pressus.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/img/hufnagel/scandicus.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/img/hufnagel/torculus.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
31 changes: 31 additions & 0 deletions assets/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,20 @@

@import url('https://unpkg.com/diva.js@6.0.2/build/diva.css');

@font-face {
font-family: 'Bravura';
src: url('./fonts/Bravura.woff2') format('woff2');
font-style: normal;
font-weight: normal;
}

@font-face {
font-family: 'Hufnagel';
src: url('./fonts/Hufnagel.woff2') format('woff2');
font-style: normal;
font-weight: normal;
}

/* ----------------------- */
/* General styles
/* ----------------------- */
Expand Down Expand Up @@ -807,6 +821,23 @@ input[type='checkbox'].debug-mode-toggle:checked + label:before {
width: 42px;
height: 42px;
}
.glyph-icon {
align-items: center;
display: flex;
height: 24px;
justify-content: center;
line-height: 0;
pointer-events: none;
width: 24px;
}
.glyph-icon svg {
display: block;
fill: currentColor;
height: 100%;
overflow: visible;
pointer-events: none;
width: 100%;
}
.longel {
width: 100px;
height: 42px;
Expand Down
35 changes: 34 additions & 1 deletion deployment/server/css/dashboard.css
Original file line number Diff line number Diff line change
Expand Up @@ -617,7 +617,7 @@ input.filename-input, select.file-input, textarea.file-input {
}

.folio_list {
height: 80%;
height: 65%;
width: 100%;
overflow: scroll;
}
Expand Down Expand Up @@ -647,6 +647,39 @@ input.filename-input, select.file-input, textarea.file-input {
opacity: 0.4;
}

#notation_type_container {
height: 15%;
width: 100%;
border-top: 1px solid #dfdfdf;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}

/* .file-container-title normally sizes to 10% of a much taller panel;
inside this short container that shrinks it below its own text height,
so it visually overlaps the radio row underneath. Override to auto. */
#notation_type_container .file-container-title {
height: auto;
border-bottom: none;
margin-bottom: 4px;
}

.notation_type_options {
display: flex;
align-items: center;
justify-content: center;
}

.notation_type_option {
font-size: 12px;
cursor: pointer;
display: inline-flex;
align-items: center;
margin: 0 6px;
}

#upload_button_container {
float: right;
height: 10%;
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
},
"dependencies": {
"d3": "^5.11.0",
"diva.js": "github:DDMAL/diva.js#master",
"diva.js": "github:DDMAL/diva.js#5f530483f653db68af1cbb92bc8cc967b51763c3",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this specific version?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

package.json referenced #master, but that branch no longer exists, so installs that resolve the dependency afresh fail. The existing yarn.lock pointed to commit 5f530483, which was already known to work with Neon, so I pinned package.json to that commit as the temporary workaround described in issue #1365 .

"http-server": "^14.1.1",
"jsonschema": "^1.2.4",
"pouchdb": "^7.1.1",
Expand Down
14 changes: 14 additions & 0 deletions src/Dashboard/DashboardContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,20 @@ export const uploadAreaHTML = `<div id="document-upload-container">
</div>
<div class="folio_list" id="paired_list"></div>

<div id="notation_type_container">
<div class="file-container-title">Notation Type</div>
<div class="notation_type_options">
<label class="notation_type_option">
<input type="radio" name="upload_notation_type" value="square" checked>
Square
</label>
<label class="notation_type_option">
<input type="radio" name="upload_notation_type" value="hufnagel">
Hufnagel
</label>
</div>
</div>

<div id="upload_button_container">
<div class="action_button" id="upload_button">
Upload
Expand Down
23 changes: 19 additions & 4 deletions src/Dashboard/Storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { v4 as uuidv4 } from 'uuid';
import PouchDB from 'pouchdb';
import { AllDocs, Doc, uploadsInfo } from '../Types';
import * as localManifest from '../../assets/manifest.json';
import { restoreHufnagelForStorage } from '../utils/ConvertMei';

export const db = new PouchDB('Neon-User-Storage');

Expand Down Expand Up @@ -31,19 +32,20 @@ export function createManifest(
title: string,
mei: File,
bg: File,
notationType: string,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you still need notationType here? I don't think you need to touch this file.

): Promise<string> {
return new Promise(async (resolve) => {
const manifest = JSON.parse(JSON.stringify(localManifest));
manifest['@id'] = id;
manifest['title'] = title;
manifest['timestamp'] = new Date().toISOString();

const meiPromise = new Promise((resolve) => {
const meiTextPromise = new Promise<string>((resolve) => {
const meiReader = new FileReader();
meiReader.addEventListener('load', () => {
resolve(meiReader.result);
resolve(meiReader.result as string);
});
meiReader.readAsDataURL(mei);
meiReader.readAsText(mei);
});

const bgPromise = new Promise((resolve) => {
Expand All @@ -54,9 +56,22 @@ export function createManifest(
bgReader.readAsDataURL(bg);
});

const meiUri = await meiPromise;
const meiText = await meiTextPromise;
const meiUri =
'data:application/mei+xml;base64,' +
window.btoa(restoreHufnagelForStorage(meiText, notationType));
const bgUri = await bgPromise;

// Pre-seed this folio's LocalSettings entry (keyed by id, same format
// LocalSettings itself writes) so the Editing page's notation type
// dropdown reflects this choice on first open. The Editing page never
// reads @notationtype back out of the MEI to initialize the dropdown -
// it relies solely on this per-folio localStorage entry - and by the
// time it's loaded there, the MEI's real notationtype has already been
// stripped to a generic value for Verovio (see ConvertMei.ts), so it
// can't be recovered from the MEI at that point either.
window.localStorage.setItem(id, JSON.stringify({ notationType }));

manifest['image'] = bgUri;
manifest['mei_annotations'] = [
{
Expand Down
7 changes: 6 additions & 1 deletion src/Dashboard/UploadArea.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ async function handleUploadUpdate(
const spinner = document.querySelector('#uploading_spinner');
spinner.classList.add('visible');

handleUploadAllDocuments(currentFolder)
const selectedNotationType: HTMLInputElement = document.querySelector(
'input[name="upload_notation_type"]:checked',
);
const notationType = selectedNotationType?.value ?? 'square';

handleUploadAllDocuments(currentFolder, notationType)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need notation type during upload?

.then((results) => {
setTimeout(async () => {
await updateDashboard();
Expand Down
6 changes: 4 additions & 2 deletions src/Dashboard/UploadTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,12 +178,13 @@ function createPairedFolio(

export async function handleUploadAllDocuments(
currentFolder: IFolder,
notationType: string,
): Promise<{ status: string; value?: string; reason?: any }[]> {
const folioPromises = fm
.getFolios()
.map(async ([name, mei, image]: [string, File, File]) => {
const id = uuidv4();
return await uploadFolio(id, name, mei, image, currentFolder);
return await uploadFolio(id, name, mei, image, currentFolder, notationType);
});

const manuscriptPromises = []; //fm.getManuscripts()
Expand All @@ -208,13 +209,14 @@ async function uploadFolio(
mei: File,
image: File,
currentFolder: IFolder,
notationType: string,
): Promise<string | null> {
const newName = fnConflictHandler(
name,
FileSystemTools.getAllNames(currentFolder),
);
return (
createManifest(id, newName, mei, image)
createManifest(id, newName, mei, image, notationType)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above. Why do you need notation type in manifest?

.then((manifest) => {
const manifestBlob = new Blob([JSON.stringify(manifest, null, 2)], {
type: 'application/ld+json',
Expand Down
43 changes: 43 additions & 0 deletions src/DisplayPanel/DisplayControls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,48 @@ function setBurgerControls(): void {
});
}

/**
* Set click listeners for Notation Type dropdown (Square / Hufnagel).
* Update the stored setting and notify the active view when the type changes.
*/
export function setNotationTypeControls(): void {
const dropdown = document.getElementById('notation-type-dropdown');
const label = document.getElementById('notation-type-label');

function applyNotationType(type: string): void {
label.textContent = `\xA0- ${type[0].toUpperCase() + type.slice(1)}`;
setSettings({ notationType: type });
document.dispatchEvent(
new CustomEvent('notationtypechange', { detail: { type } }),
);
}

function notationTypeClickaway(): void {
document.body.removeEventListener('click', notationTypeClickaway);
dropdown.classList.remove('is-active');
}

document.getElementById('notation-type-button').addEventListener('click', (evt) => {
evt.stopPropagation();
dropdown.classList.toggle('is-active');
if (dropdown.classList.contains('is-active')) {
document.body.addEventListener('click', notationTypeClickaway);
} else {
document.body.removeEventListener('click', notationTypeClickaway);
}
});

['square', 'hufnagel'].forEach((type) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks weird. I recommend you make notation type a type.

document.getElementById(`notation-type-${type}`).addEventListener('click', () => {
dropdown.classList.remove('is-active');
document.body.removeEventListener('click', notationTypeClickaway);
applyNotationType(type);
});
});

applyNotationType(getSettings().notationType ?? 'square');
}

/**
* Set listener for "Display All" button in Display panel.
*/
Expand Down Expand Up @@ -583,6 +625,7 @@ export function initDisplayControls(
setHighlightKeyControls();
setDisplayAllListener();
loadHighlightSettings();
setNotationTypeControls();

const displayContents = document.getElementById('displayContents');
const toggleDisplay = document.getElementById('toggleDisplay');
Expand Down
25 changes: 24 additions & 1 deletion src/DisplayPanel/DisplayPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,30 @@ function displayControlsPanel(handleZoom: ZoomHandler): string {

</div>
</div>


<div class="panel-content-subsection">
<div id="notation-type-options-title" class="panel-sub-title">Notation Type:</div>
<div class="dropdown" id="notation-type-dropdown">
<div class="dropdown-trigger">
<button class="side-panel-btn" id="notation-type-button" aria-haspopup="true" aria-controls="notation-type-menu" style="width: auto">
<span>Type</span>
<span id="notation-type-label">&nbsp;- Square</span>
<svg class="icon">
<use xlink:href="${__ASSET_PREFIX__}assets/img/icons.svg#dropdown-down"></use>
</svg>
</button>
</div>
<div class="dropdown-menu" id="notation-type-menu" role="menu">
<div class="dropdown-content">
<a aria-role="menuitem" class="dropdown-item" id="notation-type-square"
title="Font: Bravura. Fallback: Leipzig.">Square</a>
<a aria-role="menuitem" class="dropdown-item" id="notation-type-hufnagel"
title="Font: Hufnagel (custom). Fallback: Bravura.">Hufnagel</a>
</div>
</div>
</div>
</div>

</div>`;

return contents;
Expand Down
3 changes: 3 additions & 0 deletions src/DivaView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ class DivaView implements ViewInterface {
manifest: string,
) {
this.neonView = neonView;
document.addEventListener('notationtypechange', (evt: CustomEvent) => {
this.neonView.setNotationType(evt.detail.type);
});
this.updateCallbacks = [];
this.divaReady = false;
this.diva = new Diva('container', {
Expand Down
Loading
Loading