172 lines
5.3 KiB
TypeScript
172 lines
5.3 KiB
TypeScript
import * as bootstrap from 'bootstrap';
|
|
import xsl from 'bundle-text:./MessageLog.xsl';
|
|
import * as ui from './ui';
|
|
|
|
function showApplication(toggle: boolean) {
|
|
if (toggle) {
|
|
ui.realBody.classList.remove('d-none');
|
|
} else {
|
|
ui.realBody.classList.add('d-none');
|
|
}
|
|
}
|
|
|
|
function showLoadingIndicator(toggle: boolean) {
|
|
if (toggle) {
|
|
ui.loadingIndicator.classList.remove('d-none');
|
|
} else {
|
|
ui.loadingIndicator.classList.add('d-none');
|
|
}
|
|
}
|
|
|
|
function displayError(errorDiv: HTMLElement, category: string, ex: Error) {
|
|
console.error(ex);
|
|
const errorHeading = errorDiv.querySelector('.alert-heading') as HTMLElement | null;
|
|
|
|
//clear all error text, but not heading
|
|
const errorMessages = errorDiv.querySelectorAll('span');
|
|
errorMessages.forEach(message => errorDiv.removeChild(message));
|
|
|
|
//add new message and show the error alert
|
|
const errorSpan = document.createElement('span');
|
|
errorSpan.innerText = ex.message;
|
|
errorDiv.appendChild(errorSpan);
|
|
|
|
if (errorHeading) {
|
|
errorHeading.innerText = category;
|
|
}
|
|
errorDiv.classList.remove('d-none');
|
|
}
|
|
|
|
function displayFatalError(category: string, ex: Error) {
|
|
showApplication(false);
|
|
showLoadingIndicator(false);
|
|
displayError(ui.fatalError, category, ex);
|
|
}
|
|
|
|
function displayNonFatalError(category: string, ex: Error) {
|
|
showApplication(true);
|
|
showLoadingIndicator(false);
|
|
displayError(ui.nonFatalError, category, ex);
|
|
}
|
|
|
|
function hideErrors() {
|
|
document
|
|
.querySelectorAll('#fatal-error, #display-backup-error')
|
|
.forEach(el => el.classList.add('d-none'));
|
|
}
|
|
|
|
function importStylesheet(xsl: Node) {
|
|
//Firefox does not seem to report XML parsing errors into the
|
|
//exception tree. So we catch here, log, and return some friendly
|
|
//error.
|
|
try {
|
|
const xsltProcessor = new XSLTProcessor();
|
|
xsltProcessor.importStylesheet(xsl);
|
|
return xsltProcessor;
|
|
} catch (cause) {
|
|
console.error(cause);
|
|
throw new Error('Stylesheet parsing failed');
|
|
}
|
|
}
|
|
|
|
async function createXSLTProcessor() {
|
|
const doc = new DOMParser().parseFromString(xsl, "text/xml");
|
|
return Promise.resolve(importStylesheet(doc));
|
|
// return fetch(new URL("./MessageLog.xsl", import.meta.url))
|
|
// .then(resp => {
|
|
// if (resp.ok)
|
|
// return resp.text();
|
|
// else
|
|
// throw new Error(resp.status + ' ' + resp.statusText);
|
|
// })
|
|
// .then(str => new DOMParser().parseFromString(str, "text/xml"))
|
|
// .then(importStylesheet)
|
|
// .catch(cause => {
|
|
// throw new Error('Could not load XSL stylesheet', { cause });
|
|
// });
|
|
}
|
|
|
|
function removeChildren(element: HTMLElement) {
|
|
while (element.hasChildNodes() && element.lastChild != null) {
|
|
element.removeChild(element.lastChild);
|
|
}
|
|
}
|
|
|
|
function checkOverflow(elem: HTMLElement) {
|
|
const elemHeight = elem.scrollHeight;
|
|
const parentHeight = elem.offsetHeight;
|
|
return elemHeight > parentHeight;
|
|
}
|
|
|
|
function processFragment() {
|
|
document
|
|
.querySelectorAll('[data-bs-toggle="popover"]')
|
|
.forEach(popover => new bootstrap.Popover(popover));
|
|
|
|
document.querySelectorAll('.message-content div').forEach(div => {
|
|
if (checkOverflow(div as HTMLElement)) {
|
|
div.parentElement?.classList.add('overflow-icon');
|
|
}
|
|
});
|
|
}
|
|
|
|
function displayBackup(xsltProcessor: XSLTProcessor, file: File) {
|
|
hideErrors();
|
|
|
|
removeChildren(ui.chatDisplay);
|
|
|
|
file.text().then(xmlText => {
|
|
ui.currentlyViewing.innerText = file.name;
|
|
const parser = new DOMParser();
|
|
const xmlDoc = parser.parseFromString(xmlText, "text/xml");
|
|
const errorNode = xmlDoc.querySelector('parsererror');
|
|
|
|
if (!errorNode) {
|
|
try {
|
|
const fragment = xsltProcessor.transformToFragment(xmlDoc, document);
|
|
ui.chatDisplay.appendChild(fragment);
|
|
processFragment();
|
|
}
|
|
catch (e) {
|
|
if (e instanceof Error) {
|
|
displayNonFatalError('Could not load backup', e);
|
|
}
|
|
}
|
|
} else {
|
|
const err = new Error(errorNode.firstChild?.nodeValue || '');
|
|
displayNonFatalError('Could not load backup', err);
|
|
}
|
|
|
|
});
|
|
}
|
|
|
|
function initEvents(xsltProcessor: XSLTProcessor) {
|
|
ui.fileSelector.addEventListener("change", async (e) => {
|
|
const target = e.currentTarget as HTMLInputElement | null;
|
|
const file = target?.files?.[0];
|
|
if (file) {
|
|
displayBackup(xsltProcessor, file);
|
|
} else {
|
|
displayNonFatalError('Could not load file', new Error('No file found.'));
|
|
}
|
|
});
|
|
}
|
|
|
|
window.addEventListener('DOMContentLoaded', async () => {
|
|
window.addEventListener('resize', () => {
|
|
if (window.innerWidth <= 500) {
|
|
ui.chatDisplay.querySelector('table')?.classList.add('table-sm');
|
|
} else {
|
|
ui.chatDisplay.querySelector('table')?.classList.remove('table-sm');
|
|
}
|
|
});
|
|
|
|
createXSLTProcessor()
|
|
.then(initEvents)
|
|
.then(_ => {
|
|
showLoadingIndicator(false);
|
|
showApplication(true);
|
|
})
|
|
.catch(ex => displayFatalError('Initialiation Failed', ex));
|
|
});
|