Skip to content

Commit

Permalink
Add pronunciation clues to components pane (#6)
Browse files Browse the repository at this point in the history
* Add pronunciation clues to components pane

This includes colors for tones and pinyin hints when initial
or final are shared among ancestor characters.

* Bump service worker version
  • Loading branch information
mreichhoff authored Jan 14, 2024
1 parent d7a6a46 commit 9c11799
Show file tree
Hide file tree
Showing 5 changed files with 85 additions and 74 deletions.
2 changes: 1 addition & 1 deletion public/asset-service-worker.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// TODO: separate caches for code vs data?
const cacheName = 'hanzigraph-e0065d2d4565a33159e9b886b8574f1eeec07c22';
const cacheName = 'hanzigraph-f016177cb660846e54ae7e4f72a1c478d9390b7a';
self.addEventListener('fetch', (e) => {
if (e.request.method === 'GET' && !e.request.url.includes('firestore')) {
e.respondWith((async () => {
Expand Down
17 changes: 16 additions & 1 deletion public/css/hanzi-graph.css
Original file line number Diff line number Diff line change
Expand Up @@ -273,13 +273,28 @@ ul {
}

.character-data a.navigable {
margin-right: 12px;
margin-right: 20px;
}

.character-data h3 {
margin: 8px 0;
}

.pinyin-relationship {
font-size: 18px;
font-weight: bold;
color: var(--primary-font-color);
}

.related-characters {
font-size: 30px;
font-weight: var(--target-language-font-weight);
}

.nowrap {
white-space: nowrap;
}

.tone1 {
color: var(--tone-1-color);
}
Expand Down
70 changes: 31 additions & 39 deletions public/js/modules/explore.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { hanziBox, notFoundElement, walkThrough, examplesList } from "./dom.js";
import { getActiveGraph, getPartition } from "./options.js";
import { renderCoverageGraph } from "./coverage-graph"
import { diagramKeys, switchDiagramView } from "./ui-orchestrator.js";
import { findPinyinRelationships } from "./pronunciation-parser.js"

let coverageGraph = {};
let charFreqs = {};
Expand Down Expand Up @@ -428,6 +429,33 @@ function renderPronunciations(character, container) {
container.appendChild(definitionElement);
}

function renderRelatedCharacters(character, relatedCharacters, type, container) {
if (relatedCharacters.length === 0) {
container.innerText = type === 'compounds' ? 'This character is not a component of others.' : "No components found. Maybe we can't break this down any more.";
} else {
container.classList.add('related-characters');
}
for (const relatedCharacter of relatedCharacters) {
// TODO: why does order of src and target matter?
const pinyinRelationship = type === 'compounds' ? findPinyinRelationships(relatedCharacter, character) : findPinyinRelationships(character, relatedCharacter);
const relatedAnchor = document.createElement('a');
if (pinyinRelationship) {
relatedAnchor.classList.add('nowrap');
relatedAnchor.innerHTML = `${relatedCharacter} <span class="pinyin-relationship">(${pinyinRelationship})</span>`;
} else {
relatedAnchor.innerText = relatedCharacter;
}
relatedAnchor.classList.add(`tone${getTone(relatedCharacter)}`);
if (relatedCharacter in components) {
relatedAnchor.classList.add('navigable');
relatedAnchor.addEventListener('click', function () {
document.dispatchEvent(new CustomEvent('components-update', { detail: relatedCharacter }));
});
}
container.appendChild(relatedAnchor);
}
}

function renderComponents(word, container) {
let first = true;
for (const character of word) {
Expand All @@ -450,25 +478,14 @@ function renderComponents(word, container) {
componentsHeader.innerText = 'Components';
item.appendChild(componentsHeader);
let componentsContainer = document.createElement('div');
const joinedComponents = components[character].components.join('');
if (joinedComponents) {
componentsContainer.className = 'target';
makeComponentsNavigable(joinedComponents, componentsContainer);
} else {
componentsContainer.innerText = "No components found. Maybe we can't break this down any more.";
}
renderRelatedCharacters(character, components[character].components, 'components', componentsContainer);
item.appendChild(componentsContainer);
let componentsOfHeader = document.createElement('h3');
componentsOfHeader.innerText = 'Compounds';
item.appendChild(componentsOfHeader);
let componentOfContainer = document.createElement('div');
const joinedComponentOf = components[character].componentOf.filter(x => x in hanzi).sort((a, b) => hanzi[a].node.level - hanzi[b].node.level).join('');
if (joinedComponentOf) {
componentOfContainer.className = 'target';
makeComponentsNavigable(joinedComponentOf, componentOfContainer);
} else {
componentOfContainer.innerText = 'This character is not a component of others.'
}
const compounds = components[character].componentOf.filter(x => x in hanzi).sort((a, b) => hanzi[a].node.level - hanzi[b].node.level);
renderRelatedCharacters(character, compounds, 'compounds', componentOfContainer);
item.appendChild(componentOfContainer);
container.appendChild(item);
}
Expand Down Expand Up @@ -647,30 +664,5 @@ let makeSentenceNavigable = function (text, container, noExampleChange) {
container.appendChild(sentenceContainer);
return anchorList;
};
// TODO: combine with makeSentenceNavigable, or just drop this, it's not that complicated
function makeComponentsNavigable(text, container) {
let sentenceContainer = document.createElement('span');
sentenceContainer.className = "sentence-container";

let anchorList = [];
for (let i = 0; i < text.length; i++) {
(function (character) {
let a = document.createElement('a');
a.textContent = character;
if (character in components) {
a.className = 'navigable';
}
a.addEventListener('click', function () {
if (character in components) {
document.dispatchEvent(new CustomEvent('components-update', { detail: character }));
}
});
anchorList.push(a);
sentenceContainer.appendChild(a);
}(text[i]));
}
container.appendChild(sentenceContainer);
return anchorList;
}

export { initialize, makeSentenceNavigable, addTextToSpeech };
34 changes: 2 additions & 32 deletions public/js/modules/graph.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { switchToState, stateKeys, diagramKeys, switchDiagramView } from "./ui-orchestrator";
import { getActiveGraph } from "./options";
import { parsePinyin, trimTone } from "./pronunciation-parser";
import { parsePinyin, trimTone, findPinyinRelationships } from "./pronunciation-parser";

const parent = document.getElementById('graph-container');
const graphContainer = document.getElementById('graph');
Expand Down Expand Up @@ -190,37 +190,7 @@ function edgeLabel(element) {
}
const sourceCharacter = element.data('source')[element.data('source').length - 1];
const targetCharacter = element.data('target')[element.data('source').length];
const sourceDefs = definitions[sourceCharacter];
const targetDefs = definitions[targetCharacter];
if (!sourceDefs || !targetDefs) {
return '';
}
for (const definition of sourceDefs.filter(x => x.pinyin)) {
const srcPinyin = trimTone(definition.pinyin.toLowerCase());
const [srcInitial, srcFinal] = parsePinyin(srcPinyin);
const targetDefsWithPinyin = targetDefs.filter(x => x.pinyin);
// O(n^2), but there's never more than a few definitions.
// first pass: check for exact matches (minus tone, already expressed through color)
for (const targetDef of targetDefsWithPinyin) {
const targetPinyin = trimTone(targetDef.pinyin.toLowerCase());
if (targetPinyin === srcPinyin) {
return targetPinyin;
}
}
// second pass: we didn't find an exact match, so see if there are any initials
// or finals that match.
for (const targetDef of targetDefsWithPinyin) {
const targetPinyin = trimTone(targetDef.pinyin.toLowerCase());
const [targetInitial, targetFinal] = parsePinyin(targetPinyin);
if (targetInitial && (targetInitial === srcInitial)) {
return `${targetInitial}-`;
}
if (targetFinal && (targetFinal === srcFinal)) {
return `-${targetFinal}`;
}
}
}
return '';
return findPinyinRelationships(sourceCharacter, targetCharacter);
}

function getStylesheet() {
Expand Down
36 changes: 35 additions & 1 deletion public/js/modules/pronunciation-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,38 @@ function parsePinyin(pinyin) {
return [initial, final];
}

export { parsePinyin, trimTone }
function findPinyinRelationships(sourceCharacter, targetCharacter) {
const sourceDefs = definitions[sourceCharacter];
const targetDefs = definitions[targetCharacter];
if (!sourceDefs || !targetDefs) {
return '';
}
for (const definition of sourceDefs.filter(x => x.pinyin)) {
const srcPinyin = trimTone(definition.pinyin.toLowerCase());
const [srcInitial, srcFinal] = parsePinyin(srcPinyin);
const targetDefsWithPinyin = targetDefs.filter(x => x.pinyin);
// O(n^2), but there's never more than a few definitions.
// first pass: check for exact matches (minus tone, already expressed through color)
for (const targetDef of targetDefsWithPinyin) {
const targetPinyin = trimTone(targetDef.pinyin.toLowerCase());
if (targetPinyin === srcPinyin) {
return targetPinyin;
}
}
// second pass: we didn't find an exact match, so see if there are any initials
// or finals that match.
for (const targetDef of targetDefsWithPinyin) {
const targetPinyin = trimTone(targetDef.pinyin.toLowerCase());
const [targetInitial, targetFinal] = parsePinyin(targetPinyin);
if (targetInitial && (targetInitial === srcInitial)) {
return `${targetInitial}-`;
}
if (targetFinal && (targetFinal === srcFinal)) {
return `-${targetFinal}`;
}
}
}
return '';
}

export { parsePinyin, trimTone, findPinyinRelationships }

0 comments on commit 9c11799

Please sign in to comment.