initial commit of site code

This commit is contained in:
Lentil Hoffman 2026-07-01 02:42:23 -05:00
parent 3c9d614db2
commit de46a13dfb
Signed by: lentil
GPG Key ID: 0F5B99F3F4D0C087
8 changed files with 453 additions and 0 deletions

79
index.html Normal file
View File

@ -0,0 +1,79 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Mydeate</title>
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div id="mydeate-main" class="main-div"></div>
</body>
</html>
<script src="./js/util.js"></script>
<script src="./js/element.js"></script>
<script src="./js/elements.js"></script>
<script src="./js/pointer.js"></script>
<script src="./js/canvas.js"></script>
<script src="./js/main.js"></script>
<script defer>
// Time-stepped to support animations;
// Can also run tasks queued by the UI, if we want;
// Could also support arbitrary scheduled operations.
// Let's create an ontology for the UI, which the render loop can reference
// What kinds of things we have?
// Text : Consists of some text content; could have formatting; could have title;
// May want to specialize it to support data or whatever...
// Connection : Like a line / arrow ; May have different kinds of connections such as
// Precedence / hierarchy tree; depencency graph;
// May have multiple start / end points (a.k.a. may be a hyperedge)
// Also worth thinking about matrices - outer products
// Might as well further consider tensor products
// So then maybe we have an output / display node; this could be displaying things such as
// Values of some parameters
// Graph of (historical trends of) parameters
// Results of logic computations / expression evaluation
// Utility elements to start, for displaying document time and pointer location
// Add utility elements
addElement({
id: "time",
classes: ["monospace"],
detail: () => {
const t = currentTime;
const s = t / 1000;
const m = s / 60;
const h = m / 60;
const d = h / 24;
const timeStr = [d % 60, h % 24, m % 60, s % 60].map(x => x.toFixed(0).padStart(2, "0")).join(":");
return `runtime: ${timeStr}`;
},
});
addElement({
id: "pointer-info",
classes: ["monospace"],
detail: () => (["x", "y"]
.map(q => `${q}:${(pointer[q] ?? 0).toFixed(2).padStart(7, " ")}`)
.join(", ")
.replace(/ /g, "&nbsp;")),
});
addElement({
classes: ["monospace"],
id: 'pointer-history-length',
detail: () => `ptr hist len: ${pointerHistory.length}`,
});
addElement({
summary: () => "Element Summary",
detail: () => "Element Detail<br> With multile lines<br> How about that?",
});
// Run main loop
main();
</script>

53
js/canvas.js Normal file
View File

