158 lines
5.5 KiB
JavaScript
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();
|