import { _ } from './datalog.mjs';
import { Input, Formula, input, formula, database, view, watch, captureCreated } from './reactives.mjs';
function el(tag, attrs, kids) {
let el = document.createElement(tag);
for (let [k, v] of Object.entries(attrs)) {
if (v instanceof Input || v instanceof Formula) {
watch((oldv, newv) => {
el[k] = newv;
// el.setAttribute(k, newv);
})(v);
} else if (k.startsWith("on")) {
el.addEventListener(k.slice(2), v);
} else {
el.setAttribute(k, v);
}
}
let offset = 0;
for (let i = 0; i < kids.length; i++) {
let kid = kids[i];
if (kid instanceof Input || kid instanceof Formula) {
let node,
placeholder = document.createComment("placeholder");
watch((oldv, newv) => {
if (!newv) {
if (node) el.replaceChild(placeholder, node);
node = placeholder;
} else if (typeof newv !== "object") {
if (node && node.nodeType === Node.TEXT_NODE) {
node.textContent = newv;
} else {
let newNode = document.createTextNode(newv);
if (node) el.replaceChild(newNode, node);
node = newNode;
}
} else if (newv instanceof Node) {
if (node) el.replaceChild(newv, node);
node = newv;
} else {
console.log(`Unknown child cell content`);
}
})(kid);
el.appendChild(node);
} else if (kid instanceof Array) {
// TODO formulas/etc in array
kid.forEach(x => el.appendChild(x));
offset += kid.length;
} else if (kid instanceof Promise) {
el.appendChild(placeholder);
kid.then(x => el.replaceChild(x, placeholder));
} else if (kid instanceof EntityNodes) {
// TODO error if any EntityNodes siblings for now
kid.bind(el, offset);
if ((kids.length - i) > 1) {
throw new Error(`entities() must be last child`);
}
} else {
el.appendChild(kid);
}
offset++;
}
return el;
}
const h = new Proxy({}, {
get: (obj, name) => (attrs = {}, ...kids) => {
if (typeof attrs === "string"
|| attrs instanceof Node
|| attrs instanceof Input
|| attrs instanceof Formula
|| attrs instanceof EntityNodes) {
kids.unshift(attrs);
attrs = {};
}
return el(name, attrs, kids.map(kid => {
return typeof kid === "string" ? document.createTextNode(kid) : kid;
}));
}
});
function makeAttrCells(db, eidCell) {
let cellCache = new Map();
return new Proxy({}, {
get: (obj, attrName) => {
if (cellCache.has(attrName)) {
return cellCache.get(attrName);
}
if (attrName === "_eid") {
return eidCell;
}
let attrValCell = input(null);
watch((added, removed) => {
if (added.size()) {
let [[newv]] = added;
attrValCell.set(newv);
} else if (removed.size()) {
attrValCell.set(null);
}
})(view({
find: [_.attrVal],
use: [_.eid],
where: [[_.eid, attrName, _.attrVal]]
})(db, eidCell));
cellCache.set(attrName, attrValCell);
return attrValCell;
}
})
}
function getComparator(name) {
if (name === "asc") {
return (a, b) => a === b ? 0 : a < b ? -1 : 1;
} else if (name === "desc") {
return (a, b) => a === b ? 0 : b < a ? -1 : 1;
} else {
throw new Error(`Unknown comparator: ${name}`);
}
}
function splice(el, offset, i, node) {
if (el.children.length === offset || el.children.length === offset + i) {
el.appendChild(node);
} else {
el.insertBefore(node, el.children[offset + i]);
}
}
function binaryInsert(el, offset, nodeToAttrs, sortAttr, node, cmpFunc) {
let left = offset,
right = el.children.length,
middle,
cmp;
while (left < right) {
middle = (left + right) >> 1;
cmp = cmpFunc(
nodeToAttrs.get(node)[sortAttr].value,
nodeToAttrs.get(el.children[middle])[sortAttr].value
);
if (cmp > 0) {
left = middle + 1;
} else if (cmp < 0) {
right = middle;
} else if (cmp === 0) {
left++;
break;
} else {
throw new Error(`Bad compare value: ${cmp}`);
}
}
splice(el, offset, left, node);
}
function asCell(val, defaultVal) {
if (val instanceof Input || val instanceof Formula) {
return val;
} else if (val === undefined) {
return input(defaultVal)
} else {
return input(val);
}
}
class EntityNodes {
constructor(db, sortAttr, sortDir, makeNode) {
this.db = db;
this.sortAttr = asCell(sortAttr, "_eid");
this.sortDir = asCell(sortDir, "asc");
this.makeNode = makeNode;
}
bind(el, offset = 0) {
let eidToNode = new Map(),
nodeToEidCell = new Map(),
nodeToAttrs = new Map(),
nodeToReactives = new Map(),
eidToInserter = new Map(),
pool = [];
window.pool = pool;
watch((added, removed) => {
let node;
for (let [eid] of removed) {
node = eidToNode.get(eid);
for (let r of nodeToReactives.get(node)) {
r.pause();
}
el.removeChild(node);
eidToNode.delete(eid);
eidToInserter.get(eid).detach();
eidToInserter.delete(eid);
pool.push(node);
}
for (let [eid] of added) {
if (pool.length) {
node = pool.pop();
for (let r of nodeToReactives.get(node)) {
r.resume();
}
nodeToEidCell.get(node).set(eid);
eidToNode.set(eid, node);
} else {
let eidCell = input(eid),
attrs = makeAttrCells(this.db, eidCell),
reactives = captureCreated(() => {
node = this.makeNode(attrs);
});
nodeToReactives.set(node, reactives);
eidToNode.set(eid, node);
nodeToEidCell.set(node, eidCell);
nodeToAttrs.set(node, attrs);
}
// eidToInserter.set(eid, watch(() => {
// binaryInsert(el, nodeToAttrs, this.sortAttr.value, node, getComparator(this.sortDir.value));
// })(nodeToAttrs.get(node)[this.sortAttr.value]));
eidToInserter.set(eid, watch(() => {
binaryInsert(el, offset, nodeToAttrs, this.sortAttr.value, node, getComparator(this.sortDir.value));
})(nodeToAttrs.get(node)[this.sortAttr.value]));
}
})(view({find: [_.eid], where: [[_.eid]]})(this.db));
formula((sortAttr, sortDir) => {
while (el.children.length - offset) {
el.removeChild(el.children[offset]);
}
for (let [eid, f] of [...eidToInserter]) {
let node = eidToNode.get(eid);
f?.detach();
eidToInserter.set(eid, watch(() => {
binaryInsert(el, offset, nodeToAttrs, sortAttr, node, getComparator(sortDir));
})(nodeToAttrs.get(node)[sortAttr]));
}
})(this.sortAttr, this.sortDir);
}
}
function entities(db, sortAttr, sortDir, makeNode) {
return new EntityNodes(db, sortAttr, sortDir, makeNode);
}
function entitiesBy(db, sortAttr, makeNode) {
if (!(sortAttr instanceof Input)
&& !(sortAttr instanceof Formula )) {
sortAttr = input(sortAttr);
}
return new EntityNodes(db, makeNode, sortAttr)
}
let nbsp = "\u00A0";
function bindTo(input, cell, event = "change") {
input.addEventListener(event, ({target: {value}}) => {
cell.set(value);
})
watch((oldv, newv) => {
input.value = newv ?? "";
})(cell);
return input;
}
export { h, entities, nbsp, entitiesBy, bindTo };