Files
ascii-pwa/app.js
T

158 lines
5.5 KiB
JavaScript

import Alpine from './vendor/alpine.js';
import {render, styles, resolveFont} from './renderer.js';
import {translations} from './translations.js';
import {setupPWA} from './pwa.js';
function savePreference(key, value) {
try { localStorage.setItem(`ascii-pwa-${key}`, value); } catch { /* Storage may be disabled. */ }
}
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});
}
}
Alpine.data('asciiApp', () => {
let drawVersion = 0;
let drawTimer;
let pwa;
return {
styles,
text: 'hello world',
font: 'Standard',
ascii: '',
busy: true,
statusKey: '',
language: document.documentElement.lang,
theme: document.documentElement.dataset.theme,
installAvailable: false,
updateAvailable: false,
installed: false,
get nextFont() {
return styles[(styles.indexOf(this.font) + 1) % styles.length];
},
get canCopy() {
return !this.busy && !!this.ascii.trim();
},
t(key) {
return translations[this.language][key];
},
init() {
try {
const saved = localStorage.getItem('ascii-pwa-font');
if (saved) this.font = resolveFont(saved);
} catch { /* Use the default when saved preferences are unavailable. */ }
const params = new URL(window.location.href).searchParams;
if (params.has('text')) this.text = params.get('text').slice(0, 500);
if (params.has('font')) this.font = resolveFont(params.get('font'));
savePreference('font', this.font);
this.$watch('text', () => this.scheduleDraw());
this.$watch('font', () => {
savePreference('font', this.font);
this.draw();
});
this.$watch('language', () => {
document.documentElement.lang = this.language;
savePreference('language', this.language);
});
this.$watch('theme', () => {
document.documentElement.dataset.theme = this.theme;
savePreference('theme', this.theme);
});
pwa = setupPWA({
installAvailable: value => { this.installAvailable = value; },
updateAvailable: value => { this.updateAvailable = value; },
installed: value => { this.installed = value; },
failed: () => { this.statusKey = 'offlineFailed'; },
});
this.draw();
},
destroy() {
clearTimeout(drawTimer);
++drawVersion;
pwa?.destroy();
},
scheduleDraw() {
clearTimeout(drawTimer);
++drawVersion;
this.busy = true;
drawTimer = setTimeout(() => this.draw(), 150);
},
async draw() {
clearTimeout(drawTimer);
const version = ++drawVersion;
this.busy = true;
try {
const result = await render(this.text, this.font);
if (version !== drawVersion) return;
this.ascii = result;
this.statusKey = '';
} catch {
if (version !== drawVersion) return;
this.ascii = '';
this.statusKey = 'renderFailed';
} finally {
if (version === drawVersion) this.busy = false;
}
},
cycleFont(direction) {
this.font = styles[(styles.indexOf(this.font) + direction + styles.length) % styles.length];
},
copy() {
try {
copyText(this.ascii);
this.statusKey = 'copied';
} catch {
const range = document.createRange();
range.selectNodeContents(this.$refs.output);
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
this.statusKey = 'copyFailed';
}
},
download() {
const url = URL.createObjectURL(new Blob([this.ascii + '\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);
this.statusKey = 'downloaded';
},
share() {
const url = new URL('./', window.location.href);
url.searchParams.set('text', this.text);
url.searchParams.set('font', this.font);
try {
copyText(url.href);
this.statusKey = 'linkCopied';
} catch {
window.history.replaceState(null, '', url);
this.statusKey = 'linkFailed';
}
},
install() { return pwa.install(); },
update() { pwa.update(); },
};
});
Alpine.start();