1
0
Fork 0

Word symbols, initial data

This commit is contained in:
Fabio Iotti 2024-12-08 15:57:57 +01:00
parent 415a0a9230
commit 72c56ec015
Signed by: bruce965
GPG key ID: 4EC13D9158A96B4C
6 changed files with 81 additions and 23 deletions

View file

@ -2,8 +2,8 @@ Tunic Manual Translation
========================
This is a tool I developed to help me translate the game manual for the
[Tunic](https://en.wikipedia.org/wiki/Tunic_(video_game)) video game, which
is written in a mix of English and a constructed language.
[Tunic](https://tunicgame.com/) video game, which is written in a mix of English
and a constructed language.
> [!CAUTION]
> If you plan to play Tunic in the future please stop reading immediately,

View file

@ -46,7 +46,7 @@
<hr/>
<div class="section">
<div class="word-symbols">
<svg viewBox="0 0 24 42">
<svg viewBox="0 0 24 42" data-symbol="">
<g fill="none" stroke="#0000" stroke-linecap="round">
<path class="word_outer_top_left" d="M 3,9 12,3" />
<path class="word_outer_top_right" d="m 12,3 9,6" />
@ -60,7 +60,7 @@
<path class="word_inner_bottom_middle" d="m 12,15 v 3 m 0,3 v 12" />
<path class="word_inner_bottom_right" d="m 12,21 9,6" />
<circle class="word_circle" cx="12" cy="36.2" r="2.8" />
<path d="M 3,18 H 21" class="active" />
<path class="word_middle_line" d="M 3,18 H 21" />
</g>
</svg>
</div>
@ -70,10 +70,6 @@
<input id="word_meaning" class="button"/>
</label>
<textarea id="word_notes" class="button spacing section" placeholder="Notes"></textarea>
<hr/>
<div class="section">
<div id="word_preview"></div>
</div>
</div>
<div id="props_symbol" class="ui hidden">
@ -83,7 +79,7 @@
<hr/>
<div class="section">
<svg class="symbol-editor" viewBox="0 0 24 42">
<g fill="none" stroke="#282828" stroke-linecap="round">
<g fill="none" stroke="#282828" stroke-width="2" stroke-linecap="round">
<path d="M 3,9 12,3" />
<path d="m 12,3 9,6" />
<path d="m 3,21 v 6 M 3,9 v 9" />
@ -97,7 +93,7 @@
<path d="m 12,21 9,6" />
<circle cx="12" cy="36.2" r="2.8" />
</g>
<g fill="none" stroke="#0000" stroke-linecap="round">
<g fill="none" stroke="#0000" stroke-width="2" stroke-linecap="round">
<path id="outer_top_left" d="M 3,9 12,3" />
<path id="outer_top_right" d="m 12,3 9,6" />
<path id="outer_left" d="m 3,21 v 6 M 3,9 v 9" />

File diff suppressed because one or more lines are too long

View file

@ -26,6 +26,8 @@ const propsWord = /** @type {HTMLDivElement} */(document.getElementById('props_w
const propsSymbol = /** @type {HTMLDivElement} */(document.getElementById('props_symbol'));
const propsLink = /** @type {HTMLDivElement} */(document.getElementById('props_link'));
const wordSymbols = /** @type {SVGElement} */(/** @type {unknown} */(document.querySelector('.word-symbols')));
const wordSymbolTemplate = /** @type {SVGSVGElement} */(wordSymbols.querySelector('svg'));
const wordMeaningInput = /** @type {HTMLInputElement} */(document.getElementById('word_meaning'));
const wordNotesTextarea = /** @type {HTMLTextAreaElement} */(document.getElementById('word_notes'));
@ -255,6 +257,8 @@ const addNewElement = (type, x, y, width, height) => {
// Refresh page.
loadPageElements(pageSelect.value);
showProps(type, id);
};
/**
@ -484,7 +488,8 @@ const loadPageElements = (pageId) => {
const dictionary = data.dictionary ?? {};
if (dictionary) {
const dictionaryId = getDictionaryId(pageId, word);
const wordSymbols = getWordSymbols(pageId, word);
const dictionaryId = getDictionaryId(wordSymbols);
const dictionaryWord = dictionary[dictionaryId];
/** @type {HTMLDivElement} */(el.querySelector('.word-meaning')).textContent = dictionaryWord?.tran ?? '';
@ -508,6 +513,9 @@ const loadPageElements = (pageId) => {
showProps('SYMBOL', id);
});
if (!symbol.v)
el.classList.add('empty');
currentPageElements[id] = el;
}
@ -607,7 +615,7 @@ let lastSelectedWord = '';
* @param {number} pageId
* @param {SymbolData} word
*/
const getDictionaryId = (pageId, word) => {
const getWordSymbols = (pageId, word) => {
const page = data.pages ??= {};
const currentPage = page[pageId] ??= {};
const pageSymbols = currentPage.symbols ??= {};
@ -628,7 +636,23 @@ const getDictionaryId = (pageId, word) => {
// TODO: handle upside-down words (add a checkbox on the word rectangle), sorting symbols from right to left.
wordSymbols.sort((a, b) => a.x - b.x);
return wordSymbols.map(s => s.v.toString(36)).join('-');
return wordSymbols.map(s => s.v);
};
/** @param {number[]} wordSymbols */
const getDictionaryId = (wordSymbols) => {
return wordSymbols.map(v => v.toString(36)).join('-');
};
/** @param {number} symbol */
const getSymbolMaskForCss = (symbol) => {
let result = '';
for (let c = 'a'.charCodeAt(0); symbol; c++, symbol >>= 1) {
if (symbol & 1)
result += String.fromCharCode(c);
}
return result;
};
/**
@ -636,21 +660,28 @@ const getDictionaryId = (pageId, word) => {
* @param {SymbolData} word
*/
const loadWord = (pageId, word) => {
lastSelectedWord = getDictionaryId(pageId, word);
const selectedWordSymbols = getWordSymbols(pageId, word);
lastSelectedWord = getDictionaryId(selectedWordSymbols);
// TODO: clear existing symbols in UI.
while (wordSymbols.firstChild)
wordSymbols.firstChild.remove();
// TODO: add word symbols to UI.
for (const symbol of selectedWordSymbols) {
const symbolMask = getSymbolMaskForCss(symbol);
const el = /** @type {SVGSVGElement} */(wordSymbolTemplate.cloneNode(true));
el.setAttribute('data-symbol', symbolMask);
wordSymbols.appendChild(el);
}
const dictionary = data.dictionary ??= {};
const currentWord = dictionary[lastSelectedWord] ??= {};
wordMeaningInput.value = currentWord.tran ?? '';
wordNotesTextarea.value = currentWord.note ?? '';
// TODO: add preview image for the word to the UI.
};
wordSymbolTemplate.remove();
wordMeaningInput.addEventListener('keyup', ev => {
const dictionary = data.dictionary ??= {};
const currentWord = dictionary[lastSelectedWord] ??= {};
@ -777,6 +808,11 @@ window.addEventListener('keyup', ev => {
console.debug(ev.key);
switch (ev.key) {
case 'Escape':
ev.preventDefault();
hideProps();
break;
case 'ArrowLeft':
ev.preventDefault();
goToPrevPage();
@ -823,3 +859,13 @@ window.addEventListener('keyup', ev => {
goToPage(pageSelect.value);
setEditMode(currentEditMode);
if (Object.keys(data).length == 0) {
(async () => {
const response = await fetch("res/data.json");
const exampleData = await response.json();
data = exampleData;
goToPage(pageSelect.value);
})();
}

View file

@ -123,7 +123,19 @@ html {
width: 3em;
margin: 0 calc(-3em / 8);
.active {
.word_middle_line,
&[data-symbol*='a'] .word_outer_top_left,
&[data-symbol*='b'] .word_outer_top_right,
&[data-symbol*='c'] .word_outer_left,
&[data-symbol*='d'] .word_outer_bottom_left,
&[data-symbol*='e'] .word_outer_bottom_right,
&[data-symbol*='f'] .word_inner_top_left,
&[data-symbol*='g'] .word_inner_top_middle,
&[data-symbol*='h'] .word_inner_top_right,
&[data-symbol*='i'] .word_inner_bottom_left,
&[data-symbol*='j'] .word_inner_bottom_middle,
&[data-symbol*='k'] .word_inner_bottom_right,
&[data-symbol*='l'] .word_circle {
stroke: #fff;
}
}
@ -145,7 +157,7 @@ html {
}
}
#symbol_preview, #word_preview {
#symbol_preview {
background: black;
background-repeat: no-repeat;
@ -214,7 +226,7 @@ html {
}
body.mode-view &, body.mode-word & {
z-index: 2;
z-index: 3;
cursor: pointer;
pointer-events: auto;
--element-color: var(--element-base-color);
@ -249,6 +261,10 @@ html {
cursor: pointer;
pointer-events: auto;
--element-color: var(--element-base-color);
&.empty {
background: color(from var(--element-base-color) srgb r g b / .5);
}
}
}
@ -262,7 +278,7 @@ html {
}
body.mode-view &, body.mode-link & {
z-index: 3;
z-index: 2;
cursor: pointer;
pointer-events: auto;
--element-color: var(--element-base-color);