import { css, html, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators.js"; import { classMap } from "lit/directives/class-map.js"; @customElement("dev-reloader") export class DevReloader extends LitElement { @property({ attribute: true }) ws = ""; @property({ attribute: true, type: Boolean, reflect: true }) auto = false; @state() private _connected = false; private _armed = false; private _socket?: WebSocket; static override styles = css` button { appearance: none; background: none; border: none; padding: none; } .widget { position: fixed; z-index: 999; bottom: 1rem; left: 1rem; border: solid 1px #0002; padding: 1rem; border-radius: 9999px; cursor: pointer; display: flex; align-items: center; box-shadow: 0 0.5rem 1rem #0002; opacity: 0.5; &:hover { opacity: 1; } } .indicator { width: 8px; height: 8px; border-radius: 9999px; background: #f60; &.connected { background: #06f; } } .label { margin-left: 1rem; } `; override connectedCallback() { super.connectedCallback(); this._connected = true; this._handleDisconnect(); } private _handleDisconnect() { if (this._connected) { console.log("dev-reloader: disconnected"); this._connected = false; this._socket = undefined; const intvl = setInterval(() => { if (!this._socket || this._socket.readyState === WebSocket.CLOSED) { try { this._socket = new WebSocket(this.ws); this._socket.addEventListener("open", () => { if (this.auto && this._armed) { globalThis.location.reload(); } console.log("dev-reloader: connected"); this._connected = true; this._armed = true; clearInterval(intvl); }); this._socket.addEventListener( "close", this._handleDisconnect.bind(this), ); this._socket.addEventListener( "error", this._handleDisconnect.bind(this), ); } catch { /* no-op */ } } }, 500); } } private _toggleAuto() { this.auto = !this.auto; } protected override render() { return html` `; } }