// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. import { LitElement, css, html, svg } from "lit"; function createIsometricMatrix() { const m = new DOMMatrix(); const angle = (Math.atan(Math.SQRT2) * 180) / Math.PI; m.rotateAxisAngleSelf(1, 0, 0, angle); m.rotateAxisAngleSelf(0, 0, 1, -45); m.scaleSelf(0.7, 0.7, 0.7); return m; } export const PROJECTION = createIsometricMatrix(); export class Map extends LitElement { static styles = css` svg { margin-top: -15%; } .dragging { cursor: grab; } `; static properties = { devices: {}, }; constructor() { super(); this.devices = []; this.selected = null; this.dragging = false; this.changingElevation = false; this.onMouseUp = this.onMouseUp.bind(this); } connectedCallback() { super.connectedCallback(); window.addEventListener("mouseup", this.onMouseUp); } disconnectedCallback() { window.removeEventListener("mouseup", this.onMouseUp); super.disconnectedCallback(); } onMouseDown(event) { if (event.target.classList?.contains("handle")) { this.changingElevation = true; } else { const element = event.composedPath().find((el) => el.classList?.contains("marker")); if (element) { const key = element.getAttribute("key"); this.selected = this.devices[key]; this.dragging = true; } else { this.selected = null; } this.dispatchEvent( new CustomEvent("select", { detail: { device: this.selected } }) ); this.update(); } } onMouseUp() { if (this.dragging || this.changingElevation) this.dispatchEvent( new CustomEvent("end-move", { detail: { device: this.selected } }) ); this.dragging = false; this.changingElevation = false; this.update(); } onMouseMove(event) { if (this.dragging) { const to = this.screenToSvg(event.clientX, event.clientY); this.selected.position.x = Math.min( Math.max(Math.floor(-to.x) + this.selected.position.y, -600 + 40), 600 - 40 ); this.selected.position.z = Math.min( Math.max(Math.floor(to.y) + this.selected.position.y, -600 + 40), 600 - 40 ); this.update(); } if (this.changingElevation) { const distance = Math.min(event.movementY * 2, this.selected.position.y); this.selected.position.y -= distance; this.update(); } if (this.dragging || this.changingElevation) { this.dispatchEvent(new CustomEvent("move")); } } screenToSvg(x, y) { const svg = this.renderRoot.children[0]; const point = svg.createSVGPoint(); point.x = x; point.y = y; return point.matrixTransform(svg.getScreenCTM().inverse()); } render() { const m = PROJECTION; return html` ${this.devices.map( (device, i) => svg` ${ device == this.selected ? svg` ` : "" } ${ device.position.y == 0 ? "" : svg` ` } ` )} `; } } customElements.define("pika-map", Map);