813 lines
27 KiB
JavaScript
813 lines
27 KiB
JavaScript
function isModal() {
|
|
var hasModal = !!document.querySelector('.modal');
|
|
var hasMenuBar = !!document.querySelector('.menubar .selected');
|
|
var hasPopupMenu = !!document.querySelector('.menu.popup');
|
|
return hasModal || hasMenuBar || hasPopupMenu;
|
|
}
|
|
|
|
/* String helpers */
|
|
|
|
/**
|
|
* @param {string} s - original string
|
|
* @param {number} digits - char length of generating string
|
|
* @param {string} ch - string to be used for padding
|
|
* @return {string} generated string ($digits chars length) or $s
|
|
*/
|
|
function stringPadLeft(s, digits, ch) {
|
|
if (s.length > digits) {
|
|
return s;
|
|
}
|
|
for (let i = s.length; i < digits; i++) {
|
|
s = ch + s;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
/** Generate a string expression of now
|
|
* @return {string} like a "2022_08_31_2245" string (for 2022-08-31 22:45; local time)
|
|
*/
|
|
function GetCurrentDateTimeString() {
|
|
const now = new Date();
|
|
const padLeft2 = (num) => { return stringPadLeft(num.toString(), 2, '0') };
|
|
return (`${now.getFullYear()}_${padLeft2(now.getMonth()+1)}_${padLeft2(now.getDate())}` +
|
|
`_` + `${padLeft2(now.getHours())}${padLeft2(now.getMinutes())}`);
|
|
}
|
|
|
|
/* CSS helpers */
|
|
function hasClass(element, className) {
|
|
return element.classList.contains(className);
|
|
}
|
|
function addClass(element, className) {
|
|
element.classList.add(className);
|
|
}
|
|
function removeClass(element, className) {
|
|
element.classList.remove(className);
|
|
}
|
|
function removeClassFromAllChildren(element, className) {
|
|
element.querySelectorAll('.' + className).forEach(function(element) {
|
|
removeClass(element, className);
|
|
})
|
|
}
|
|
|
|
/* Mnemonic helpers */
|
|
function setLabelWithMnemonic(element, labelText) {
|
|
var label = document.createElement('span');
|
|
addClass(label, 'label');
|
|
element.appendChild(label);
|
|
|
|
var matches = labelText.match('(.*?)&(.)(.*)?');
|
|
if(matches) {
|
|
label.appendChild(document.createTextNode(matches[1]));
|
|
if(matches[2]) {
|
|
var mnemonic = document.createElement('u');
|
|
mnemonic.innerText = matches[2];
|
|
label.appendChild(mnemonic);
|
|
addClass(element, 'mnemonic-Key' + matches[2].toUpperCase());
|
|
}
|
|
if(matches[3]) {
|
|
label.appendChild(document.createTextNode(matches[3]));
|
|
}
|
|
} else {
|
|
label.appendChild(document.createTextNode(labelText))
|
|
}
|
|
}
|
|
|
|
/** Touchevent helper
|
|
* @param {TouchEvent} event
|
|
* @return {boolean} true if same element is target of touchstart and touchend
|
|
*/
|
|
function isSameElementOnTouchstartAndTouchend(event) {
|
|
const elementOnTouchStart = event.target;
|
|
const elementOnTouchEnd = document.elementFromPoint(event.changedTouches[0].clientX, event.changedTouches[0].clientY);
|
|
return elementOnTouchStart == elementOnTouchEnd;
|
|
}
|
|
|
|
/* Button helpers */
|
|
function isButton(element) {
|
|
return hasClass(element, 'button');
|
|
}
|
|
|
|
/* Button DOM traversal helpers */
|
|
function getButton(element) {
|
|
if(!element) return;
|
|
if(element.tagName == 'U') {
|
|
element = element.parentElement;
|
|
}
|
|
if(hasClass(element, 'label')) {
|
|
return getButton(element.parentElement);
|
|
} else if(isButton(element)) {
|
|
return element;
|
|
}
|
|
}
|
|
|
|
/* Button behavior */
|
|
window.addEventListener('click', function(event) {
|
|
var button = getButton(event.target);
|
|
if(button) {
|
|
button.dispatchEvent(new Event('trigger'));
|
|
}
|
|
});
|
|
window.addEventListener("touchend", (event) => {
|
|
if (!isSameElementOnTouchstartAndTouchend(event)) {
|
|
return;
|
|
}
|
|
const button = getButton(event.target);
|
|
if (button) {
|
|
button.dispatchEvent(new Event('trigger'));
|
|
}
|
|
});
|
|
|
|
window.addEventListener('keydown', function(event) {
|
|
var selected = document.querySelector('.button.selected');
|
|
if(!selected) return;
|
|
|
|
var outSelected, newSelected;
|
|
if(event.key == 'ArrowRight') {
|
|
outSelected = selected;
|
|
newSelected = selected.nextElementSibling;
|
|
if(!newSelected) {
|
|
newSelected = outSelected.parentElement.firstElementChild;
|
|
}
|
|
} else if(event.key == 'ArrowLeft') {
|
|
outSelected = selected;
|
|
newSelected = selected.previousElementSibling;
|
|
if(!newSelected) {
|
|
newSelected = outSelected.parentElement.lastElementChild;
|
|
}
|
|
} else if(event.key == 'Enter') {
|
|
selected.dispatchEvent(new Event('trigger'));
|
|
} else if(event.key == 'Escape' && hasClass(selected, 'default')) {
|
|
selected.dispatchEvent(new Event('trigger'));
|
|
}
|
|
|
|
if(outSelected) removeClass(outSelected, 'selected');
|
|
if(newSelected) addClass(newSelected, 'selected');
|
|
|
|
event.stopPropagation();
|
|
});
|
|
|
|
/* Editor helpers */
|
|
function isEditor(element) {
|
|
return hasClass(element, 'editor');
|
|
}
|
|
|
|
/* Editor DOM traversal helpers */
|
|
function getEditor(element) {
|
|
if(!element) return;
|
|
if(isEditor(element)) {
|
|
return element;
|
|
}
|
|
}
|
|
|
|
/* Editor behavior */
|
|
window.addEventListener('keydown', function(event) {
|
|
var editor = getEditor(event.target);
|
|
if(editor) {
|
|
if(event.key == 'Enter') {
|
|
editor.dispatchEvent(new Event('trigger'));
|
|
} else if(event.key == 'Escape') {
|
|
editor.style.display = 'none';
|
|
}
|
|
event.stopPropagation();
|
|
}
|
|
}, {capture: true});
|
|
|
|
/* Menu helpers */
|
|
function isMenubar(element) {
|
|
return hasClass(element, 'menubar');
|
|
}
|
|
function isMenu(element) {
|
|
return hasClass(element, 'menu');
|
|
}
|
|
function isPopupMenu(element) {
|
|
return isMenu(element) && hasClass(element, 'popup')
|
|
}
|
|
function hasSubmenu(menuItem) {
|
|
return !!menuItem.querySelector('.menu');
|
|
}
|
|
|
|
/* Menu item helpers */
|
|
function isMenuItemSelectable(menuItem) {
|
|
return !(hasClass(menuItem, 'disabled') || hasClass(menuItem, 'separator'));
|
|
}
|
|
function isMenuItemSelected(menuItem) {
|
|
return hasClass(menuItem, 'selected') || hasClass(menuItem, 'hover');
|
|
}
|
|
function deselectMenuItem(menuItem) {
|
|
removeClass(menuItem, 'selected');
|
|
removeClass(menuItem, 'hover');
|
|
removeClassFromAllChildren(menuItem, 'selected');
|
|
removeClassFromAllChildren(menuItem, 'hover');
|
|
}
|
|
function selectMenuItem(menuItem) {
|
|
var menu = menuItem.parentElement;
|
|
removeClassFromAllChildren(menu, 'selected');
|
|
removeClassFromAllChildren(menu, 'hover');
|
|
if(isMenubar(menu)) {
|
|
addClass(menuItem, 'selected');
|
|
} else {
|
|
addClass(menuItem, 'hover');
|
|
}
|
|
}
|
|
function triggerMenuItem(menuItem) {
|
|
selectMenuItem(menuItem);
|
|
if(hasSubmenu(menuItem)) {
|
|
selectMenuItem(menuItem.querySelector('li:first-child'));
|
|
} else {
|
|
var parent = menuItem.parentElement;
|
|
while(!isMenubar(parent) && !isPopupMenu(parent)) {
|
|
parent = parent.parentElement;
|
|
}
|
|
removeClassFromAllChildren(parent, 'selected');
|
|
removeClassFromAllChildren(parent, 'hover');
|
|
if(isPopupMenu(parent)) {
|
|
parent.remove();
|
|
}
|
|
|
|
menuItem.dispatchEvent(new Event('trigger'));
|
|
}
|
|
}
|
|
|
|
/* Menu DOM traversal helpers */
|
|
function getMenuItem(element) {
|
|
if(!element) return;
|
|
if(element.tagName == 'U') {
|
|
element = element.parentElement;
|
|
}
|
|
if(hasClass(element, 'label')) {
|
|
return getMenuItem(element.parentElement);
|
|
} else if(element.tagName == 'LI' && isMenu(element.parentElement)) {
|
|
return element;
|
|
}
|
|
}
|
|
function getMenu(element) {
|
|
if(!element) return;
|
|
if(isMenu(element)) {
|
|
return element;
|
|
} else {
|
|
var menuItem = getMenuItem(element);
|
|
if(menuItem && isMenu(menuItem.parentElement)) {
|
|
return menuItem.parentElement;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Menu behavior */
|
|
window.addEventListener('click', function(event) {
|
|
var menuItem = getMenuItem(event.target);
|
|
var menu = getMenu(menuItem);
|
|
if(menu && isMenubar(menu)) {
|
|
if(hasClass(menuItem, 'selected')) {
|
|
removeClass(menuItem, 'selected');
|
|
} else {
|
|
selectMenuItem(menuItem);
|
|
}
|
|
event.stopPropagation();
|
|
} else if(menu) {
|
|
if(!hasSubmenu(menuItem)) {
|
|
triggerMenuItem(menuItem);
|
|
}
|
|
event.stopPropagation();
|
|
} else {
|
|
document.querySelectorAll('.menu .selected, .menu .hover')
|
|
.forEach(function(menuItem) {
|
|
deselectMenuItem(menuItem);
|
|
event.stopPropagation();
|
|
});
|
|
document.querySelectorAll('.menu.popup')
|
|
.forEach(function(menu) {
|
|
menu.remove();
|
|
});
|
|
}
|
|
});
|
|
window.addEventListener("touchend", (event) => {
|
|
if (!isSameElementOnTouchstartAndTouchend(event)) {
|
|
return;
|
|
}
|
|
var menuItem = getMenuItem(event.target);
|
|
var menu = getMenu(menuItem);
|
|
if(menu && isMenubar(menu)) {
|
|
if(hasClass(menuItem, 'selected')) {
|
|
removeClass(menuItem, 'selected');
|
|
} else {
|
|
selectMenuItem(menuItem);
|
|
}
|
|
event.stopPropagation();
|
|
event.preventDefault();
|
|
} else if(menu) {
|
|
if(!hasSubmenu(menuItem)) {
|
|
triggerMenuItem(menuItem);
|
|
} else {
|
|
addClass(menuItem, "selected");
|
|
addClass(menuItem, "hover");
|
|
}
|
|
event.stopPropagation();
|
|
} else {
|
|
document.querySelectorAll('.menu .selected, .menu .hover')
|
|
.forEach(function(menuItem) {
|
|
deselectMenuItem(menuItem);
|
|
event.stopPropagation();
|
|
});
|
|
document.querySelectorAll('.menu.popup')
|
|
.forEach(function(menu) {
|
|
menu.remove();
|
|
});
|
|
}
|
|
});
|
|
window.addEventListener('mouseover', function(event) {
|
|
var menuItem = getMenuItem(event.target);
|
|
var menu = getMenu(menuItem);
|
|
if(menu) {
|
|
var selected = menu.querySelectorAll('.selected, .hover');
|
|
if(isMenubar(menu)) {
|
|
if(selected.length > 0) {
|
|
selected.forEach(function(menuItem) {
|
|
if(selected != menuItem) {
|
|
deselectMenuItem(menuItem);
|
|
}
|
|
});
|
|
addClass(menuItem, 'selected');
|
|
}
|
|
} else {
|
|
if(isMenuItemSelectable(menuItem)) {
|
|
selectMenuItem(menuItem);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
window.addEventListener('keydown', function(event) {
|
|
var allSelected = document.querySelectorAll('.menubar .selected, .menubar .hover,' +
|
|
'.menu.popup .selected, .menu.popup .hover');
|
|
if(allSelected.length == 0) return;
|
|
|
|
var selected = allSelected[allSelected.length - 1];
|
|
var outSelected, newSelected;
|
|
var isMenubarItem = isMenubar(getMenu(selected));
|
|
|
|
if(isMenubarItem && event.key == 'ArrowRight' ||
|
|
!isMenubarItem && event.key == 'ArrowDown') {
|
|
outSelected = selected;
|
|
newSelected = selected.nextElementSibling;
|
|
while(newSelected && !isMenuItemSelectable(newSelected)) {
|
|
newSelected = newSelected.nextElementSibling;
|
|
}
|
|
if(!newSelected) {
|
|
newSelected = outSelected.parentElement.firstElementChild;
|
|
}
|
|
} else if(isMenubarItem && event.key == 'ArrowLeft' ||
|
|
!isMenubarItem && event.key == 'ArrowUp') {
|
|
outSelected = selected;
|
|
newSelected = selected.previousElementSibling;
|
|
while(newSelected && !isMenuItemSelectable(newSelected)) {
|
|
newSelected = newSelected.previousElementSibling;
|
|
}
|
|
if(!newSelected) {
|
|
newSelected = outSelected.parentElement.lastElementChild;
|
|
}
|
|
} else if(!isMenubarItem && event.key == 'ArrowRight') {
|
|
if(hasSubmenu(selected)) {
|
|
selectMenuItem(selected.querySelector('li:first-child'));
|
|
} else {
|
|
outSelected = allSelected[0];
|
|
newSelected = outSelected.nextElementSibling;
|
|
if(!newSelected) {
|
|
newSelected = outSelected.parentElement.firstElementChild;
|
|
}
|
|
}
|
|
} else if(!isMenubarItem && event.key == 'ArrowLeft') {
|
|
if(allSelected.length > 2) {
|
|
outSelected = selected;
|
|
} else {
|
|
outSelected = allSelected[0];
|
|
newSelected = outSelected.previousElementSibling;
|
|
if(!newSelected) {
|
|
newSelected = outSelected.parentElement.lastElementChild;
|
|
}
|
|
}
|
|
} else if(isMenubarItem && event.key == 'ArrowDown') {
|
|
newSelected = selected.querySelector('li:first-child');
|
|
} else if(event.key == 'Enter') {
|
|
triggerMenuItem(selected);
|
|
} else if(event.key == 'Escape') {
|
|
outSelected = allSelected[0];
|
|
} else {
|
|
var withMnemonic = getMenu(selected).querySelector('.mnemonic-' + event.key);
|
|
if(withMnemonic) {
|
|
triggerMenuItem(withMnemonic);
|
|
}
|
|
}
|
|
|
|
if(outSelected) deselectMenuItem(outSelected);
|
|
if(newSelected) selectMenuItem(newSelected);
|
|
|
|
event.stopPropagation();
|
|
});
|
|
|
|
/* Mnemonic behavior */
|
|
window.addEventListener('keydown', function(event) {
|
|
var withMnemonic;
|
|
if(event.altKey && event.key == 'Alt') {
|
|
addClass(document.body, 'mnemonic');
|
|
} else if(!isModal() && event.altKey && (withMnemonic =
|
|
document.querySelector('.menubar > .mnemonic-' + event.code))) {
|
|
triggerMenuItem(withMnemonic);
|
|
event.stopPropagation();
|
|
} else {
|
|
removeClass(document.body, 'mnemonic');
|
|
}
|
|
});
|
|
window.addEventListener('keyup', function(event) {
|
|
if(event.key == 'Alt') {
|
|
removeClass(document.body, 'mnemonic');
|
|
}
|
|
});
|
|
|
|
|
|
// FIXME(emscripten): Should be implemnted in guihtmlcpp ?
|
|
class FileUploadHelper {
|
|
constructor() {
|
|
this.modalRoot = document.createElement("div");
|
|
addClass(this.modalRoot, "modal");
|
|
this.modalRoot.style.display = "none";
|
|
this.modalRoot.style.zIndex = 1000;
|
|
|
|
this.dialogRoot = document.createElement("div");
|
|
addClass(this.dialogRoot, "dialog");
|
|
this.modalRoot.appendChild(this.dialogRoot);
|
|
|
|
this.messageHeader = document.createElement("strong");
|
|
this.dialogRoot.appendChild(this.messageHeader);
|
|
|
|
this.descriptionParagraph = document.createElement("p");
|
|
this.dialogRoot.appendChild(this.descriptionParagraph);
|
|
|
|
this.currentFileListHeader = document.createElement("p");
|
|
this.currentFileListHeader.textContent = "Current uploaded files:";
|
|
this.dialogRoot.appendChild(this.currentFileListHeader);
|
|
|
|
this.currentFileList = document.createElement("div");
|
|
this.dialogRoot.appendChild(this.currentFileList);
|
|
|
|
this.fileInputContainer = document.createElement("div");
|
|
|
|
this.fileInputElement = document.createElement("input");
|
|
this.fileInputElement.setAttribute("type", "file");
|
|
this.fileInputElement.addEventListener("change", (ev)=> this.onFileInputChanged(ev));
|
|
this.fileInputContainer.appendChild(this.fileInputElement);
|
|
|
|
this.dialogRoot.appendChild(this.fileInputContainer);
|
|
|
|
this.buttonHolder = document.createElement("div");
|
|
addClass(this.buttonHolder, "buttons");
|
|
this.dialogRoot.appendChild(this.buttonHolder);
|
|
|
|
this.AddButton("OK", 0, false);
|
|
this.AddButton("Cancel", 1, true);
|
|
|
|
this.closeDialog();
|
|
|
|
document.querySelector("body").appendChild(this.modalRoot);
|
|
|
|
this.currentFilename = null;
|
|
|
|
// FIXME(emscripten): For debugging
|
|
this.title = "";
|
|
this.filename = "";
|
|
this.filters = "";
|
|
}
|
|
|
|
dispose() {
|
|
document.querySelector("body").removeChild(this.modalRoot);
|
|
}
|
|
|
|
AddButton(label, response, isDefault) {
|
|
// FIXME(emscripten): implement
|
|
const buttonElem = document.createElement("div");
|
|
addClass(buttonElem, "button");
|
|
setLabelWithMnemonic(buttonElem, label);
|
|
if (isDefault) {
|
|
addClass(buttonElem, "default");
|
|
addClass(buttonElem, "selected");
|
|
}
|
|
buttonElem.addEventListener("click", () => {
|
|
this.closeDialog();
|
|
});
|
|
|
|
this.buttonHolder.appendChild(buttonElem);
|
|
}
|
|
|
|
getFileEntries() {
|
|
const basePath = '/';
|
|
/** @type {Array<object} */
|
|
const nodes = FS.readdir(basePath);
|
|
const files = nodes.filter((nodename) => {
|
|
return FS.isFile(FS.lstat(basePath + nodename).mode);
|
|
}).map((filename) => {
|
|
return basePath + filename;
|
|
});
|
|
return files;
|
|
}
|
|
|
|
generateFileList() {
|
|
let filepaths = this.getFileEntries();
|
|
const listElem = document.createElement("ul");
|
|
for (let i = 0; i < filepaths.length; i++) {
|
|
const listitemElem = document.createElement("li");
|
|
const stat = FS.lstat(filepaths[i]);
|
|
const text = `"${filepaths[i]}" (${stat.size} bytes)`;
|
|
listitemElem.textContent = text;
|
|
listElem.appendChild(listitemElem);
|
|
}
|
|
return listElem;
|
|
}
|
|
|
|
updateFileList() {
|
|
this.currentFileList.innerHTML = "";
|
|
this.currentFileList.appendChild(this.generateFileList());
|
|
}
|
|
|
|
onFileInputChanged(ev) {
|
|
const selectedFiles = ev.target.files;
|
|
if (selectedFiles.length < 1) {
|
|
return;
|
|
}
|
|
const selectedFile = selectedFiles[0];
|
|
const selectedFilename = selectedFile.name;
|
|
this.filename = selectedFilename;
|
|
this.currentFilename = selectedFilename;
|
|
|
|
// Prepare FileReader
|
|
const fileReader = new FileReader();
|
|
const fileReaderReadAsArrayBufferPromise = new Promise((resolve, reject) => {
|
|
fileReader.addEventListener("load", (ev) => {
|
|
resolve(ev.target.result);
|
|
});
|
|
fileReader.addEventListener("abort", (err) => {
|
|
reject(err);
|
|
});
|
|
fileReader.readAsArrayBuffer(selectedFile);
|
|
});
|
|
|
|
fileReaderReadAsArrayBufferPromise
|
|
.then((arrayBuffer) => {
|
|
// Write selected file to FS
|
|
console.log(`Write uploaded file blob to filesystem. "${selectedFilename}" (${arrayBuffer.byteLength} bytes)`);
|
|
const u8array = new Uint8Array(arrayBuffer);
|
|
const fs = FS.open("/" + selectedFilename, "w");
|
|
FS.write(fs, u8array, 0, u8array.length, 0);
|
|
FS.close(fs);
|
|
|
|
// Update file list in dialog
|
|
this.updateFileList();
|
|
})
|
|
.catch((err) => {
|
|
console.error("Error while fileReader.readAsArrayBuffer():", err);
|
|
});
|
|
}
|
|
|
|
showDialog() {
|
|
this.updateFileList();
|
|
|
|
this.is_shown = true;
|
|
this.modalRoot.style.display = "block";
|
|
}
|
|
|
|
closeDialog() {
|
|
this.is_shown = false;
|
|
this.modalRoot.style.display = "none";
|
|
}
|
|
};
|
|
|
|
// FIXME(emscripten): Workaround
|
|
function createFileUploadHelperInstance() {
|
|
return new FileUploadHelper();
|
|
}
|
|
|
|
// FIXME(emscripten): Should be implemnted in guihtmlcpp ?
|
|
class FileDownloadHelper {
|
|
constructor() {
|
|
this.modalRoot = document.createElement("div");
|
|
addClass(this.modalRoot, "modal");
|
|
this.modalRoot.style.display = "none";
|
|
this.modalRoot.style.zIndex = 1000;
|
|
|
|
this.dialogRoot = document.createElement("div");
|
|
addClass(this.dialogRoot, "dialog");
|
|
this.modalRoot.appendChild(this.dialogRoot);
|
|
|
|
this.messageHeader = document.createElement("strong");
|
|
this.dialogRoot.appendChild(this.messageHeader);
|
|
|
|
this.descriptionParagraph = document.createElement("p");
|
|
this.dialogRoot.appendChild(this.descriptionParagraph);
|
|
|
|
this.buttonHolder = document.createElement("div");
|
|
addClass(this.buttonHolder, "buttons");
|
|
this.dialogRoot.appendChild(this.buttonHolder);
|
|
|
|
this.closeDialog();
|
|
|
|
document.querySelector("body").appendChild(this.modalRoot);
|
|
}
|
|
|
|
dispose() {
|
|
document.querySelector("body").removeChild(this.modalRoot);
|
|
}
|
|
|
|
AddButton(label, response, isDefault) {
|
|
// FIXME(emscripten): implement
|
|
const buttonElem = document.createElement("div");
|
|
addClass(buttonElem, "button");
|
|
setLabelWithMnemonic(buttonElem, label);
|
|
if (isDefault) {
|
|
addClass(buttonElem, "default");
|
|
addClass(buttonElem, "selected");
|
|
}
|
|
buttonElem.addEventListener("click", () => {
|
|
this.closeDialog();
|
|
this.dispose();
|
|
});
|
|
|
|
this.buttonHolder.appendChild(buttonElem);
|
|
}
|
|
|
|
createBlobURLFromArrayBuffer(arrayBuffer) {
|
|
const u8array = new Uint8Array(arrayBuffer);
|
|
let dataUrl = "data:application/octet-stream;base64,";
|
|
let binaryString = "";
|
|
for (let i = 0; i < u8array.length; i++) {
|
|
binaryString += String.fromCharCode(u8array[i]);
|
|
}
|
|
dataUrl += btoa(binaryString);
|
|
|
|
return dataUrl;
|
|
}
|
|
|
|
prepareFile(filename) {
|
|
this.messageHeader.textContent = "Your file ready";
|
|
|
|
const stat = FS.lstat(filename);
|
|
const filesize = stat.size;
|
|
const fs = FS.open(filename, "r");
|
|
const readbuffer = new Uint8Array(filesize);
|
|
FS.read(fs, readbuffer, 0, filesize, 0);
|
|
FS.close(fs);
|
|
|
|
const blobURL = this.createBlobURLFromArrayBuffer(readbuffer.buffer);
|
|
|
|
this.descriptionParagraph.innerHTML = "";
|
|
const linkElem = document.createElement("a");
|
|
let downloadfilename = "solvespace_browser-";
|
|
downloadfilename += `${GetCurrentDateTimeString()}.slvs`;
|
|
linkElem.setAttribute("download", downloadfilename);
|
|
linkElem.setAttribute("href", blobURL);
|
|
// WORKAROUND: FIXME(emscripten)
|
|
linkElem.style.color = "lightblue";
|
|
linkElem.textContent = downloadfilename;
|
|
this.descriptionParagraph.appendChild(linkElem);
|
|
}
|
|
|
|
showDialog() {
|
|
this.is_shown = true;
|
|
this.modalRoot.style.display = "block";
|
|
}
|
|
|
|
closeDialog() {
|
|
this.is_shown = false;
|
|
this.modalRoot.style.display = "none";
|
|
}
|
|
};
|
|
|
|
function saveFileDone(filename, isSaveAs, isAutosave) {
|
|
console.log(`saveFileDone(${filename}, ${isSaveAs}, ${isAutosave})`);
|
|
if (isAutosave) {
|
|
return;
|
|
}
|
|
const fileDownloadHelper = new FileDownloadHelper();
|
|
fileDownloadHelper.AddButton("OK", 0, true);
|
|
fileDownloadHelper.prepareFile(filename);
|
|
console.log(`Calling shoDialog()...`);
|
|
fileDownloadHelper.showDialog();
|
|
console.log(`shoDialog() finished.`);
|
|
}
|
|
|
|
|
|
class ScrollbarHelper {
|
|
/**
|
|
* @param {HTMLElement} elementquery CSS query string for the element that has scrollbar.
|
|
*/
|
|
constructor(elementquery) {
|
|
this.target = document.querySelector(elementquery);
|
|
this.rangeMin = 0;
|
|
this.rangeMax = 0;
|
|
this.currentRatio = 0;
|
|
|
|
this.onScrollCallback = null;
|
|
this.onScrollCallbackTicking = false;
|
|
if (this.target) {
|
|
// console.log("addEventListner scroll");
|
|
this.target.parentElement.addEventListener('scroll', () => {
|
|
if (this.onScrollCallbackTicking) {
|
|
return;
|
|
}
|
|
window.requestAnimationFrame(() => {
|
|
if (this.onScrollCallback) {
|
|
this.onScrollCallback();
|
|
}
|
|
this.onScrollCallbackTicking = false;
|
|
});
|
|
this.onScrollCallbackTicking = true;
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {number} ratio how long against to the viewport height (1.0 to exact same as viewport's height)
|
|
*/
|
|
setScrollbarSize(ratio) {
|
|
// if (isNaN(ratio)) {
|
|
// console.warn(`setScrollbarSize(): ratio is Nan = ${ratio}`);
|
|
// }
|
|
// if (ratio < 0 || ratio > 1) {
|
|
// console.warn(`setScrollbarSize(): ratio is out of range 0-1 but ${ratio}`);
|
|
// }
|
|
// console.log(`ScrollbarHelper.setScrollbarSize(): ratio=${ratio}`);
|
|
this.target.style.height = `${100 * ratio}%`;
|
|
}
|
|
|
|
getScrollbarPosition() {
|
|
const scrollbarElem = this.target.parentElement;
|
|
const scrollTopMin = 0;
|
|
const scrollTopMax = scrollbarElem.scrollHeight - scrollbarElem.clientHeight;
|
|
const ratioOnScrollbar = (scrollbarElem.scrollTop - scrollTopMin) / (scrollTopMax - scrollTopMin);
|
|
this.currentRatio = (scrollbarElem.scrollTop - scrollTopMin) / (scrollTopMax - scrollTopMin);
|
|
let pos = this.currentRatio * (this.rangeMax - this.pageSize - this.rangeMin) + this.rangeMin;
|
|
// console.log(`ScrollbarHelper.getScrollbarPosition(): ratio=${ratioOnScrollbar}, pos=${pos}, scrollTop=${scrollbarElem.scrollTop}, scrollTopMin=${scrollTopMin}, scrollTopMax=${scrollTopMax}, rangeMin=${this.rangeMin}, rangeMax=${this.rangeMax}, pageSize=${this.pageSize}`);
|
|
if (isNaN(pos)) {
|
|
return 0;
|
|
} else {
|
|
return pos;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {number} value in range of rangeMin and rangeMax
|
|
*/
|
|
setScrollbarPosition(position) {
|
|
const positionMin = this.rangeMin;
|
|
const positionMax = this.rangeMax - this.pageSize;
|
|
const currentPositionRatio = (position - positionMin) / (positionMax - positionMin);
|
|
|
|
const scrollbarElement = this.target.parentElement;
|
|
const scrollTopMin = 0;
|
|
const scrollTopMax = scrollbarElement.scrollHeight - scrollbarElement.clientHeight;
|
|
const scrollWidth = scrollTopMax - scrollTopMin;
|
|
const newScrollTop = currentPositionRatio * scrollWidth;
|
|
scrollbarElement.scrollTop = currentPositionRatio * scrollWidth;
|
|
|
|
// console.log(`ScrollbarHelper.setScrollbarPosition(): pos=${position}, currentPositionRatio=${currentPositionRatio}, calculated scrollTop=${newScrollTop}`);
|
|
|
|
if (false) {
|
|
// const ratio = (position - this.rangeMin) * ((this.rangeMax - this.pageSize) - this.rangeMin);
|
|
|
|
const scrollTopMin = 0;
|
|
const scrollTopMax = this.target.scrollHeight - this.target.clientHeight;
|
|
const scrollWidth = scrollTopMax - scrollTopMin;
|
|
const newScrollTop = ratio * scrollWidth;
|
|
// this.target.parentElement.scrollTop = ratio * scrollWidth;
|
|
this.target.scrollTop = ratio * scrollWidth;
|
|
|
|
console.log(`ScrollbarHelper.setScrollbarPosition(): pos=${position}, ratio=${ratio}, calculated scrollTop=${newScrollTop}`);
|
|
}
|
|
}
|
|
|
|
/** */
|
|
setRange(min, max, pageSize) {
|
|
this.rangeMin = min;
|
|
this.rangeMax = max;
|
|
this.currentRatio = 0;
|
|
|
|
this.setPageSize(pageSize);
|
|
}
|
|
|
|
setPageSize(pageSize) {
|
|
if (this.rangeMin == this.rangeMax) {
|
|
// console.log(`ScrollbarHelper::setPageSize(): size=${size}, but rangeMin == rangeMax`);
|
|
return;
|
|
}
|
|
this.pageSize = pageSize;
|
|
const ratio = (this.rangeMax - this.rangeMin) / this.pageSize;
|
|
// console.log(`ScrollbarHelper::setPageSize(): pageSize=${pageSize}, ratio=${ratio}`);
|
|
this.setScrollbarSize(ratio);
|
|
}
|
|
|
|
setScrollbarEnabled(enabled) {
|
|
if (!enabled) {
|
|
this.target.style.height = "100%";
|
|
}
|
|
}
|
|
};
|
|
|
|
window.ScrollbarHelper = ScrollbarHelper;
|