Mandala = async function* ({
design = Designs.Anko,
size = Math.min(width, mutable outerHeight * 0.8)
} = {}) {
const mandala = svg`<svg width=${size} height=${size} />`;
yield mandala;
const { board_fill, num_sectors, piece_types } = design;
const outer_radius = size / 2;
const center = [outer_radius, outer_radius];
const sector_angle_size = (2 * Math.PI) / num_sectors;
const board_points = _.times(num_sectors, (i) =>
fromPolar(i * sector_angle_size, outer_radius, center)
);
const inner_radius =
(11 / 12) * V.dist(center, V.div(V.add(...board_points.slice(0, 2)), 2));
const style = `\
<style>
.mandala-piece:hover { cursor:grab; }
.mandala-piece:active { cursor:grabbing; }
</style>`;
let true_coords = mandala.createSVGPoint();
mandala.append(html`${style}`, svg`${Board()}`);
for await (const piece of Pieces()) {
await Promises.delay(20);
mandala.append(piece);
}
return mandala;
function Board() {
return `<path d="M${board_points}z" fill=${board_fill} />`;
}
function Piece(type_info, i) {
const { origin, path } = type_info;
const points = _.map(path, (coords) => {
const scaled_coords = V.mult(coords, 0.97);
const [x, y] = V.add(origin, scaled_coords);
return fromPolar((i + x) * sector_angle_size, y * inner_radius, center);
});
const fill = `hsl(${(i / num_sectors) * 360}deg,65%,50%)`;
return `<g><polygon points=${points} fill=${fill} /></g>`;
}
// generates all piece types for each sector
async function* Pieces() {
for (const type_info of piece_types) {
for (const i of _.times(num_sectors)) {
const polygon = Piece(type_info, i % num_sectors); // create shape
const piece = svg`${polygon}`; // create element
piece.classList += "mandala-piece"; // add style
piece.onmousedown = onMouseDownFn(piece); // listen for events
piece.onmouseup = onMouseUpFn(piece);
yield piece;
}
}
}
function onMouseDownFn(piece) {
const GrabPiece = GrabPieceFn(piece);
return (e) => {
const { buttons } = e;
if (buttons & 1) return GrabPiece(e); // primary-button
};
}
function onMouseUpFn(piece) {
const ReleasePiece = ReleasePieceFn(piece);
return (e) => {
const { buttons } = e;
if (buttons & 1) return ReleasePiece(e); // primary-button
};
}
function GrabPieceFn(piece) {
return (e) => {
// piece.setAttributeNS(null, "pointer-events", "none");
const true_coords = GetTrueCoords(e);
const trans_matrix = e.target.getCTM();
// const grab_point = {
// x: true_coords.x - Number(trans_matrix.e),
// y: true_coords.y - Number(trans_matrix.f)
// };
const grab_point = true_coords;
// debugger;
const DragPiece = DragPieceFn(grab_point);
piece.addEventListener("mousemove", DragPiece);
piece.addEventListener(
"mouseup",
() => {
piece.removeEventListener("mousemove", DragPiece);
},
{ once: true }
);
};
}
function ReleasePieceFn(piece) {
return (e) => {};
}
function DragPieceFn(grab_point) {
return (e) => {
const piece = e.currentTarget;
true_coords = GetTrueCoords(e);
const new_point = {
x: true_coords.x,
y: true_coords.y
};
piece.setAttributeNS(
null,
"transform",
`translate(${new_point.x}, ${new_point.y})`
);
};
}
// compensates for zoom and pan
function GetTrueCoords(evt) {
const { currentScale, translation } = mandala;
return {
x: (evt.clientX - translation.x) / currentScale,
y: (evt.clientY - translation.y) / currentScale
};
}
}