alinea
Version:
Headless git-based CMS
226 lines (224 loc) • 7.06 kB
JavaScript
import {
YMap,
YMapEvent
} from "../../chunks/chunk-QUIANN6B.js";
import "../../chunks/chunk-AJJSW27C.js";
import "../../chunks/chunk-NZLE2WMY.js";
// src/core/shape/ListShape.ts
import { createId } from "../Id.js";
import { generateKeyBetween } from "../util/FractionalIndexing.js";
import { RecordShape } from "./RecordShape.js";
import { ScalarShape } from "./ScalarShape.js";
var ListRow;
((ListRow2) => {
ListRow2.id = "_id";
ListRow2.index = "_index";
ListRow2.type = "_type";
})(ListRow || (ListRow = {}));
function sort(a, b) {
if (a[ListRow.index] < b[ListRow.index]) return -1;
if (a[ListRow.index] > b[ListRow.index]) return 1;
return 0;
}
var ListShape = class {
constructor(label, shapes, initialValue, postProcess) {
this.label = label;
this.initialValue = initialValue;
this.postProcess = postProcess;
this.shapes = Object.fromEntries(
Object.entries(shapes).map(([key, type]) => {
return [
key,
new RecordShape(label, {
[ListRow.id]: new ScalarShape("Id"),
[ListRow.index]: new ScalarShape("Index"),
[ListRow.type]: new ScalarShape("Type"),
...type.shapes
})
];
})
);
}
shapes;
create() {
return this.initialValue ?? [];
}
toV1(value) {
if (!Array.isArray(value)) return [];
return value.map(this.normalizeRow).filter(Boolean);
}
normalizeRow = (row) => {
if (ListRow.type in row) return row;
const { id, type, index, ...data } = row;
if (!id || !type) return void 0;
const shape = this.shapes[type];
const updated = shape.toV1(data);
return {
[ListRow.type]: type,
[ListRow.id]: id,
[ListRow.index]: index,
...updated
};
};
toY(value) {
const map = new YMap();
const rows = Array.isArray(value) ? value : [];
let currentIndex = null;
for (const row of rows) {
const type = this.shapes[row[ListRow.type]];
if (!type) continue;
currentIndex = generateKeyBetween(currentIndex, null);
map.set(
row[ListRow.id],
type.toY({ ...row, [ListRow.index]: currentIndex })
);
}
return map;
}
fromY(map) {
const rows = [];
if (!map || typeof map.keys !== "function") return rows;
for (const key of map.keys()) {
const row = map.get(key);
if (!row || typeof row.get !== "function") continue;
const type = row.get(ListRow.type);
const rowType = this.shapes[type];
if (rowType) rows.push(rowType.fromY(row));
}
rows.sort(sort);
return rows;
}
applyY(value, parent, key) {
if (!Array.isArray(value)) return;
const current = parent.get(key);
if (!current) return void parent.set(key, this.toY(value));
const currentKeys = new Set(current.keys());
const valueKeys = new Set(value.map((row) => row[ListRow.id]));
const removed = [...currentKeys].filter((key2) => !valueKeys.has(key2));
const added = [...valueKeys].filter((key2) => !currentKeys.has(key2));
const changed = [...valueKeys].filter((key2) => currentKeys.has(key2));
for (const id of removed) current.delete(id);
for (const id of added) {
const row = value.find((row2) => row2[ListRow.id] === id);
if (!row) continue;
const type = row[ListRow.type];
const rowType = this.shapes[type];
if (!rowType) continue;
current.set(id, rowType.toY(row));
}
for (const id of changed) {
const row = value.find((row2) => row2[ListRow.id] === id);
if (!row) continue;
const type = row[ListRow.type];
const currentRow = current.get(id);
if (!currentRow) continue;
const currentType = currentRow.get(ListRow.type);
if (currentType !== type) {
current.delete(id);
current.set(id, this.shapes[type].toY(row));
continue;
}
const rowType = this.shapes[type];
if (!rowType) continue;
rowType.applyY(row, current, id);
}
}
init(parent, key) {
if (!parent.has(key)) parent.set(key, this.toY(this.create()));
}
watch(parent, key) {
const map = parent.get(key);
return (fun) => {
function w(events, transaction) {
for (const event of events) {
if (event.target === map) fun();
if (event instanceof YMapEvent && event.keysChanged.has(ListRow.index))
fun();
}
}
map.observeDeep(w);
return () => {
map.unobserveDeep(w);
};
};
}
mutator(parent, key) {
const res = {
replace: (id, row) => {
const record = parent.get(key);
const rows = this.fromY(record);
const index = rows.findIndex((r) => r[ListRow.id] === id);
res.remove(id);
res.push(row, index);
},
push: (row, insertAt) => {
const type = row[ListRow.type];
const shape = this.shapes[type];
const record = parent.get(key);
const rows = this.fromY(record);
const id = createId();
const before = insertAt === void 0 ? rows.length - 1 : insertAt - 1;
const after = before + 1;
const keyA = rows[before]?.[ListRow.index] || null;
const keyB = rows[after]?.[ListRow.index] || null;
const item = shape.toY({
...shape.create(),
...row,
[ListRow.id]: id,
[ListRow.index]: generateKeyBetween(keyA, keyB)
});
record.set(id, item);
},
remove(id) {
const record = parent.get(key);
record.delete(id);
},
move: (oldIndex, newIndex) => {
const record = parent.get(key);
const rows = this.fromY(record);
const from = rows[oldIndex];
const into = rows.filter((row2) => row2[ListRow.id] !== from[ListRow.id]);
const prev = into[newIndex - 1];
const next = into[newIndex];
const a = prev?.[ListRow.index] || null;
const b = next?.[ListRow.index] || null;
const index = generateKeyBetween(a, b);
const row = record.get(from[ListRow.id]);
row.set(ListRow.index, index);
},
read: (id) => {
const record = parent.get(key);
const rows = this.fromY(record);
return rows.find((row) => row._id === id);
}
};
return res;
}
async applyLinks(value, loader) {
const tasks = [];
if (!Array.isArray(value)) return;
for (const row of value) {
const type = row[ListRow.type];
const shape = this.shapes[type];
if (shape) tasks.push(shape.applyLinks(row, loader));
}
await Promise.all(tasks);
if (this.postProcess) await this.postProcess(value, loader);
}
searchableText(value) {
let res = "";
const rows = Array.isArray(value) ? value : [];
for (const row of rows) {
const id = row[ListRow.id];
const type = row[ListRow.type];
const shape = this.shapes[type];
if (!id || !type || !shape) continue;
res += shape.searchableText(row);
}
return res;
}
};
export {
ListRow,
ListShape
};