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
|
||||
[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,
|
||||
|
|
12
index.html
12
index.html
|
@ -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
|
@ -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);
|
||||
})();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue