Files
ascii-pwa/app.js
T
2026-10-10 12:51:33 +02:00

168 lines
5.8 KiB
JavaScript

import {render, styles, resolveFont} from './renderer.js';
import {translations} from './translations.js';
import {setupPWA} from './pwa.js';
const text = document.getElementById('text');
const style = document.getElementById('style');
const output = document.getElementById('output');
const status = document.getElementById('status');
const language = document.getElementById('language');
const theme = document.getElementById('theme');
let lang = document.documentElement.lang;
let statusKey = '';
const t = key => translations[lang][key];
const showStatus = key => {
statusKey = key;
status.textContent = key ? t(key) : '';
};
const savePreference = (key, value) => {
try { localStorage.setItem(`ascii-pwa-${key}`, value); } catch { /* Storage may be disabled. */ }
};
function translate() {
document.documentElement.lang = lang;
language.value = lang;
document.querySelectorAll('[data-i18n]').forEach(element => {
element.textContent = t(element.dataset.i18n);
});
output.setAttribute('aria-label', t('outputLabel'));
document.querySelector('.logo').setAttribute('aria-label', t('homeLabel'));
updateFontNavigation();
showStatus(statusKey);
}
let drawVersion = 0;
let drawTimer;
function updateFontNavigation() {
const next = styles[(styles.indexOf(style.value) + 1) % styles.length];
document.getElementById('next-font').textContent = t('nextFont').replace('{name}', next);
}
async function draw() {
const version = ++drawVersion;
const copy = document.getElementById('copy');
const download = document.getElementById('download');
copy.disabled = true;
download.disabled = true;
try {
const result = await render(text.value, style.value);
if (version !== drawVersion) return;
output.textContent = result;
showStatus('');
} catch {
if (version !== drawVersion) return;
output.textContent = '';
showStatus('renderFailed');
}
document.getElementById('copy').disabled = !output.textContent.trim();
document.getElementById('download').disabled = !output.textContent.trim();
}
// Explicit shared URLs take precedence; a normal visit always opens the homepage.
const params = new URL(window.location.href).searchParams;
style.replaceChildren(...styles.map(font => {
const option = document.createElement('option');
option.value = font;
option.textContent = font;
return option;
}));
style.value = 'Standard';
try {
const savedFont = localStorage.getItem('ascii-pwa-font');
if (savedFont) style.value = resolveFont(savedFont);
} catch { /* Use the default when saved preferences are unavailable. */ }
if (params.has('text')) text.value = params.get('text').slice(0, 500);
if (params.has('font')) style.value = resolveFont(params.get('font'));
savePreference('font', style.value);
theme.value = document.documentElement.dataset.theme;
translate();
draw();
document.getElementById('render-form').addEventListener('submit', event => {
event.preventDefault();
draw();
});
style.addEventListener('change', () => {
clearTimeout(drawTimer);
savePreference('font', style.value);
updateFontNavigation();
draw();
});
text.addEventListener('input', () => {
clearTimeout(drawTimer);
++drawVersion;
document.getElementById('copy').disabled = true;
document.getElementById('download').disabled = true;
drawTimer = setTimeout(draw, 150);
});
for (const [id, direction] of [['prev-font', -1], ['next-font', 1]]) {
document.getElementById(id).addEventListener('click', () => {
const index = (styles.indexOf(style.value) + direction + styles.length) % styles.length;
style.value = styles[index];
style.dispatchEvent(new Event('change'));
});
}
language.addEventListener('change', () => {
lang = language.value;
savePreference('language', lang);
translate();
});
theme.addEventListener('change', () => {
document.documentElement.dataset.theme = theme.value;
savePreference('theme', theme.value);
});
function copyText(value) {
const field = document.createElement('textarea');
const focused = document.activeElement;
field.value = value;
field.readOnly = true;
field.style.cssText = 'position:fixed;left:-9999px;top:0;font-size:16px';
document.body.append(field);
try {
field.focus({preventScroll: true});
field.select();
field.setSelectionRange(0, field.value.length);
if (!document.execCommand('copy')) throw new Error('Copy failed');
} finally {
field.remove();
focused?.focus({preventScroll: true});
}
}
document.getElementById('copy').addEventListener('click', () => {
try {
copyText(output.textContent);
showStatus('copied');
} catch {
const range = document.createRange();
range.selectNodeContents(output);
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
showStatus('copyFailed');
}
});
document.getElementById('download').addEventListener('click', () => {
const url = URL.createObjectURL(new Blob([output.textContent + '\n'], {type: 'text/plain;charset=utf-8'}));
const link = document.createElement('a');
link.href = url;
link.download = 'ascii-art.txt';
document.body.append(link);
link.click();
link.remove();
setTimeout(() => URL.revokeObjectURL(url), 1000);
showStatus('downloaded');
});
document.getElementById('share').addEventListener('click', () => {
const url = new URL('./', window.location.href);
url.searchParams.set('text', text.value);
url.searchParams.set('font', style.value);
try {
copyText(url.href);
showStatus('linkCopied');
} catch {
window.history.replaceState(null, '', url);
showStatus('linkFailed');
}
});
setupPWA({
failed() { showStatus('offlineFailed'); },
});