initial commit of site code
This commit is contained in:
parent
3c9d614db2
commit
de46a13dfb
79
index.html
Normal file
79
index.html
Normal 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, " ")),
|
||||||
|
});
|
||||||
|
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
53
js/canvas.js
Normal 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
68
js/element.js
Normal 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
57
js/elements.js
Normal 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
26
js/main.js
Normal 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
104
js/pointer.js
Normal 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
14
js/util.js
Normal 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
52
style.css
Normal 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;
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user