@ -0,0 +1,53 @@
// Background canvas
let bgCanvas;
let fgCanvas;
let bgCtx;
let fgCtx;
function initializeCanvas() {
initializeBackground();
initializeForeground();
fullscreen();
window.addEventListener('resize', fullscreen);
}
function initializeBackground() {
bgCanvas = document.createElement("canvas");
mainDiv.appendChild(bgCanvas);
bgCanvas.classList.add("fullscreen");
bgCanvas.classList.add("background");
bgCtx = bgCanvas.getContext("2d");
clearCanvas(bgCanvas, bgCtx);
}
function initializeForeground() {
fgCanvas = document.createElement("canvas");
mainDiv.appendChild(fgCanvas);
fgCanvas.classList.add("fullscreen");
fgCanvas.classList.add("foreground");
fgCtx = fgCanvas.getContext("2d");
clearCanvas(fgCanvas, fgCtx);
}
function clearCanvas(canvas, ctx) {
if (!canvas && !ctx) {
clearCanvas(bgCanvas, bgCtx);
clearCanvas(fgCanvas, fgCtx);
return;
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (ctx === bgCtx) {
bgCtx.fillStyle = '#ccc';
bgCtx.fillRect(0, 0, bgCanvas.width, bgCanvas.height);
}
}
function fullscreen() {
const dim = {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight,
};
Object.assign(bgCanvas, dim);
Object.assign(fgCanvas, dim);
}

68
js/element.js Normal file
View File

@ -0,0 +1,68 @@
class Element {
elements = undefined;
id = undefined;
summary = () => "";
detail = () => "";
el = undefined;
classes = [];
constructor(elements, {id, summary, detail, classes, width, height, x, y}) {
this.elements = elements;
this.id = id;
this.summary = summary;
this.detail = detail;
// Create DOM element(s)
this.el = document.createElement("div");
this.el.id = id;
this.el.classList.add('element');
for (const className of (classes ?? [])) {
this.el.classList.add(className);
}
if (width !== undefined && height !== undefined) {
this.setSize(width, height);
}
if (x !== undefined && y !== undefined) {
this.setPosition({x, y});
}
if (summary) {
this.summaryEl = document.createElement("div");
this.summaryEl.classList.add("summary");
this.el.appendChild(this.summaryEl)
}
if (detail) {
this.detailEl = document.createElement("div");
this.detailEl.classList.add("detail");
this.el.appendChild(this.detailEl)
}
// Handle pointer down to initiate drag
this.el.addEventListener("pointerdown", (e) => {
e.preventDefault();
const {clientX: x, clientY: y} = e;
startDrag({element: this, x, y});
});
}
setPosition({x, y}) {
this.el.style.position = "absolute";
this.el.style.top = `${y}px`;
this.el.style.left = `${x}px`;
return this;
}
setSize(width, height) {
this.el.style.width = `${width}px`;
this.el.style.height = `${height}px`;
return this;
}
render() {
if (this.summary) {
this.summaryEl.innerHTML = this.summary();
}
if (this.detail) {
this.detailEl.innerHTML = this.detail();
}
}
}

57
js/elements.js Normal file
View File

@ -0,0 +1,57 @@
// globals: mainDiv, crypto
class Elements {
elements = new Map(); // id -> Element
classes = [];
div = undefined;
constructor({classes} = {}) {
this.classes = classes;
this.div = document.createElement("div");
this.div.classList.add("elements");
this.div.classList.add("droptarget");
for (const className of (classes ?? [])) {
this.div.classList.add(className);
}
mainDiv.appendChild(this.div);
}
add(props) {
// Make sure we have a unique id
let id = props.id;
if (!id) {
if (crypto?.randomUUID) {
id = crypto.randomUUID();
} else {
id = 1;
while (this.elements.has(id)) {
id++;
}
}
}
// Create element
const element = new Element(this, {...props, id});
// Append to div
this.div.appendChild(element.el);
// Add to collection
this.elements.set(id, element);
return element;
}
remove(id) {
const element = this.elements.get(id);
if (element) {
this.div.removeChild(element.el);
this.elements.delete(id);
}
}
getElementFromDOM(target) {
return Array.from(this.elements.values()).find(({el}) => el === target);
}
renderAll() {
for (const element of this.elements.values()) {
element.render();
}
}
}

26
js/main.js Normal file
View File

@ -0,0 +1,26 @@
const mainDiv = document.getElementById("mydeate-main");
const elements = new Elements();
// Initialize variables for main loop
let run = true;
let currentTime;
function main() {
initializeCanvas();
initializePointer();
requestAnimationFrame(loop);
}
function loop(elapsedTime) {
if (run) {
currentTime = document.timeline.currentTime;
clearCanvas();
updatePointerHistory({decay: true});
drawPointerHistory();
elements.renderAll();
}
requestAnimationFrame(loop);
}
function addElement(params) {
return elements.add(params);
}

104
js/pointer.js Normal file
View File

@ -0,0 +1,104 @@
// Let's set up pointer tracking. We can use the main div as the pointer target.
// Note that we may later add pointer handlers on layered elements
const pointer = {x: undefined, y: undefined};
// Trace the path of the cursor
const pointerHistory = [];
const maxHistory = 50;
const dropTargetMaxRange = 20;
const drag = {
start: {x: undefined, y: undefined, t: undefined},
element: undefined,
placeholder: undefined,
}
function startDrag({element, x, y}) {
drag.element = element;
drag.start = {x, y, t: currentTime};
// We use a placeholder to represent the new position
const {x: ox, y: oy, width, height} = element.el.getBoundingClientRect();
drag.placeholder = elements.add({
...element,
id: `${this.id}-placeholder`,
classes: [...Array.from(element.el.classList.values()), "moving", "placeholder"],
width, height, x, y,
});
drag.element.el.classList.add("moving");
mainDiv.classList.add("dragging");
}
function initializePointer() {
mainDiv.addEventListener("pointermove", (e) => {
const {clientX: x, clientY: y} = e;
Object.assign(pointer, {x, y});
if (drag.start.t) {
const displacement = {
x: x - drag.start.x,
y: y - drag.start.y,
};
const {x: ox, y: oy} = drag.element.el.getBoundingClientRect();
drag.placeholder.setPosition({
x: ox + displacement.x,
y: oy + displacement.y,
});
// We can check here if we're near one or more drop targets to offer.
// We should indicate which available drop target is currently active.
// If there are multiple options, we can show them.
// const placeholder;
// const nearbyTargets = Array.from(elements.values()).filter(({el}) => {
// const {x, y, width, height} = el.getBoundingClientRect();
// const linearDist = minLinearDist(el.getBoundingClientRect(), );
// if (linearDist <= dropTargetMaxRange) {
// Visually activate the drop target
// }
//})
}
});
mainDiv.addEventListener("pointerup", (e) => {
if (!drag.start.t) return;
const {clientX: x, clientY: y} = e;
// Displacement
const d = {
x: x - drag.start.x,
y: y - drag.start.y,
};
const {x: ox, y: oy, width, height} = drag.element.el.getBoundingClientRect();
drag.element.setPosition({ x: ox + d.x, y: oy + d.y}).setSize(width, height);
drag.element.el.classList.remove("moving");
elements.remove(drag.placeholder.id);
drag.start = {};
drag.placeholder = undefined;
drag.element = undefined;
mainDiv.classList.remove("dragging");
});
}
function updatePointerHistory({decay} = {decay: true}) {
if (pointer.x === undefined || pointer.y === undefined) return;
if (!pointerHistory.length) {
pointerHistory.push({...pointer, t: currentTime});
return;
}
const lastPointer = pointerHistory[pointerHistory.length - 1];
if (decay || pointer.x !== lastPointer.x || pointer.y !== lastPointer.y) {
pointerHistory.push({...pointer, t: currentTime});
}
while (pointerHistory.length > maxHistory) {
pointerHistory.shift();
}
}
function drawPointerHistory() {
if (pointerHistory.length < 2) return;
for (let i = 1; i < pointerHistory.length; i++) {
fgCtx.beginPath();
const opacity = i / pointerHistory.length;
fgCtx.strokeStyle = `rgba(128, 0, 0, ${opacity})`;
fgCtx.moveTo(pointerHistory[i - 1].x, pointerHistory[i - 1].y);
fgCtx.lineTo(pointerHistory[i].x, pointerHistory[i].y);
fgCtx.stroke();
}
}

14
js/util.js Normal file
View File

@ -0,0 +1,14 @@
function minLinearDist(A, B) {
return Math.min(
// vertical distances (4) between all edges
(A.y) - (B.y),
(A.y + A.height) - (B.y + B.height),
(A.y) - (B.y + B.height),
(A.y + A.height) - (B.y),
// horizontal distances (4) between all edges
(A.x) - (B.x),
(A.x + A.width) - (B.x + B.width),
(A.x) - (B.x + B.width),
(A.x + A.width) - (B.x),
);
}

52
style.css Normal file
View File

@ -0,0 +1,52 @@
.fullscreen {
position: absolute;
top: 0;
left: 0;
}
.background {
z-index: -1;
}
.foreground {
z-index: 1;
pointer-events: none;
}
.main-div {
position: absolute;
top: 0;
left: 0;
z-index: 0;
}
.main-div * {
font-family: sans-serif;
font-size: 11pt;
line-height: 13pt;
}
.main-div.dragging .droptarget {
background-color: rgba(0, 0, 200, 0.2);
}
.elements {
flex-direction: column;
}
.element {
background-color: #eee;
display: flex;
flex-direction: column;
}
.element .summary, .element .detail {
display: flex;
}
.element .summary {
font-weight: bold;
}
.main-div .monospace .detail {
font-family: monospace;
}
.element.moving {
/* border: 1px rgba(0, 128, 0, 0.7) dashed; */
background-color: rgb(150, 100, 150);
opacity: 0.3;
}
.element.moving.placeholder {
background-color: rgb(100, 200, 100);
opacity: 0.7;
}