variabletime/bin/web/js/utils.js

511 lines
16 KiB
JavaScript
Raw Normal View History

2023-09-24 18:39:52 +02:00
/////////////////////////////////////
const UUID = function() {
let allowedIdChars = "0123456789abcdef";
// fallback in case we cannot crypto
const notUniqueId = (t = 16) => {
let out = "";
for (let i = 0; i < t; i++) {
out += allowedIdChars[(Math.random() * allowedIdChars.length + Math.floor(performance.now())) % allowedIdChars.length];
}
return out;
}
// from https://github.com/ai/nanoid/blob/main/nanoid.js
const uniqueId = (t = 16) => {
const indices = crypto.getRandomValues(new Uint8Array(t));
let out = "";
for (var i = 0; i < t; i++) {
out += allowedIdChars[indices[i] % allowedIdChars.length];
}
return out;
}
this.getUuid = () => {
return typeof crypto === 'object' && typeof crypto.getRandomValues === 'function' ?
uniqueId() : notUniqueId();
}
}
const uuid = new UUID();
const getUuid = () => {
return uuid.getUuid();
}
const makeEven = (n) => {
const nr = Math.round(n);
return nr - nr % 2;
}
const getMix = (before_s, after_s, time_s, clamp = true) => {
const diff = after_s - before_s;
const travel = time_s - before_s;
if (diff === 0 || travel === 0) {
return 0;
} else if (clamp) {
return Math.min(Math.max(travel / diff));
} else {
return travel / diff;
}
}
const mix = (a, b, m, t) => {
if (Math.abs(a - b) < t) {
return b;
} else {
return a * (1.0 - m) + b * m;
}
};
const mixObject = (a, b, m) => {
const out = JSON.parse(JSON.stringify(a));
const a_keys = Object.keys(a);
const b_keys = Object.keys(b);
let keys = [...new Set([...a_keys, ...b_keys])];
keys.forEach((key) => {
if (!a.hasOwnProperty(key)) {
out[key] = b[key];
} else if (!b.hasOwnProperty(key)) {
out[key] = a[key];
} else {
if (typeof a[key] === 'object') {
out[key] = mixObject(a[key], b[key], m);
} else if (typeof a[key] === 'number') {
out[key] = a[key] * (1.0 - m) + b[key] * m;
} else {
out[key] = b[key];
}
}
});
return out;
};
/////////////////////////////////////
const htmlToElement = (html) => {
var template = document.createElement('template');
html = html.trim(); // Never return a text node of whitespace as the result
template.innerHTML = html;
return template.content.firstChild;
}
/////////////////////////////////////
// download(textData, 'lol.txt', 'text/plain');
// download(jsonData, 'lol.json', 'application/json');
function downloadFile(content, fileName, contentType) {
var a = document.createElement("a");
var file = new Blob([content], {
type: contentType
});
a.href = URL.createObjectURL(file);
a.download = fileName;
a.click();
}
function uploadFile(expectedType = 'application/json') {
return new Promise((resolve, reject) => {
var input = document.createElement('input');
input.type = 'file';
input.addEventListener('change', () => {
let json;
let files = input.files;
if (files.length == 0) return;
const file = files[0];
console.log('file', file);
let reader = new FileReader();
if (expectedType === 'application/zip' || file.type === 'application/zip' || file.type.indexOf('audio') === 0) {
2023-09-24 18:39:52 +02:00
reader.onload = (e) => {
const f = e.target.result;
console.log(e, file.name, file.size, file.type, f);
resolve({
name: file.name,
size: file.size,
type: file.type,
arrayBuffer: f,
});
};
reader.onerror = (e) => reject(e.target.error.name);
reader.readAsArrayBuffer(file);
} else if (expectedType === 'application/json') {
reader.onload = (e) => {
console.log(e);
const f = e.target.result;
// This is a regular expression to identify carriage
// Returns and line breaks
//const lines = file.split(/\r\n|\n/);
if (file.type === expectedType) {
try {
json = JSON.parse(f);
resolve(json);
} catch (e) {
reject("Caught: " + e.message)
}
}
};
reader.onerror = (e) => reject(e.target.error.name);
reader.readAsText(file);
} else if (expectedType.indexOf('font') >= 0) {
console.log('expect font');
reader.onload = (e) => {
console.log(e);
const f = e.target.result;
if (file.type.indexOf('font') >= 0) {
console.log('is font');
//var uint8View = new Uint8Array(f);
//console.log('trying to save the font file, file, uint8View', file, uint8View);
//FS.createDataFile(config.fs.idbfsFontDir, file.name, uint8View, true, true);
resolve({
name: file.name,
size: file.size,
type: file.type,
arrayBuffer: f
});
} else {
const extension = file.name.split('.').reverse().shift()
const fileType = `font/${extension}`;
if(confirm(`${file.name} has type ${file.type} instead of the expected ${fileType}. are you sure this is a font?`)) {
const outputFile = {
isFont: true,
name: file.name,
size: file.size,
type: file.type,
arrayBuffer: f
};
console.log({outputFile});
resolve(outputFile);
} else {
reject('not a font');
}
}
};
reader.onerror = (e) => reject(e.target.error.name);
reader.readAsArrayBuffer(file);
} else {
alert(`unknown filetype ${file.type}, what are you uploading?`);
resolve(false);
}
});
input.click();
//var a = document.createElement('a');
//a.onclick = () => {
//var e = document.createEvent('MouseEvents');
//e.initEvent('click', true, false);
//input.dispatchEvent(e);
//};
//a.click();
});
}
const makeDraggable = (elmnt, draggedCallback = false) => {
var pos1 = 0,
pos2 = 0,
pos3 = 0,
pos4 = 0;
if (elmnt.querySelector('.header .move')) {
// if present, the header is where you move the DIV from:
elmnt.querySelector('.header .move').onmousedown = dragMouseDown;
} else {
// otherwise, move the DIV from anywhere inside the DIV:
elmnt.onmousedown = dragMouseDown;
}
function dragMouseDown(e) {
e = e || window.event;
e.preventDefault();
// get the mouse cursor position at startup:
pos3 = e.clientX;
pos4 = e.clientY;
document.onmouseup = closeDragElement;
// call a function whenever the cursor moves:
document.onmousemove = elementDrag;
}
function elementDrag(e) {
e = e || window.event;
e.preventDefault();
// calculate the new cursor position:
pos1 = pos3 - e.clientX;
pos2 = pos4 - e.clientY;
pos3 = e.clientX;
pos4 = e.clientY;
// set the element's new position:
elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
}
function closeDragElement() {
// stop moving when mouse button is released:
document.onmouseup = null;
document.onmousemove = null;
if (typeof draggedCallback === 'function') {
draggedCallback();
}
}
}
const cyrb53 = (str, seed = 0) => {
let h1 = 0xdeadbeef ^ seed,
h2 = 0x41c6ce57 ^ seed;
for (let i = 0, ch; i < str.length; i++) {
ch = str.charCodeAt(i);
h1 = Math.imul(h1 ^ ch, 2654435761);
h2 = Math.imul(h2 ^ ch, 1597334677);
}
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);
return 4294967296 * (2097151 & h2) + (h1 >>> 0);
};
const hashFromString = (str, prefix = 'hash') => {
return `${prefix}${cyrb53(str)}`;
};
function getBaseName(filePath) {
return filePath.substring(filePath.lastIndexOf('/') + 1, filePath.lastIndexOf('.'))
}
function verifyVariableTimeProject(vt_project) {
const exampleProject = {
projectId: 'exampleProject',
variable_time_version: VARIABLE_TIME_VERSION,
theatre: 'complete theatre saveFile',
layerOrder: ['layer-0', 'layer-4', 'layer-2'],
};
if (!vt_project) {
console.error('Utils::verifyVariableTimeProject::couldNotVerify',
'project equals false',
'this is what we received',
vt_project,
'compare to following example',
exampleProject);
return false;
}
if (typeof vt_project === 'string') {
console.error('Utils::verifyVariableTimeProject::couldNotVerify',
'project is a string, please first parse json ',
'this is what we received',
vt_project,
'compare to following example',
exampleProject);
return false;
// do not allow strings
//try {
//vt_project = JSON.parse(vt_project);
//} catch (e) {
//console.error('Utils::verifyVariableTimeProject::couldNotVerify',
//'project is a string,
//but could not parse json ',
//'this is what we received',
//vt_project,
//'compare to following example',
//exampleProject);
//return false;
//}
}
if (typeof vt_project !== 'object') {
console.error('Utils::verifyVariableTimeProject::couldNotVerify',
'project is not an object',
'this is what we received',
vt_project,
'compare to following example',
exampleProject);
return false;
}
const exampleKeys = Object.keys(exampleProject);
for (let i = 0; i < exampleKeys.length; i++) {
if (!vt_project.hasOwnProperty(exampleKeys[i])) {
console.error('Utils::verifyVariableTimeProject::couldNotVerify',
`${exampleKeys[i]} missing`,
'this is what we received',
vt_project,
'compare to following example',
exampleProject);
return false;
}
}
return true;
}
function clone(a) {
return JSON.parse(JSON.stringify(a));
2023-10-08 15:57:29 +02:00
}
2023-09-24 18:39:52 +02:00
function getParents(elem, until = null) {
const parents = [];
let done = false;
while (!done) {
elem = elem.parentNode;
if (elem === until) {
done = true;
} else if (elem === null) {
// until is not a parent
return null;
} else {
parents.push(elem);
}
}
return parents;
}
function arraysEqual(a, b, sortingMatters = false) {
if (a === b) return true;
if (a == null || b == null) return false;
if (a.length !== b.length) return false;
if (!Array.isArray(a) || !Array.isArray(b)) return false;
let _a = sortingMatters ? a : a.toSorted();
let _b = sortingMatters ? b : b.toSorted();
for (var i = 0; i < _a.length; ++i) {
if (_a[i] !== _b[i]) return false;
}
return true;
}
const mapValue = (value, low1, high1, low2, high2, clamp=false) => {
const mapped = low2 + (high2 - low2) * (value - low1) / (high1 - low1);
return clamp ? Math.min(high2 > low2 ? high2 : low2, Math.max(low2 < high2 ? low2 : high2, mapped)) : mapped;
}
2023-09-24 21:36:18 +02:00
const isMobile = () => {
if (/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)) {
return true;
} else if (navigator.maxTouchPoints && navigator.maxTouchPoints > 2 && /MacIntel/.test(navigator.platform)) {
return true;
}
return false;
};
// NOTE:
// promises must be delivered inside a function like:
//
// const promises = [];
//
// promises.push(() => { return new Promise((resolve) => { console.log('lalala ONE'); resolve() }); });
// promises.push(() => { return new Promise((resolve) => { console.log('lalala TWO'); resolve() }); });
// promises.push(() => { return new Promise((resolve) => { console.log('lalala THREE'); resolve() }); });
//
// sequencialPromises(promises, () => { console.log('i am done'); });
const sequencialPromises = async (iterable, callback = false) => {
for (const x of iterable) {
await x();
}
if (typeof callback === 'function') {
callback();
}
};
2023-10-07 18:01:00 +02:00
// NOTE: this is not perfect,
// but good enough for our use case
// theoretically we would have to get
// rid of all special characters
const toCssClass = (text, prefix = '') => {
return prefix + 'vt_' + text
.replaceAll('.','-dot-')
.replaceAll('#','-hash-')
;
};
const renameProperty = (o, old_key, new_key) => {
Object.defineProperty(o, new_key,
Object.getOwnPropertyDescriptor(o, old_key));
delete o[old_key];
};
const flattenObject = (o, ignoreKeys = [], pathSymbol = '.') => {
if (typeof o !== 'object') {
return o;
}
let doItAgain = false;
Object.keys(o).forEach((k) => {
if (typeof o[k] === 'object' &&
ignoreKeys.indexOf(k) < 0) {
doItAgain = true;
Object.keys(o[k]).forEach((sk) => {
const nk = `${k}${pathSymbol}${sk}`;
o[nk] = o[k][sk];
});
delete o[k];
}
});
if (doItAgain) {
flattenObject(o, ignoreKeys, pathSymbol);
}
};
const deFlattenObject = (o, ignoreKeys = [], pathSymbol = '.') => {
Object.keys(o).forEach((k) => {
if (ignoreKeys.indexOf(k) < 0) {
const ks = k.split(pathSymbol);
if (ks.length > 1) {
let sos = o[k];
for (let i = ks.length - 1; i > 0; i--) {
sos = {[ks[i]]: sos};
}
2023-10-08 15:57:29 +02:00
if (typeof o[ks[0]] === 'object') {
o[ks[0]] = {...o[ks[0]], ...sos};
} else {
o[ks[0]] = sos;
}
2023-10-07 18:01:00 +02:00
delete o[k];
}
}
});
};
/////////////////////////////////////
// you can test these functions in
// the browser like so:
/*
import("./web/js/utils.js").then((module) => {
const toast = module.toCssClass('lol.lol');
console.log(toast);
});
*/
//
2023-09-24 18:39:52 +02:00
/////////////////////////////////////
export {
getUuid,
htmlToElement,
downloadFile,
uploadFile,
makeDraggable,
getBaseName,
hashFromString,
verifyVariableTimeProject,
makeEven,
mix,
getMix,
mixObject,
clone,
getParents,
arraysEqual,
mapValue,
2023-09-24 21:48:53 +02:00
isMobile,
sequencialPromises,
2023-10-07 18:01:00 +02:00
toCssClass,
renameProperty,
flattenObject,
deFlattenObject,
2023-09-24 18:39:52 +02:00
}