Word symbols, initial data
This commit is contained in:
parent
415a0a9230
commit
72c56ec015
6 changed files with 81 additions and 23 deletions
|
@ -2,8 +2,8 @@ Tunic Manual Translation
|
||||||
========================
|
========================
|
||||||
|
|
||||||
This is a tool I developed to help me translate the game manual for the
|
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
|
[Tunic](https://tunicgame.com/) video game, which is written in a mix of English
|
||||||
is written in a mix of English and a constructed language.
|
and a constructed language.
|
||||||
|
|
||||||
> [!CAUTION]
|
> [!CAUTION]
|
||||||
> If you plan to play Tunic in the future please stop reading immediately,
|
> If you plan to play Tunic in the future please stop reading immediately,
|
||||||
|
|
12
index.html
12
index.html
|
@ -46,7 +46,7 @@
|
||||||
<hr/>
|
<hr/>
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<div class="word-symbols">
|
<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">
|
<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_left" d="M 3,9 12,3" />
|
||||||
<path class="word_outer_top_right" d="m 12,3 9,6" />
|
<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_middle" d="m 12,15 v 3 m 0,3 v 12" />
|
||||||
<path class="word_inner_bottom_right" d="m 12,21 9,6" />
|
<path class="word_inner_bottom_right" d="m 12,21 9,6" />
|
||||||
<circle class="word_circle" cx="12" cy="36.2" r="2.8" />
|
<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>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
|
@ -70,10 +70,6 @@
|
||||||
<input id="word_meaning" class="button"/>
|
<input id="word_meaning" class="button"/>
|
||||||
</label>
|
</label>
|
||||||
<textarea id="word_notes" class="button spacing section" placeholder="Notes"></textarea>
|
<textarea id="word_notes" class="button spacing section" placeholder="Notes"></textarea>
|
||||||
<hr/>
|
|
||||||
<div class="section">
|
|
||||||
<div id="word_preview"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="props_symbol" class="ui hidden">
|
<div id="props_symbol" class="ui hidden">
|
||||||
|
@ -83,7 +79,7 @@
|
||||||
<hr/>
|
<hr/>
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<svg class="symbol-editor" viewBox="0 0 24 42">
|
<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 3,9 12,3" />
|
||||||
<path d="m 12,3 9,6" />
|
<path d="m 12,3 9,6" />
|
||||||
<path d="m 3,21 v 6 M 3,9 v 9" />
|
<path d="m 3,21 v 6 M 3,9 v 9" />
|
||||||
|
@ -97,7 +93,7 @@
|
||||||
<path d="m 12,21 9,6" />
|
<path d="m 12,21 9,6" />
|
||||||
<circle cx="12" cy="36.2" r="2.8" />
|
<circle cx="12" cy="36.2" r="2.8" />
|
||||||
</g>
|
</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_left" d="M 3,9 12,3" />
|
||||||
<path id="outer_top_right" d="m 12,3 9,6" />
|
<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" />
|
<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
|
@ -26,6 +26,8 @@ const propsWord = /** @type {HTMLDivElement} */(document.getElementById('props_w
|
||||||
const propsSymbol = /** @type {HTMLDivElement} */(document.getElementById('props_symbol'));
|
const propsSymbol = /** @type {HTMLDivElement} */(document.getElementById('props_symbol'));
|
||||||
const propsLink = /** @type {HTMLDivElement} */(document.getElementById('props_link'));
|
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 wordMeaningInput = /** @type {HTMLInputElement} */(document.getElementById('word_meaning'));
|
||||||
const wordNotesTextarea = /** @type {HTMLTextAreaElement} */(document.getElementById('word_notes'));
|
const wordNotesTextarea = /** @type {HTMLTextAreaElement} */(document.getElementById('word_notes'));
|
||||||
|
|
||||||
|
@ -255,6 +257,8 @@ const addNewElement = (type, x, y, width, height) => {
|
||||||
|
|
||||||
// Refresh page.
|
// Refresh page.
|
||||||
loadPageElements(pageSelect.value);
|
loadPageElements(pageSelect.value);
|
||||||
|
|
||||||
|
showProps(type, id);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -484,7 +488,8 @@ const loadPageElements = (pageId) => {
|
||||||
|
|
||||||
const dictionary = data.dictionary ?? {};
|
const dictionary = data.dictionary ?? {};
|
||||||
if (dictionary) {
|
if (dictionary) {
|
||||||
const dictionaryId = getDictionaryId(pageId, word);
|
const wordSymbols = getWordSymbols(pageId, word);
|
||||||
|
const dictionaryId = getDictionaryId(wordSymbols);
|
||||||
const dictionaryWord = dictionary[dictionaryId];
|
const dictionaryWord = dictionary[dictionaryId];
|
||||||
|
|
||||||
/** @type {HTMLDivElement} */(el.querySelector('.word-meaning')).textContent = dictionaryWord?.tran ?? '';
|
/** @type {HTMLDivElement} */(el.querySelector('.word-meaning')).textContent = dictionaryWord?.tran ?? '';
|
||||||
|
@ -508,6 +513,9 @@ const loadPageElements = (pageId) => {
|
||||||
showProps('SYMBOL', id);
|
showProps('SYMBOL', id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!symbol.v)
|
||||||
|
el.classList.add('empty');
|
||||||
|
|
||||||
currentPageElements[id] = el;
|
currentPageElements[id] = el;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -607,7 +615,7 @@ let lastSelectedWord = '';
|
||||||
* @param {number} pageId
|
* @param {number} pageId
|
||||||
* @param {SymbolData} word
|
* @param {SymbolData} word
|
||||||
*/
|
*/
|
||||||
const getDictionaryId = (pageId, word) => {
|
const getWordSymbols = (pageId, word) => {
|
||||||
const page = data.pages ??= {};
|
const page = data.pages ??= {};
|
||||||
const currentPage = page[pageId] ??= {};
|
const currentPage = page[pageId] ??= {};
|
||||||
const pageSymbols = currentPage.symbols ??= {};
|
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.
|
// 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);
|
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
|
* @param {SymbolData} word
|
||||||
*/
|
*/
|
||||||
const loadWord = (pageId, 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 dictionary = data.dictionary ??= {};
|
||||||
const currentWord = dictionary[lastSelectedWord] ??= {};
|
const currentWord = dictionary[lastSelectedWord] ??= {};
|
||||||
|
|
||||||
wordMeaningInput.value = currentWord.tran ?? '';
|
wordMeaningInput.value = currentWord.tran ?? '';
|
||||||
wordNotesTextarea.value = currentWord.note ?? '';
|
wordNotesTextarea.value = currentWord.note ?? '';
|
||||||
|
|
||||||
// TODO: add preview image for the word to the UI.
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
wordSymbolTemplate.remove();
|
||||||
|
|
||||||
wordMeaningInput.addEventListener('keyup', ev => {
|
wordMeaningInput.addEventListener('keyup', ev => {
|
||||||
const dictionary = data.dictionary ??= {};
|
const dictionary = data.dictionary ??= {};
|
||||||
const currentWord = dictionary[lastSelectedWord] ??= {};
|
const currentWord = dictionary[lastSelectedWord] ??= {};
|
||||||
|
@ -777,6 +808,11 @@ window.addEventListener('keyup', ev => {
|
||||||
console.debug(ev.key);
|
console.debug(ev.key);
|
||||||
|
|
||||||
switch (ev.key) {
|
switch (ev.key) {
|
||||||
|
case 'Escape':
|
||||||
|
ev.preventDefault();
|
||||||
|
hideProps();
|
||||||
|
break;
|
||||||
|
|
||||||
case 'ArrowLeft':
|
case 'ArrowLeft':
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
goToPrevPage();
|
goToPrevPage();
|
||||||
|
@ -823,3 +859,13 @@ window.addEventListener('keyup', ev => {
|
||||||
|
|
||||||
goToPage(pageSelect.value);
|
goToPage(pageSelect.value);
|
||||||
setEditMode(currentEditMode);
|
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);
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
|
@ -123,7 +123,19 @@ html {
|
||||||
width: 3em;
|
width: 3em;
|
||||||
margin: 0 calc(-3em / 8);
|
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;
|
stroke: #fff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -145,7 +157,7 @@ html {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#symbol_preview, #word_preview {
|
#symbol_preview {
|
||||||
background: black;
|
background: black;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
|
|
||||||
|
@ -214,7 +226,7 @@ html {
|
||||||
}
|
}
|
||||||
|
|
||||||
body.mode-view &, body.mode-word & {
|
body.mode-view &, body.mode-word & {
|
||||||
z-index: 2;
|
z-index: 3;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
--element-color: var(--element-base-color);
|
--element-color: var(--element-base-color);
|
||||||
|
@ -249,6 +261,10 @@ html {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
--element-color: var(--element-base-color);
|
--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 & {
|
body.mode-view &, body.mode-link & {
|
||||||
z-index: 3;
|
z-index: 2;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
--element-color: var(--element-base-color);
|
--element-color: var(--element-base-color);
|
||||||
|
|
Loading…
Reference in a new issue